Odd Binding behavior in WPF - wpf

I will try and explain this as concise as possible. I have 2 objects, the first which we will call object A that has an Id property and the second we will call object B, which has a ParentId property. The obvious relationship is that object B's ParentId is set to an object A's Id property. I am using the MVVM pattern, so on the viewmodel I have 2 ObservableCollections, one containing objects A the other objects B. On construction of the viewmodel, I create and fill the ObservableCollection<'A'> named ListItems. My xaml is simple,
<StackPanel>
<ListBox IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding ListItems}">
</ListBox>
<ComboBox SelectedValuePath="ParentId" SelectedValue="{Binding Path=ListItems/Id, Mode=OneWay}" ItemsSource="{Binding ComboItems}">
</ComboBox>
<Button Click="Button_Click" Content="Push Me"/>
</StackPanel>
As you can see the combobox's SelectedValue is bound to the ListItems current item's Id property. So essentially the listbox and combobox are in a master details.
If you press the button, it will fill the ObservableCollection<'B'> name ComboItems, which in turn populates the combobox. Now here is where the oddity begins. When I start the program, if the only thing I do is press the button, and then afterwords select an item in the listbox, the combobox will properly select an item due to the SelectedValue binding. But if I start the program and first select an item in the listbox and then press the button, the current combobox item will not change with the current listbox item. The binding appears to be forever broken. Does anyone know why this happens?
Ps. If I set the ItemsSource on the combobox before I set the SelectedValue/SelectedValuePath, the master/detail binding will never work. I know there is order to xaml, but that seems a little fragile. So if anyone has input on that also, I am all ears.
Thanks, Nate
EDIT -
When binding SelectedValue, it is very fragile. If the binding is working, i.e. have not selected anything in the listbox and then filled the combobox, if you choose an item in the combobox, the binding will break. After much time wasted with this, I chose to bind SelectedItem. This binding does not break in any of the conditions I have previously specified. I would however take any answers as to why SelectedValue binding is so ridiculous. Thanks again to all that have answered or will answer.

Yeah this is a problem we stumble upon quite a lot.
The problem is that after the ItemsSource property gets a new value, the SelectedValue binding will be cleared. Sucks, and until today we have not found a proper solution.
Here are a few workarounds:
Reset the SelectedValue binding in code, as soon as the new ItemsSource has been set. You can do this in a converter, or somewhere you'll know which will replace the ItemsSource binding (like the DataContextChanged event).
Instead of using the Binding on ItemsSource, try using a CollectionViewSource and a Filter. Put all your items in the CollectionViewSource object and filter the items when your combobox changes value.
Manually get your item the old fashion way when your listbox throws a SelectionChanged event.
Mind you, all solutions are not the prettiest in the book. I would go for option 2, its the cleanest IMO ;)
Hope this helps!

Related

How to make a TextBox update its source ListBox's SelectedValue (without code-behind?)

