My control has a property that maps to a private variable. When the property is set, I also need to store a certain other variable. When the private variable of the property is set by my own control code, this special handling must not occur. All good.
I now need to refactor that to a DependencyProperty. I only have a change handler here, and all (also internal) accesses to that property must go through the DependencyProperty framework. There is no more private variable that I could set directly. Every change done by my own code now looks exactly the same as changes from external sources like the user of the control or DataBinding.
In my property change handler, how can I determine whether a value change came from my code or somewhere else?
This is the old code:
private DateTime selectedTime;
private int intendedDay;
public DateTime SelectedTime
{
get { return selectedTime; }
set
{
selectedTime = value;
intendedDay = selectedTime.Day;
}
}
In this code, I can set selectedTime directly, not affecting intendedDay which must only be set when SelectedTime is assigned a new value altogether from the outside, or when I see fit in my control.
The DependencyProperty only allows me to detect any changes:
public static DependencyProperty SelectedTimeProperty = DependencyProperty.Register(
"SelectedTime",
typeof(DateTime),
typeof(DateTimeTextBox),
new PropertyMetadata(SelectedTimeChanged));
public DateTime SelectedTime
{
get { return (DateTime) GetValue(SelectedTimeProperty); }
set { SetValue(SelectedTimeProperty, value); }
}
private static void SelectedTimeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
DateTimeTextBox control = (DateTimeTextBox) obj;
// This must not happen where I previously assigned the private variable:
control.intendedDay = control.SelectedTime.Day;
}
private int intendedDay;
So when I would previously assign the private variable, I can now only set the dependency property, which will also change intendedDay.
Why not wrap your dependency property in a normal property? Setting the property will still set the dependency property and setting the private one will not set the intendedDay value.
private DateTime selectedTime
{
get { return (DateTime) GetValue(SelectedTimeProperty); }
set { SetValue(SelectedTimeProperty, value); }
}
public DataTime SelectedTime
{
get { return selectedTime; }
set
{
selectedTime = value;
intendedDay = selectedTime.Day;
}
}
Like Clemens explained in his comment, I added a private variable that indicates whether I am currently updating that property from my own code. I set it to true whenever I want to suppress external value change handling, and false afterwards. So I can distinguish whether it was an internal or external value change.
Related
Not a full expert in C# here. Sorry in advance if this is a very common question.
consider the following property.
public bool IsOn{ get;set; }
above getter/setter property has anonymous backing field according to https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties
Is there a way for me to see what this "anonymous backing field" name is so that I can expand the setter without adding extra code. All I want to do is log the content of the values that's getting set to in a dll where this code exist. For example,
public bool IsOn
{
get;
set
{
Log(value);
"field name generated by c#" = value;
}
}
Or do I have to create a field manually every time I want to see what the value is being set to? if so it seems like a very unproductive approach to have them used when we consider about usability. Mind you this is only one of the setters I want to log out. there are many more needs logging on this specific dll
An Auto-Implemented Property is a property that has the default get- and set-accessors. If you have to add logic to them, you have to create a usual property with a backing field:
private bool _IsOn;
public bool IsOn
{
get { return _IsOn; }
set
{
Log(value);
_IsOn = value;
}
}
However, a property that is logging somewhere is not a real property anymore, that's a heavy side-effect in my opinion. I would make it a method:
private bool _isActive = false;
public void ChangeState(bool active)
{
Log(active);
_isActive = active;
}
public bool IsActivated => _isActive;
I have this simple example in the ViewModel of a WPF application:
class VM_DiskPartition : DependencyObject
{
// (...) Other properties
public bool IsLowOnSpace
{
get { return (bool)GetValue(IsLowOnSpaceProperty); }
set { SetValue(IsLowOnSpaceProperty, value); }
}
public static readonly DependencyProperty IsLowOnSpaceProperty = DependencyProperty.Register("IsLowOnSpace", typeof(bool), typeof(VM_DiskPartition), new PropertyMetadata(false, OnLowOnSpaceChanged));
private static void OnLowOnSpaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
((VM_DiskPartition)d).CoerceValue(BgColorProperty);
}
public Brush BgColor
{
get { return (Brush)GetValue(BgColorProperty); }
set { SetValue(BgColorProperty, value); }
}
public static readonly DependencyProperty BgColorProperty = DependencyProperty.Register("BgColor", typeof(Brush), typeof(VM_DiskPartition), new PropertyMetadata(Brushes.Red, null, Coerce_BgColor));
private static object Coerce_BgColor(DependencyObject d, object baseValue)
{
return UIUtils.GetBgColor(((VM_DiskPartition)d).IsLowOnSpace);
}
}
I want the BgColor property to have its default value automatically set by its coercion function.
Is there a more elegant way to achieve this instead of calling CoerceValue(BgColorProperty) from the constructor?
The reason is that I may have many properties like this in the future and it doesn't look very clean to use a lot of CoerceValue() calls in the constructor.
Maybe it's better to use Converters in this scenario? I was trying to go without them and create new ViewModel properties instead.
You seem to be somewhat confused... the DependencyObject and DependencyProperty classes are UI classes. They don't belong in a view model. In view models, we use normal CLR properties and the INotifyPropertyChanged interface to handle property change notification. Therefore, there's no need to use them in a view model at all.
If you want to set a default value in a view model, you simply do this:
private int number = 5; // <-- default value
public int Number
{
get { return number; }
set { number = value; NotifyPropertyChanged("Number"); }
}
If you want property value coercion in a view model, you just do this:
public int Number
{
get { return number; }
set { number = Math.Max(0, value); NotifyPropertyChanged("Number"); }
}
UPDATE >>>
Looking again at your code, it occurs to me that it shouldn't be in a view model at all. It looks like it should be in the code behind of some UserControl. We put data in view models, not UI elements like Brushes. If you want to set a default value for a DependencyProperty, the correct way to do it is how you have shown us:
public static readonly DependencyProperty BgColorProperty =
DependencyProperty.Register("BgColor", typeof(Brush), typeof(VM_DiskPartition),
new PropertyMetadata(Brushes.Red/* <-- default value */, null, Coerce_BgColor));
Property coercion is for ensuring that a value stays within certain bounds like the example I gave above that ensures that the value will never be negative.
Here is my problem. I recently created a custom control, which works pretty well.
But i have a problem when i use it, i have a little problem :
In my control, i made a property named Value, defined like this :
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), new PropertyMetadata(1000));
public int Value
{
get
{
return (int)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
this.ValueText.Text = value.ToString();
}
}
When I do a databinding to this value, the binding works, but the default value is set to 1000, so it first print 1000. But actually, the property bound to Value isn't equal to 1000.
I would like to print in ValueText.Text the value of the bound property when the Value property is created.
Edit : Question is simple, how can I remove that default value and directly print the bound property ?
You should be able to setup a PropertyChanged event in your DependancyProperties metadata to update ValueText when Value changes.
somthing like this:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown),
new PropertyMetadata(1000, (sender, e) => (sender as NumericUpDown).ValueText.Text = e.NewValue.ToString()));
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
The property setter will not get called as things change via WPF's data binding, so this technique will not work.
The default, initial value will always be 1000, but data binding may override it. You will need to add a Callback to appropriately notify you when the dependency property value is changed.
For details, see the Dependency Property Callbacks page to see how to implement a property changed callback correctly. This is the appropriate place to set your other (ValueText) property.
This is the part where is not working. My dependency property has a default value which is Entradas.Entero, and that value must be run this line:
Grid.SetColumnSpan(button0, 3);
And it should refresh it in my user control design, however there's no changes in it.
public partial class TableroUserControl : UserControl
{
public enum Entradas
{
Entero, Decimal
}
public Entradas Entrada
{
get { return (Entradas)GetValue(EntradaProperty); }
set { SetValue(EntradaProperty, value); }
}
static void textChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
Button button0 = ((TableroUserControl)property).button0;
switch ((Entradas)args.NewValue)
{
case Entradas.Entero:
Grid.SetColumnSpan(button0, 3);
break;
case Entradas.Decimal:
Grid.SetColumnSpan(button0, 2);
break;
}
}
public static readonly DependencyProperty EntradaProperty =
DependencyProperty.Register("Entrada", typeof(Entradas), typeof(TableroUserControl), new PropertyMetadata(Entradas.Entero, new PropertyChangedCallback(textChangedCallBack)));
public TableroUserControl()
{
InitializeComponent();
}
}
You might also consider doing this with a value converter. You should be able to bind the Grid.ColumnSpan attached property of button0 to the Entrada property of your user control. Then use a value converter to convert it to an integer. This way you don't have to deal with callbacks and state/timing issues.
Your dependency property is initialized to Entero. So unless the value is changed to Decimal and then again changed back to Entero you wont hit the property changed callback code.
Make sure that the colspan setter code is hit.
I have a dependency property on a control which is a custom class.
Now is there an event that gets raised BEFORE the value is being changed?
I know that OnPropertyChanged is raised after the property has already changed.
I need some event before so that I can cancel the changing....in order to preserve the state of the control.
I cannot set back the dependency property back to its old value as that will mean that I lose state in the control.
Thanks!
If its your DependencyProperty, you can use the ValidateValueCallback to validate the incoming value and reject it, if its not as you desire.
In the following example, only values greater than 0 will be accepted:
public int Test {
get { return (int)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test", typeof(int), typeof(YourClass),
new UIPropertyMetadata(0), delegate(object v) {
return ((int)v) > 0; // Here you can check the value set to the dp
});
If your data objects implement INotifyPropertyChanging, then you can handle the PropertyChanging event which is raised before the property value changes.
INotifyPropertyChanging was introduced in .NET 3.5
You may check value of property in property declaration set section. Suppose we have CustomColor dep property:
public Color CustomColor
{
get { return GetValue(CustomColorProperty) as Color;}
set
{
//check value before setting
SetValue(CustomColorProperty, value);
}
}
Also there will be helpfull for you PropertyChangedCallback, ValidateValueCallback, CoerceValueCallback delegates.