Data-bound combobox not updating its datasource on SelectedItem - c#

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();
}
}
};

Related

Right way to set selected text of combobox in winforms?

I have a form that takes in an object in it's constructor and populates controls on the form from properties in that object. I am having an issue where I can't set a ComboBox's SelectedText property, or at least it isn't working how I expect it to.
public Form(ValueHoldingObject obj)
{
// yeah I know this is not a very clean way to populate the combobox, the issue
// isn't limited to the combobox so I don't think this is relevant
List<int> items = Repo.GetAllItems().Reverse();
foreach (int id in checkInPrizeIds.Take(100))
// Insert at beginning to put more recently used items at the top
combobox.Items.Insert(0, id);
combobox.DropDownHeight = 200;
combobox.SelectedText = obj.StringProperty;
}
When I am testing this form the text of the combobox isn't being populated. If I add a breakpoint on the line where I assign the text it DOES get assigned, so some event is firing (multiple focus change events probably) and making it work the way I want. Obviously I can't use a breakpoint as a fix in production code. Am I assigning this value incorrectly? Should I be using a different method to populate the values?
Further testing has reviled that it isn't just the combobox, all of my controls are only being populated correctly if I have the breakpoint.
In the constructor, you need to set the selected item, for example:
foreach ( var item in combobox.Items )
if ( (string)item == obj.StringProperty )
combobox.SelectedItem = item;
Or:
foreach ( var item in combobox.Items )
if ( (int)item == Convert.ToInt32(obj.StringProperty) )
combobox.SelectedItem = item;
It's confusing but despite its name, the property SelectedText is not really the selected item... because combo box items are objects and not strings: texts shown are a representation of the item objects using ToString().
Therefore setting the selected text will not guarantee to select an item and we can prefer setting the SelectedItem.
In addition to these considerations, you set the selected text property in the constructor after populating the combo box and that can cause problems because it is before the form and the control are drawn or something like that... that is to say perhaps before the ToString() methods are called on items to prepare the visual cache, so setting the selected text can't get a match with the list.
Setting the selected text selects an existing item if done in the form load or shown events.
private void Form_Load(object sender, EventArgs e)
{
combobox.SelectedText = obj.StringProperty;
}
ComboBox.SelectedText doesn't give me the SelectedText
ComboBox.SelectedText Property

Using a Model With a ComboBox

I'm having trouble accessing the ID of a model I am adding to a ComboBox. At the moment I have a model, a preset, that contains an ID and Text. I create a list of presets through OleDB and then filter the list down before adding the contents to a ComboBox. Here is the code that does this;
var ps = new PresetService();
List<PresetModel> presetList = ps.HandleGetPresets();
List<PresetModel> filteredList = presetList.Where(filteredPreset => filteredPreset.PresetReferenceFoxPro == 3).ToList();
try
{
foreach (PresetModel preset in filteredList)
{
presetComboBox.Items.Add(preset.PresetText);
}
}
catch (Exception ex)
{
var hEs = new HandleExceptionService();
hEs.HandleException(ex.ToString());
return false;
}
Here you can see that I am adding the preset's text to the ComboBox. The issue comes however when someone wants to add a Company using one of the presets. In this case I am actually not interested in the text, I just want to add the preset's ID to the database against a company instead.
Using SelectedItem is not allowing me to access the ID, it returns a null exception. This is what I've triedl
var selectedPreset = presetComboBox.SelectedItem as PresetModel;
var presetIDToAdd = selectedPreset.PresetID;
I assume that this does not work because I have simply added the preset's Text value to the ComboBox and not it's ID. How can I add preset to a ComboBox, only display it's text but access it's ID when needed?
The problem here is that you are adding the PresetText to the ComboBox, not the PresetModel itself:
presetComboBox.Items.Add(preset.PresetText);
On top of that, you are then trying to cast the SelectedItem to a PresetModel, but it's actually the PresetText.
I would suggest using the following method.
Firstly, add the whole object to the ComboBox, like this:
presetComboBox.Items.Add(preset);
You can then define an ItemTemplate on your ComboBox to display the PresetText, or, to make things easier, just set the DisplayMemberPath:
<ComboBox ...
DisplayMemberPath="PresetText"/>
This will allow you to cast the SelectedItem to a PresetModel, but also still displaying the PresetText property in the ComboBox.

