I thought there were tons of answers in the net, but strangely none seems to fit my issue.
Simple enough: I have a wpf project with a comboBox whose SelectedItem is bound to my ViewModel (which implements INotifyPropertyChanged). The ItemsSource of my ComboBox is bound to a CollectionViewSource's View.
[all property names in this example a fictional, they shall just illustrate things so one get's the drift]
<ComboBox ItemsSource = "{Binding MyCollectionViewSource.View}"
SelectedItem = "{Binding MyItem, Mode = TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex = "{Binding MyIndex}"
</ComboBox>
The CollectionViewSource's source is an ObservableCollection.
MyCollectionViewSource.Source = MyObservableCollection;
Now, I have a Textbox. The comboBox shows strings. The SelectedItem is shown in the TextBox - therefore the user can change that string (re-name the SelectedItem of the ComboBox) and click a button, that holds the UpdateContentCommand.
<TextBox Text="{Binding MyText}"/>
Now, it does what it's supposed to do - but not in detail.
The SelectedItem shown in the ComboBox (more exactly: it's display, when dropdown is closed) has say a value of 'dog'. If I re-name it in the TextBox to 'cat', it still shows 'dog'. If I click on the ComboBox, and the dropdown appears, I can see there is no more item called 'dog', but one named 'cat'.
So, a workaround I found is as follows:
right in my Command-Method I set the SelectedIndex to -1, directly followed by SelectedIndex = 0 (or whichever index my SelectedItem has). This way it looks as if re-naming the SelectedItem did instantaneously update it.
So far I tried:
UpdateSourceTrigger = PropertyChanged in my XAML
Mode = TwoWay in XAML
MyCollectionViewSource.View.Refresh() in the command-method in my viewModel.
But nothing worked so far, and my workaround is more a hack.
Do I miss something obvious??
Related
I have a WPF control inheriting from ComboBox whose ItemsSource is bound to a list of elements, and whose SelectedItem is bound to another field. I have two references to the same list in my application: one resides in my MainWindow class, and one resides in my App class.
The list of elements used as the ItemsSource is assigned to the ComboBox at the time it is created. Thus, I expect to get an ItemsSourceChanged event.
The strange thing is, if the ItemsSource is bound to the MainWindow's list, then the ComboBox becomes populated with the correct SelectedItem drawn from the bound field. However, if I bind the ItemsSource to the App's copy of the list, then the SelectedItem becomes overwritten with null due to the ItemsSourceChanged event.
Here is the XAML for the ComboBox when it's binding to the App copy of the list:
<local:TagSelectionComboBox FilteredTagsList="{Binding Path=TagsList, Source={x:Static Application.Current}}" SelectedItem="{Binding TagValue}"></local:TagSelectionComboBox>
Here is the XAML for the ComboBox when it's binding to the MainWindow copy of the list:
<local:TagSelectionComboBox FilteredTagsList="{Binding TagsList, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding TagValue}"></local:TagSelectionComboBox>
Here is the MainWindow's property used for binding:
public ObservableCollection<Tag> TagsList
{
get { return proj.Sim.Tags; }
}
And here is the App's property used for binding:
public ObservableCollection<Tag> TagsList
{
get { return proj.Sim.Tags; }
}
So these two properties on App and MainWindow are returning the same list. Stepping through in the debugger confirms: proj.Sim.Tags contains the same list in both cases.
Why does changing from one binding source to another alter the binding behavior? Is something else going on?
I've found that if I explicitly set IsSynchronizedWithCurrentItem="True", then the behavior is the same in both cases. So it's almost like IsSynchronizedWithCurrentItem="True" is the default behavior when I use the MainWindow (RelativeSource) binding but IsSynchronizedWithCurrentItem="False" is the default behavior when I use the App (x:Static) binding.
Background: the ComboBox-derived control in this question is basically the same as YantingChen's answer for this question:
Dynamic filter of WPF combobox based on text input
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?
I've bound a ComboBox to my TextBox
<TextBlock Grid.Row="1" Name="DescriptionText" Text="{Binding ElementName=ScreenLocations, Path=SelectedItem.Description}" />
I have 4 ComboBoxes in my grid. What I want to do is, every time I select an item from any ComboBox, update the TextBox with the selected objects Description property.
Is it possible to bind multiple ComboBoxes to one TextBox, or would I need to use an event of some sort?
Create a property in your ViewModel and bind all your comboboxes' 'selectedItem' property to it (Use Mode="OneWayToSource", this will prevent changes on selectedItem of one ComboBox to affect the other), then bind your TextBox to the same property created in the VM with Mode="OneWay". Don't forget to implement INotifyPropertyChanged in your VM.
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!
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
}