WPF - Using CollectionViewSource is Causing Erroneous Setter Call - wpf

WPF, MVVM
I'm finding that if I use a CollectionViewSource with my ComboBox, when I close the window, an extra call to the SelectedValue Setter is executing, if SelectedValue is bound to a string property. If I set the ItemsSource binding directly to the VM, this call does not happen. The extra call is causing values to change in the VM, resulting in incorrect data. I have other ComboBoxes setup the same way, but they bind to integer values.
CollectionViewSource definition:
<CollectionViewSource x:Key="AllClientsSource" Source="{Binding AllClients}" >
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ClientName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
ComboBox with CollectionViewSource:
<ComboBox Grid.Column="2"
ItemsSource="{Binding Source={StaticResource AllClientsSource}}"
DisplayMemberPath="ClientName" SelectedValuePath="ClientId"
SelectedValue="{Binding Path=ClientId}"
Visibility="{Binding Path=IsEditingPlan, Converter={StaticResource BoolVisibility}}" />
ComboBox direct to VM (Forgoing sorting):
<ComboBox Grid.Column="2" ItemsSource="{Binding AllClients}"
DisplayMemberPath="ClientName" SelectedValuePath="ClientId"
SelectedValue="{Binding Path=ClientId}"
Visibility="{Binding Path=IsEditingPlan, Converter={StaticResource BoolVisibility}}" />
Can anyone tell me why there is an extra setter call using the CollectionViewSource? What's different about the string binding? Is there a way to properly work around it?
EDIT: I tried changing it up and using the SelectItem property on the ComboBox. Same result. So it seems that if the item is a scalar data type, it works as expected. If it's an object, you get an extra setter call with a null value. Again, if I remove the CollectionViewSource from the equation, it works as expected.
EDIT, AGAIN: I added a link to a sample project that illustrates the issue. Targets .Net 4.5.
Run the project.
Click to display View One
Select a Client and the client's name will display on the right.
Click to display View Two
Go back to View One - Note that the selected client is no longer selected.
Click to display View Three
Select a Region and the region's name is displayed on the right.
Go back to View Two
Go back to View Three - Note that the selected region is still selected.
The only difference between the views is that One and Two use a CollectionViewSource. Three binds directly to the ViewModel. When you move to a new tab from One or Two, the setter for the selected item is getting called with a null value. Why? What's the best work-around?
Thanks.

Apparently this is caused when the CollectionViewSource is removed from the visual tree... I moved the CollectionViewSource to the ViewModel and exposed it as a property and the issue is effectively worked-around.

Related

How to update an object's inner property from UI to VM? (WPF MVVM)

I have a property in my ViewModel, I'll call it "Project" which contains several nested lists inside of it. None of such lists has an associated property in the view model since I can show everything in xaml by using triggers and bindings.
My xaml shows the Project hierarchy in a treeview and its details in several views (a content control selects the right view depending on which item is selected on the treeview). One of those "details" is a property for the objects contained in one of the nested lists, I'm showing it in a textbox so the user can edit it, the problem I'm having is I don't see that property updated in the Project property in the VM once I make changes to it in the textbox.
I was told I have to create a property in the VM for that specific object's property I'm trying to edit, I just don't know how since the object is deep inside one of the nested lists of my Project object.
Common mistakes are:
Using OneWay binding mode instead of TwoWay
In XAML:
<TextBox Text="{Binding Path=Name, Mode=TwoWay}" />
Not realizing that the update is not propagated to the VM until after the TextBox lost focus.
this is the default for the TextBox:
<TextBox Text="{Binding Path=Name, UpdateSourceTrigger=LostFocus}" />
You could change it to:
<TextBox Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" />

Why is my ComboBox SelectedItem null?