How to scroll a ListView bound to a CollectionViewSource to a desired item after it's update

I'am currently developing an WinRT app an need a ListView ordered by date and grouped by day. The ListView is bound to an ICollectionView in my ViewModel
public Windows.UI.Xaml.Data.ICollectionView GroupedData {
get
{
return cvSource.View;
}
}
private Windows.UI.Xaml.Data.CollectionViewSource cvSource;
In my XAML I can bind then the ListView to this property:
<ListView ItemsSource="{Binding GroupedData}"
Now I'am doing some calculations and Filtering on my basicData, which is stored in a List<>. After i've done this, the grouping happens via LINQ:
var result = from DataObject in basicData
group DataObject by DataObject.Date
into date_grp orderby date_grp.Key
select date_grp;
Finally I set the source of the CollectionView to this new result and fire OnPropertyChanged
cvSource.Source = result.ToList();
OnPropertyChanged("GroupdedData");
This is working as I expected, but the ListView now selects the first element every time I populate a new source. I got rid of this as described on Stackoverflow by sellmeadog
NowI like to manually select an item. This should be the previous selected item before the source of the CollectionView is changed. What is the best way to save this previous item, see if its in the newly created CollectionView, select it and scroll to it?
Best regards
For the selecting senario, add a new property to the ViewModel in and bind SelectedItem property of the ListView to it:
public Windows.UI.Xaml.Data.ICollectionView GroupedData {
get
{
return cvSource.View;
}
}
public YourObjectType CurrentItem {
get {
return this.currentItem;
}
set {
if (this.currentItem != value) {
this.currentItem = value;
this.OnPropertyChanged("CurrentItem");
}
}
}
private YourObjectType currentItem;
private Windows.UI.Xaml.Data.CollectionViewSource cvSource;
Then before setting the source, hold a reference to the current item
var current = this.CurrentItem;
cvSource.Source = result.ToList();
this.CurrentItem = current;
assuming that your DataObjects type overrides Equals method, ListView finds and selects it in the collection. If not, you may need to add code finding it's instance in the new collection and assign it to CurrentItem property.
But by selecting the item doesn't mean ListViewScrolls to it. You may need to call ListView.BringIntoView in order to scroll to the selected item.
You need ObservableComputations. Using this library you can code like this:
private INotifyCollectionChanged _groupedData
public INotifyCollectionChanged GroupedData =>
_groupedData = _groupedData ?? basicData.Grouping(dataObject => dataObject.Date)
.Selecting(date_grp => date_grp.Key);
GroupedData reflects all the changes in the basicData collection. Do not forget to add the implementation of the INotifyPropertyChanged interface to DataObject class, so that GroupedData collection reflects the changes in dataObject.Date property. GroupedData is a singleton instance so you do not lost item selection in the ListView.

How to bind the "SelectedValue" property of a combobox that gets his elements from a datasource?

I think I almost figured it out, but I have a bug: the binding through the DataSource property makes the first element from the bound datasource the default value of the combo, but this is bad for me because this default value will be propagated to the first row of the datasource that is bound through the "SelectedValue" property and it overwrites the correct value with a bad value. How to solve this?
Here is my code: (In the northwind database I want to be able to select from a combo an employee that will be inserted in an Order)
this.comboBox1.DataBindings.Add(new System.Windows.Forms.Binding("SelectedValue", this.ordersBindingSource, "EmployeeID", true));
this.comboBox1.DataSource = this.employeesBindingSource;
this.comboBox1.DisplayMember = "FullName";
this.comboBox1.ValueMember = "EmployeeID";
i do this :
DataRow dr=this.ordersBindingSource.Current;
Combo.Text="";
if(dr!=null)
{
if(dr[Combo.ValueMember]!=DBNull.Value)
{
Combo.SelectedValue=dr[Combo.ValueMember].ToString();
Combo.Text=dr[Combo.DisplayMember].ToString();
}
}
do this in recordChange

Adding custom options in bound dropdown in asp.net

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.

Categories

Resources