DependencyProperty does not work if the value is from a binding - c#

I created UserControl with viewmodel. It has DependencyProperty which only works if the value is passed directly. If the value is passed through the binding, it no longer works.
Here is the view code:
This is a closed element not associated with any other. All listed items belong to him. This is a code shortening, I am not going to present whole, immeasurable structures.
View
public partial class SomeView : UserControl
{
public SomeView()
{
InitializeComponent();
SetBinding(ActiveProperty, new Binding(nameof(SomeViewModel.Active)) { Mode = BindingMode.OneWayToSource });
}
#region ActiveProperty
public static readonly DependencyProperty ActiveProperty = DependencyProperty.Register(nameof(Active), typeof(bool), typeof(VNCBoxView));
public bool Active
{
get { return (bool)GetValue(ActiveProperty); }
set { SetValue(ActiveProperty, value); }
}
}
VievModel
public class SomeViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool active;
public bool Active
{
get { return active; }
set
{
active = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Active)));
}
}
}
UserControl
<UserControl ...>
<UserControl.DataContext>
<viewModels:SomeViewModel />
</UserControl.DataContext>
<Grid>
<TextBlock Text="{Binding Active}" />
</Grid>
</UserControl>
===================================================
When working with a ready component, which is an individual, separate entity, the problem occurs depending on how it is used.
I remind you that the elements used in the view in question are a closed whole that does not connect with the element in which it is used. It is the transfer of value that is the matter of the problem.
This is working usage:
<local:SomeView Active="True" />
In viewmodel, the setter is invoked twice, once with false and then with true.
If the value comes from binding, it doesn't work:
<local:SomeView Active="{Binding SomeParentProperty}" />
In viewmodel, setter is only called once with the value false.
Setters in a view are never called, in both cases.
Please help

There is no IsConnected property in the SomeViewModel instance in the current DataContext of the UserControl, hence the Binding
<local:SomeView Active="{Binding IsConnected}" />
won't work. It tries to resolve the PropertyPath against the current DataContext, unless you explicitly specify its Source, RelativeSource or ElementName.
This is the exact reason why UserControls should never explicitly set their own DataContext, and hence never have something like an own, private view model.
The elements in the UserControl's XAML would not bind to properties of such a private view model object, but directly to the properties of the UserControl, for example like
<TextBlock Text="{Binding Active,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>

When you set the DataContext explicitly in the UserControl like this:
<UserControl.DataContext>
<viewModels:SomeViewModel />
</UserControl.DataContext>
...you can no longer bind to SomeView's DataContext in the consuming view like this:
<local:SomeView Active="{Binding IsConnected}" />
...because SomeViewModel doesn't have any IsConnected property.
You should avoid setting the DataContext explicitly and let the UserControl inherit its DataContext from its parent element. You can still bind to the dependency property of the UserControl itself using a RelativeSource or an ElementName:
<UserControl ...>
<Grid>
<TextBlock Text="{Binding Active, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</Grid>
</UserControl>
Besides, SomeViewModel seems superfluous in your example since the UserControl already has an Active property.

Related

How to write getters and setters to convert WPF dependency property? [duplicate]

I am trying (and failing) to do data binding on a dependency property in xaml. It works just fine when I use code behind, but not in xaml.
The user control is simply a TextBlock that bind to the dependency property:
<UserControl x:Class="WpfTest.MyControl" [...]>
<TextBlock Text="{Binding Test}" />
</UserControl>
And the dependency property is a simple string:
public static readonly DependencyProperty TestProperty
= DependencyProperty.Register("Test", typeof(string), typeof(MyControl), new PropertyMetadata("DEFAULT"));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
I have a regular property with the usual implementation of INotifyPropertyChanged in the main window.
private string _myText = "default";
public string MyText
{
get { return _myText; }
set { _myText = value; NotifyPropertyChanged(); }
}
So far so good. If I bind this property to a TextBlock on the main window everything works just fine. The text update properly if the MyText changes and all is well in the world.
<TextBlock Text="{Binding MyText}" />
However, if I do the same thing on my user control, nothing happens.
<local:MyControl x:Name="TheControl" Test="{Binding MyText}" />
And now the fun part is that if I do the very same binding in code behind it works!
TheControl.SetBinding(MyControl.TestProperty, new Binding
{
Source = DataContext,
Path = new PropertyPath("MyText"),
Mode = BindingMode.TwoWay
});
Why is it not working in xaml?
The dependency property declaration must look like this:
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register(
nameof(Test),
typeof(string),
typeof(MyControl),
new PropertyMetadata("DEFAULT"));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
The binding in the UserControl's XAML must set the control instance as the source object, e.g. by setting the Bindings's RelativeSource property:
<UserControl x:Class="WpfTest.MyControl" ...>
<TextBlock Text="{Binding Test,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</UserControl>
Also very important, never set the DataContext of a UserControl in its constructor. I'm sure there is something like
DataContext = this;
Remove it, as it effectively prevents inheriting a DataContext from the UserConrol's parent.
By setting Source = DataContext in the Binding in code behind you are explicitly setting a binding source, while in
<local:MyControl Test="{Binding MyText}" />
the binding source implicitly is the current DataContext. However, that DataContext has been set by the assignment in the UserControl's constructor to the UserControl itself, and is not the inherited DataContext (i.e. the view model instance) from the window.

