WPF ComboBox - how to maintain selection when source collection changed? - wpf

How can I maintain selection on a combobox if the currently selected item is replaced in the ItemsSource collection? In this case the collection is an ObservableCollection and of course if the currently selected item is replaced the combobox loses its selection - nothing is selected.
The ComboBox looks like:
<ComboBox
Name="combobox"
SelectedValuePath="Id"
DisplayMemberPath="Description"
SelectedValue="{Binding Source={StaticResource cvs}, Path=Id, Mode=TwoWay}"/>
I cannot simply set the selected item on the combobox manually each time as the collection is manipulated in another generic class I cannot touch!
Thanks!

you bind the selectedVaule to your Id Property. so if you want the new added Item to be the selected one, just set your Id property to the new Item and call OnPropertyChanged("Id")
myCollection.Remove(oldItem);
myCollection.Add(newItem);
Id = newItem;
OnPropertyChanged("Id")

Use this code to select the new item:
combobox.SelectedItem = newItem;
UPDATE:
If the combobox is unknown to the part of the code that replaces the item, you need to do something like the following:
Subscribe to the CollectionChanged event of your collection.
When the event is fired and it says that a new item was added, execute the code as shown above.

Related

using WPF Caliburn, How do I change datagrid binding based on the combobox selection?

about to add the following features
If select this combobox, I want to change the itemssource of the datagrid.
Are there any examples related to this?
You can do the following:
Create a WPF project.
Create a view (xaml) with the combobox and datagrid inside it.
Create a view model for this newly created view and declare public properties (collection/list) for the ItemsSource of the combobox and the grid. Also have a property for the selected item of the combobox.
Set this view model as the data context of your view.
In the setter of the combobox's selected item - change the property which is bound to the datagrid's ItemsSource to the collection that you by calling a method or however you wish.
I did this:
Add the namespace for caliburn in xaml
xmlns:cal="http://www.caliburnproject.org"
Here is the combobox:
<ComboBox ItemsSource="{Binding ComboBoxItemSource}" SelectedItem="{Binding SelectedItem}" cal:Message.Attach="[Event SelectionChanged] = [ComboBoxSelectionChanged()]" />
and the viewmodel should be having this method:
public void ComboBoxSelectionChanged()
{
// here based on the SelectedItem you can change the ItemSource for the dataGrid.
}
Whenever you are changing the selectedItem of Combobox the method will get hit and based on the logic that you need you can assign the ItemSource for the dataGrid.
Hope this helps :)

WPF controls, disable changes to `SelectedItem` when bound property to `ItemsSource` changes

When I use ComboBox or other controls that have ItemsSource and SelectedItem property bindings then each time upon the initial binding during runtime and also each time when the bound collection to ItemsSource changes I experience that the content of bound SelectedItem object is changed.
How can I disable this?
For example:
I have <ComboBox MinWidth="300" ItemsSource="{Binding AvailableMasters}" SelectedItem="{Binding SelectedMaster}">
When I run the application the SelectedMaster property is assigned the first item in AvailableMasters. Also, each time the AvailableMasters collection changes (for example, by assigning a new collection to the property) the SelectedMaster is again adjusted.
The desired behavior is that SelectedItem (SelectedMaster) is only populated/changed when the end-user clicks with the mouse on that item / chooses that item from the ComboBox or other control.
Set a flag/bool property before you update the collection and use it in SelectedMaster property. Or do you need only XAML solution?

Trying to get the currently selected item from a ComboBox DataTemplate inside a DataGrid off an DropDownClosed Event Handler

I previously had a ListView that was displaying an ObservableCollection of "Player" object properties, which I'm trying to convert into a DataGrid. I have most of it working, but currently having some issues with seeing changes on one particular property (Status), which is represented by a ComboBox. The idea is to allow players to override the "Status" value between a set of enums representing things like "Alive, Dead, Poisoned," etc. I've hooked up an EventHandler for when the ComboBox is closed and inside that handler, try to grab the sender object as a Player so I can send out the valid player values.
Here's a snippet of the XAML where I'm creating the ComboBox via a DataTemplate.
<DataGridTemplateColumn Header="Status">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Name="cbStatus"
ItemsSource={Binding Source={StaticResource statusTypes}}"
SelecteItem="{Binding statusType, Mode=TwoWay}"
DropDownClosed="cbStatusType_DropDownClosed"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
Inside the Event Handler, I'm using the following to try and grab an "Player" object based on the values coming back from that particular row of the GUI.
Player playerOverridden = (Player)(sender as FrameworkElement).DataContext;
However, when I'm debugging the new playerOverridden when the ComboBox closes and a new value is selected, I'm not seeing that value being captured in playerOverridden.
This is pretty much the exactly what I was doing in a ListView with GridViewColumn.CellTemplates and it was working just fine. Not sure why the Status value is coming back as whatever it was initially set to instead of what the player has selected from the ComboBox.
Had to set the UpdateSourceTrigger on the SelectedItemBinding to be PropertyChanged, it works. Not sure why this has to be set explicitly inside a DataGrid where it's not something I needed to do in the ListView.
SelectedItem={"Binding statusType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

Start ListView with no items selected

When my app starts, there is a ListView populated with data. How do I set it so that the first row is not selected automatically? Ie I don't want any items to be selected until the user clicks one
I have tried:
setting the property bound to the ListView's SelectedItem to null (the property is bound two-way),
setting the FallBackValue of the SelectedItem to null
but neither of these worked. Is there a way to do this, or am i stuck with the first item being selected when the app starts?
If you dont put SelectedItem or SelectedIndex it is not selecting anithing, but you want to set than try this code:
<ListView IsSynchronizedWithCurrentItem="True">
<ListViewItem>Some text1</ListViewItem>
<ListViewItem>Some text1</ListViewItem>
<ListViewItem>Some text1</ListViewItem>
<ListViewItem>Some text1</ListViewItem>
</ListView>

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