WPF - cascading ComboBoxes, dependent ones don't update - c#

I have three ComboBoxes such that C's items list is dependent on the selected item in B and B's items list is dependent on the selected item in A. I have ObservableCollections of particular classes for the ItemsSource on each ComboBox, and I want the Name property of the selected item to be pushed to a property in another class. For example:
ObservableCollection<AClass> Items
=> ItemsSource of cbo_A ComboBox
And selectedInstanceOfAClass.Name should be pushed to Data.AClassName property. The problem I have is that when I choose a value in the A ComboBox the first time, the B ComboBox gets the appropriate items based on the selected item in A, as expected. Same for when I select an item in B for the first time--C gets the right items. However, when I choose a different value in A, the items in B get updated but the selected value of B stays the same, and when I try to select a new value in B from its new items, the selection doesn't change--it stays the same selected value as what I initially selected in B.
Here's the XAML I have right now:
<ComboBox x:Name="cbo_A"
ItemsSource="{Binding Path=Lists.AItems, Mode=OneWay}"
DisplayMemberPath="Name" SelectedValuePath="Name"
SelectedValue="{Binding Path=Data.AClassName, ValidatesOnDataErrors=True,
Mode=OneWayToSource}" />
<ComboBox x:Name="cbo_B"
ItemsSource="{Binding ElementName=cbo_A, Path=SelectedItem.BItems, Mode=OneWay}"
SelectedValuePath="Name" DisplayMemberPath="Name"
SelectedValue="{Binding Path=Data.BClassName, ValidatesOnDataErrors=True,
Mode=OneWayToSource}"/>
<ComboBox x:Name="cbo_C"
ItemsSource="{Binding ElementName=cbo_B, Path=SelectedItem.CItems, Mode=OneWay}"
SelectedValuePath="Name" DisplayMemberPath="Name"
SelectedValue="{Binding Path=Data.CClassName, Mode=OneWayToSource,
ValidatesOnDataErrors=True}" />
What is causing the weird behavior with the selected value not updating even when I explicitly click the ComboBox and try to change the value?
Edit: when debugging and I had a SelectionChanged handler on cbo_A and another on cbo_B, I entered the cbo_B handler before the cbo_A one, so the selected item from B's perspective was still the old item, because A had not been updated yet. :/
Edit 2: if I flip the order of my ComboBoxes in XAML, suggested by this question, such that C comes before B comes before A, I enter A's handler first. When I then enter the handler for cbo_B, the SelectedItem property shows the old value (previously selected, before I chose a new value in cbo_A), even though I clicked a value from the new list of items showing up in the ComboBox.
Edit 3: I'm trying a version of this answer and getting the same problem: I see new values in ComboBox B upon changing the selected value of ComboBox A, but I cannot change the selected value in B after I've already set it once. I'm beginning to think I've got something else going awry.

Wow. So, don't go quickly implementing a bunch of IEquatable<T> Equals methods for new classes, because you may end up doing what I did:
if (ReferenceEquals(this, other))
{
return false; // Say what??
}
Sigh. I got my ComboBoxes to work by:
Correcting my BClass.Equals(BClass other) method;
Using this answer about having another view model class with different properties like SelectedAItem and AItems; and
Using this answer about putting my XAML declarations in a different order so cbo_C comes before cbo_B and cbo_B comes before cbo_A.
I may try simplifying things and see what parts are absolutely necessary for my ComboBoxes.

Related

WPF Combobox is setting the new object's value on the old object when selecteditem is changed via datacontext