Synchronizing Dependency Properties (View) with Properties (ViewModel)

Does anybody know how I can synchronize my properties, which are in a ViewModel, with my Dependency Properties, which are in the View?
I am trying to make a UserControl, which will then be hosted by a WPF-Window (MainWindow.xaml). The UserControl has an own ViewModel which contains ICommands and properties.
The problem is, that I also have to return certain properties to the MainWindow(.xaml) and also set them.
Currently my classes are looking like that:
MainWindow.xaml
<TextBox Name="tbInput" VerticalAlignment="Top" HorizontalAlignment="Stretch" Grid.Row="0"></TextBox>
<local:View x:Name="appEntryView" Pfad="{Binding ElementName=tbInput, Path=Text}" Grid.Row="1" Margin="10"/>
View.xaml
<UserControl x:Class="DependencyProperties.Test"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DependencyProperties_Intro"
x:Name="obj"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding ElementName=obj, Path=Pfad}"/>
</Grid>
View.xaml.cs
public partial class View: UserControl, INotifyPropertyChanged
{
public String Pfad
{
get { return (String)GetValue(PfadProperty); }
set { SetValue(PfadProperty, value); OnNotifyPropertyChanged("Pfad"); }
}
// Using a DependencyProperty as the backing store for Path. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PfadProperty =
DependencyProperty.Register("Pfad", typeof(String), typeof(GraphSharpTest), new PropertyMetadata(default(string)));
public View()
{
InitializeComponent();
this.DataContext = new ViewModel();
var name = "Pfad";
var binding = new Binding(name) { Mode = BindingMode.TwoWay };
this.SetBinding(PfadProperty, binding);
}
}
ViewModel.cs
public class ViewModel: INotifyPropertyChanged
{
private String m_Pfad;
public String Pfad
{
get { return m_Pfad; }
set { m_Pfad = value; OnNotifyPropertyChanged("Pfad"); }
}
public void OnNotifyPropertyChanged(String info)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(info));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The dependency property works fine, but the setter method of "Pfad" in the ViewModel never gets called at all.
Thanks in advance!
Raising PropertyChanged in the CLR properties of dependency properties is a common mistake. You should not place any code there as it is not used by bindings at all. They exist merely for setting the property once in code or XAML, thus you also will not hit any breakpoints you set there.
var name = "Pfad";
var binding = new Binding(name) { Mode = BindingMode.TwoWay };
this.SetBinding(PfadProperty, binding);
I take it you want to forward the value to your view-model. This is not going to work as you can only bind the property once. Right now you also bind the property here:
<local:View x:Name="appEntryView" Pfad="{Binding ElementName=tbInput, Path=Text}" Grid.Row="1" Margin="10"/>
You could subscribe to the dependency property changes using the respective meta data when registering it, providing a callback, there you could set the value in the view-model.
The thing is: The view-model is private to the View, there really is no point in doing this synchronization if no-one has access to the data. You probably want the property to be either settable from the outside, treating the UserControl more like a control, discarding the view-model, or you want the view-model to be passed from outside as DataContext, and the view binds directly to it.
You need to be careful with explicitly setting the DataContext of UserControls in their definition, as it can obfuscate what is happening and lead to bindings unexpectedly breaking. If you want to set properties on the UserControl instance i would recommend avoiding it.

WPF User Control Dependency Property not working when bound to a ViewModel property

