May I ask why does both comboboxes trigger each other such that both have same values?
Can't I share a single list and have 2 comboboxes with different selected text?
private void Form1_Load(object sender, EventArgs e)
{
BindingList<string> list = new BindingList<string>();
list.Add("A");
list.Add("B");
list.Add("C");
list.Add("D");
bind(cbo1, list);
bind(cbo2, list);
}
private void bind(ComboBox combobox, BindingList<string> list)
{
// commented lines are in actual code,
// but appears unimportant in this question
//combobox.DropDownStyle = ComboBoxStyle.DropDown;
//combobox.AutoCompleteSource = AutoCompleteSource.ListItems;
//combobox.AutoCompleteMode = AutoCompleteMode.Suggest;
combobox.DataSource = list;
//combobox.Focus();
//combobox.Text = string.Empty;
//combobox.SelectedText = string.Empty;
}
UPDATE:
Ok, now I found out the issue is that the DataSource is managed by some BindingContext and CurrencyManager to automatically synchronise the list. But I feel someone must know how to disable this behaviour.
I don't wish to use 2 different lists because I want to be able to modify this single list at runtime and have the changes be reflected on all ComboBoxes. Any method to achieve this would be greatly appreciated.
You can "solve" it like this:
// combobox.DataSource = list;
var curr = new BindingSource(list, null);
combobox.DataSource = curr;
There is a default BindingSource (Currencymanager) linked to each Form that was keeping the 2 cbx in sync. But I'm not sure what the exact rules are here. I'm not even sure if the above is a good idea or not.
For small lists I would just make separate copies.
You cannot use the same object as the datasource for 2 seperate combo boxes. You should have list1 and list2 defined and populate each combobox with each one. Using the same datasource means that a selection in one combobox is reflected in the other.
Related
The context is as below:
A ComboBox is binded to a BindingSource.
That BindingSource is then binded to a DataModel.
That ComboBox has a DataSource of a list of objects. Yet, this list maybe set to
null when under some conditions, and maybe restored under some other conditions.
My problem is on the last step of the below scenario:
At the begining, ComboBox's dataSource is a List of 3 items (eg. item1, item2, item3)
Now comboBox show item1 as the default selected item
User pick item 3 on comboBox
Now comboBox show item3 as the selected item
...some other event happened and cause comboBox's dataSource become null
Now comboBox has no selected item (which selectedIndex is -1)
...some other event happened and cause comboBox's dataSource become the original list
8. Now comboBox's selected item is back to item3, but NOT item1
I would like to understand why will it be so, as i don't want the prvious value being preserved after the dataSource has been null.
Here is how I setup the code, with 1 comboBox binded to the a bindingSource and data-model, and 2 buttons to simulate change of comboBox's dataSource.
BindingModel model = new BindingModel();
public Form1()
{
InitializeComponent();
// BindingSource <--> model
cbxBindingSource.DataSource = typeof(BindingModel);
cbxBindingSource.Add(model);
// ComboxBox <-SelectedValue-> BindingSource <--> model.Result
comboBox.DataBindings.Add(new Binding("SelectedValue", cbxBindingSource, "Result", true, DataSourceUpdateMode.OnPropertyChanged));
}
// Simulate a re-usable list
List<BinderHelper<string>> list = new List<BinderHelper<string>>
{
new BinderHelper<string>("Item1", "1"),
new BinderHelper<string>("Item2", "2"),
new BinderHelper<string>("Item3", "3"),
new BinderHelper<string>("Item4", "4"),
};
private void button2_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a well-populated data-source
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = list;
}
private void button3_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a empty data-source
comboBox.DataSource = null;
}
private class BindingModel
{
// Simple model to bind to the selected value of the comboBox
public string Result { get; set; }
}
private class BinderHelper<T>
{
// Use for binding purpose
public string DisplayMember { get; set; }
public T ValueMember { get;set; }
public BinderHelper(string display, T value)
{
DisplayMember = display;
ValueMember = value;
}
}
However, if i create a new list, with new items everytime, then the issue will not be seen.
However, it is not possible in my actual code as the list is always the same instance, just a matter of put it on datasource or not.
Sample as below:
private void button2_Click(object sender, EventArgs e)
{
// Create new list everytime
List<BinderHelper<string>> list = new List<BinderHelper<string>>
{
new BinderHelper<string>("Item1", "1"),
new BinderHelper<string>("Item2", "2"),
new BinderHelper<string>("Item3", "3"),
new BinderHelper<string>("Item4", "4"),
};
// Simulate triggers that will result in a well-populated data-source
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = list;
}
A workaround i did is to check the current dataSource:
If the current dataSource is null, and the new dataSource is not,
Then set SelectedIndex = 0
But i would really want to avoid this manual intervention other than changing the datasource.
Appreciate anyone have a solution on that, thanks in adv!
Updated the working code with suggestions from Ivan:
public Form1()
{
InitializeComponent();
model = new BindingModel();
// BindingSource <--> model
cbxToModelBindingSource.DataSource = typeof (BindingModel);
cbxToModelBindingSource.Add(model);
// New dedicated source to provide the list in combox
// BindingSource <--> comboBox
listToCbxBindingSource = new BindingSource(components);
listToCbxBindingSource.DataSource = typeof (BinderHelper<string>);
comboBox.DataSource = listToCbxBindingSource;
// ComboxBox <--SelectedValue--> BindingSource <--> model.Result
comboBox.DataBindings.Add(new Binding("SelectedValue", cbxToModelBindingSource, "Result", true, DataSourceUpdateMode.OnPropertyChanged));
}
// Simulate triggers that will result in a well-populated data-source
private void button2_Click(object sender, EventArgs e)
{
listToCbxBindingSource.DataSource = list;
}
// Simulate triggers that will result in a empty data-source
private void button3_Click(object sender, EventArgs e)
{
listToCbxBindingSource.DataSource = null;
}
// Additional button to test two-way-bindings
private void button4_Click(object sender, EventArgs e)
{
model.Result = "4";
}
The described behavior is caused by the WinForms data binding infrastructure, in particular the BindingContext class. For each unique data source object it maintains a corresponding BindingManageBase object, which along with other things provides Position and Current property for the data sources - something that they normally don't have - consider IList for example. For list type data source it's an instance of a CurrencyManager class and is created the first time binding manager is requested for a data source object. The important part related to your issue is that there is no mechanism of removing the already created binding managers. Think of it like a dictionary with data source as a key, binding manager as a value created the first time you request a non existing key.
ComboBox control is one of the controls that uses and modifies the Position property when you data bind the list portion of it. When you assign an list to the DataSource property, it synchronizes the selection with the Position, which for newly created binding manger for non empty list is 0. Then when you select an item in the combo box, the Position is updated accordingly. When you set DataSource to null, the combo box items are cleared, but the existing binding manager with the last set position remains inside the BindingContext. The next time you set the same list as data source, it gets the existing binding manager with the last set position, hence the behavior you are getting.
To get rid of this behavior, you should use intermediate BindingSource component. Along with other things, it provides it's own Position, Current property/management and dedicated associated CurrencyManager which allows it do be used a regular data source for controls, at the same time hiding the actual data source from them (and in particular from BindingContext).
Here is how I see the solution. Add another BindingSource component (similar to yours cbxBindingSource), let call it for instance listBindingSource and statically bind the combo box to it:
public Form1()
{
InitializeComponent();
// Make properties of the expected object type available for binding
listBindingSource.DataSource = typeof(BinderHelper<string>);
comboBox.DisplayMember = "DisplayMember";
comboBox.ValueMember = "ValueMember";
comboBox.DataSource = listBindingSource;
// the rest ...
}
Now all you need is to manipulate the DataSource property of the binding source instead of the bound control(s).
private void button2_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a well-populated data-source
listBindingSource.DataSource = list;
}
private void button3_Click(object sender, EventArgs e)
{
// Simulate triggers that will result in a empty data-source
listBindingSource.DataSource = null;
}
You can simply handle the issue by verifying item.count , so if is equal to zero do nothing
if (Combo1.item.count==0)
{
return;
}
keep above code where ever you would like
Let me know if it helps
I found that Items.Clear does not always clear a listbox when the listbox has been filled via a DataSource. Setting the DataSource to Null allows it to be cleared with Items.Clear().
Is this the wrong way to do it this way? Is my thinking a bit wrong to do this?
Thanks.
Below is the code I prepared to illustrate my problem. It includes one Listbox and three buttons.
If you click the buttons in this order everything Everything works:
Fill List With Array button
Fill List Items With Array button
Fill List Items With DataSource button
But if you click the "Fill List Items With DataSource" button first, clicking on either of the other two buttons causes this error: "An unhandled exception of type 'System.ArgumentException' occurred in System.Windows.Forms.dll" with "Items collection cannot be modified when the DataSource property is set."
Comments?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnFillListWithArray_Click(object sender, EventArgs e)
{
string[] myList = new string[4];
myList[0] = "One";
myList[1] = "Two";
myList[2] = "Three";
myList[3] = "Four";
//listBox1.DataSource = null; <= required to clear list
listBox1.Items.Clear();
listBox1.Items.AddRange(myList);
}
private void btnFillListItemsWithList_Click(object sender, EventArgs e)
{
List<string> LStrings = new List<string> { "Lorem", "ipsum", "dolor", "sit" };
//listBox1.DataSource = null; <= required to clear list
listBox1.Items.Clear();
listBox1.Items.AddRange(LStrings.ToArray());
}
private void btnFillListItemsWithDataSource_Click(object sender, EventArgs e)
{
List<string> LWords = new List<string> { "Alpha", "Beta", "Gamma", "Delta" };
//listBox1.DataSource = null; <= required to clear list
listBox1.Items.Clear();
listBox1.DataSource = LWords;
}
}
According to Microsoft it looks like setting the Datasource to Null then Clearing the list is acceptable.
Source: http://support.microsoft.com/kb/319927
If your listbox is bound to a datasource, then that datasource becomes the 'master' of the listbox. You then don't clear the listbox, but you need to clear the datasource.
So if the listbox is bound to LWords, you do Lwords.clear() and the listbox would be cleared.
And that is correct behaviour, because that is what being databound is all about.
If you set the datasource to null, you are basically telling the listbox that it is no longer databound. And of course as a side effect of that it becomes empty.
But depending on the situation you might not want the listbox just to be cleared, but you might want to clear the datasource and the listbox both.
Suppose you want to clear LWords via your GUI, and that LWords is the source of your listbox, you press a button and you set the datasource to null, you see the listbox becoming empty, thinking that LWords is not empty, but LWords is not empty at all, and then in this situation that would be a bug.
I have a listbox which is bounded to xml datasource.
I want to create a button that refreshes the list box.
listbox1.refresh is not working.
Thaks
private void LoadXML()
{
presets.Clear();
if(System.IO.File.Exists(GetXMLFileName()))
{
XDocument xDoc = XDocument.Load(GetXMLFileName());
XElement xMain = xDoc.Element("Main");
foreach(var xPreset in xMain.Elements())
{
Preset preset = new Preset(xPreset);
presets.Add(preset);
}
}
else
{
for (int i = 0; i < maxPresets; i++)
presets.Add(new Preset() { Id = i });
}
listBox1.Items.Clear();
listBox1.DataSource = presets;
listBox1.DisplayMember = "name";
}
Take a look at this line:
listBox1.DataSource = presets;
Although you are setting DataSource, after the first call to LoadXML(), you are setting it to the same value it already had. Since the collection reference is the same, the assignment is effectively a no-op: the underlying data source didn't change, so the list box won't refresh. The simplest fix would be to reset DataSource to null before setting it to presets. Ideally, though, you should simply bind the data source to an ObservableCollection, or some other collection that supports change notifications.
Also, as #user2880486 noted, you should not use both Items and DataSource; they are designed to be mutually exclusive. Use one or the other, but not both.
Can I not remove items from a databound listview?
I bind the list view to a collection of users. lets say the listview is displaying name/address from the test object. If I try to remove the first item as below, it still displays all the records in the collection. This does not serve any good purpose. I just want to know what am I missing.
I'm suspecting it is because the datasource is still pointing to the collection which is unchanged. but if I manually change the listview items, shouldn't it take precedence?
protected void Page_Load(object sender, EventArgs e)
{
lvTest.DataSource = new List<TestObj>{ obj1, obj2..}; //pseudo code
lvTest.DataBind();
lvTest.Items.RemoveAt(0);
}
UPDATE
I understand this is not a good way of doing this. But the purpose of this question is to know why this does not work.
I think this might be a bug in the listview control. If you check the items count in the debugger after the first item is removed, you can see that the actual count is reduced by 1, but it still renders it.
Also, if you did lvTest.Items[0].Visible = false; after the RemoveAt(0), it actually makes the second item invisible which means that listview considers the first item removed but renders it regardless.
Update 2
On request, markup and codebehind used for test are below.
<asp:ListView ID="lvTest" runat="server" >
<LayoutTemplate><asp:PlaceHolder ID="itemPlaceholder" runat="server" /></LayoutTemplate>
<ItemTemplate>
<div><%# Eval("CompanyName")%><hr /></div>
</ItemTemplate>
<EmptyDataTemplate><div>None</div></EmptyDataTemplate>
</asp:ListView>
protected void Page_Load(object sender, EventArgs e)
{
lvTest.DataSource = GetCompanyList();
lvTest.DataBind();
lvTest.Items.RemoveAt(0);
}
public static List<Company> GetCompanyList()
{
List<Company> c = new List<Company>();
Company c1 = new Company();
c1.CustomerID = "2122";
c1.ContactName = "testc1";
c1.CompanyName = "test2";
Company c2 = new Company();
c2.CustomerID = "2123";
c2.ContactName = "testc2";
c2.CompanyName = "test2";
c.Add(c1);
c.Add(c2);
return c;
}
Yes you should be able to. As I understand it, the databinding is purely a process to populate the entries on the listview, so removing them after should work.
However, if this is what you want to do, a databound list does not seem like the right answer. Either get the data first, modify it, and populate the listview with the amended list; or amend the datastream to reflect jsut what you need - which you say is not an option.
What you are saying is that you want it to be databound, but you don't have a data source that actually matches the list you require. This means it should not be databound.
Another possibility that might be interesting is to use javascript/jquery to remove the items, which migh indicate whether this is a rendering problem or an object problem.
You may have to rebind the list with:
lvTest.DataBind();
Just for kicks, try binding the ListView only on the first page load.
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
lvTest.DataSource = new List<TestObj>{ obj1, obj2 ..};
lvTest.DataBind();
}
lvTest.Items.RemoveAt(0);
}
Have you tried removing it from the datasource?
List<TestObj> l = lvTest.DataSource;
l.RemoveAt(0);
I don't think you can remove the items directly from the listview - they have to be removed from the bound datasource.
Another way to populate the listbox is with a loop and add it to the items:
List<string> l = new List<string>{"A", "B", "C"};
//this.listBox1.DataSource = l;
foreach (string s in l)
{
this.listBox1.Items.Add(s);
}
You should be able to use the RemoveAt now with it.
List data = new List<TestObj>{ obj1, obj2..}; //pseudo code
lvTest.DataSource = data;
lvTest.DataBind();
// if you want to remove, remove from data source and rebind
data.RemoveAt(0)
lvTest.DataSource =data;
lvTest.DataBind();
if you remove item from list view it will remove item from list view item collection but you already data blinded to list view with fist items, until you rebind, those items will display in the list view.
But when rebinding what is happening is it take data from data source not from list view items. again it will rebind fist items because list view remove not doing any change on data source.
best way is update the data source and rebind it.
I've had problems with this type of thing. Give this a try (VB but you get it)
for i as int = lvTest.Items.count -1 to 0
lvTest.Items.RemoveAt(i)
Next
There seems to be problems when the items in the list have to "reposition" themselves.
I am trying to add some data inside my DataGrid.
I added some columns with the designer. Now I want to add rows with data inside the DataGrid.
Here's my code so far:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var dataContext = new PurchaseOrderDataContext();
var purchaseOrderTable = dataContext.GetTable<PurchaseOrder>();
var query = from a in purchaseOrderTable
select a;
var purchaseOrders = query;
foreach (var purchaseOrder in purchaseOrders)
{
// I believe that this method is the right one, but what do I pass to it?
// dataGrid1.Items.Add(test);
}
}
All I want to know is: What kind of object do I need to use to add something in the DataGrid, and what kind of object do I need to pass to that last method? Also how do I add, let's say, text to a certain column of a row I added?
Thanks a lot!
In general, you would bind the ItemsSource of the grid to a collection that supports change notification (IObservableCollection is idea) and just add to the collection. If the collection supports change notification, the grid will automatically display the new row.
Try this:
dataGrid1.ItemsSource = query;