I am dynamically creating a Winforms multi-select ListBox and adding it into a flowpanel control. I bind a datasource from an object I created and verified that the DataSource has does in fact have approximately 14 elements. When I do a listBox.SetSelected(0, true) I get an System.ArgumentOutOfRangeException error thrown.
I have determined the problem is that while the DataSource has 14 elements, the Item collection has none (0) and is therefore throwing the exception. My question is why are these two different from one another, and why would I not simply do a foreach item in datasource add to the item collection?
The following is the code I have so far:
case InsertableItemParameter.ParameterType.ListBox:
//note: two-way bindings are not possible with multiple-select listboxes
Label lblListBox = new Label();
lblListBox.Text = param.DisplayText;
ListBox listBox = new ListBox();
listBox.DataSource = param.Values;
listBox.DisplayMember = "Value";
listBox.SelectionMode = SelectionMode.MultiExtended;
listBox.Size = new System.Drawing.Size(flowPanel.Size.Width - lblListBox.Size.Width - 10, 100);
listBox.SetSelected(0, true); //will throw argument out of range exception here!
listBox.SetSelected(1, true);
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox);
flowPanel.SetFlowBreak(listBox, true);
break;
Below is an alternative solution I attempted and worked, but again why would I use DataSource versus Items collection?
case InsertableItemParameter.ParameterType.ListBox:
//note: two-way bindings are not possible with multiple-select listboxes
Label lblListBox = new Label();
lblListBox.Text = param.DisplayText;
ListBox listBox = new ListBox();
//listBox.DataSource = param.Values;
listBox.DisplayMember = "Value";
listBox.SelectionMode = SelectionMode.MultiExtended;
listBox.Size = new System.Drawing.Size(flowPanel.Size.Width - lblListBox.Size.Width - 10, 100);
listBox.BeginUpdate();
foreach (String paramater in param.Values)
{
listBox.Items.Add(paramater);
}
listBox.EndUpdate();
listBox.SetSelected(0, true);
listBox.SetSelected(1, true);
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox);
flowPanel.SetFlowBreak(listBox, true);
break;
ANSWER: Thanks for all the responses. The issue here is visibility and win-form rendering. While the difference between DataSource and Items collections were not really addressed save a few people, the true source of my problem was resolved by calling the SetSelected() method after the form was done drawing. This causes a lot of problems in the design of my application which I have to resolve, but this is was the problem. See the reply I marked as the answer.
Your problem probably lies elsewhere, because this code works fine:
string[] ds = {"123","321"};
listBox1.DataSource = ds;
listBox1.SetSelected(1, true);
MessageBox.Show(listBox1.Items.Count.ToString()); //returns 2
Tested in a brand new C# project with a listBox1 put on the form, and the above code sitting in Form_Load.
EDIT: I did not realize that creating a ListBox in runtime could make a difference, and especially because it matters when to set selected items. This code works:
string[] ds = { "123", "321" };
ListBox lst = new ListBox();
lst.DataSource = ds;
lst.Size = new Size(100,100);
this.Controls.Add(lst);
//make sure to call SetSelected after adding the ListBox to the parent
lst.SetSelected(1, true);
Thanks to #Brad for pointing this out. So back on the original question, replace this:
listBox.SetSelected(0, true);
listBox.SetSelected(1, true);
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox);
with this:
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox);
listBox.SetSelected(0, true);
listBox.SetSelected(1, true);
And it should work.
You have two options of how to get data to be available in a ListBox. You can set the DataSource or you can add the items manually via listBox.Items.Add(paramater). You cannot do both because they will step on each other hence your error
...cannot add items to the Item collection when DataSource is set.
Items From MSDN
This property enables you to obtain a reference to the list of items that are currently stored in the ListBox. With this reference, you can add items, remove items, and obtain a count of the items in the collection. For more information about the tasks that can be performed with the item collection, see the ListBox.ObjectCollection class reference topics.
Datasource From MSDN
An object that implements the IList or IListSource interfaces, such as a DataSet or an Array. The default is null
I'm not an expert on this matter, but from what I read it appears that Items allows you to add/modify the contents in the list whereas Datasource retrieves and sets the content.
The Items collection is populated from the DataSource only when the Control is visible. since you create your control dynamically, it is not added to the parent control and therefore not visible. Therefore you first need to have a Control that is visible on the screen. In your code you set DataSource and then set the selected items before your Control is visible on the FlowChart since it isn't added to the Parent control. You should change the sequence of the statements. You should add the listBox to the FlowPanel which will populate Items collection from the DataSource upon which you can execute SetSelected() method. Try this and note the changed order of the execution of your initial code:
ListBox listBox = new ListBox();
listBox.DataSource = param.Values;
listBox.DisplayMember = "Value";
listBox.SelectionMode = SelectionMode.MultiExtended;
listBox.Size = new System.Drawing.Size(flowPanel.Size.Width - lblListBox.Size.Width - 10, 100);
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox); //notice that you first add the listBox to the flowChart
listBox.SetSelected(0, true); //and then you have items in the Items collection which you can select
listBox.SetSelected(1, true);
Im not sure why there are two different Collections. The Items property seems more simple.
I found the reason for the exception:
apparently you have to do things in a specific order, like this:
//init the listbox
var listBox1 = new ListBox();
listBox1.Location = new System.Drawing.Point(122, 61);
listBox1.Size = new System.Drawing.Size(205, 147);
listBox1.SelectionMode = SelectionMode.MultiExtended;
Controls.Add(listBox1); //<-- point of interest
//then set the DataSource
listBox1.DataSource = lst;
listBox1.DisplayMember = "Name";
listBox1.ValueMember = "Age";
//then set the selected values
listBox1.SetSelected(0, true);
listBox1.SetSelected(1, true);
My Test class look like this:
public class Test
{
private static Random r = new Random();
public Test (string name)
{
Name = name;
Age = r.Next(16, 45);
}
public string Name { get; set; }
public int Age{ get; set; }
}
And lst is declared like this:
var lst = new List<Test>()
{
new Test("jens"),
new Test("Tom"),
new Test("John"),
new Test("Don"),
new Test("Jenny"),
};
Related
I have Dictionary marketDictionary = new Dictionary(); were MarketDetail contains 4 values/objects. Im trying to use listbox.datasource to display one of the items in MarketDetail for each key.
I can do this fine, but I want to display the item in the listbox. What it displays is the key and the name of value MarketDetail. I understand that these are the key pairs I need when selecting listbox item.
im using:
listBox1.SelectionMode = SelectionMode.None;
listBox1.DisplayMember = "key";
listBox1.ValueMember = "value";
listBox1.DataSource = new BindingSource(marketDictionary, null);
listBox1.SelectionMode = SelectionMode.One;
This works perfectly from the point of selecting an item in the list, getting key pair and doing what I need. But is there a way of displaying either my own text for each item added to listbox or the value.item from dictionary. I hope this makes sense. regard Robert
After lots of reading and testing I found a solution that works for me. I had not realised that when i built the class for my objects in marketDictionary i should have used get/set.
So i now link datasource to marketDictionary.Values and i can reference the items in display member. So now i can add a new item in my object class to use for my display member. Perfect. The other thing i have learnt is to use datasource line last, this stopped the selectchange firing.
so now ive got:
public class MarketDetail
{
public string marketId { get; set; }
public string course { get; set; }
}
public void myRaceList()
{
listBox1.DisplayMember = "course";
listBox1.ValueMember = "marketId";
listBox1.DataSource = new BindingSource(marketDictionary.Values, null);
}
I'm trying to set DataSource to DataGridViewComboBoxColumn of my DataGridView. First I'm trying to bind a data source to my DataGridView, where bindingList is a List of my custom class Plugin with properties Name (string), Id (string) and Dependencies (List):
var bindingList = PluginsHandler.GetPlugins();
var source = new BindingSource(bindingList, null);
pluginsDataGridView.AutoGenerateColumns = false;
pluginsDataGridView.DataSource = source;
pluginsDataGridView.Columns["pluginName"].DataPropertyName = "Name";
pluginsDataGridView.Columns["pluginID"].DataPropertyName = "Id";
So I can set my first two columns, but now I want to bind data to a third column of type DataGridViewComboBoxColumn. I try to do it on DataBindingComplete event:
private void pluginsDataGridView_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
for (int i = 0; i < pluginsDataGridView.Rows.Count; i++)
{
var comboCell = (DataGridViewComboBoxCell) pluginsDataGridView.Rows[i].Cells["pluginDependencies"];
var entry = pluginsDataGridView.Rows[i].DataBoundItem as IPlugin;
comboCell.DataSource = entry.Dependencies;
}
}
Sadly comboBox is empty. Funny thing happens when I incorrectly put these lines after the first block of code I posted:
var dependenciesColumn = (DataGridViewComboBoxColumn) pluginsDataGridView.Columns["pluginDependencies"];
dependenciesColumn.DataPropertyName = "Dependencies";
Then binding seem to start to work, as I can see that there are some entries in comboboxes, but when I try to hover mouse on combobox, I am getting an error that says DataGridViewComboBoxCell value is not valid).
How can I make it work?
Instead of assigning each ComboCell a data Source, set the DataSource of the column. I assume the Dependencies property of PlugIn class is a List<string>.
pluginsDataGridView.Columns["pluginDependencies"].DataSource = //list of dependencies
You have to set the DataPropertyName of the Dependencies ComboBoxColumn to get the initial values. If you don't set it you won't see any value in the column when the application is loaded.
pluginsDataGridView.Columns["pluginDependencies"].DataPropertyName = "Dependencies"
Edit:
You have a list of dependencies for a plug-in. i.e, more than one value. Usually, to select one value from list of values, you associate list with ComboBoxColumn. Achieving your requirement of multiple values from a list using standard ComboBoxColumn is difficult. Write a custom CheckedComboBoxColumn where you can display and select multiple values.
Maybe this is a very simple question for you .NET guys but since I come from a MFC C++ dev teams, this behavior all got our dev team wondering what was going on. (In MFC, when we create a dropdown datasource, the source is shared but the value of each dropdown is not affected)
It's a very simple situation. I have 3 dropdownlist in my form that ask for streets. The street you are on and the others 2 closest streets. Each of these dropdownlist have the same datasource. So what we did is that (by the way ComboList is just a class that inherit ListItem with some more functions)
//Combo Street
ComboList cboNomRue = new ComboList();
Tools.GetCombo(cboNomRue, Tools.ComboTypeRt.RT_NOM_RUE, true, true);
ddlNomRue.DisplayMember = "Numero";
ddlNomRue.ValueMember = "ID";
ddlNomRue.DataSource = cboNomRue;
//Combo Street From
ddlDe.DisplayMember = "Numero";
ddlDe.ValueMember = "ID";
ddlDe.DataSource = cboNomRue;
//Combo Street To
ddlA.DisplayMember = "Numero";
ddlA.ValueMember = "ID";
ddlA.DataSource = cboNomRue;
Using this code, when the user change the value in ddlNomRue, the 3 dropdownlist change to that value! So to fix that, I did that.
//Combo Street
ComboList cboNomRue = new ComboList();
Tools.GetCombo(cboNomRue, Tools.ComboTypeRt.RT_NOM_RUE, true, true);
ddlNomRue.DisplayMember = "Numero";
ddlNomRue.ValueMember = "ID";
ddlNomRue.DataSource = cboNomRue;
ComboList cboNomRue2 = new ComboList(cboNomRue);
ComboList cboNomRue3 = new ComboList(cboNomRue);
//Combo Street From
ddlDe.DisplayMember = "Numero";
ddlDe.ValueMember = "ID";
ddlDe.DataSource = cboNomRue2;
//Combo Street To
ddlA.DisplayMember = "Numero";
ddlA.ValueMember = "ID";
ddlA.DataSource = cboNomRue3;
I don't like this since it's duplicating variables when only one is really needed. Is this the good way of dealing with this situation or is there anything else I could do?
Thanks
I am not clear about the ComboList Class used by you, but if you use the List to store the item which is to be displayed in comboBox is the better option.
The way adopted by you is not good because of creating object again and again. To overcome this, use List and just call it function AsReadOnly();
Example:
List<string> Mylist = new List<string>();
Mylist.Add("salman");
Mylist.Add("khan");
Mylist.Add("yousafzai");
Mylist.Add("ranizai");
Mylist.Add("kachokhail");
this.tUsersTableAdapter.Fill(this.dbAIASDataSet.tUsers);
comboBox1.DataSource = Mylist.AsReadOnly();
comboBox1.DisplayMember = "ID";
comboBox1.ValueMember = "ID";
comboBox2.DataSource = Mylist.AsReadOnly();
comboBox2.DisplayMember = "ID";
comboBox2.ValueMember = "ID";
But if you must have to use the Object of ComboList then implement the interface icloneable in ComboList or use any other way to create a shallow copy.
The form automatically create a default BindingContext for all objects on the form that support data binding. The BindingContext allows the form to know the count and current position of the collection entries within that object. When you select an item on the first list, you change its position and since the other two controls are bound with the same BindingContext, they all change as well. To fix this behavior, simply create a different BindingContext for each control as follow:
ddlNomRue.BindingContext = new BindingContext();
ddlDe.BindingContext = new BindingContext();
ddlA.BindingContext = new BindingContext();
Now you can bind the same ComboList to all three controls and you will still get the behavior that you want.
I want to be able to bind a List to multiple DataGridViews such that manipulation through one of the gridviews would be propogated to all other gridviews.
List<Domain> data;
1st approach:
BindingList<Domain> list = new ..;
data.ForEach( d => { list .Add(d); } );
grid1.DataSource = list;
grid2.DataSource = list;
This didn't work. The grids share properties other than the data.
2nd approach:
BindingList<Domain> list1 = new ..;
BindingList<Domain> list2 = new ..;
data.ForEach( d => { list1.Add(d); list2.Add(d); } );
grid1.DataSource = list1;
grid2.DataSource = list2;
This approach works for updates. However, adds and deletes weren't propograted.
3rd approach:
BindingList<Domain> list = new ..;
data.ForEach( d => { list .Add(d); } );
BindingSource ds1 = new BindingSource();
BindingSource ds2 = new BindingSource();
ds1.DataSource = list;
ds2.DataSource = list;
grid1.DataSource = ds1;
grid2.DataSource = ds2;
This propogates adds and deletes, however, when a new row is added to 1 view, but not yet commited, an empty row is displayed in all other grids. Seems like a new record is inserted into the List before the editing completes.
How can I properly bind multiple datagridviews to one List? (This is extremely easy in Flex.) I'd appreciate any reference to the relevant section in MDSN.
I made a little test app, just to make sure that sharing a binding source was possible the way I remembered. You can find it here (for at least 30 days).
What I found that propbably caused your problem is that all your grids probably have adding/deleting rows enabled. A new row in grid1 is displayed as a new row in grid2, but grid2 (quite unnecessarily) displays a template row below that.
I made a little main window with a read-only grid on a binding source and an edit dialog with an editable grid on the same binding source. The binding source is the only wiring between the two, no event handling to signal updates, no reassigning of datasources. Everything is synced perfectly, even the current row (because of the CurrencyManager). A new row in the dialog only shows as one empty row in the 'main' window.
Hope this helps, and is not over-simplified.
I have a bound dropdown list populated with a table of names through a select, and databinding. it shoots selectedindexchanged that (through a postback) updates a certain gridview.
What happens is, since it runs from changing the index, the one that always comes selected (alexander) can only me chosen if you choose another one, then choose alexander. poor alexander.
What I want is to put a blanc option at the beginning (default) and (if possible) a option as second.
I can't add this option manually, since the binding wipes whatever was in the dropdown list and puts the content of the datasource.
Set the AppendDataBoundItems property to True. Add your blank, then data bind.
ddl.AppendDataBoundItems = true;
ddl.Items.Add("Choose an item");
ddl.DataSource = foo;
ddl.DataBind();
The AppendDataBoundItems property
allows you to add items to the
ListControl object before data
binding occurs. After data binding,
the items collection contains both the
items from the data source and the
previously added items.
protected void SetAddrList()
{
TestDataClassDataContext dc = new TestDataClassDataContext();
dc.ObjectTrackingEnabled = false;
var addList = from addr in dc.Addresses
from eaddr in dc.EmployeeAddresses
where eaddr.EmployeeID == _curEmpID && addr.AddressID == eaddr.AddressID && addr.StateProvince.CountryRegionCode == "US"
select new
{
AddValue = addr.AddressID,
AddText = addr.AddressID,
};
if (addList != null)
{
ddlAddList.DataSource = addList;
ddlAddList.DataValueField = "AddValue";
ddlAddList.DataTextField = "AddText";
ddlAddList.DataBind();
}
ddlAddList.Items.Add(new ListItem("<Add Address>", "-1"));
}
I created this code example using adventure works to do a little practice with Linq and it is very similar to the previous answer. Using linq still shouldn't matter for the answer the last dddlAddList.Items.Add is what you need. "Add Address" = first selected option and -1 = the value.