I have two user-controls: a LocationTreeView, and a LocationPicker. The LocationTreeView organizes Locations into a tree structure. Because of the number of locations involved, only parts of the tree are loaded at once (one level at a time as items are expanded).
The LocationPicker is little more than a textblock with a button that opens a modal window with a LocationTreeView on it.
When I bind my LocationPicker's "SelectedLocation" property to my Viewmodel, it works fine. When I bind my LocationTreeView to the viewmodel, the binding doesn't seem to have any effect at all. When I bind my LocationTreeView to a "dummy" LocationPicker (which is bound to my viewmodel) it works.
How can I get my LocationTreeView to bind to my viewmodel?
public partial class LocationTreeView: UserControl
{
public EventHandler LocationChanged;
...
public static readonly DependencyProperty SelectedLocationProperty =
DependencyProperty.Register("SelectedLocation",typeof(Location), typeof(LocationTreeView),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedLocationChanged));
...
public static void SelectedLocationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LocationTreeView sender = (d as LocationTreeView);
Location loc = e.NewValue as Location;
//Navigate the treeview to the selected location
sender.LoadLTreeViewPathToLocation(loc);
}
public Location SelectedLocation
{
get { return (Location)GetValue(SelectedLocationProperty); }
set
{
if (SelectedLocation != value)
{
SetValue(SelectedLocationProperty, value);
if (LocationChanged != null)
{
LocationChanged(this, EventArgs.Empty);
}
}
}
}
...
}
Binding on this control works fine when bound to another control, but not when bound to my viewmodel. I've set a breakpoint in the SelectedLocationChanged callback, it doesn't seem to get fired when I set the viewmodel property (which DOES implement INotifyPropertyChanged)
public partial class LocationPicker: UserControl
{
public static readonly DependencyProperty SelectedLocationProperty =
DependencyProperty.Register("SelectedLocation",typeof(Location), typeof(LocationPicker),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
...
public Location SelectedLocation
{
get { return (Location)GetValue(SelectedLocationProperty); }
set { SetValue(SelectedLocationProperty, value); }
}
...
private void Button_Click(object sender, RoutedEventArgs e)
{
// create a window with a locationtreeview on it. Set the treeview's
// selectedlocation property, open the window, wait for the window to close,
// set this.SelectedLoctation to the treeview's selected location.
}
}
I apologize for the leaving out so much code. My work enviroment prevents me from being able to copy/paste.
I've left out the code for the ViewModel. I am quite confident that it is not the issue.
Update:
The LocationTreeView has a ViewModel that is set in the xaml
<UserControl.DataContext>
<VM:LocationTreeViewViewModel />
</UserControl.DataContext>
The LocationPicker does not have a ViewModel.
On the window that I am using the controls, the xaml looks something like this
<Widow.DataContext>
<VM:TestWindowViewModel />
</Window.DataContext>
<Grid>
...
<UC:LocationPicker x:Name="picker" SelectedLocation="{Binding Location}" />
<!-- this does not work -->
<UC:LocationTreeView SelectedLocaiton="{Binding Location}" />
<!-- but this works --->
<UC:LocationTreeView SelectedLocaiton="{Binding SelectedLocation, ElementName=picker}" />
...
</Grid>
If you want to data bind from your view model to the LocationTreeView, then you should use the property in the view model to data bind to. If your view model had a property named SelectedLocationInViewModel in it, then you should use that to data bind to:
<UC:LocationTreeView SelectedLocation="{Binding SelectedLocationInViewModel}" />
I think that I see what your problem is now... you want to define some properties in the UserControl and data bind to them, but also data bind to properties from the view model that is set as the DataContext. You need to use a RelativeSource Binding to do that... just look at the Binding Paths in these examples:
To data bind to properties declared in a UserControl from within the UserControl:
<ItemsControl ItemsSource="{Binding PropertyName, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourUserControl}}}" />
To data bind to properties declared in any object set as the DataContext:
<ItemsControl ItemsSource="{Binding PropertyName}" />

WPF with MVVM and DataAnnotations, Validation Errors in a UserControl

I have a UserControl that will be reused throughout an application we are developing.
We are using a framework based on MVVMLight.
For the sake of simplicity lets say the user control contains only one textbox and exposes one dependency property named "Quantity". The textbox on the user control is databound to the dependency property "Quantity".
When the user control is used on a view, the "Quantity" dependency property of the usercontrol is databound to a property in a ViewModel. (This ViewModel is the datacontext of our view by way of the MVVMLight ViewModelLocator).
This all works great! The bindings work, properties are set like I would expect. All is well until it comes to validation.
We are using DataAnnotations to decorate our ViewModel properties. The ViewModel contains a custom implementation of INotifyDataErrorInfo. We have implemented custom styles for most input controls to show a red border around the control, and a message next to the control displaying the validation error message. All of this works great in a normal case (eg. Textbox on a View bound to a property in a view model).
When I attempt the same approach using this user control, what I end up with is a red border around the entire user control and no error indication on the actual textbox. It appears that the fact that there is an error is being reflected in the UI, but it's just not making it to the control I want it to.
I've searched on stackoverflow for this problem, of those questions with solutions, none seem to work for my situation.
My first guess is that because the actual textbox is bound directly to the dependency property itself and not the property on my view model, it is not being notified properly of the errors generated. Is there some way to propogate those errors generated in the viewmodel through the usercontrol and then to the textbox?
Any help or suggestions you can give would be great, thanks.
Here is the UserControl xaml.
<UserControl x:Class="SampleProject.UserControls.SampleControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" x:Name="sampleControl"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="LayoutRoot" DataContext="{Binding ElementName=sampleControl}">
<TextBox Text="{Binding Path=Quantity, ValidatesOnDataErrors=True}" Width="100" Height="30" />
</Grid>
</UserControl>
The UserControl code behind.
public partial class SampleControl : UserControl
{
public SampleControl()
{
InitializeComponent();
}
public static readonly DependencyProperty QuantityProperty =
DependencyProperty.Register("Quantity", typeof(int?), typeof(SampleControl),
new FrameworkPropertyMetadata{DefaultValue=null, BindsTwoWayByDefault = true});
public int? Quantity
{
get { return (int?)GetValue(QuantityProperty); }
set { SetValue(QuantityProperty, value); }
}
}
Used on a view.
<userControls:SampleControl Grid.Row="1" Quantity="{Binding Path=Quantity, ValidatesOnDataErrors=True}" Height="60" Width="300"/>
The ViewModel property.
[Required(ErrorMessage = "Is Required")]
[Range(5, 10, ErrorMessage = "Must be greater than 5")]
public int? Quantity
{
get { return _quantity; }
set { Set(() => Quantity, ref _quantity, value); }
}
private int? _quantity;
(*Note, The Set method in the setter is just a helper method in the base viewmodel that sets the backing property and raises the PropertyChanged event for it.)
Try removing the DataContext from the UserControl. Instead of setting that, Bind directly from the TextBox to the actual property using a RelativeSource Binding:
<TextBox Text="{Binding Quantity, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type YourControlNamespace:SampleControl,
ValidatesOnDataErrors=True}}}" Width="100" Height="30" />
UPDATE >>>
Failing that, as long as the view models that are bound to this property will always have a property of the same name to bind to, you can get this Binding to search through parents' DataContexts like this:
<TextBox Text="{Binding Quantity, RelativeSource={RelativeSource Mode=FindAncestor,
AncestorLevel=2, ValidatesOnDataErrors=True}}}" Width="100" Height="30" />
You will need to change the 2 to be the correct number of parent elements that the TextBox has before reaching the control with access to the correct property. For example, using a level of 2 means that the Framework will try to find a property named Quantity to Bind to in the DataContext of the TextBoxs parent's parent control. It is trickier getting this working with AncestorLevel though as I believe that 'hidden' elements like Grids are not included as parents.
You need to pick up the bindings set on the usercontrol and place them on the controls, there is no need to bind the usercontrol to it's own DataContext.
This can be done after the usercontrol is loaded.
To prevent a red border round the user control, remove the default error template:
Validation.ErrorTemplate="{x:Null}"
Sample user control XAML:
UserControl x:Class="DxUserControlValidation.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Validation.ErrorTemplate="{x:Null}"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Vertical">
<TextBlock Text="Value 1:" Margin="2"/>
<TextBox Name="txtBox1" Margin="2"/>
<TextBlock Text="Value 2:" Margin="2"/>
<TextBox Name="txtBox2" Margin="2"/>
</StackPanel>
public partial class MyUserControl : UserControl
{
public static readonly DependencyProperty Value1Property;
public static readonly DependencyProperty Value2Property;
static MyUserControl()
{
Value1Property = DependencyProperty.Register("Value1", typeof(string), typeof(MyUserControl), new FrameworkPropertyMetadata { DefaultValue = null, BindsTwoWayByDefault = true });
Value2Property = DependencyProperty.Register("Value2", typeof(string), typeof(MyUserControl), new FrameworkPropertyMetadata { DefaultValue = null, BindsTwoWayByDefault = true });
}
public MyUserControl()
{
InitializeComponent();
Loaded += (s, e) =>
{
Binding value1Binding = BindingOperations.GetBinding(this, Value1Property);
if (value1Binding != null) txtBox1.SetBinding(TextBox.TextProperty, value1Binding);
Binding value2Binding = BindingOperations.GetBinding(this, Value2Property);
if (value2Binding != null) txtBox2.SetBinding(TextBox.TextProperty, value2Binding);
};
}
public string Value1
{
get { return (string)GetValue(Value1Property); }
set { SetValue(Value1Property, value); }
}
public string Value2
{
get { return (string)GetValue(Value2Property); }
set { SetValue(Value2Property, value); }
}
}
If there is no binding, you van assign the value directly to the control:
if (value2Binding != null) txtBox2.SetBinding(TextBox.TextProperty, value2Binding);
else txtBox2.Text = Value2;

