Trying to create my own custom AttachedProperty for a WPF DependencyObject failed to actually do what I wanted it to do, and I am a bit worried that I (again) did not understand a WPF concept fully.
I made a very simple test class to show where my problem lies. From the MSDN Documentation, I copied
public class TestBox : TextBox
{
public static readonly DependencyProperty IsBubbleSourceProperty = DependencyProperty.RegisterAttached(
"IsBubbleSource",
typeof(Boolean),
typeof(TestBox)
);
public static void SetIsBubbleSource(UIElement element, Boolean value)
{
element.SetValue(IsBubbleSourceProperty, value);
}
public static Boolean GetIsBubbleSource(UIElement element)
{
return (Boolean)element.GetValue(IsBubbleSourceProperty);
}
public Boolean IsBubbleSource
{
get
{
return (Boolean)GetValue(IsBubbleSourceProperty);
}
set
{
SetValue(IsBubbleSourceProperty, value);
}
}
}
Now, placing my new and funky TextBox into a Grid like this
<Grid vbs:TestBox.IsBubbleSource="true">
<vbs:TestBox x:Name="Test" Text="Test" >
</vbs:TestBox>
</Grid>
I expected every child that does not set the IsBubbleSource property itself to "inherit" it from its parent grid. It does not do this; a MessageBox.Show(Test.IsBubbleSource.ToString()) shows "false". The attached property is set to true. I checked this using an OnPropertyChanged event handler. Did I miss something?
Thanks!
By default, attached properties are not inherited. You have to specify it when you define the property:
public static readonly DependencyProperty IsBubbleSourceProperty = DependencyProperty.RegisterAttached(
"IsBubbleSource",
typeof(Boolean),
typeof(TestBox),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits)
);
Related
I have tried to bind my view model property to Validation.HasErrors attached property of a text box (which is read-only). I found a good working solution in this answer by Johan Larsson: https://stackoverflow.com/a/39392158
But I am not an expert on WPF, so I have a hard time understanding how and why it works. I am really puzzled because I don't know all the implicit rules of WPF and XAML engines. I understand the basics of attached properties, binding, and XAML markup, but I don't understand how it comes together in the end.
Can someone clarify what is happening here? Here is the code from the solution:
public static class OneWayToSource
{
public static readonly DependencyProperty BindingsProperty = DependencyProperty.RegisterAttached(
"Bindings",
typeof(OneWayToSourceBindings),
typeof(OneWayToSource),
new PropertyMetadata(default(OneWayToSourceBindings), OnBinidngsChanged));
public static void SetBindings(this FrameworkElement element, OneWayToSourceBindings value)
{
element.SetValue(BindingsProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(FrameworkElement))]
public static OneWayToSourceBindings GetBindings(this FrameworkElement element)
{
return (OneWayToSourceBindings)element.GetValue(BindingsProperty);
}
private static void OnBinidngsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((OneWayToSourceBindings)e.OldValue)?.ClearValue(OneWayToSourceBindings.ElementProperty);
((OneWayToSourceBindings)e.NewValue)?.SetValue(OneWayToSourceBindings.ElementProperty, d);
}
}
public class OneWayToSourceBindings : FrameworkElement
{
private static readonly PropertyPath DataContextPath = new PropertyPath(nameof(DataContext));
private static readonly PropertyPath HasErrorPath = new PropertyPath($"({typeof(Validation).Name}.{Validation.HasErrorProperty.Name})");
public static readonly DependencyProperty HasErrorProperty = DependencyProperty.Register(
nameof(HasError),
typeof(bool),
typeof(OneWayToSourceBindings),
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
internal static readonly DependencyProperty ElementProperty = DependencyProperty.Register(
"Element",
typeof(UIElement),
typeof(OneWayToSourceBindings),
new PropertyMetadata(default(UIElement), OnElementChanged));
private static readonly DependencyProperty HasErrorProxyProperty = DependencyProperty.RegisterAttached(
"HasErrorProxy",
typeof(bool),
typeof(OneWayToSourceBindings),
new PropertyMetadata(default(bool), OnHasErrorProxyChanged));
public bool HasError
{
get { return (bool)this.GetValue(HasErrorProperty); }
set { this.SetValue(HasErrorProperty, value); }
}
private static void OnHasErrorProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetCurrentValue(HasErrorProperty, e.NewValue);
}
private static void OnElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
BindingOperations.ClearBinding(d, DataContextProperty);
BindingOperations.ClearBinding(d, HasErrorProxyProperty);
}
else
{
var dataContextBinding = new Binding
{
Path = DataContextPath,
Mode = BindingMode.OneWay,
Source = e.NewValue
};
BindingOperations.SetBinding(d, DataContextProperty, dataContextBinding);
var hasErrorBinding = new Binding
{
Path = HasErrorPath,
Mode = BindingMode.OneWay,
Source = e.NewValue
};
BindingOperations.SetBinding(d, HasErrorProxyProperty, hasErrorBinding);
}
}
}
Xaml part:
<StackPanel>
<TextBox Text="{Binding ValueInVM, UpdateSourceTrigger=PropertyChanged}">
<local:OneWayToSource.Bindings>
<local:OneWayToSourceBindings HasError="{Binding ValueInVM}" />
</local:OneWayToSource.Bindings>
</TextBox>
<CheckBox IsChecked="{Binding ValueInVM, Mode=OneWay}" />
</StackPanel>
You may find this question/answer more helpful. The most upvoted answer (but not the accepted answer...) has an associated blog post that goes over the details a bit more.
That blog post includes PushBinding and PushBindingManager - the two main pieces you need to get all this working well (plus a couple other little classes, but they're no biggie).
These are a bit more well done (in my opinion) than the code you referenced in your post. It's what I use in my own code (or, at least, what I derived mine from).
First, the most crucial point to consider:
Read-only dependency properties do not have public property setters. Looking at the example FishCount property in Microsoft's documentation:
internal static readonly DependencyPropertyKey FishCountPropertyKey =
DependencyProperty.RegisterReadOnly(
name: "FishCount",
propertyType: typeof(int),
ownerType: typeof(Aquarium),
typeMetadata: new FrameworkPropertyMetadata());
// Declare a public get accessor.
public int FishCount =>
(int)GetValue(FishCountPropertyKey.DependencyProperty);
You can see that there's only a public getter - no setter. Now, let's add to this, the documentation for read-only attached properties states (emphasis mine)
Read-only attached properties are a rare scenario, because the primary scenario for an attached property is its use in XAML. Without a public setter, an attached property cannot be set in XAML syntax.
So - if we can't set it in XAML - maybe we can set it in C#.
That's what the code that you're referencing does.
The general approach to this problem is to:
Provide an approach (usually an attached property) that supports binding via XAML (i.e., read-write properties)
Hook into 👆 the above so that when a change is made, some logic is performed in C# that will:
Create a OneWay binding between the read-only target property (e.g., Validation.HasErrors) and an implementation-specific property (let's call it ListenerProperty)
Subscribe to the property changed event for ListenerProperty.
When ListenerProperty changes, update a second implementation-specific property, say... MirrorProperty to the same value.
Create a OneWayToSource binding between MirrorProperty and your view model to push the value back to your view model
I've made an illustration of how PushBinding works (the example from the blog post I linked)
When I set the value of IsClosed during runtime, OnIsClosedChanged() is called fine.
However, the Designer sets the value of the property but does not call the OnIsClosedChanged().
public static DependencyProperty IsClosedProperty = DependencyProperty.Register("IsClosed", typeof(bool), typeof(GroupBox), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
public bool IsClosed {
get {
return (bool)this.GetValue(IsClosedProperty);
}
set {
if ((bool)this.GetValue(IsClosedProperty) == value)
return;
this.SetValue(IsClosedProperty, value);
OnIsClosedChanged();
}
}
private void OnIsClosedChanged() {
_rowDefContent.Height = new GridLength((IsClosed ? 0 : 1), GridUnitType.Star);
}
Obviously IsClosed is not modified by the Designer and only IsClosedProperty receives the xaml change.
My question is: How can I run IsClosed after the value has been modified in the Designer. Or at least add some logic to the non-runtime changes.
You would have to register a PropertyChangedCallback with property metadata.
The reason is that dependency properties set in XAML or by bindings or some other source do not invoke the CLR wrapper (the setter method). The reason is explained in the XAML Loading and Dependency Properties article on MSDN:
For implementation reasons, it is computationally less expensive to
identify a property as a dependency property and access the property
system SetValue method to set it, rather than using the property
wrapper and its setter.
...
Because the current WPF implementation of the XAML processor behavior
for property setting bypasses the wrappers entirely, you should not
put any additional logic into the set definitions of the wrapper for
your custom dependency property. If you put such logic in the set
definition, then the logic will not be executed when the property is
set in XAML rather than in code.
Your code should look like this:
public static readonly DependencyProperty IsClosedProperty =
DependencyProperty.Register(
"IsClosed", typeof(bool), typeof(GroupBox),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender,
(o, e) => ((GroupBox)o).OnIsClosedChanged()));
public bool IsClosed
{
get { return (bool)GetValue(IsClosedProperty); }
set { SetValue(IsClosedProperty, value); }
}
private void OnIsClosedChanged()
{
_rowDefContent.Height = new GridLength((IsClosed ? 0 : 1), GridUnitType.Star);
}
Found the answer myself now. ValidateValueCallback comes really close! (as Alex K has pointed out) But it is a static method and I don't get any reference to the instance which has been changed. The key is to use a PropertyChangedCallback in FrameworkPropertyMetadata which is also an argument passed to the Property.Register method.
See:
public static DependencyProperty IsClosedProperty = DependencyProperty.Register("IsClosed", typeof(bool), typeof(GroupBox), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(OnIsClosedChangedPCC)));
public bool IsClosed {
get {
return (bool)this.GetValue(IsClosedProperty);
}
set {
this.SetValue(IsClosedProperty, value);
OnIsClosedChanged();
}
}
private static void OnIsClosedChangedPCC(DependencyObject d, DependencyPropertyChangedEventArgs e) {
GroupBox current = (GroupBox)d;
current.IsClosed = current.IsClosed;
}
private void OnIsClosedChanged() {
_rowDefContent.Height = new GridLength((IsClosed ? 0 : 1), GridUnitType.Star);
}
That does now re-set the IsClosedValue which triggers the OnIsClosedChanged to run.
Thank's for your help guys!
I have a UserControl with 2 custom DependencyPropertys (ColumnsCount, RowsCount):
public partial class CabinetGrid : UserControl
{
public static readonly DependencyProperty ColumnsCountProperty =
DependencyProperty.Register("ColumnsCount", typeof (int), typeof (CabinetGrid));
public static readonly DependencyProperty RowsCountProperty =
DependencyProperty.Register("RowsCount", typeof (int), typeof (CabinetGrid));
public int ColumnsCount
{
get { return (int) GetValue(ColumnsCountProperty); }
set { SetValue(ColumnsCountProperty, value); }
}
public int RowsCount
{
get { return (int) GetValue(RowsCountProperty); }
set { SetValue(RowsCountProperty, value); }
}
}
And here's the DataBinding:
<view:CabinetGrid Grid.Column="1" Grid.Row="2" x:Name="GridRack" ColumnsCount="{Binding SelectedRoom.ColumnCount}" />
whereas the window's DataContext has a property SelectedRoom which invokes PropertyChanged-Event.
Thru debugging, I got to know that the DataContext of the UserControl is set properly.
However, when SelectedRoom has changed (=> I selected another item in a list), the DependencyProperty ColumnsCount of my UserControl is not updated.
I am very frustrated, as I already spent an entire day debugging through this unexpected shit, using tools like XAMLSpy and WpfSpoon.
Please, help.
EDIT:
Clemens already pointed out, that a breakpoint in the CLR-Property wrapping the DependencyProperty (ColumnsCount) is not fired. This is a major issue, since I have to call some methods on the change. I'm trying to use the PropertyChangedCallback, but am currently experiencing some errors.
In order to get notified about value changes of a dependency property, you should specify a PropertyChangedCallback in the PropertyMetadata when you register the property.
public static readonly DependencyProperty ColumnsCountProperty =
DependencyProperty.Register(
"ColumnsCount", typeof(int), typeof(CabinetGrid),
new PropertyMetadata(OnColumnsCountPropertyChanged));
private static void OnColumnsCountPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var cabinetGrid = (CabinetGrid)obj;
// do something with the CabinetGrid instance
}
I've a simple control with dependency property like this
public class StatusProgress : Control
{
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(StatusProgress),
new FrameworkPropertyMetadata(null, (d, e) => (d as StatusProgress).TextUpdated(e.OldValue as string)));
private void TextUpdated(string text)
{
Trace.WriteLine("test");
}
}
then I have view model
public class ViewModelPageAnalyse : ViewModelPageBase
{
private string _progressText;
public string ProgressText
{
get { return _progressText; }
set
{
_progressText = value;
OnPropertyChanged(); // base class INotifyPropertyChanged implementation
}
}
}
Then there is a user control (displayed in window with ContentControl). User control is bound to view model with data template (maybe this is important)
<DataTemplate DataType="{x:Type local:ViewModelPageAnalyse}">
<local:UserControlAnalyse/>
</DataTemplate>
And this is the control in user control
<local:StatusProgress Text="{Binding ProgressText}"/>
Now the interesting part. When ProgressText in view model is set/changed, property changed callback is called twice. I see twice "test" in the debugger output window.
More interesting: when view is changed, for some reason callback is again called with e.NewValue = null, while there is nothing directly sets ProgressText to null.
I tried already to check if value is changed in the ProgressText setter before rising event, tried to set binding mode one-way, problem still - callback is called twice with same value, call stack looks same, but there are really a lot of calls within wpf to be really sure.
While double-shot is not a real issue, it bother me. Callback with null value is what my real problem (I think they are related). Anyone knows what is wrong?
Found a reason of the first problem: it was other control with Content. During transition it created a new Model (because Content is ViewModel) instead of reassigning existing user control. Totally my fault. Second problem still and I found this question (with workaround which is not suiting me).
Need help with
PropertyChanged callback is called with default value when ContentControl ViewModel is changed.
Which means null for Text in my case. Anyone? I couldn't figure out why is it called. My guess it is called by DataTemplate manager, if I can say so.
try to change this line:
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(StatusProgress),
new FrameworkPropertyMetadata(null, (d, e) => (d as StatusProgress).TextUpdated(e.OldValue as string)));
with this:
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(StatusProgress)
, new PropertyMetadata(""));
I am new to binding. I have binded slider value to my control's property and my controls property get changed when I change the slider value.
Now, when I need to change the slider value by changing my property value, it does not work..
I modified the xaml from some internet source, but still not get the expected output.
can anyone help me out...
<Grid>
<cc:MyControl Name="mycntrl" ZoomPercentage="{Binding ElementName=slider,Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></cc:MyControl>
<Slider Name="slider" Margin="20,20,20,400" Minimum="100" Maximum="400"></Slider>
</Grid>
Updated:
My code behind for my ZoomPercentage dependency property is below
public double ZoomPercentage
{
get
{
return (double)GetValue(ZoomPercentageProperty);
}
set
{
SetValue(ZoomPercentageProperty, value);
}
}
My dependency registration
public static readonly DependencyProperty ZoomPercentageProperty = DependencyProperty.Register("ZoomPercentage", typeof(double), typeof(MyControl), new FrameworkPropertyMetadata(ZoomPercentagePropertyChanged));
public static void ZoomPercentagePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (args.OldValue != null)
{
if ((double)args.NewValue != (double)args.OldValue)
{
MyControl mycontrol = obj as MyControl;
mycontrol .ZoomTo((int)((double)args.NewValue));
}
}
}
Your ZoomPercentage property should be implemented as a Dependencyproperty
Something like this
public class MyControl:UserControl
{
public MyControl() : base() { }
public double ZoomPercentage
{
get { return (double)this.GetValue(ZoomPercentageProperty); }
set { this.SetValue(ZoomPercentageProperty, value); }
}
public static readonly DependencyProperty ZoomPercentageProperty = DependencyProperty.Register(
"ZoomPercentage", typeof(double), typeof(MyControl:),new PropertyMetadata(0));
}
read more here
If you want a data bound control in the UI to update after changes made in code then you have to do one of two things. One option is to correctly implement the INotifyPropertyChanged interface in the class that you declared your Value property.
The other is to declare your Value property as a DependencyProperty, although you should only really do this in the code behind of your Window or UserControl and opt for the first method if you are using a view model. The purpose of these two methods is for you to 'plug in' to WPF notification framework, so that your UI control will update. Please read the linked pages for more information.