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
Related
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 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
}
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)
);
I am having trouble setting my ContentProperty to "Text". The error I am given is:
Invalid ContentPropertyAttribute on type 'MyType', property 'Text' not found.
The code behind looks like this:
[ContentProperty("Text")]
public partial class MyType: UserControl
{
public MyType()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text",
typeof (string),
typeof(MyType)));
public static string GetText(DependencyObject d)
{
return (string) d.GetValue(TextProperty);
}
public static void SetText(DependencyObject d, string value)
{
d.SetValue(TextProperty, value);
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
}
I have actually got it to work if I name the CLR property something other than the DependencyProperty - am I using DependencyProperties incorrectly?
I thought it would be because typeof(LinkText) should be typeof(MyType), but I was able to get my test project to compile. Could you post the XAML file which is causing the error?
EDIT: Followup
Your problem is the two static methods you have in your code sample. Try removing those, and it should compile and work. The static methods only work with Attached Properties, not Dependency Properties.
The error is coming from you're default value, set in:
... new PropertyMetadata(false) ...
Because the TextProperty is of type string, it expects a string for the default value. Try:
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof (string),
typeof(MyType),
new PropertyMetadata(String.Empty));