I defined property in my usercontrol like this:
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set
{
SetValue(IsSelectedProperty, value);
StackPanelDetails.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
}
}
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof (bool), typeof (ucMyControl));
But when I set its property in xaml, it want trigger it (set is not called).
<DataTemplate><local:ucTopicItem IsSelected="False" /></DataTemplate>
What could be the problem?
The setter of your dependency property will not be called when the property is set in XAML. WPF will instead call the SetValue method directly.
See MSDN XAML Loading and Dependency Properties for an explanation why the setter is not called.
You would have to register a PropertyChangedCallback with property metadata.
You should use a property changed handler in your dependency property directly. This way you ensure that it gets called when set in XAML:
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool), typeof(ucMyControl), new PropertyMetadata(false, OnIsSelectedChanged));
private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Implement change logic
}
Related
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've created a custom control, means a class deriving from control associating a default lookless theme defined via Themes/Generic.xaml. So far so good.
Now, I want to use the control like any other of the main WPF-controls (textbox, listbox, label, textblock, ...) and bind to the defined properties.
The custom control defines a property called Value, that I like to set a Binding to. But nothing ever is written to the bound property in the DataContext.
Well, here's what I've got so far:
In Custom Control class, there is as follows:
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value", typeof(string), typeof(MyClass),
new FrameworkPropertyMetadata("", new PropertyChangedCallback(onValuePropertyChangedCallback)));
private static void onValuePropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
MyClass myClass = (MyClass)sender;
myClass.Value = (string)args.NewValue;
}
public string Value
{
get { return (string) GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
When I use the control, it's like that
<local:MyClass MyValue="{Binding CurrentValue}" ... />
The CurrentValue-property of the DataContext is never affected, never changes it's value.
What am I doing wrong?
The Binding to should be two-way in order to update the source property:
<local:MyClass Value="{Binding CurrentValue, Mode=TwoWay}" ... />
If you want this to be the default binding mode, you could set an appropriate flag when you register your property:
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value), typeof(string), typeof(MyClass),
new FrameworkPropertyMetadata(
"", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
Note that your onValuePropertyChangedCallback was entirely redundant. You don't need to set the property again, when its value has just changed.
I created a dependency property on a custom user control and problem is that the "OnPeriodTypeChangedHandler" Property Change Call Back only fire once when control is created for the first time, subsequently if I try to call the property via Binding it doesn't fire at all. Any ideas?
#region PeriodType
public static readonly DependencyProperty PeriodTypeProperty =
DependencyProperty.Register(
"PeriodType",
typeof(PeriodTypeEnum),
typeof(Period),
new FrameworkPropertyMetadata(
PeriodTypeEnum.None,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnPeriodTypeChangedHandler)
)
);
public static void OnPeriodTypeChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// Get instance of current control from sender
// and property value from e.NewValue
// Set public property on TaregtCatalogControl, e.g.
((Period)sender).PeriodType = (PeriodTypeEnum)e.NewValue;
if ((PeriodTypeEnum)e.NewValue == PeriodTypeEnum.Month)
((Period)sender).Periods = ((Period)sender).GetMonthlyPeriods();
if ((PeriodTypeEnum)e.NewValue == PeriodTypeEnum.Quarter)
((Period)sender).Periods = ((Period)sender).GetQuarterlyPeriods();
}
public PeriodTypeEnum PeriodType
{
get
{
return (PeriodTypeEnum)GetValue(PeriodTypeProperty);
}
set
{
SetValue(PeriodTypeProperty, value);
}
}
#endregion
In case you want your DP to bind TwoWay by default, you can specify it at time of DP registration using FrameworkPropertyMetadataOptions.BindsTwoWayByDefault. This way you don't have to set mode to TwoWay at time of binding.
public static readonly DependencyProperty PeriodTypeProperty =
DependencyProperty.Register(
"PeriodType",
typeof(string),
typeof(MyTextBox),
new FrameworkPropertyMetadata(
PeriodTypeEnum.None,
FrameworkPropertyMetadataOptions.AffectsRender
| FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, <- HERE
new PropertyChangedCallback(OnPeriodTypeChangedHandler)
)
);
I think I found the solution in this particular instance by changing the Mode property to "TwoWay" in ViewModel for its data binding property.
I have a custom text box defined as follows:
public class CustomTextBox : TextBox
{
public static DependencyProperty CustomTextProperty =
DependencyProperty.Register("CustomText", typeof(string),
typeof(CustomTextBox));
static CustomTextBox()
{
TextProperty.OverrideMetadata(typeof(SMSTextBox),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.Journal |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(CustomTextBox_OnTextPropertyChanged));
}
public string CustomText
{
get { return (string)GetValue(CustomTextProperty); }
set { SetValue(CustomTextProperty, value); }
}
private static void CustomTextBox_OnTextPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
CustomTextBox customTextBox = d as CustomTextBox;
customTextBox.SetValue(CustomTextProperty, e.NewValue);
}
}
I'm binding the Custom Text property in the XAML -
<local:CustomTextBox CustomText="{Binding ViewModelProperty}" />
The problem I'm facing is that when I enter anything in the CustomTextBox, the changes are not reflected in the ViewModelProperty i.e. the ViewModelProperty is not getting updated. The CustomTextProperty is getting updated but I suppose I need to do something extra to make the binding work as well.
What am I not doing? I would appreciate any help regarding this.
Thank you
I guess the binding needs to be two-way.
<local:CustomTextBox
CustomText="{Binding ViewModelProperty, Mode=TwoWay}" />
You wouldn't need to specify the Mode if you made the CustomText property bind two-way by default:
public static readonly DependencyProperty CustomTextProperty =
DependencyProperty.Register(
"CustomText", typeof(string), typeof(CustomTextBox),
new FrameworkPropertyMetadata(
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
You may also have to define a PropertyChangedCallback for the CustomText property that updates the Text property (i.e. the other direction of what you have implemented now). Otherwise the TextBox won't display anything that is initially contained in the ViewModel property and of course woudln't be updated when the ViewModel property changes.
How do I expose the ActualWidth property of one of the components of my user control to users?
I have found plenty of examples of how to expose a normal property by creating a new dependency property and binding, but none on how to expose a read-only property like ActualWidth.
What you need is a ReadOnly dependency property. The first thing you need to do is to tap into the change notification of the ActualWidthProperty dependency on the control that you need to expose. You can do that by using the DependencyPropertyDescriptor like this:
// Need to tap into change notification of the FrameworkElement.ActualWidthProperty
Public MyUserControl()
{
DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty
(FrameworkElement.ActualWidthProperty, typeof(FrameworkElement));
descriptor.AddValueChanged(this.MyElement, new EventHandler
OnActualWidthChanged);
}
// Dependency Property Declaration
private static DependencyPropertyKey ElementActualWidthPropertyKey =
DependencyProperty.RegisterReadOnly("ElementActualWidth", typeof(double),
new PropertyMetadata());
public static DependencyProperty ElementActualWidthProperty =
ElementActualWidthPropertyKey.DependencyProperty;
public double ElementActualWidth
{
get{return (double)GetValue(ElementActualWidthProperty); }
}
private void SetActualWidth(double value)
{
SetValue(ElementActualWidthPropertyKey, value);
}
// Dependency Property Callback
// Called when this.MyElement.ActualWidth is changed
private void OnActualWidthChanged(object sender, Eventargs e)
{
this.SetActualWidth(this.MyElement.ActualWidth);
}
ActualWidth is a public readonly property (coming from FrameworkElement) and is exposed by default. What is the case that you are trying to achieve?