I have a WPF/MVVM (using MVVM-Light) app setup with a ComboBox that is inside a DataTemplate. The XAML of the ComboBox looks like this:
<ComboBox x:Name="cbTeachers"
Grid.Column="1"
Style="{StaticResource ComboBox}"
ItemsSource="{Binding Teachers}"
Grid.Row="3"
DisplayMemberPath="Name"
SelectedValuePath="Id"
IsSynchronizedWithCurrentItem="False"
SelectedItem="{Binding Path=SelectedTeacher}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding TeacherSelectedCommand}"
CommandParameter="{Binding SelectedItem, ElementName=cbTeachers}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
The Teachers property for the ItemsSource is a type called ObservableRangeCollection and is based on the code found here: http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx, but it's very similar to a standard ObservableCollection. The SelectedTeacher property is set when another property is set and the code looks very similiar to this:
this.SelectedTeacher = (from t in this.Teachers where t.Id == this.DataItem.Teacher.Id select t).Single();
The problem I am running into, which makes zero sense to me, is SelectedTeacher is getting reset to null once I set it. I can step through the debugger and see SelectedTeacher has a value and when I put a breakpoint on the setter for the property it definitely has the value. But then that property gets hit again with a null value. I checked the call stack and it showed the only preceeding line as being External Code (which makes sense since I only set that property in one place and it only gets hit once, as expected). Expanding the External Code option in the call stack window shows the typical WPF call stack of maybe 40 methods so it's definitely internal to WPF and not something I am doing to make it reset. In fact, when I remove the SelectedItem="{Binding SelectedTeacher}" the setter for that property doesn't get called a second time (thus it retains its value), but of course the ComboBox doesn't show the selected item either. I tried implementing a SelectedIndex option in my viewmodel but that didn't work either. The ComboBox just won't select the item. I can change the selected item in the ComboBox just fine, but the initial setting won't take.
Any ideas? Based on everything I've searched it might be related to me using a DataTemplate, but I have to because that template is part of a parent ContentTemplateSelector implementation.
As a side note, I have multiple properties that bind to controls in this DataTemplate and this is the only one that doesn't work. The others work perfectly. I have also tried the ComboBox with and without the "IsSynchronizedWithCurrentItem" flag and it made no difference.
have you tried to remove to EventTrigger stuff and just to use
SelectedItem="{Binding Path=SelectedTeacher, Mode=TwoWay}"
with Mode=TwoWay?
its not clear to me what you want to achieve with your EventTrigger?

Combobox's SelectedValue (or SelectedItem) OneWay binding not working. Any ideas?

In the below window, the Existing Reports combo is bound to an observeablecollection of reportObjects. I have a reportObject property currentReport bound to the combo's SelectedValue property, OneWay. However, that's not working when bound in XAML.
SelectedValue="{Binding currentReport, Mode=OneWay}"
TwoWay binds fine, but I can't do it that way without writing an undo() method to the reportObject class. I'm binding the currentReport's properties to the various textboxes for editing. I want to bind OneWay so the source doesn't get changed. The currentReport's properties are all TwoWay bound to the corresponding textboxes so when I update the table in SQL [Save], it'll pull from that object, who's data is current.
<TextBox Text="{Binding currentReport.reportName, Mode=TwoWay}"
All of the properties bound from currentReport to the textboxes work fine as well. The only problem is the OneWay binding from the SelectedValue to the currentReport object. Does anyone have any ideas how to get this to work? I saw there was a bug, but the post I saw was 2009.
Sorry about the yellow. Not my idea. =)
EDIT: Added this XAML just in case.
<ComboBox ItemsSource="{Binding reportsCollection}" SelectionChanged="cboReports_SelectionChanged"
DisplayMemberPath="displayName"
SelectedValue="{Binding currentReport, Mode=TwoWay}"
x:Name="cboReports" Width="342" Height="40" VerticalAlignment="Center"/>
Forget about you need to change values - that is a separate problem - need to review your data design. Start with the UI problem question. If you want a user to be able to select an item from a combo box then it must have two way binding. Your first question is SelectedValue="{Binding currentReport, Mode=OneWay}" is failing why?

Compare objects to set selected value in pre-loaded combobox silverlight MVVM

I'm trying to set seletected value to a pre-loaded combobox using silverlight with MVVM.
I load these combobox items before selecting value.
For example I have a combobox to select a country. My first step is to load a List which is bound to the combobox source. This is loading perfectly.
After this, I have a "SelectedCountry" object bound to the selectedItem of the combobox in a two-way binding.
This is working perfect when I select any of the combobox values and my SelectedCountry object is correctly selected.
The problem comes when I try to assign the selected value in my ViewModel. This way, the combobox selecteditem is not updated.
I suppose this is because, on fact, they are not the same object (they have the same values but they are diferent references).
Should this work if I re-implement the equals method? Or should I find the same object by searching into the List?? This would be very easy because this two countries would be the same if they have the same id... but I can have more complex objects and I think the equals method would be better.
Thanks in advance!!
Edit for adding some code example:
<ComboBox Grid.Column="7" Margin="6,0" Name="cBTipoPoliza" VerticalAlignment="Center" TabIndex="4" ItemsSource="{Binding TiposPolizas, Mode=OneWay}" SelectedItem="{Binding TipoPoliza, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding nombre_tipo}" />
</DataTemplate>
</ComboBox.ItemTemplate>
Usually I just override the .Equals() method to check if they are equal by ID or Name
You should try to avoid having multiple copies of the same object in memory at the same time. One method to do this is to have your VM be the source of objects. When you load the list have the VM do it and expose an AvailableCountries ObservableCollection property on the VM that your ComboBox can bind to.
If your objects are semantically equal based on ID, definitely override Equals and == and != and hashcode. However be careful because if you're enabling people to update the objects you can run into collisions (even within the same instance of the app) where one screen is holding onto stale data.

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.

Resources