I'm learning WPF and am really trying to drill down on binding until I can do it like a boss. But I'm having a bit of an issue.
In xaml, I have a ListBox like so:
<ListBox Name="AccountsDisplay"
SelectedValuePath="Username"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Accounts}"
/>
And I have a TextBox that's pulling "Username" from said ListBox.
<TextBox Text="{Binding ElementName=AccountsDisplay, Path=SelectedValue}"/>
Note: Accounts is both an ObservableCollection and all objects added to it are of type Account, which is purely a data class that extends INotifyPropertyChanged, and has properties such as Username, Password, etc.
The TextBox is pulling the Username property properly, and updates any time I change selection in the ListBox (which is populated with pretty lil' Account info entries, as intended), but I cannot then click on the TextBox and attempt to update the Username portion of entries in the ListBox.
My gut tells me I'm going about this TextBox the wrong way, since I won't be able to make other TextBoxes and pull any additional Account properties (thanks to SelectedValuePath already having a value), but I'm too new to WPF & XAML to see where the error is in my ways!
Am I barking up the right tree, or is there a more appropriate way to get a TextBox to synchronize with (and edit) the data in another UI Element?
Consider binding to the property of the actual DataContext of the list item selected.
I do not use SelectedValue because I am not sure of it's purpose.
Because of my ignorance regarding the use of that particular property, I just rely on SelectedItem.
I can then specify the property name that I want to bind to relative to the DataContext of the selected list item.
<TextBox Text="{Binding ElementName=AccountsDisplay, Path=SelectedItem.Username}"/>

Wpf SelectedItem wont work for a Combobox in a ListView

I´ve got a problem with a Combobox in a ListView.
I´ve got a class called "Substrate". This class contains an object of a class called "SubstrateType". I want to show the objects of the class "Substrate" in a Listview. Each property of the "Substrate" is presented in the columns of the Listview. For the different "SubstrateType" I want to use a combobox.
In the XAML I tried it like this:
<ComboBox Name="CBType"
ItemsSource="{Binding ElementName=SettingsSubstrate, Path=TypeList}"
SelectedItem="{Binding Path=Substrate.SubstrateType}"
DisplayMemberPath="Description"/>
In the code-behind I got two ObservableCollections. One for all Substrates and one for all possible SubstrateTypes. The combobox displays all SubstrateTypes if you click on it. But the combobox has no selecteditem if you dont select one.
http://i44.tinypic.com/2eakxav.png
Thanks in advance.
I do not know your exact code, but if your ListView rows display objects of type Substrate, then your Binding Path for the SelectedItem should be just SubstrateType because the DataContext of a ListViewItem is already set to the Substrate object:
SelectedItem="{Binding Path=SubstrateType}"
Furthermore, you need to make sure that your SubstrateType instances are actually considered as equal. If the SubstrateType instance in your Substrate object is not exactly the same as the one from the TypeList property, it will not be selected. You can fix that by overriding the Equals(...) method and define your custom comparison for equality.
If this does not work, please provide more code, e.g. the surrounding XAML and the code of Substrate and the code-behind/ViewModel/whatever.

Multiple ListBoxes binding their SelectedItem to the same property in ViewModel - better way?

I have a WPF listview, and in one column the cell may contain one or more ListBoxes.
When I right-click a ListBox I'm building a context menu where each item has a DelegateCommand. Currently I'm setting the command parameter to a SelectedListBox property on the page viewmodel itself as my delegate command needs to know which ListBox has been right-clicked.
However this is leading to weird behaviour, which I'm assuming is because I'm binding multiple ListBoxes to the same page-level property (SelectedListBox).
The relevant XAML for the cell template for the listview is as follows:
<DataTemplate x:Key="MultipleListBoxCellTemplate">
<ListBox SelectedItem="{Binding Path=DataContext.SelectedListBox, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Page}}}" />
</DataTemplate>
Is there a better way to get which ListBox has been right-clicked to my viewmodel, or can anyone think of another approach? Much appreciated :)
When you are building the context menu, you know which list box was selected, yes? I would probably wrap that up in the ICommand you are binding the context item to. This way, each command knows exactly which ListBox it was created by and can get the selected item from there.
Alternately, you may be able to get around the issue with using SelectedItem by changing your binding to OneWayToSource so that the data only flows from the View back to the ViewModel. You may still have timing issues, which I suspect is your current problem, but depending on exactly what is going on, that may resolve it.

WPF CheckBox IsChecked can't be reset when page(data source) is updated

I have question on the checkbox.
First of all,
I have a usercontrol which has a list box like this and this user control will be switched by 2 button and then the data source is changed and then the the displayed officer status will be changed:
When I check the checkbox, Officers[0].IsOnDuty will be changed to true.
The problem is:
When I click another button and switch to another data source, this checked check box is still checked but the Officers[0].IsOnDuty for this data source is false.
How to solve this?
The data context of the list box item is an item for your officers collection, not the collection itself. And using a one way binding is incorrect, as the data source (the officer) will not be updated. So change the DataTemplate to:
<CheckBox IsChecked="{Binding Path=IsOnDuty, Mode=TwoWay}" />
*Here is the list box xaml:
<ListBox ItemsSource="{Binding OfficersCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Officers[0].IsOnDuty, Mode=OneWay}" />
*
The problem with your approach is that once you change the ItemsSource (by switching to the next page) your chekcbox is still bound to the item of the first collection. I think this happens because you explicitly use an indexer for the binding Path=Officers[0].IsOnDuty
Your samplelist box xaml does not really make sense. the ItemsSoruce is a OfficerCollection and your ItemTemplate binds to a collection of Officers too. Depending on what you are trying to accomplish you should do one of the following:
If your are just interested in the first officer (as your sample suggest), add a DependencyProperty FirstOfficer (or a INotifyPropertyChanged) property to your collection and bind to it: IsChecked="{Binding Path=Officers.FirstOfficer, Mode=OneWay}"
If you however are interested in all Officers and want checkboxes for all of them you should create a DataTemplate for the Officer type and use this as the ItemTemplate.
Generally you can stay out of a lot of trouble if you stick with MVVM and really tailor your ViewModel objects very close to what the View needs so you can bind your View to the ViewModel in the simplest possible way. Think of the ViewModel as the View you want to build but without a visual representation.

