I am new with WPF and dependency properties and my question might be totally newbie...
I have the following dependency property:
public static readonly DependencyProperty IsEditableProperty =
DependencyProperty.Register("IsEditable", typeof(bool), typeof(EditContactUserControl),
new FrameworkPropertyMetadata(false, OnIsEditablePropertyChanged));
public bool IsEditable
{
get { return (bool)GetValue(IsEditableProperty); }
set { SetValue(IsEditableProperty, value); }
}
private static void OnIsEditablePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
EditContactUserControl control = source as EditContactUserControl;
bool isEditable = (bool)e.NewValue;
if (isEditable)
control.stackPanelButtons.Visibility = Visibility.Visible;
else
control.stackPanelButtons.Visibility = Visibility.Collapsed;
}
The problem is that I want to have the code in the OnIsEditablePropertyChanged to be executed also for the default value of my property, which doesn't happen.
What am I doing wrong, or how should I do this in your opiniion?
Thank you in advance.
Instead of changing the visibility in code, you should Bind the Visibility property in XAML and use a boolean to Visibility Converter.
If you do this, it doesn't matter if the property is initialized or not.
The OnPropertyChanged callback won't be called on startup: The "default" value is in fact never really "set". Default: The value of the property when it isn't set.
If you want to execute some code at control startup, put it in the ApplyTemplate method override (in the case of a TemplatedControl) or at the end of your constructor (in the case of a UserControl)
Avoid duplicating this code in the constructor and in the property changed callback: Put it in a common method called by both ie:
void OnIsEditableChangedImpl(bool newValue)
{
....
}
I think a much better approach would be to set up stackPanelButtons.Visibility = Visibility.Collapsed in your XAML as default too, in which case you don't need to run all this code on startup!
Related
I have a custom control with following code:
public partial class TableSelectorControl : UserControl
{
private Brush _cellHoverBrush = new SolidColorBrush(Colors.CadetBlue) { Opacity = 0.3 };
public static readonly DependencyProperty ActiveSelectionProperty =
DependencyProperty.Register("ActiveSelection", typeof(TableSelectorSelection),
typeof(TableSelectorControl));
public TableSelectorSelection ActiveSelection
{
get => (TableSelectorSelection)GetValue(ActiveSelectionProperty);
set
{
SetValue(ActiveSelectionProperty, value);
_cellHoverBrush = value.HoverBrush;
}
}
}
As you can see, I'm trying to set _cellHoverBrush on each ActiveSelectionProperty update, which is done from ViewModel. Binding works well and the ActiveSelectionProperty seemes to change, but the setter is not firing. I surely can use a FrameworkProperyMetadata, but I don't want _cellHoverBrush to become static, the idea is to change it with respect to selected ActiveSelection. How can I achieve this?
I can provide more info, if needed.
There are two types of properties in WPF: .NET Framework properties and dependency properties (which are specific for WPF). Each dependency property has associated a .Net Framework property, but this property is only a wrapper over WPF dependencies properties. This is done to standardize the way we work with properties in WPF. When a dependency property is used in bindings from .xaml files, the WPF framework will not use the .Net wrapper property to get or set the value. This is why, it's not indicated to use other code than GetValue and SetValue in your .NET wrapper property.
For what you need, you should use PropertyChangedCallback, like in the example below:
public partial class TableSelectorControl : UserControl
{
private Brush _cellHoverBrush = new SolidColorBrush(Colors.CadetBlue) { Opacity = 0.3 };
public static readonly DependencyProperty ActiveSelectionProperty =
DependencyProperty.Register("ActiveSelection", typeof(TableSelectorSelection),
typeof(TableSelectorControl), new PropertyMetadata(new PropertyChangedCallback(OnActiveSelectionChanged)));
public TableSelectorSelection ActiveSelection
{
get => (TableSelectorSelection)GetValue(ActiveSelectionProperty);
set => SetValue(ActiveSelectionProperty, value);
}
private static void OnActiveSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tableSelCtrl = d as TableSelectorControl;
if (tableSelCtrl != null)
{
tableSelCtrl._cellHoverBrush = (e.NewValue as TableSelectorSelection)?.HoverBrush;
}
}
}
Using the PropertyChangedCallback of FrameworkPropertyMetadata doesn't necessarily mean you need to make your field static. Your handler method will get a reference to the instance that is invoking it which you can then modify - you will need to cast it to your type first though.
The PropertyChanged walkthrough on this page shows one way you might do it.
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/dependency-property-callbacks-and-validation
I have a WPF control that is based on the TextBox control:
public class DecimalTextBox : TextBox
I have a dependency property that is bound to, which manages the numeric value, and is responsible for setting the Text property:
public decimal NumericValue
{
get { return (decimal)GetValue(NumericValueProperty); }
set
{
if (NumericValue != value)
{
SetValue(NumericValueProperty, value);
SetValue(TextProperty, NumericValue.ToString());
System.Diagnostics.Debug.WriteLine($"NumericValue Set to: {value}, formatted: {Text}");
}
}
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (decimal.TryParse(Text, out decimal num))
{
SetValue(NumericValueProperty, num);
}
}
This works well when entering a value into the textbox itself (it updates the underlying values, etc...). However, when the bound property of NumericValue is changed, despite updating the NumericValue DP, the Text property is not updated. In the tests that I've done, it would appear that the reason for this is that the set method above is not called when the bound value is updated. The binding in question looks like this:
<myControls:DecimalTextBox NumericValue="{Binding Path=MyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Can anyone point me in the right direction as to why this property setter is not firing, or is there a better way to approach this?
As explained in Custom Dependency Properties and XAML Loading and Dependency Properties, you should not call anything else than GetValue and SetValue in the CLR wrapper of a dependency property:
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.
In order to get notified about value changes, you'll have to register a PropertyChangedCallback with the dependency property metadata.
public static readonly DependencyProperty NumericValueProperty =
DependencyProperty.Register(
"NumericValue", typeof(decimal), typeof(DecimalTextBox),
new PropertyMetadata(NumericValuePropertyChanged));
public decimal NumericValue
{
get { return (decimal)GetValue(NumericValueProperty); }
set { SetValue(NumericValueProperty, value); }
}
private static void NumericValuePropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var textBox = (DecimalTextBox)obj;
textBox.Text = e.NewValue.ToString();
}
The WPF binding is not actually using your getter and setter, but instead directly interacts with the dependency property NumericValueProperty. In order to update the text, subscribe to the PropertyChanged event of the NumericValueProperty instead of trying to do anything special in the setter.
Subscribe to the change in your DependencyProperty definition, similar to the following:
// Using a DependencyProperty as the backing store for NumericValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NumericValueProperty =
DependencyProperty.Register("NumericValue", typeof(decimal), typeof(DecimalTextBox), new FrameworkPropertyMetadata(0.0m, new PropertyChangedCallback(OnNumericValueChanged)));
private static void OnNumericValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var self = d as DecimalTextBox;
// if the new numeric value is different from the text value, update the text
}
I created a custom renderer for a Label. Now I wanted to manipulate with its focus state, so I created bindable property and event. It works fine when I change the bindable property from custom renderer like so: Element.IsFocused = true;
But when I change it from the view model, it affects XAML view, but for some reasons doesn't call the setter for this property. Here is the code:
In custom class:
public new static readonly BindableProperty IsFocusedProperty =
BindableProperty.Create(nameof(IsFocused), typeof(bool), typeof(FloatingEntry), false);
public new bool IsFocused
{
get { return (bool)GetValue(IsFocusedProperty); }
set
{
SetValue(IsFocusedProperty, value);
if (value) Focus();
}
}
In XAML:
IsFocused="{Binding PasswordEntryIsFocused}"
In View Model:
private bool _passwordEntryIsFocused;
public bool PasswordEntryIsFocused
{
get { return _passwordEntryIsFocused; }
set
{
SetProperty(ref _passwordEntryIsFocused, value);
}
}
In View Model in some method: PasswordEntryIsFocused = true;
It's not about new keyword, I tried without it.
And binding works, because I tried to bind it with a visual property, like IsVisible and it was working like it should, but setter is always called only from a custom renderer.
I think I may miss something in a context of bindable property work.
But when I change it from the view model, it affects XAML view, but for some reasons doesn't call the setter for this property.
Yes, that is a common mistake with WPF. The XAML generated code does not call the setter, instead it changes the bound dependency property immediately. You can't break on this event, unless you attach the PropertyChangedCallback event.
I'm working with a custom control that has several user-defined dependency properties. I'm running into the same issue described in this question.
My control is setting the default value of a custom dependency property in its constructor. When I use the control in a DataTemplate, the value set in the constructor is always used, even if I try to set it in XAML.
The answer to the linked question explains that the value set in the C# code has a higher priority, and a better approach would be to specify the default value in the dependency property's metadata.
In my case, I can't specify a default because the dependency property doesn't have a single default value that applies in all cases. The default values depend on another property, so I must look them up when the control is created and not when the property is registered.
Here's some code to help illustrate my problem:
public partial class MyControl : UserControl
{
public static readonly DependencyProperty MyProperty =
DependencyProperty.Register(
"MyProperty",
typeof(int),
typeof(MyControl),
new FrameworkPropertyMetadata(
int.MinValue,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback("OnMyPropertyChanged")));
public MyControl() : base()
{
InitializeComponent();
this.MyProperty = GetDefaultPropertyValue();
}
public int MyProperty
{
get { return (int)GetValue(MyProperty); }
set { SetValue(MyProperty, value); }
}
private int GetDefaultPropertyValue()
{
// look up the appropriate default based on some other criteria
return 42;
// (in reality, the default value for "MyProperty"
// depends on the value of a "Mode" custom DependencyProperty.
// this is just hard coded for testing)
}
}
The XAML usage looks something like this:
<!-- View displays 4 (desired) -->
<local:MyControl MyProperty="4" />
<!-- View displays default of 42 (desired) -->
<local:MyControl />
<!-- View displays default of 42 (wanted 4) -->
<DataTemplate x:Key="MyTemplate">
<local:MyControl MyProperty="4"/>
</DataTemplate>
To summarize:
The desired behavior is that the value from XAML is used first. If the value is not specified in the XAML, then I would like to fallback to the default value set in the control's constructor.
If I just include the control directly in a view, I get the expected behavior. If the control is used inside a DataTemplate, then I always get the default set in the constructor (even when the data template explicitly sets another value).
Is there any other way to specify the default value when the control is used in a template? The only option I can think of is to break the control up into several separate but similar controls, each of which uses a default value that is registered with the dependency property (which removes the need to have the default set based on the a Mode property).
Setting the default value in OnApplyTemplate while adding a small check should solve this:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Only set the default value if no value is set.
if (MyProperty == (int)MyPropertyProperty.DefaultMetadata.DefaultValue)
{
this.MyProperty = GetDefaultPropertyValue();
}
}
Please note that although this will work, it's not ideal since setting the property's value by code will essentially clear any data bindings for this property. For example, the following binding will no longer work once you call MyProperty = 42 in code:
<local:MyControl MyProperty="{Binding SomeProperty}" />
It should be possible to set the value while maintaining any bindings by using SetCurrentValue(MyPropertyProperty, GetDefaultPropertyValue()); to modify the property instead of MyProperty = GetDefaultPropertyValue(), but I'm not sure I like that too much either.
A better solution
What I would do is introduce a new read-only property in addition to the existing one, which will act as a calculated property. For example:
private static readonly DependencyPropertyKey MyCalculatedPropertyPropertyKey =
DependencyProperty.RegisterReadOnly("MyCalculatedProperty", typeof(int), typeof(MyControl),
new PropertyMetadata(int.MinValue));
public static readonly DependencyProperty MyCalculatedPropertyProperty = MyCalculatedPropertyPropertyKey.DependencyProperty;
public int MyCalculatedProperty
{
get { return (int)GetValue(MyCalculatedPropertyProperty); }
private set { SetValue(MyCalculatedPropertyPropertyKey, value); }
}
private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyControl)d).MyCalculatedProperty = (int)e.NewValue;
}
public MyControl()
: base()
{
InitializeComponent();
MyCalculatedProperty = GetDefaultPropertyValue();
}
ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
public string MyText { ... }
}
XAML:
<my:MySpecialTextBox Text="{Binding MyText}" />
Custom Control:
public class MySpecialTextBox : TextBox
{
static MySpecialTextBox()
{
TextProperty.OverrideMetadata(typeof(MySpecialTextBox),
new FrameworkPropertyMetadata
{
BindsTwoWayByDefault = true,
DefaultValue = string.empty,
PropertyChangedCallback = OnTextPropertyChanged
});
}
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as MySpecialTextBox;
if (control != null)
{
control.Text = SomeAdjustedValue((string)e.NewValue);
}
}
}
The problem is that while the DependencyProperty in the custom control does adjust properly, it does not update the ViewModel. I realize that this seems as if it should be a CoerceValueCallback due to the naming of SomeAdjustedValue, but Coercion does not change the ViewModel value either. I can't seem to update the value in my ViewModel if it was the trigger for the OnTextPropertyChanged callback to begin with... I did a debug trace and it does not go through the ViewModel a second time with the new value. Not sure what to do here to fix this.
In the FrameworkPropertyMetadata, there are different constructors you can use. Use one with the FrameworkPropertyMetadataOptions parameter. The FrameworkPropertyMetadataOptions.BindsTwoWayByDefault option will turn on two way binding by default, otherwise it's just one way.
Edit: So you did, I should stop trying to answer questions when I'm sick.
A disclaimer first- this smells like logic that should live inside of the ViewModel, not in the UI binding.
That said, if you are set on doing it this way, I think that you need to first check whether the "Adjusted Value" is different than the one already provided (to avoid looping indefinitely), then use DependencyProperty.SetValue to set the value of the dependency property on the control, rather than just setting it's Text property.
TextBox.Text is a binding, and you are replacing that binding with a string value in OnTextPropertyChanged, so the property is no longer bound to your datasource.
I think you would need to get the binding on TextBox.Text and update the source, however I wouldn't recommend doing that since you'd be mixing your Business Logic layer with your UI layer.
If you only want a display some custom formatting, I would do it in a Converter so it doesn't actually change your data source's value. If you want to change the actual data source value, I would do that with the ViewModel's PropertyChanged event
Did you try this?
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as MySpecialTextBox;
if (control != null)
{
control.SetCurrentValue(TextBox.TextProperty, SomeAdjustedValue((string)e.NewValue));
}
}
The SetCurrentValue() method ensures that the binding is preserved, where a simple SetValue() which is what is called under the hood if you use the Text property setter will remove any binding.