I am trying to implement the following:
Two combo boxes on a Winforms form, the first has a list of parent categories, the second is children of the parent, the child list changes contents depending on the selection in the parent.
I'm trying to do this properly using databinding but I'm finding a strange quirk with the ComboBox control.
I set the datasource of the parent manually:
cboParent.DataSource = ParentDataSource where ParentDataSource is IList<ParentDTO>.
I can then bind the seletedItem to the DTO thus:
cboParent.DataBindings.Add(new Binding("SelectedItem", bindingSource, "Parent", true, DataSourceUpdateMode.OnPropertyChanged)); binding to Parent a ParentDTO object on my overarching DTO.
All pretty standard so far. This works and writes back the change to my DTO object as soon as I select anything new in the list, great!
I then bind the child combo box datasource to a list in the overarching DTO:
cboChild.DataBindings.Add(new Binding("DataSource", bindingSource, "Children", true, DataSourceUpdateMode.OnPropertyChanged)); where Children is an IList<ChildDTO> on the overarching DTO.
This also works fine and as soon as I change the parent selection the presenter changes the list Children on the DTO and the values shown in cboChildren changes, fantastic I hear you cry (and I did myself)!
Unfortunately it seems that if you're using databinding to set the datasource on a ComboBox the SelectedItemChanged, SelectedIndexChanged and SelectedValueChanged events don't fire at all, ever! This means that OnProperyChanged databinding won't work for the second combobox. OnValidation does work but it seems a little odd to me and I was wondering if anyone had encountered this before and if they'd worked out how to make it work?
Thanks in advance
Stu
I have derived the ComboBox for a few reasons, for example to support binding to null DTO's.
This helped me to insert this functinulity to fix the problem that Binding is not updated:
public new object SelectedItem
{
get
{
return base.SelectedItem;
}
set
{
base.SelectedItem = value;
if (value == null || value == System.DBNull.Value)
{
this.SelectedIndex = -1;
}
**foreach (Binding binding in DataBindings)
{
if (binding.PropertyName == "SelectedItem")
{
binding.WriteValue();
}
}**
}
}
You may want to do so also if property name is SelectedValue or selectedIndex.
If this helped someone please post back!
Best Regards,
Efy
well still need help? you need to create bindingSources and a aux textbox for a filter and use the handy property bindingSource.filter
Here is how:
Dim ds As New DataSet
Dim bind1 As New BindingSource
Dim bind2 As New BindingSource
'' here I add data to the dataset.. you MUST do your own populate way
ds.Merge(GetProducts) ' returns a dataset filled with products
ds.Merge(GetCategories) ' returns a dataset filled with categories
'' after the ds has data
' create binds
bind1.DataSource = ds
bind1.DataMember = "products"
' crete binds
bind2.DataSource = ds
bind2.DataMember = "categories"
txtMaster.DataSource = bind1
txtMaster.DisplayMember = "product_name"
txtMaster.ValueMember = "product_id"
txtDetails.DataSource = bind2
txtDetails.DisplayMember = "category_name"
txtDetails.ValueMember = "category_id"
txtAux.DataBindings.Add("Text", bind1, "product_id") ' bind1 contais products data
' this perform a filter on bind2... that contains categories data
Private Sub txtMaster_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtMaster.SelectedIndexChanged
bind2.Filter = "product_id = '" & txtAux.Text & "'"
End Sub
hope it help
Related
I have a databound combobox. It's bound as follows:
cbo.DataBindings.Add(new Binding("SelectedItem", this.bindingSource, "Order", true, DataSourceUpdateMode.OnPropertyChanged, null));
And it's populated as follows:
cbo.DataSource = customer.Orders.Where(x => x.Month == month).ToList();
cbo.ValueMember = "OrderId";
cbo.DisplayMember = "OrderDate";
cbo.SelectedIndex = -1;
The data source is a BindingSource which is in turn bound to a DTO class.
If I select an item and move focus to another control using TAB the SelectedItem is set correctly.
However, if I select an item and click on another control the SelectedItem will be the previously selected item when tabbing out of the control.
I've tried a mixture of binding SelectedItem, SelectedValue, OnPropertyChanged and OnValidation but all result in the same problem if not tabbing out.
A part-solution was given in this SO answer, however, in my case, it's not working.
The current workaround is to do:
(bindingSource.Current as Customer).Order = selectedOrder;
Which should be unnecessary because the datasource is bound. Is there a better way to enforce the change to be committed when clicking another control instead of tabbing out?
The problem is that ComboBox does not provide SelectedItemChanged event which is required for correctly binding to SelectedItem.
You should bind to SelectedValue instead, which works correctly. The problem is that there is no way to specify using the DisplayMember as display and the data source object as ValueMember. One way to solve it is to use a specialized object (a.k.a. view model) for the list elements. For instance, using anonymous type projection:
cbo.DisplayMember = "OrderDate";
cbo.ValueMember = "Order";
cbo.DataSource = customer.Orders.Where(x => x.Month == month)
.Select(o => new { o.OrderDate, Order = o })
.ToList();
cbo.DataBindings.Add(new Binding("SelectedValue", bindingSource, "Order", true, DataSourceUpdateMode.OnPropertyChanged, null));
Theoretically "OnPropertyChanged" will enforce the databinding to write value into underlying object when the control lost focus. but somehow it does not; so I guess somewhere the databinding was not done properly:
is the databinding bound to the correct object?
is the previous databinding cleared when the bound object is being changed?
did you try to change this.bindingSource to your object directly, instead of going through bindingSource wrapper?
normally if you wish to enforce data written back to object for databinding, you can call binding.WriteValue().
something like below:
cbo.LostFocus += (object sender, EventArgs e) => {
foreach (Binding b in cbo.DataBindings)
{
if (string.Equals(b.PropertyName, "SelectedItem"))
{
b.WriteValue();
}
}
};
I have two comboBox cb_Brand and cb_Model on a winForm.
cb_Model populates values on brand Select.
the problem is: if we select the brand any and select the any model under that brand, cb_Model does not loose the value of previous model selected.
for example: If we select the brand Audi and model A3
and the select the Brand Ford, when I click on cb_Model to select the model, it displayed the A3 as selected model, but still other models in list are belong to ford.
my code is:
private void cb_Brand_SelectedIndexChanged(object sender, EventArgs e)
{
// Clear Current Data
cb_Model.Text = "";
cb_Model.Items.Clear();
CarModel _carmodel = new CarModel ();
// Get Selected Car Brnad
int CarBrandID = _carmodel .GetCarBrandID(cb_Brand.Text);
//Enable choice of Model
SortedList<int, Model> colM;
colM = Model.ReadModel(CarBrandID);
cb_Model.DisplayMember = "ModelText";
foreach (Model objM in colM.Values)
{
cb_Model.Items.Add(objM);
}
}
Any Idea Please..
Thanks
unable to find the reason but sorted out with a temp fix:
private void cb_Model_Click(object sender, EventArgs e)
{
cb_Model.Text = "";
}
Thanks a lot guys
cheers
Instead of adding the items manually like this:
foreach (Model objM in colM.Values)
{
cb_Model.Items.Add(objM);
}
Let .NET take care of it for you and replace it with this:
cb_Model.DataSource = colMValues;
Which will bind the data to the list and refreshes the comboboxes items automatcially when a data source is set.
You will also not need these lines anymore:
// Clear Current Data
cb_Model.Text = "";
cb_Model.Items.Clear();
Have a read of this for more info on binding lists (and other data sources) to ComboBoxes:
How to: Bind a Windows Forms ComboBox or ListBox Control to Data (MSDN)
#w69rdy suggests an excellent solution.
The reason cb_Model did not change it's value is because you never changed the value. cb_Model.Items.Clear() does not change the selected index; only the items are removed from the combo box.
Using the code sample provided in your question:
// Clear Current Data
cb_Model.Text = "";
cb_Model.Items.Clear();
cb_Model.SelectedIndex = -1; // would effectively clear the previously selected value.
i had same problem now and Combobox's ResetText method solved the problem for me
This would work
combobox.ResetText();
I've tried your example. For me it worked as it should have.
You could try setting the cb_model.SelectedText to "" or SelectedItem to null
I found that keeping the scope of the data source near the loading of the combo box worked for me. I had a datatable with class level scope and it did not clear but then I brought it into function level scope and had it clear after the load and this worked.
I have a similar problem,tried cmb.resettext it clears text but not value.In my load form I have the below code:
Dim cmd As New SqlCommand("SELECT stud_id,name FROM student_details WHERE stud_id NOT IN (SELECT stud_id FROM student_details WHERE hostel_id!=0)", sqlcont.Conn)
Dim dr As SqlDataReader = cmd.ExecuteReader
Dim dat As New DataTable
Dim j As Integer
For j = 0 To dat.Rows.Count - 1
dr.Read()
Next
dat.Load(dr)
cmbstud.DisplayMember = "name"
cmbstud.ValueMember = "stud_id"
cmbstud.DataSource = New BindingSource(dat, Nothing)
dr.Close()
In my btnhostel click event I have the below code:
frmallocateHostel_Load(Nothing, Nothing)
this I put in attempt to reload my dataset and thus my comboboxes.Using cmbstud.resettext simply clears the text not the value.
I have same problem then I used
combobox1.SelectedIndex=-1
and it works.
I have a problem which I can't figure out.
I have an DataGridViewComboboxCell,
List<ComboBoxItem> klanten = new List<ComboBoxItem>();
foreach (ICustomer customer in CustomerFactory.CreateCustomers())
{
klanten.Add(new ComboBoxItem(customer.Naam, customer.Id));
}
klanten.Add(new ComboBoxItem("Klant aanvraag", -1));
uxInvoerenKlant.DataSource = klanten;
uxInvoerenKlant.DisplayMember = "Text";
uxInvoerenKlant.ValueMember = "Value";
When the option "Klant aanvraag" is selected the user gets a window where the user can choose another customer.
This is for the reason the user was not assigned for a specific project for that customer.
When the user chose one it will be changed in the Combobox with the following code.
uxUrenInvoeren[collumnIndex, row.Index].Value = uxInvoerenKlant.Items[klantIndex];
klantindex is the customer that needs to be selected, because it is retrieved from the combobox. It is the right kind of object in my opinion.
After this, the datagridview_dataerror event is raised where I get the Format exception with the following exception text.
DataGridViewComboBoxCell value is not valid.
What is the problem?
I found the problem myself.
The uxUrenInvoeren[collumnIndex, row.Index].Value contained the value of the ComboBoxItem and not the ComboBoxItem itself. The code now looks like this:
ComboBoxItem item = uxInvoerenKlant.Items[klantIndex] as ComboBoxItem;
if (item != null)
{
uxUrenInvoeren[collumnIndex, row.Index].Value = item.Value;
}
In this way it goes well.
Thanks for the help!
I think it may be your value of -1. Maybe you need to start with 0
You should add the selected value to the items collection of the combobox, the exception is raised since the value assigned is not found in the Item collection of the ComboBoxColumn and hence not a valid value.
Try adding it using Add
(dataGridView1.Columns[0] as DataGridViewComboBoxColumn).Items.Add
Solution :
Private Sub gvPrint_DataError(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewDataErrorEventArgs) Handles gvPrint.DataError
If e.Context = DataGridViewDataErrorContexts.Formatting Or e.Context = DataGridViewDataErrorContexts.PreferredSize Then
e.ThrowException = False
End If
End Sub
I need to get the currently selected object from da databound DataGridView.
I do not need the object of the current selected cell, but the object on which the whole row is based, in this case a BusinessObject whos properties make the columns of the grid.
I could go over the DataSource, but that itself is just an object and can be a BindingSource or a IBindingList or something like that - so not easy standartized way to get the wanted object.
Behind that is the need to just check the businessObject for a property called IsChanged and ask the user to save or discard the changes, before the bindingsource selects the next item. Therefore I must find out the current object inside RowValidating-Event of the DataGridView, since the BindingSource does not offer an event to stop changing before change occurs.See here for the well known problem
Thanks for reading ;-)
DataGridViewRow.DataBoundItem contains the 'business' object it is bound to.
Here is my code put this into your Person class
public static explicit operator Person(DataRow dr)
{
Person p = new Person();
p.adi = dr.ItemArray[0].ToString();
p.id = Int32.Parse(dr.ItemArray[1].ToString());
p.soyadi = dr.ItemArray[2].ToString();
p.kartNo = dr.ItemArray[3].ToString();
p.dogumTarihi = DateTime.Parse( dr.ItemArray[4].ToString() );
p.adres = dr.ItemArray[5].ToString();
p.meslek = dr.ItemArray[6].ToString();
p.telefon = dr.ItemArray[7].ToString();
p.gsm = dr.ItemArray[8].ToString();
p.eposta = dr.ItemArray[9].ToString();
return p;
}
and this is a update button click
DataRow row = (dataGridView1.SelectedRows[0].DataBoundItem as DataRowView).Row;
Person selected = (Person)row;
You can also use this short code.
Person selected = dataGridView1.SelectedRows[0].DataBoundItem as Person;
What about this way?
foreach (DataGridViewRow item in this.dataGridView1.SelectedRows)
{
MessageBox.Show(item.Cells[0].Value.ToString());
}
We can get multiple selected rows data.
Since you did state the IBindingList - yes as others have said the DataBoundItem property will give you what you need - there is an issue with it that I had experienced previously and ended up with a null reference but right now I can not think of the scenario in which it happened.
If you are databound using a BindingSource - you can capture the CurrentChanged, CurrentItemChanged events of your BindingSource , then you need not have an additional IsChanged Property on your B.O. .. , also the underlying datasource could also indicate modified - for example if you have BindingSource bound to a datatable the row would give you a modified flag.
You can get the selected cell value like this
yourDGV.CurrentCell.Value;
If you want the value in the form of a String just use ToString() method like this
yourDGV.CurrentCell.Value.ToString();
This should do it
I'm dynamically adding rows to a datagridview this way:
Question question = new Question();
List<Question> questions = question.GetQuestionsByQuestionnaire(questionnaireId);
if (questions != null)
{
dgvQuestions.Columns.Add("Question", "Vraag");
dgvQuestions.Columns.Add("QuestionType", "Vraag type");
dgvQuestions.Columns.Add("Category", "Categorie");
for (int i = 0; i < questions.Count; i++ )
{
int row = dgvQuestions.Rows.Add();
dgvQuestions.Rows[row].Cells["Question"].Value = questions[i].Question;
dgvQuestions.Rows[row].Cells["QuestionType"].Value = questions[i].QuestionType;
dgvQuestions.Rows[row].Cells["Category"].Value = questions[i].Category;
dgvQuestions.Rows[row].Tag = questions[i];
}
}
I don't get any errors, but the cell value stays null and I'm 100% sure that Question, QuestionType and Category contains data. What am i missing here?
I'm not sure about why this is the case, but I'd go for a mix of dynamic data but typed dataset.
What you'd do is:
Create a typed DataSet, add a "Questions" table with the columns you need
Put an instance of your DataSet from the Toolbox on your form (must recompile before that), name it for example myDataSource.
Put a BindingSource on your form, assign the myDataSource to the DataSource property and select your table for the DataMember property.
Assign the binding source to the DataSource property of your DataGridView
Add data to the data source by using for example myDataSource.Questions.NewQuestionsRow() and myDataSource.Questions.AddQuestionsRow(...).
I've just encountered something similar. You might want to make sure EnableViewState is set to True for your GridView.
Make sure VitualMode is set to False for your GridView.