I have my custom control named "FileSelectDialog" with Dependency Property:
public static readonly DependencyProperty FilePathProperty =
DependencyProperty.Register("FilePath", typeof(string), typeof(FileSelectDialog));
public string FilePath
{
get { return (string)GetValue(FilePathProperty); }
set { SetValue(FilePathProperty, value); }
}
Then I'm trying to bind to this dependency property like this:
<controls:FileSelectDialog FilePath="{Binding FolderName}"/>
But nothing happing, no initial text shown in my control, no updated text's saving to 'FolderName' property! I got such error in Output window:
System.Windows.Data Error: 40 : BindingExpression path error: 'FolderName' property not found on 'object' ''FileSelectDialog' (Name='FolderSelector')'. BindingExpression:Path=FolderName; DataItem='FileSelectDialog' (Name='FolderSelector'); target element is 'FileSelectDialog' (Name='FolderSelector'); target property is 'FilePath' (type 'String')
So, as far as I understand, control try to find property 'FolderName on itself, while it must look for it in parents control DataContext. For example, when I use simple textbox:
<TextBox Text="{Binding Path=FolderName}"/>
All is working fine.
Seems a basic DataContext issue to me
How did you set the DataContext of your FileSelectDialog Control ? seems you set the dataContext in code as 'Me'/'this' or in xaml with 'RelativeSource Self' or something like this.
no initial text shown in my control
I understand you are exposing this property in a custom control, but are you updating some of the control in your customcontrol with the value set in your dependency property?
You may need to attach a callback and show the value set in your DP in some control in your customcontrol.
Like:
public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register("FilePath", typeof(string), typeof(FileSelectDialog), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.None,HandleFilePathPropertyChanged));
private static void HandleFilePathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control= (FileSelectDialog)d;
control.SomeUIControl.Text= (string)e.NewValue;
}
If already doing this, then the second problem is the error shown in Binding. For this try setting the DataContext of your control to the object which has the source property.
<controls:FileSelectDialog x:Name="customControl" FilePath="{Binding FolderName}"/>
... code-behind.
customControl.DataContext = sourceObject.
You have to witre the property changed call back function
public static readonly DependencyProperty FilePathProperty =
DependencyProperty.Register("FilePath", typeof(string),
typeof(FileSelectDialog),new UIPropertyMetadata(
new PropertyChangedCallback(PropertyChanged)));
private static void PropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
//Do your Stuff
}
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)
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
}
My page xaml:
<header:DefaultText x:Name="header" HeaderText="{Binding Resources.HeaderTitle}"/>
My DefaultText.cs DependencyProperty:
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set
{ //This part is never called when binding, but it is with static text
SetValue(HeaderTextProperty, value);
SetText(value);
}
}
public readonly DependencyProperty HeaderTextProperty = DependencyProperty.Register("HeaderText", typeof(string), typeof(DefaultText), new PropertyMetadata(string.Empty));
My problem is that when I set the HeaderText property with a binding the setter isn't called, but when I use a normal string without binding it is called.
I already tried the answers of similar questions like: WPF Binding to variable / DependencyProperty
XAML binding internally doesn't call your Setter method but sets the dependency property's value directly, as is pointed out at MSDN:
The WPF XAML processor uses property system methods for dependency
properties when loading binary XAML and processing attributes that are
dependency properties. This effectively bypasses the property
wrappers. When you implement custom dependency properties, you must
account for this behavior and should avoid placing any other code in
your property wrapper other than the property system methods GetValue
and SetValue.
What you need to do is register a callback method that fires whenever the dependency property changes:
public static DependencyProperty HeaderTextProperty = DependencyProperty.Register(
"HeaderText",
typeof(string),
typeof(DefaultText),
new PropertyMetadata(string.Empty, PropertyChangedCallback)
);
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
// this is the method that is called whenever the dependency property's value has changed
}
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' writing a control library. In this library there are some custom panels which are populated with user UIElements. Since every child element in my lib must have a "Title" property, I wrote the following:
// Attached properties common to every UIElement
public static class MyLibCommonProperties
{
public static readonly DependencyProperty TitleProperty =
DependencyProperty.RegisterAttached(
"Title",
typeof(String),
typeof(UIElement),
new FrameworkPropertyMetadata(
"NoTitle", new PropertyChangedCallback(OnTitleChanged))
);
public static string GetTitle( UIElement _target )
{
return (string)_target.GetValue( TitleProperty );
}
public static void SetTitle( UIElement _target, string _value )
{
_target.SetValue( TitleProperty, _value );
}
private static void OnTitleChanged( DependencyObject _d, DependencyPropertyChangedEventArgs _e )
{
...
}
}
Then, if I write this:
<dl:HorizontalShelf>
<Label dl:MyLibCommonProperties.Title="CustomTitle">1</Label>
<Label>1</Label>
<Label>2</Label>
<Label>3</Label>
</dl:HorizontalShelf>
everything works fine and the property gets the specified value, but if I try to bind that property to some other UIElement DependencyProperty like this:
<dl:HorizontalShelf>
<Label dl:MyLibCommonProperties.Title="{Binding ElementName=NamedLabel, Path=Name}">1</Label>
<Label>1</Label>
<Label>2</Label>
<Label Name="NamedLabel">3</Label>
</dl:HorizontalShelf>
an exception would be thrown: "A 'Binding' cannot be set on the 'SetTitle' property of type 'Label'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject."
What am I missing? Binding seems to work fine if instead of binding to "Name" I bind to some other attached property defined in MyLibCommonProperties.
Thanks in advance.
Replace the UIElement in your DependencyProperty definition to MyLibCommonProperties
public static readonly DependencyProperty TitleProperty =
DependencyProperty.RegisterAttached(
"Title",
typeof(String),
typeof(MyLibCommonProperties), // Change this line
new FrameworkPropertyMetadata(
"NoTitle", new PropertyChangedCallback(OnTitleChanged))
);
I think it might be because the binding implicitly uses the parent class specified to call SetTitle() so it is calling Label.SetTitle() instead of MyLibCommonProperties.SetTitle()
I had the same issue with some custom TextBox properties. If I used typeof(TextBox) then I couldn't bind to the value, but if I used typeof(TextBoxHelpers) then I could