Property resets when changing Usercontrols DependencyProperty - wpf

I've constructed a "Manager" of "Widgets" where i have a ListView/GridView with widgets.
The ListView binds the SelectedItem to a SelectedWidget property on the (Manager) ViewModel.
The SelectedWidget binds to a Usercontrol called WidgetConfig via a DependencyProperty called "Widget" where you can modify the properties of the selected Widget.
In the WidgetConfig , there are 2 RadioButtons:
<RadioButton GroupName="Lead" IsChecked="{Binding Widget.prop1, Mode=TwoWay, ElementName=root}" />
<RadioButton GroupName="Lead" IsChecked="{Binding Widget.prop2, Mode=TwoWay, ElementName=root}" />
When i select the 'prop2', the INPC signal is correctly sent and the model is updated accordingly. If then i click on the 'prop1', the INPC signal is sent + i get an additional signal from the prop2 radiobutton.
Here's the problem:
if i select prop2 and then select another Widget in the ListView. I get a INPC signal and prop2 has changed back to false!
what's going on ?
Some info:
The WidgetConfiguration is implemented using PostSharp NotifyPropertyChanged aspect.
The WidgetConfig UserControl is implemented by hand with DependencyProperty and INotifyPropertyChanged interface
EDIT:
I've created a reproducible example here : https://github.com/Montago/INCP-DP-BindingExample

What you have is a chain of two-way bindings, which are changing values on your Model:
Two-way binding between ListControl.SelectedItem(DP) and the ViewModel.SelectedModel(INPC).
Two-way binding between ViewModel.SelectedModel(INPC) and ModelControl.ActiveModel(DP).
Two-way binding between ModelControl.ActiveModel(DP) and Model.Prop1/Model.Prop2/Model.Name(INPC).
When you select First, Second and then again First, Second's prop2 is overwritten to false by WPF data binding.
A way to fix this is to remove the two-way binding that makes the overwrite of prop2, i.e. specify Mode=OneWay on RadioButton bindings.
My guess of what is happening: RadioButton's logic and two-way binding. When Second is selected in the list box and you change the selection to First, WPF's data binding sets Prop1 radio button as selected (because First.prop1 == true). At that point, the second radio button is still selected, so to respect the group principle, it needs to get unselected. However, there is still the two-way binding active on the Prop2 radio and thus Second.prop2 gets set to false.

Related

Is WPF UpdateSourceTrigger always needed to notify model of changes in collection?

Question: Was UpdateSourceTrigger always necessary to have properties update the source? I seem to recall that Mode=TwoWay was enough a long time ago. But now, I have to UpdateSourceTrigger=PropertyChanged? Maybe I'm losing it...
<DataGridTemplateColumn Header="Hub" Width="SizeToHeader">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="XHub" IsChecked="{Binding Hub, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
When I omit that part of the binding the model is never called when a value in the ObservableCollection changes.
Both Mode and UpdateSourceTrigger properties of the binding depends on the dependency property you are binding to. According to MSDN:
The default is Default, which returns the default UpdateSourceTrigger value of the target dependency property. However, the default value for most dependency properties is PropertyChanged, while the Text property has a default value of LostFocus.
A programmatic way to determine the default UpdateSourceTrigger value of a dependency property is to get the property metadata of the property using GetMetadata and then check the value of the DefaultUpdateSourceTrigger property.
So in your case you are binding to IsChecked that is defined in ToogleButton class as following:
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof (bool?), typeof (ToggleButton),
(PropertyMetadata) new FrameworkPropertyMetadata(
BooleanBoxes.FalseBox,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
new PropertyChangedCallback(ToggleButton.OnIsCheckedChanged)));
So BindsTwoWayByDefault makes Mode=TwoWayredundant, while
querying metadata:
var def = ((FrameworkPropertyMetadata)CheckBox.IsCheckedProperty.GetMetadata(typeof(CheckBox))).DefaultUpdateSourceTrigger;
results in PropertyChanged, that makes the UpdateSourceTrigger=PropertyChanged part also redundant.
Edit: about this part:
When I omit that part of the binding the model is never called when a value in the ObservableCollection changes.
Sincerely, I cannot explain myself the behavior that you've noticed (but it's quite easy to reproduce). I was expecting the binding to update viewmodel according to dependency property default UpdateSourceTrigger. But I've noticed that the ViewModel is updated not even on focus lost, but when you either move focus to next row or press enter. So the explanation that Colin Eberhardt gives in his blog seems the best I can find. Obviously this behavior is strongly related to DataGrid and if you would have the same checkbox outside of the grid, then the ViewModel would update as expected without explicit UpdateSourceTrigger set to PropertyChanged.
When you bind to a DataTable, you are actually binding to your DataTable's DefaultView, which is of type DataView. As a result, each row of your table will be bound to a DataRowView. If you look at the documentation for DataRowView you will find that it implements the IEditableObject interface which is the significant factor here. This interface allows you to perform transactional changes to your object, i.e. you can change the object's properties within a 'transaction', then commit then all in a single atomic action. By default, when you bind to a DataGrid this occurs when the user finishes editing a row, either by moving focus or hitting Enter.
Was UpdateSourceTrigger always necessary to have properties update the source?
No. The UpdateSourceTrigger property of a binding specifies what triggers the update of a source property whereas the Mode property lets you control the direction of the data flow. These are two different things.
Even if you don't explicitly set the UpdateSourceTrigger property your Hub source property will indeed still be set, but not until the you step out of the cell of the DataGrid.