I have a ComboBox in WPF. The ComboBox is inside of a grid, and the grid's DataContext is bound to the SelectedItem of a ListView. The ItemsSource of the ComboBox is set to a StaticResource, located in the window resources. The ItemsSource does not change. I have tried to use both SelectedValue and SelectedItem but both of them cause the same issue for me. The issue is that when the SelectedItem of the ListView is changed, the ComboBox is actually setting the property value from the PREVIOUSLY selected item, to the property value of the NEWLY selected item. Clearly I am doing something wrong, because I have used comboboxes many times in the past without this issue. I have scoured the web and can't find an answer. The closest, most similar question I found was: Strange behaviour (or bug?) with ComboBox in WPF when changing DataContext and having bound ItemsSource and SelectedItem
But it doesn't seem to have a solution. The solutions listed in comments did not work for me.
I created SelectionChanged events for both the ListView and the ComboBox and set breakpoints at each of them and the property that is being set. The property is actually being set BEFORE either one of those are triggered. So even if I wanted to create some hack workaround, I couldn't.
For the record, the ComboBox functionality works perfectly fine. When an object is selected in the ListView, I can see the Template name property, as I should, and the list of items is correct. If I manually change the selected item, the property is changed to a new item, just like it should. The problem is that when I change the selected item in the ListView, the "Template" property of the newly selected object is being set to the "Template" property of the previously selected object. So the combobox is changing before anything else.
The xaml for the ListView and ComboBox are below.
<ListView x:Name="my_ListBox" FlowDirection="RightToLeft"
Margin="5" Grid.RowSpan="2" SelectedIndex="0"
ItemsSource="{Binding Source={StaticResource myList}}"
DisplayMemberPath="Name"
SelectionChanged="my_ListBox_SelectionChanged"/>
<Grid DataContext="{Binding ElementName=my_ListBox, Path=SelectedItem}">
<ComboBox Name="comboBox_myTemplate"
ItemsSource="{Binding Source={StaticResource myTemplatesList}}"
SelectedValue="{Binding Template}"
SelectionChanged="comboBox_myTemplate_SelectionChanged"
DisplayMemberPath="Name" FontSize="20" Margin="5"/>
</Grid>
If I set "IsSynchronizedWithCurrentItem="True"" in the ComboBox the problem is resolved. If someone wants to explain what exactly that is doing and how it works I'd love to hear it. Thanks.

WPF Combobox - Displaying Count in Label

I've got a simple WPF ComboBox, displaying Orders/Positions on the Financial Markets.
<ComboBox Name="TradeDropDown"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{Binding Path=ActiveOrders}"
DisplayMemberPath="OrderLabel"
SelectedItem="{Binding Path=SelectedOrder, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True" />
I need to see at a glance how many items are in the list. I've added a TextBlock above with summary information.
I don't like it, and would prefer to have the items in the dropdown listed like:
(1/2) Working Short 425K
(2/2) Filled Long 979K
etc - and have the 1/2 numbers correctly update as items are added and removed from the list.
The Items are stored in a BindingList.
Is there an easy way to do this?
Is there an easy way to do this?
Add another property to the class where the OrderLabel property is defined that returns a string like "(1/2) Working Short 425K" and set the DisplayMemberPath property of the ComboBox to the name of this property.
Make sure that the class implements the INotifyPropertyChanged interface.
You then set the new property to a new value and raise the PropertyChanged event whenever you want to update the label in the ComboBox.

ComboBox to select first Value when created

I have a combo box in a ItemControl. xaml is
<ComboBox ItemsSource="{Binding DataContext.NodeMembershipFunction,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl},
AncestorLevel=1}}"
DisplayMemberPath="_Name"
SelectedValue="{Binding Condition, Mode=TwoWay}"
SelectedValuePath="_Type">
</ComboBox>
My combobox works fine with above so I am not posting any code to explain above.
My problem is that when I add a new item to my ItemControl, the combox has nothing selected (which is correct according to the code I have). Is there any way to add a trigger or something in above which selects first item only when nothing is selected eg on adding new itemcontrol?
Set IsSynchronizedWithCurrentItem="True" on comboBox instance so that it always be in sync with current item of collection.
<ComboBox IsSynchronizedWithCurrentItem="True"..../>
Moreover, adding item in collection won't make SelectedItem to go away unless you are re-initializing the entire list.
I would suggest to use ObservableCollection<T> for property NodeMembershipFunction in case not doing it already and add item directly to the collection instead of repopulating it.

WPF Combobox display value doesn't update

