I am using a LINQ To SQL class, which is initialized before the constructor (the tables are added to it, it works):
DataClasses1DataContext dc = new DataClasses1DataContext();
Then I have a listbox:
listBox1.DataSource = dc.MyTable;
listBox1.DisplayMember = "Name";
listBox1.ValueMember = "PersonID";
Then I want to add a new record:
MyTable mt = new MyTable();
mt.Name = "John Smith";
mt.PersonID = 30;
dc.MyTable.InsertOnSubmit(mt);
dc.SubmitChanges();
I want to refresh the listbox's datasource, by doing:
listBox1.DataSource = dc.MyTable;
...but the new record doesn't show up.
However, if I update the datasource like this:
var q = from x in dc.MyTable select x;
listBox1.DataSource = q;
This works.
My only question is: why? I saw that some people set the DataSource to null, then back to the table, but that doesn't solve my problem.
(it's a winforms project)
listBox1.DataSource = dc.MyTable.ToList() seems to solve the problem, answered by Ehsan Sajjad in the comments.
When you update your datasource by assigning the exact same object, the control will never know that something changed. If you don't want to implement MVVM, MVC or something similar, you could just clear the value:
listBox1.DataSource = null;
listBox1.DataSource = dc.MyTable;
ToList() can cause performace (https://stackoverflow.com/a/15027581/4550393) issues when having lots of items. Since you don't really need ToList() in this case the better solution would be to clear the list and to rebind it thereafter:
listBox1.Items.Clear();
listBox1.DataSource = dc.MyTable;
Related
I'm binding list of entities to a data grid view like this:
var orders = context.Order.ToList();
BindingList<Order> orderList = new BindingList<Order>(orders);
dataGridView1.DataSource = orderList;
User can edit or add new directly on datagridview. When user click Save button, in order to optimize performance, I want to retrieve list of entities that has been changed/new to perform insert/update. How can I achieve this?
EDIT Define add new row to gridview:
BindinList<Order> orders = (BindingList<Order>)dataGridView1.Datasource;
order.Add(new Order());
EDIT 2 Solve:
BindinList<Order> orders = (BindingList<Order>)dataGridView1.Datasource;
Order order = new Order();
context.Order.Add(order);
order.Add(order);
List<Object> modifiedOrAddedEntities = context.ChangeTracker.Entries()
.Where(x => x.State == System.Data.Entity.EntityState.Modified
|| x.State == System.Data.Entity.EntityState.Added)
.Select(x=>x.Entity).ToList();
When binding EF entities to a DataGridView it is often preferable to create an IBindingList from the DbSet.Local ObservableCollection. This way you get two way databinding and your new entities are automatically added to the context when adding via BindingSource.Add() or IBindingList.Add(). The simplest way to get this working, once properly bound, is to set DataGridView.AllowUserToAddRows to true and new rows the users enter will be new entities Added to the context.
context.Orders.Load();
BindingList<Order> bindingList = context.Orders.Local.ToBindingList();
BindingSource ordersBindingSource = new BindingSource();
ordersBindingSource.DataSource = bindingList;
dataGridView1.DataSource = ordersBindingSource ;
System.Data.Entity must be referenced to use .ToBindingList() and you must be using EF4.1 or greater.
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"),
};
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 am dynamically creating a combobox like this:
public Control GenerateList(Question question)
{
// Get a list with answer possibilities
List<QuestionAnswer> answers = question.GetAnswers();
// Get a collection of given answers
Collection<QuestionnaireAnswer> givenAnswers = question.GetFilledAnswers();
ComboBox cmb = new ComboBox();
cmb.Name = "cmb";
cmb.DataSource = answers;
cmb.DisplayMember = "Answer";
cmb.ValueMember = "Id";
// Check an answer is given to the question
if (givenAnswers != null && givenAnswers.Count > 0)
{
cmb.SelectedValue = givenAnswers[0].AnswerId;
}
cmb.DropDownStyle = ComboBoxStyle.DropDownList;
cmb.SelectedIndexChanged += new EventHandler(cmb_SelectedIndexChanged);
cmb.Leave += new EventHandler(cmb_Leave);
return cmb;
}
The problem is,when executing cmb.SelectedValue = givenAnswers[0].AnswerId; cmb.SelectedValue is always null.
When debugging and I explore answers (the datasource) I see that Id (ValueMember) is exactle the same as AnswerId (in the if statement). Both have the same type (long) and the same value, but SelectedValue stays null.
Is there something I don't see?
EDIT:
It looks like the combobox stays empty. When I replace cmb.SelectedValue = givenAnswers[0].AnswerId; with cmb.SelectedIndex = 0; I get an ArgumentOutOfRangeException. This while the answers collections count is 2. So the datasource isn't null... Very strenge huh?
Solution:
The SelectedValue, SelectedIndex, SelectedItem properties can't be set until the control is added to the form. After the control is added to the form, the selectedValue, -Index and -Item properties can be set.
Solution:
The SelectedValue, SelectedIndex, SelectedItem properties can't be set until the control is added to the form. After the control is added to the form, the selectedValue, -Index and -Item properties can be set.
I met this weird issue before, at last I gave up and used another way:
cmb.Items.FindByValue(givenAnswers[0].AnswerId).Selected = true;
It worked fine...Hope you good luck!
cmb.SelectedIndex = cmb.FindStringExact("Desired Value")
The cmb.FindStringExact("Desired String") returns the index of the value you would like to select and the cmb.SelectedIndex sets the combobox to that index.
Thanks to Billious for showing me the light!
FYI - This is the VB.NET Winforms Version.
Are you looking at the same property?
cmb.ValueMember = "Id";
..
cmb.SelectedValue = givenAnswers[0].AnswerId;
You're refering to another ValueMember then the Id you're posting into the SelectedValue.
Besides that you might want to try to set your Display- and Value-member before databinding. It's faster.
Make sure QuestionAnswer has public accessors corresponding (same name) to the Display/Value Members you use.
I ran into the same problem, and found that my issue was I was treating SelectedValue as an integer, when in actual fact it was an object. The "FindByValue" solution from Danny Chen above doesn't work in WinForms, so I tried using "FindStringExact" and searched on the DisplayMember:
cmb.Items.FindStringExact(<Display string>)
Not an ideal solution, but it worked.