Bind multiply Comboboxes to a TextBox

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.

Binding UI-element to UI-element like Textbox to Textbox

I have UI-elements like a textbox and I want to bind them to a ViewModel. I need to access many properties of the textbox like the text- or the IsEnabled-property.
Is it possible that I bind the textbox directly to another textbox in the ViewModel with all their properties instead of binding every single property to properties?
Yes, using an ElementName, but you still bind all properties though.
<!-- Bound to ViewModel -->
<TextBox Name="tbOne" IsEnabled="{Binding OneIsEnabled}" Text={Binding TextOne}/>
<TextBox Name="tbTwo" IsEnabled="{Binding ElementName=tbOne, Path=IsEnabled}" Text={Binding ElementName=tbOne, Path=Text}/>
There is no built-in way to bind all dependency properties of the TextBox to another. Personally, I would prefer binding directly to the ViewModel.
An alternative solution would be to create a UserControl that internally clones the TextBox with all bindings:
<CloneControl Target="{Binding ElementName=tbOne}"/>
Here, the ControlControl would inspect the target, and have code that create a new TextBox, and sets the bindings in code. This is only useful if you are doing this very often, and there is a slightly performance price to pay, as you are adding another level of controls to the UI tree.

Retrieving value of a label in which is binded to an element value

In my current scenario (WPF, MVVM), I have a user control which hosts a visio diagram. This user control is located on a view, next to a number of labels and a datagrid element.
The user control contains a DependencyProperty object SelectedNode which value is updated with the information received from the Visio diagram. The labels' content are binded so that they display the information contained in the SelectedNode (e.g. id, name):
<Label Grid.Row="1" Grid.Column="1" x:Name="lbNodeIdValue" HorizontalAlignment="Left"
Content="{Binding ElementName=visioControlUC, Path=SelectedNode.Id, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
Every time I change the selection in the diagram, the label's content changes as expected.
Next to this label, I would like to display a datagrid containing information based on the id displayed in the label. This is where I ran into problems, as I can't seem to be able to get the value of the Content property of the label in the viewmodel class.
I have tried using the MultiBinding property on the Content element of the label, and creating a second binding with Mode=OneWayToSource to set the value of the label's Content to a property I have defined in the viewmodel class.
What would be a proper way to retrieve this value in my viewmodel class?
Thanks,
Adrian
Ideally your Datagrid's ViewModel should get the value of the selected label from the other ViewModel. You should not rely on Views to transfer application data between ViewModels.
It sounds like the SelectedNode value originates from the UserControl, and not the ViewModel, so you'll need to bind the UserControl.SelectedNodeId to a ViewModel somewhere so the ViewModels have access to this data
<local:myUserControl x:Name="visioControlUC"
SelectedNode="{Binding SelectedNodeId}" />
If the value is needed by more than one ViewModel, I would highly recommend some kind of event system, such as MVVM Light's Messenger or Prism's EventAggregator. This would allow your ViewModels to subscribe to something like a SelectedNodeChangedEventMessage, and the ViewModel which actually contains the SelectedNodeId can broadcast that message anytime the value changes. You can find an example of both on my blog post about Communication between ViewModels.