Where is IsSynchronizedWithCurrentItem property (or equivalent) for a TreeView?

Tell me it ain't so.
I have a typical windows/file explorer like setup.
Left Side I have a TreeView all data bound showing nodes in a hierachy
Right Side I have a ListView showing Node.Properties
ListView has a IsSynchronizedWithCurrentItem property, which rocks. e.g. If I had another ListView showing a list of nodes and both listViews have this property set to true. Changing selection of node in NodesListView will update the PropertiesListView automatically.
Now I need the same thing with a NodesTreeView and a PropertiesListView... and seems like TreeView has no such property.
Is there a more 'the WPF way' kind of solution to this problem ? Or do I have to handle the NodeSelectionChanged event of the Tree and refresh the listView via code.
A really simple solution is to bind your "details" UI elements to the SelectedValue property of the TreeView. For example, if your TreeView looked like this:
<TreeView Name="CategoryName" ItemsSource="{Binding Source={StaticResource A_Collection}, Path=RootItems}" />
Then you could bind details UI elements (like a textbox) using:
<TextBox Text="{Binding ElementName=CategoryTreeView, Path=SelectedValue.Name}"/>
Would cause the text box to be bound to Name property of the items currently selected in the TreeView.
If you want to bind many UI items as details for the selected TreeView item, consider setting up a DataContext on the elemtent that contains all the details controls (DockPanel / Grid / StackPanel, etc).
<ListView Name="listView1"
ItemsSource="{Binding Path=SelectedItem.Modules,
ElementName=treeView1, Mode=OneWay}"
IsSynchronizedWithCurrentItem="True">
Where ".Modules" is the collection of child items off the selected treeview item you want to display. Don't worry about wiring up the "SelectedItemChanged" event on the treeview.
Why exactly it doesn't implement the property, I do not know, but i have a suggestion down below.
Your code above will work, however, it is not what the IsSynchronizedWithCurrentItem property does. Any ItemsControl binds to the ICollectionView of the ItemsSource property. To get that ICollectionView, we can call CollectionViewSource.GetDefaultCollectionView(object o). Depending on the type of object o, you get a different concrete implementation of the ICollectionView inteface. CollectionView and ListCollectionView are two concrete classes that come to mind.
The ICollectionView interface contains a member called CurrentItem. What the IsSynchronizedWithCurrentItem does is: whenever an item is clicked on the ItemsControl, it sets the CurrentItem for the collection view. The ICollectionView also has two events: CurrentItemChanging and CurrentItemChanged. When the IsSynchronizedWithCurrentItem property is set, the ItemsControl will update the SelectedItem based on what the ICollectionView's CurrentItem is. Makes sense?
In master/detail WPF scenarios, we simply are binding to ICollectionViews and their CurrentItem (the CurrentItem syntax is something like {Binding Items/Name}, where Name is the Name property on the collection's CurrentItem.
So although your code works for your purposes, it doesn't do what that property does. To do what the property does, you need to do the following:
When an item is selected, you need to figure out which collection it belongs to. How do we do this? I believe this is why TreeView doesn't implement it. The selected item is displayed in a TreeViewItem. The DataContext is the object itself, but what is the parent collection ? I guess to get it you could either cache it in some hashmap (silly, but will work) or you can walk up the logical tree and get the TreeViewItem's parent that happens to be an ItemsControl. The ItemsSource property will be your collection.
Get the ICollectionView for that collection.
Need to cast that ICollectionView into a CollectionView (ICollectionView doesn't implement CurrentItem setter)
Call SetCurrent(.. , ..) on the CollectionView instance
Now, anything that is bound to that ICollectionView's CurrentItem will be updated.
This became longer than I expected. Let me know if any further clarification is necesary.
My solution to this turned out to be pretty tiny.. don't know if it is equivalent to IsSynchronizedWithCurrentItem. ListView refreshes as expected.
// the XAML
<TreeView DockPanel.Dock="Left" x:Name="tvwNodes" ItemsSource="{Binding}" SelectedItemChanged="OnNewNodeSelected"/>
<ListView x:Name="lvwProperties" ItemsSource="{Binding Path=Properties}"
// the code-behind
private void OnNewNodeSelected(object sender, RoutedPropertyChangedEventArgs<object> e)
{
lvwProperties.DataContext = tvwNodes.SelectedItem; // this returns the selected Node obj
}

Resources