I am having a furious struggle with the WPF Combobox in our bi-languge application.
I have the combobox binded to a collection. I have a button that replaces the values of the collection with their corresponding string values in another language.
What is the problem: I select a value from the drop down list. I change the language, by pressing a button, then the displayed value remains on the old language, but when the drop down is dropped the values in in are replaced with the right ones.
Here is my XAML:
<ComboBox x:Name="ProjectClassComboBox"
Width="150"
ItemsSource="{Binding Path=ProjectClassCollection}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=RegionContext.CurrentItem.ClassNomenclatureId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Update: Because it is asked in the comments I must add that
the collectionis a custom class of ours, DERIVED from ObservableCollection, that is fairly complex. The collection items must be implementing INotifyPropertyChanged, and the collection has a listener to the PropertyChanged event of each item.
It just looks like the displayed text of the combo is not updated, when the drop down list and the selected item value is updated.
Binding ObservableCollection (and derrived class too) works only in case where you add or delete items, cause that's the action that invokes change event. If you need to manipulate data inside collection I suggest using BindingList. Maybe some kind of wrapper would be solution for you.
Do one thing. In the button click,
1. get the selected index in the combo box
2. Replace all the strings in the collection
3. set the selecteditem property of the combobox using the selected index that we have stored earlier in the step 1.
So, it's a bit late but we just encountered the same problem on a project that needs to support multiple languages.
Our solution was to return a new instance of ObservableCollection on the property getter.
So, your code should look like this:
<ComboBox x:Name="ProjectClassComboBox"
ItemsSource="{Binding Path=ProjectClassCollection}"/>
And in your ViewModel:
public ObservableCollection<Project> ProjectClassCollection
{
get {return new ObservableCollection<Project>(){_projectClassCollection};}
set {...}
}
This code is a quick snippet from my memory. It will not work if you just copy-pasta, but the idea is that another collection instance worked for us.
We tried to call OnPropertyChanged(nameof(ProjectClassCollection)) but that didn't work. We tried to set UpdateSourceTrigger=PropertyChanged in XAML but that didn't work either. Having a new instance worked.
Hope this helps and saves you time, cheers!

Databinding to a Combobox, two List<object>, selected value should be copied AND shown

The Problem:
Combobox shows no selected item on startup. To be exact: Item and Value are null, index is -1 and valuepath is empty. While the functionality of the binding itself seems to work, it simply refuses to show a value on startup.
What I am trying to do:
I have two lists of objects. ListOne is the full list of all available objects.
ListTwo is a list of 21 "selected" objects out of the objects in list one.
So I want to bind ListOne to a combobox with the namevalue of the object to show. Meanwhile, the selected object should be bound to ListTwo[index].
Basically, if I change the selection in the combobox I want ListTwo[index] to be a copy of the now selected object.
What I have right now:
The Object in question:
public class Rezept
{
public string strName { set; get; }
public string strSomething { set; get; }
}
The XAML-Code:
<ComboBox x:Name="comboboxName" ItemsSource="{Binding listOne}" SelectedValue="{Binding listTwo[0], Mode=TwoWay}" DisplayMemberPath="strName"/>
This seems to be working, as in selecting something updates ListTwo just fine. But on startup the combobox has nothing selected.
Another XAML-Code try:
<ComboBox x:Name="comboboxName" ItemsSource="{Binding listOne}" SelectedItem="{Binding listTwo[0], Mode=TwoWay}" DisplayMemberPath="strName"/>
This seems to behave exactly as the first, even tho it sounds more "correct".
Try three on the XAML:
<ComboBox x:Name="comboboxName" ItemsSource="{Binding listOne}" SelectedItem="{Binding listTwo[0], Mode=TwoWay}" DisplayMemberPath="strName" SelectedValue="{Binding listTwo[0].strName}"/>
This changes nothing. Neither does binding to SelectedValuePath it seems.
What do i have to change so the combobox actually shows ListTwo[0] on startup? I did make sure ListTwo[0] actually has the right value. It does get populated by a xml file on startup tho, after the UI is loaded. Would this be a case of needing a "OnPropertyChanged"-Event somewhere? And if so, where? I am out of ideas otherwise.
Could still use a hand here tho :-(
Instead of binding with SelectedValue property, you should have bind it with SelectedItem property like as follows:
<ComboBox x:Name="comboboxName" ItemsSource="{Binding listOne}" SelectedItem="{Binding listTwo[0], Mode=TwoWay}" DisplayMemberPath="strName"/>
So, since noone seems to have a solution (including myself after another 5 days of searching) I will at least post my VERY dirty workaround.
I made a List where I added every combobox in question via a loop (21 ones). Then I did another loop and manually set the Index of listOne in every Combobox to match the right value in listTwo. So it simply checks which index contains the right name and set it to that like this:
for(int i = 0, i < 21, i++)
{
listCombo[i].SelectedIndex = listOne.IndexOf(listOne.Where(x => x.name == listTwo[i].name).First();
}
This only has to be called once one on startup of the programm, since listTwo actually has the right values in the right order (as seen in the XML save file as well).
Id still much rather find out how this is supposed to work in XAML Databinding tho. So if anyone has a real answer later on, feel free to poke me so i can update this one.

Categories