Setting a property on the ViewModel from the View in WPF

I have a dependency property on my ViewModel which is the DataContext for my View. The ViewModel has no reference to the View. The property on the ViewModel is going to reference a control on the view, but I need to be able to set this property in XAML.
How is this possible? One thought I had was to develop a custom control which has a Property property and a Value property, so you could do something like this in the View to set the property on the ViewModel:
<PropertySetter Property="{Binding MyViewModelDependencyProperty}" Value="{Binding ElementName=aControlOnMyView" />
Before I went down this route, I wanted to check if there was any other approach I could take?
Thanks for the detailed reply Ray, but if I give you a bit more detail about the problem I'm trying to solve, you might get a better idea of why I mentioned the approach I did.
Basically, what I'm trying to do is set the focus to a textbox when the user hits a button. I've written an attached property which you can attach to the Button control, specify what the trigger event is (in this case the 'Click' event), and then what control to focus on. This works really nicely, and keeps everything in XAML.
However, I now have a use case where the focus should be set to an arbitrary text box from the click event on a button which is part of a toolbar. This toolbar is itself a user control which is sitting inside another user control, which is inside another user control! This toolbar needs to be reusable across various different forms, and each time, the control to set focus on after you click the button will be different per form.
That's why I had the idea of making the focus control (i.e. a textbox) a property on the view model itself (on my ViewModel base to be precise), and have the ViewModel base code (which the toolbar is bound to), set the focus to the control when the button is clicked (and the e.g. Add/Edit method is called on the ViewModel base).
In unit test land, the control to focus on property will be null, so it's .Focus() method just won't be called. So I can't see an issue there. My problem is then how you set the focus control property from XAML, which is why I had the PropertySetter idea.
I don't like the fact that the ViewModel has any reference to controls sitting on the view, but I can't see another way to achieve what I need. What if the logic that dictates whether to set focus to the control is quite complex? This would sit in the ViewModel surely? Therefore, is there any harm in the ViewModel having this UIElement property? It still knows nothing about the specific View it is bound to, it just knows that there is a control which it needs to set focus to when some action happens on the ViewModel.
My first reaction (and it's a strong one) is so say "Don't do that!" By giving your view model a reference to a part of your UI you are breaking the encapsulation that makes view models so powerful and useful.
For example, what if you want to unit test your view model or serialize it to disk? In each case the piece of your UI will not be present, because there will be no view at all. Your tests will miss coverage and your reconstitution will be incomplete.
If your view model actually needs references to UI objects and there is no better way to architect it, the best solution is to have the view model itself construct those controls it requires a reference to. Then your view can incorporate that control as the Content of a ContentPresenter via binding and provide a Style to configure the control, including a ControlTemplate to provide its content. Thusly:
public class MyViewModel
{
public ListBox SpecialControl { get; set; }
public MyViewModel()
{
SpecialControl = new ListBox();
}
}
and
<DataTemplate TargetType="{x:Type local:MyViewModel}">
<DataTemplate.Resources>
<Style TargetType="ListBox" ... />
</DataTemplate.Resources>
...
<ContentPresenter Content="{Binding SpecialControl}" />
</DataTemplate>
Other possibilities are:
Have the view model actually derive from the Control class, then override OnApplyTemplate() and use GetTemplateChild to find a template item whose name starts with "PART_"
Implement an attached property that takes a property name, finds that property in the DataContext, and sets it to the DependencyObject to which the property is attached.
Implement your PropertySetter idea
My option #2 would look like this:
<DataTemplate TargetType="{x:Type MyViewModel}">
...
<TextBox local:PropertyHelper.SetViewModelToThis="SpecialControl" />
...
</DataTemplate>
The code in the SetViewModelToThis PropertyChangedCallback would get the view model from the DataContext, reflect on it to find the "SpecialControl" property, then set it to the TextBox. Note that the implementation of SetViewModelToThis must take into account the possiblity that DataContext is not set right away, and that it maybe changed requiring the old setting to be removed and a new one made.
First of all, the DataContext of the control should be the ViewModel object and not a property of it. Second, when you TwoWay bind a property of ViewModel to your control, changes in the control's value will update (in your case, 'set') the value of ViewModel's property.

Resources