I have a winforms application.
I have a Populate method that creates a bunch of controls on each page on my tabcontrol. The Populate method takes two arguments - the tab page and a List with the strings for a bunch of labels. There is a separate List for each tabpage and the names of the Lists are the same as the names of the tabpages. I want to iterate through the pages and pass the appropriate List to the Populate method by name, i.e. to pass the List by a string that is its name. As far as I know I need Reflection for that.
Code:
namespace Test
{
public partial class Form1 : Form
{
List<string> Hongdoe = new List<string>(new string[] { "Chin", "Foa", "Hu", "Dan" });
List<string> Donfu = new List<string>(new string[] { "Faa", "Su", "Pi", "Mou" });
//TabPage1.Name = Hongdoe
//TabPage2.Name = Donfu
foreach (TabPage tp in Tab_Control.TabPages)
{
//I want to tell the program "Find the variable/list that is named as 'tp.Name'
var ListName = typeof(Form1).GetField(tp.Name)
Populate(tp, ListName);
}
}
void Populate (TabPage tp, List<string> list)
{
for (int i = 0; i < list.Count; i++)
{
//Create labels
Label lab = new Label();
lab.Text = list[i];
lab.Location = new Point(i * 10, i * 10));
tp.Controls.Add(lab);
}
}
}
But sofar it returns null. I also tried using "GetProperty", "GetValue" but no success.
(before I edited this question I used a variable to demonstrate my problem simply)
You don't need to use reflection. You can use a Dictionary<string, List<string>> and use list names (tab page names) as keys and list of strings as values. Then you can get a list using the key from dictionary.
Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>();
private void ProductList_Load(object sender, EventArgs e)
{
//Initialize dictionary with keys and values
dictionary["page1"] = new List<string> { "string 1", "string 2" };
dictionary["page2"] = new List<string> { "string 3", "string 4" };
//...
}
Then you can call your Populate method this way:
Populate(tp, dictionary[tp.Name]);
Note
You don't need to pass list to the method and it's enough to pass TabPage to the method and you can get the list using dictionary[tabPage.Name]
You can use a TableLayoutPanel or a FlowLayoutPanel in tab pages to add labels to. This way they will be arranged automatically.
Just for learning purpose if you want to use reflection:
var list = (List<string>)this.GetType().GetField("Hongdoe",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance).GetValue(this);
Related
I want to display my List<string> in a datatable, from another form. The data from the List<string> is from textbox and combobox. However, the data from textbox is never repeated but the combobox might be similar with previous data displayed. And if this happened, the data displayed in datatable will be “repeated” (I’m unsure on how to describe it). Here is the actual outcome. I would like to see my displayed data to be like this. Below is my code:
Transavestate - class that hold my List
public static List<string> transnumber_list = new List<string>();
public static List<string> combos_list = new List<string>();
Form1 - form that user input values of textbox and combobox
private void button1_Click(object sender, EventArgs e)
{
//Save values in the List
Transavestate.transnumber_list.Add(Textbox1.Text)
Transavestate.combos_list.Add(comboBox2.SelectedItem.ToString());
//Go to Form 2
this.Hide();
Form2 f2 = new Form2 ();
f2.Show();
}
Form2 - form to display values of textbox and combobox
private DataSet ds;
private DataTable dt;
//Method to insert data into dtg1
private void CreateDataSet()
{
ds = new DataSet();
dt = new DataTable("Vehicle Number");
dt.Columns.Add("Column 1", typeof(string));
dt.Columns.Add("Column 2", typeof(string));
foreach (var item in Transavestate.transnumber_list)
{
foreach (var items in Transavestate.combos_list)
{
dt.Rows.Add(item, items);
}
}
ds.Tables.Add(dt);
this.dataGridView1.DataSource = dt;
dataGridView1.AllowUserToAddRows = false;
}
//To run the method
private void dataGridView1_VisibleChanged(object sender, EventArgs e)
{
CreateDataSet();
}
//Go back to Form1
private void button2_Click(object sender, EventArgs e)
{
this.Hide();
Form1 f1 = new Form1();
f1.Show();
}
It seems to me that you have two sequences of strings: a sequence of transNumbers and a sequence of comboItems (in fact they are your transnumber_list and your combos_list, but I don't want to limit myself to the fact that they are Lists, and the names are inconsistent (the first is non-plural, the 2nd is plural?)
Anyway, you say that your sequence of transNumbers contains only unique items, but that your comboItems might contain duplicates (based on some string equality comparer)
Something like this:
IEnumerable<string> transNumbers = new string[] {"1", "2"};
IEnumerable<string> comboItems = new string[] {"A", "A", "A", "B", "A", "B"};
As a result you want something like:
TransNumber ComboItem
"1" "A"
"1" "B"
"2" "A"
"2" "B"
So you want to combine every TransNumber, with every unique comboItem.The order seems not to be important.
With LINQ This is fairly easy. We'll use Enumerable.SelectMany to combine the two sequences and we'll use Enumerable.Distinct to get rid of your duplicates
// SelectMany:
// parameter source: transNumbers
// parameter collectionSelector: comboItems without Duplicates
var transNumberCombiItemCombinations = transNumbers.SelectMany(
// collectionSelector: comboItems without duplicates
combiItems.Distinct(),
// ResultSelector, take a transNumber from the source,
// and a comboItem from collectionSelector to make a new object
(transNumber, comboItem => new
{
Column1 = transNumber,
Column2 = comboItem,
});
If you remove all comments, you'll see that it is really a small piece of code.
Distinct will remove the duplicates. If you consider "myname" and "MYNAME" to be equal, you'll need to provide an IEqualityComparer<string>, for instance StringComparer.OrdinalIgnoreCase.
SelectMany" will do your foreach inside foreach.
The resultSelector will take one transNumber and one comboItem as input to create the output you want. In this case, one object that has two properties: Column1 and Column2.
The result is an IEnumerable. All you have to do is enumerate it into a list or something and add the result to your rows collection:
dt.Rows.AddRange(transNumberCombiItemCombinations.ToList());
I am trying to know if a number I pass to the function is contained in any of the lists I have. Here is an extract of the code. I have initialized the list grid as it is said in many similar posts here, but that doesn't work.
The error says
"System.NullReferenceException: 'Object reference not set to an instance of an object.'"
referring to grid. I know it seems duplicated, but I have checked the others posts similar to this and I can not find a specific answer to my problem.
public partial class MainWindow : Window
{
//This list is much bigger
public List<int> grid1 = new List<int>();
public List<int> grid2 = new List<int>();
public List<int> grid3 = new List<int>();
public List<int> grid4 = new List<int>();
public List<int> grid5 = new List<int>();
public MainWindow()
{
InitializeComponent();
}
private bool numberValide(int number, int Grid) // Grid {1..5}
{
List<int> = new List<int>();
grid = (List<int>)this.FindName("grid" + Grid);
if (grid != null)
{
if (!grid.Contains(number))
{
grid.Add(number); //the error is here
return true;
}
else
{
return false;
}
}
else
{
grid.Add(number); //error also here
return true;
}
}
}
The reason for your issue is FindName() is looking for a XAML element by that name, its not going to check for class members. So this line of code:
grid = (List<int>)this.FindName("grid" + Grid);
Will always be null, because FindName("grid" + Grid) will return null, then you are casting that null to a List<int>. So grid will be a null List<int>
Having all these Lists in your class is a pretty bad design. You can simplify things a bit by using a Dictionary. I am not entirely sure I follow what your logic is, but this is my attempt to convert:
public partial class MainWindow : Window
{
public Dictionary<int, List<int>> gridDictionary = new Dictionary<int, List<int>>();
private bool numberValide(int number, int gridIndex) // Grid {1..5}
{
//Checks if dictionary has an entry at gridIndex
if(gridDictionary.ContainsKey(gridIndex))
{
if (!gridDictionary[gridIndex].Value.Contains(number))
{
//Add number to list in dictionary
gridDictionary[gridIndex].Value.Add(number);
return true;
}
else
{
return false;
}
}
else
{
//Adds a new entry to the dictionary with a list containing number
gridDictionary.Add(gridIndex, new List<int>() { number });
return true;
}
}
Keep in mind that Dictionaries must have unique keys, so you couldn't do:
dictionary.Add(1, "one");
dictionary.Add(1, "uno");
That would throw an exception because a duplicate key exists already. For your use case this should be fine because all your grid variables had to be uniquely named anyway to compile.
I suspect the problem here is that you think FindName will let you find a field in a class. That is not what it does. It finds a control in a window. grid1, grid2, etc are not controls. They are fields
If you want to find fields, you would have to use reflection...but, the better way would be to just have a List<List<int>> instead.
So:
List<List<int>> grids = new List<List<int>>();
Then somewhere (maybe the constructor) you populate that list:
for (var i=0; i < numberOfGrids; i++)
{
grids.Add(new List<int>());
}
And then later when you want to retrieve a grid in your numberValide:
var grid = grids[Grid]; // where Grid is the index to your Grid -
// your variable names are confusing
But now, since your original problem was to see if a number exists in any of your lists of ints, you can use Linq to simplify this greatly:
var doesExist = grids.Any(g => g.Contains(number));
In my project I need to create several lisview dinamically, I have a array with some value string[] = array{"BILL", "ORDER", "DELEVERY FORM", "RCL", "ESTIMATION", ...}; And I would like create a listview to each value from array.
void CreateListView(string[] array)
{
foreach(value in array)
{
ListView listView[value] = new ListView();
this.Controls.Add(listView[value]);
}
}
From my understanding you want to create the listviews and name them according to the given array. If that is the case, then this should satisfy.
void CreateListView(string[] array)
{
foreach (var value in array)
{
ListView listView = new ListView {Name = value};
Controls.Add(listView);
}
}
Hi when I create textboxes on Windows Application Form I cannot name it as box[0], box[1] and so on. The purpose why I want to do like this is because I want to use them in a loop.
Actually I found TextBox[] array = { firstTextBox, secondTextBox }; works too!
How about making a list of them after you create them? In your form initialization function, you can do something like:
List<TextBox> myTextboxList = new List<TextBox>();
myTextBoxList.Add(TextBox1);
myTextBoxList.Add(TextBox2);
mytextBoxList.Add(TextBox3);
Now you can itterate through with your "myTextboxList" with something like below:
Foreach (TextBox singleItem in myTextboxList) {
// Do something to your textboxes here, for example:
singleItem.Text = "Type in Entry Here";
}
You can create textboxes on runtime and just put them in an array...
If you want to do it in design time, you will have to do some control filtering logic on the whole this.Controls array in order to access only the wanted textboxes. Consider if (currControl is TextBox) if all textboxes in the form are ones you want in the array.
Another option for design time, is putting all wanted textboxes in a panel which will be their parent, and then iterating over the panel's children (controls) and cast them to TextBox.
A runtime solution would be something like:
var arr = new TextBox[10];
for (var i = 0; i < 10; i++)
{
var tbox = new TextBox();
// tbox.Text = i.ToString();
// Other properties sets for tbox
this.Controls.Add(tbox);
arr[i] = tbox;
}
I wouldn't use an array for this, personally. I would use some form of generic collection, like List.
List<TextBox> textBoxList = new List<TextBox>();
//Example insert method
public void InsertTextBox(TextBox tb)
{
textBoxList.Add(tb);
}
//Example contains method
public bool CheckIfTextBoxExists(TextBox tb)
{
if (textBoxList.Contains(tb))
return true;
else
return false;
}
You don't necessarily have to use the Contains method, you could also use Any(), or maybe even find another way- all depends on what you're doing. I just think using a generic collection gives you more flexibility than a simple array in this case.
for C# just use this to create an array of text boxes
public Text [] "YourName" = new Text ["how long you want the array"];
then add the text boxes to the array individually.
TextBox Array using C#
// Declaring array of TextBox
private System.Windows.Forms.TextBox[] txtArray;
private void AddControls(int cNumber)
{
// assign number of controls
txtArray = new System.Windows.Forms.TextBox[cNumber + 1];
for (int i = 0; i < cNumber + 1; i++)
{
// Initialize one variable
txtArray[i] = new System.Windows.Forms.TextBox();
}
}
TextBox[] t = new TextBox[10];
for(int i=0;i<required;i++)
{
t[i]=new TextBox();
this.Controls.Add(t[]);
}
I've a list
List<String> SampleList=new List<String>();
I need to fill a listView with the contents of the list
For example the "SampleList" contains
a
b
c
d
The listView should be filled like
S.No Item
1 a
2 b
3 c
4 d
Now i'm using for loop for this method
like
for(int i=0;i<SampleList.Count;i++)
{
listView1.Items.Add((i+1).ToString());
listView1.Items[i].SubItems.Add(SampleList[i]);
}
is there any other way to do this like data binding ?
Thanks in advance
Not quite like databinding, but you could use VirtualMode and RetrieveVirtualItem
listView1.VirtualMode = true;
listView1.RetreiveVirtualItem += new RetrieveVirtualItemEventHandler( this.RetrieveVirtualItem );
listView1.VirtualListSize = SampleList.Count;
private void RetreiveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
ListViewItem lvItem = new ListViewItem((e.ItemIndex + 1).ToString());
lvItem.SubItems.Add(SampleList[e.ItemIndex]);
e.Item = lvItem;
}
Does it have to be a ListView? ListBox is simple:
using (Form form = new Form())
{
List<string> strings = new List<string> {"abc", "def", "ghi"};
form.Controls.Add(new ListBox() {DataSource = strings});
Application.Run(form);
}
For a richer display, DataGridView would also do this, but you need an extra bit of indirection (since it supports multiple columns, it needs a wrapper object per row):
using (Form form = new Form())
{
List<string> strings = new List<string> {"abc", "def", "ghi"};
var indirect = (from s in strings
select new {Text = s}).ToList();
form.Controls.Add(new DataGridView() { DataSource = indirect });
Application.Run(form);
}
This also gives you opportunity to add in extra data, for example the number:
var indirect = strings.Select((s,i) =>
new {Index = i + 1, Text = s}).ToList();
Unfortunately windows forms ListView doesn't support DataBinding. But if you update the list frequently, maybe you can use INotifyProperty interface.