WPF UserControl Binding Problem

I like to create a UserControl with own Header Property.
public partial class SomeClass: UserControl, INotifyPropertyChanged
{
public SomeClass()
{
InitializeComponent();
}
private string header;
public string Header
{
get { return header; }
set
{
header = value;
OnPropertyChanged("Header");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
in UserContol xaml:
Label Name="lbHeader" Grid.Column="0" Content="{Binding Path=Header}"
If I set the value: AA2P.Header = "SomeHeeaderText"; than the label.Caption will not changed. How can I solve that problem?
In Windows xaml:
uc:SomeClass x:Name="AA2P"
If I give directly a value to label (lbHeader.Content = header;) instead of OnPropertyChanged("Header"); its work but, why it does not work with OnPropertyChanged?
I need to use DataContext for somethig else. I try to use dependency property but something is wrong.
public partial class tester : UserControl
{
public tester()
{
InitializeComponent();
}
public string Header
{
get { return (string)GetValue(MyDependencyProperty); }
set { SetValue(MyDependencyProperty, value); }
}
public static readonly DependencyProperty MyDependencyProperty =
DependencyProperty.Register("MyDependencyProperty", typeof(string), typeof(string));
}
<UserControl ... x:Name="mainControl">
<TextBlock Text="{Binding ElementName=mainControl, Path=MyDependencyProperty}"/>
</UserControl>
<Window ...>
<my:tester Header="SomeText" />
</Window>
It does not work. What I do wrong?
Thanks!
The easiest approach is to just the DataContext of your object. One way of doing that is directly in the constructor like this:
public SomeClass()
{
InitializeComponent();
DataContext = this;
}
Setting the DataContext will specify where new data should be fetched from. There are some great tips and information in the article called WPF Basic Data Binding FAQ. Read it to better understand what the DataContex can be used for. It is an essential component in WPF/C#.
Update due to update of the question.
To my understanding you should change the first argument of DependencyProperty.Register to the name of the property that you want to bind to, here "Header" as well as the second argument to the type of your class, here SomeClass. That would leave you with:
public static readonly DependencyProperty MyDependencyProperty =
DependencyProperty.Register("Header", typeof(SomeClass), typeof(string));
But i seldom use dependency properties so I am not positive that this is it, but its worth a try..
If you need the Data context for something else. You can also utilize the ElementName property in the Binding.
<UserControl
x:Class="MyControl.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="mainControl">
<TextBlock Text="Binding ElementName=mainControl, Path=MyDependencyProperty}"/>
</UserControl>
[Edit]
I should add something. Make the "Header" property a dependency property, this will make your live much easier. In UI Controls you should make property almost always a dependency property, every designer or user of your control will thank you.
The UserControl itself needs the DataContext of where it is used later. But the controls inside the UserControl need the UserControl as their DataContext, otherwise they also will inherit the DataContext from the later usage context. The trick is to set the DataContext of the UserControl's child to that of the UserControl, so it now can use the dependency properties of the UserControl.
<UserControl x:Class="MyControl.MyUserControl">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl,AncestorLevel=1}}">...</Grid>
</UserControl>
If you do this this way the children of the Grid can have simple {Binding dp's name} without additionally ElementName parameters.

Categories

Resources