In my application I have a TextBox where the user can input a number beetween 1 and 10. The xaml of this TextBox looks like:
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" x:Name="tbInput"/>
The property Value where the TextBox is bound to looks like:
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value", typeof(decimal), typeof(NumericUpDown), new PropertyMetadata((d, e) =>
{
((NumericUpDown)d).Value = (decimal)e.NewValue;
}));
public decimal Value
{
get { return (decimal)GetValue(ValueProperty); }
set
{
if (Value == value || value > Maximum || value < Minimum)
return;
SetValue(ValueProperty, value);
OnPropertyChanged("Value");
OnValueChanged();
}
}
It works if the user types in a number. But if the user types in a char or a string or something else than it doesn't work. I expected that my TextBox wouldn't accept the invalid value but a breakpoint in the setter of the Value-Property is not reached.
What do I have to do to only allow numbers or to decline the user-input if it's not correct?
The statement
((NumericUpDown)d).Value = (decimal)e.NewValue;
in the PropertyChangedCallback doesn't make sense, because it just sets the Value property another time to the same value.
You should instead move the code from the property setter to the callback. Moreover, you should not call anything but SetValue in the setter of the CLR wrapper of a dependency property. The reason is explained in the XAML Loading and Dependency Properties article on MSDN.
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(decimal), typeof(NumericUpDown),
new PropertyMetadata(
(d, e) =>
{
((NumericUpDown)d).OnValueChanged();
}));
public decimal Value
{
get { return (decimal)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value);}
}
In order to validate the value passed to your dependency property, you may use the DependencyProperty.Register overload that takes a ValidateValueCallback argument:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(decimal), typeof(MainWindow),
new PropertyMetadata((d, e) => ((NumericUpDown)d).OnValueChanged()),
v => (decimal)v >= Minimum && (decimal)v <= Maximum);
Related
I want to declare a BindableProperty which acts as a convenience property to another BindableProperty in Xamarin.Forms. Setting one will update the other. I won't give the full context and the actual types, but a very simple and understandable scenario would be as follows:
I have a view which defines a Number and a Numberp1 property. Number is bindable, while the Numberp1 property acts as the convenience property here.
//Definition
public class MyView : ContentView
{
public static BindableProperty NumberProperty = BindableProperty.Create(nameof(Number), typeof(int), typeof(MyView));
public int Number { get => (int)GetValue(NumberProperty); set => SetValue(NumberProperty, value); }
public int Numberp1 { get => Number + 1; set => Number = value - 1; }
}
//Usage
<local:MyView Number="{Binding Number}"/>
Things go well until a customer discovers that Numberp1 isn't bindable and would like me to make it bindable.
<local:MyView Numberp1="{Binding Numberp1}"/>
//error : No property, bindable property, or event found for 'Numberp1', or mismatching type between value and property.
How would I make both of these properties bindable but make them update each other? I tried investigating using Converters but they seem to only be usable at the Binding, not the BindableProperty definition.
Things go well until a customer discovers that Numberp1 isn't bindable and would like me to make it bindable.
Please create another BindableProperty for Numberp1,the Placeholder for the BindableProperty 'Numberp1Property ' should always match the name without 'Property'.
public static BindableProperty NumberProperty = BindableProperty.Create(nameof(Number), typeof(int), typeof(MyView11), null, propertyChanged: OnNumberChanged);
private static void OnNumberChanged(BindableObject bindable, object oldValue, object newValue)
{
var num = (MyView11)bindable;
num.Number = (int)newValue;
}
public static BindableProperty NumberpProperty = BindableProperty.Create(nameof(Numberp), typeof(int), typeof(MyView11), null, propertyChanged: OnNumber1Changed);
private static void OnNumber1Changed(BindableObject bindable, object oldValue, object newValue)
{
var num = (MyView11)bindable;
num.Numberp = (int)newValue;
}
public int Number
{
get => (int)GetValue(NumberProperty);
set => SetValue(NumberProperty, value);
}
public int Numberp
{
get => Number + 1;
set => Number = value - 1;
}
Adding propertyChanged event for BindableProperty.
They both have to be defined as BindableProperty. Unfortunately, you can't rely on the setter methods being called, because the runtime doesn't necessarily go through the setter, it can directly call SetValue, which updates the definitive source of the value for a BindableProperty.
However you can rely on the fact that BindableProperty does get you PropertyChanged notifications automatically for either of your linked properties.
So, first define both properties:
public static BindableProperty Number1Property = BindableProperty.Create(nameof(Number1), typeof(int), typeof(MyView));
public static BindableProperty Number2Property = BindableProperty.Create(nameof(Number2), typeof(int), typeof(MyView));
public int Number1 { get => (int)GetValue(Number1Property); set => SetValue(Number1Property, value); }
public int Number2 { get => (int)GetValue(Number2Property); set => SetValue(Number2Property, value); }
Then, in the constructor, listen for changes:
public MyView ()
{
InitializeComponent();
PropertyChanged += MyView_PropertyChanged;
}
And finally, make sure that changes to either one is propagated to the other, where Number2 = Number1 + 1:
private void MyView_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == Number1Property.PropertyName)
{
if (Number1 + 1 != Number2)
Number2 = Number1 + 1;
}
if (e.PropertyName == Number2Property.PropertyName)
{
if (Number1 + 1 != Number2)
Number1 = Number2 - 1;
}
}
EDIT As noted by #Knoop, the OP wanted the values to be related, not exactly the same.
I have a CustomControl with a "SetState" DependencyProperty. That is bound to a MultiBinding (one-way) with converter. That all works.
So everything is properly updated, PropertyChanged event is fired, converter gets the values, generates the output value but the SetState proeprty is not updated IF it is for example true and the converter yields to true as well. Since i want the reset logic (see code below) to execute i want SetState always to be updated even though the value it will be set to is the same as the current value.
(if the converter results in false and SetState is true SetState will be properly updated to false as expected)
MWE (typed on the fly pardon my errors)
View
<local:CustomControl>
<local:CustomControl.SetState>
<MultiBinding Converter="{local:MyConverter}">
<Binding Path="MyProp1" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="MyProp2" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</local:CustomControl.SetState>
</local:CustomControl>
Custom Control
public class CustomControl : FrameworkElement
{
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(
nameof(SetState),
typeof(bool),
typeof(CustomControl),
new FrameworkPropertyMetadata(false, (s, e) =>
{
((CustomControl)s).SetState = (bool)e.NewValue;
}));
private bool _state;
// For brevity i have used a boolean in the MWE
public bool SetState
{
get { return _state; }
set
{
// This is not called when the binding produces the same value as this property already has
// but i want this to be set anyway to trigger the reset logic even though SetState property already equal the value being set.
// Custom reset logic here
_state = value;
}
}
}
Converter
internal class MyConverter : MarkupExtension, IMultiValueConverter
{
private static MyConverter _instance;
public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
{
// Imagine some complex logic here
return ((bool)value[0] || (bool)value[1]);
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _instance ?? (_instance = new MyConverter());
}
}
View model
internal class MyViewModel : INotifyPropertyChanged
{
private bool _myProp1;
private bool _myProp2;
// Imagine the properties being altered at run-time
public bool MyProp1
{
get { return _myProp1; }
private set { _myProp1 = value; OnPropertyChanged(); }
}
public bool MyProp2
{
get { return _myProp2; }
private set { _myProp2 = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The solution ended up being rather simple, for the DependencyProperty use the CoerceValueCallback instead of the PropertyChangedCallback. The former is always called, the later only if the value has actually been changed.
So in code it becomes
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(
nameof(SetState),
typeof(bool),
typeof(CustomControl),
new PropertyMetadata(false, null,
(d, v) =>
{
((CollapsibleColumnDefinition)d).IsExpanded = (bool)v;
return v;
}));
I briefly tried to manually force the dependency property to refresh (note bug) but the CoerceValueCallback does exactly what i wanted and is cleaner.
I am trying to define a dependency property like this:
public static readonly DependencyProperty DependencyPropertyName= DependencyProperty.Register("DepName", typeof(EnumName), typeof(MyWindow1), new FrameworkPropertyMetadata("FrameWorkProperty", FrameworkPropertyMetadataOptions.AffectsRender, Target));
private static void Target(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
//some logic here
}
public EnumName DepName
{
get { return (EnumName)GetValue(DependencyPropertyName); }
set { SetValue(DependencyPropertyName, value); }
}
And i get this error, and dont understand why:
{"Default value type does not match type of property 'DepName'."}
The default value type (String) of your Dependency Property does not match the Type of your property DepName (EnumName).
Change the default type in your dependency property and it should work.
public static readonly DependencyProperty DependencyPropertyName= DependencyProperty.Register(
"DepName",
typeof(EnumName),
typeof(MyWindow1),
new FrameworkPropertyMetadata(
EnumName.SomeValue, // this is the defalt value
FrameworkPropertyMetadataOptions.AffectsRender,
Target));
We have a DependencyObject which defines a Value property as a DP. It also defines a Presets collection representing friendly names for some pre-defined values.
The way our UI should work is when we bind to the Value property, if the value matches a preset, we show the friendly name, otherwise we just show the value directly.
Using a converter is out since there's no reliable way to both pass in the Presets (which are defined per-item, they aren't shared) and also do two-way binding, so our thought is to expose a FriendlyValue property on the object and use that for binding in the UI, letting it handle the conversion internally.
Since FriendlyValue depends on the already-existing Value DependencyProperty, we figured we'd just wrap the conversion logic in CLR getters/setters, but that means when the actual Value DP changes, it needs to notify the UI that FriendlyValue has been updated too, and since FriendlyValue is a CLR property, we need to support INPC for that specific property.
My question is, is that the correct/suggested way to handle this, or should I go with a second DP, monitoring its change handlers and setting the other property accordingly, adding state variables to stop one from setting the other, which then sets the first, which then again updates the other, etc.
Here's the code for the object's properties...
public static readonly DependencyProperty PresetsProperty = DependencyProperty.Register(
"Presets",
typeof(List<Preset>),
typeof(MyObject),
new UIPropertyMetadata(null));
public List<Preset> Presets
{
get { return (List<Preset>)GetValue(PresetsProperty); }
set { SetValue(PresetsProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(string),
typeof(MyObject),
new UIPropertyMetadata(null, (s,e) => {
var myObject = (MyObject)s;
myObject.OnPropertyChanged("FriendlyValue");
}));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public string FriendlyValue
{
get
{
var foundPreset = Presets.FirstOrDefault(preset => preset.Value == this.Value);
return (foundPreset != null)
? foundPreset.FriendlyName
: this.Value;
}
set
{
var foundPreset = Presets.FirstOrDefault(preset => preset.FriendlyName == value);
this.Value = (foundPreset != null)
? foundPreset.Value
: value;
// Note: We don't raise INPC notification here. It's raised in the Value's change handler
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
So is this considered good practice for built-in converter behavior?
Why not make both Value and FriendlyValue dependency properties? I see no reasons to use two techniques at the same time.
Consider:
using Preset = Tuple<string, string>;
public class MyObject : DependencyObject
{
private readonly IList<Tuple<string, string>> _presets = new List<Preset> {
new Preset("1", "good"),
new Preset("2", "bad"),
};
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value", typeof(string), typeof(MyObject),
new PropertyMetadata(null,
(o, e) => ((MyObject)o).ValuePropertyChanged((string)e.NewValue)));
private static readonly DependencyProperty FriendlyValueProperty = DependencyProperty.Register(
"FriendlyValue", typeof(string), typeof(MyObject),
new PropertyMetadata(null,
(o, e) => ((MyObject)o).FriendlyValuePropertyChanged((string)e.NewValue)));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public string FriendlyValue
{
get { return (string)GetValue(FriendlyValueProperty); }
set { SetValue(FriendlyValueProperty, value); }
}
private void ValuePropertyChanged (string newValue)
{
var preset = _presets.FirstOrDefault(p => p.Item1 == newValue);
FriendlyValue = preset != null ? preset.Item2 : newValue;
}
private void FriendlyValuePropertyChanged (string newValue)
{
var preset = _presets.FirstOrDefault(p => p.Item2 == newValue);
Value = preset != null ? preset.Item1 : newValue;
}
}
Notes:
Code for presets simplified for brevity.
This will not cause stack overflow, because change callback are not called if value is not changed.
I hope I understood your logic correctly. There's one issue: if you change FriendlyValue to one of the friendly values from the presets, this will change Value to the value from the found preset and in turn change FriendlyValue to the name of the preset. I don't know if that behavior is expected.
I added a ValidateValueCallback to a DependencyProperty called A. Now in the validate callback, A shall be compared to the value of a DependencyProperty called B. But how to access the value of B in the static ValidateValueCallback method validateValue(object value)? Thanks for any hint!
Sample code:
class ValidateTest : DependencyObject
{
public static DependencyProperty AProperty = DependencyProperty.Register("A", typeof(double), typeof(ValidateTest), new PropertyMetadata(), validateValue);
public static DependencyProperty BProperty = DependencyProperty.Register("B", typeof(double), typeof(ValidateTest));
static bool validateValue(object value)
{
// Given value shall be greater than 0 and smaller than B - but how to access the value of B?
return (double)value > 0 && value <= /* how to access the value of B ? */
}
}
Validation callbacks are used as sanity checks for the given input value against a set of static constraints. In your validation callback, checking for a positive value is a correct use of the validation, but checking against another property is not. If you need to ensure a given value is less than a dependent property, you should use property coercion, like so:
public static DependencyProperty AProperty = DependencyProperty.Register("A", typeof(double), typeof(ValidateTest), new PropertyMetadata(1.0, null, coerceValue), validateValue);
public static DependencyProperty BProperty = DependencyProperty.Register("B", typeof(double), typeof(ValidateTest), new PropertyMetaData(bChanged));
static object coerceValue(DependencyObject d, object value)
{
var bVal = (double)d.GetValue(BProperty);
if ((double)value > bVal)
return bVal;
return value;
}
static bool validateValue(object value)
{
return (double)value > 0;
}
While this won't throw an exception if you set A > B (like the ValidationCallback does), this is actually the desired behavior. Since you don't know the order in which the properties are set, you should therefore support the properties being set in any order.
We also need to tell WPF to coerce the value of property A if the value of B changes, as the coerced value could change:
static void bChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(AProperty);
}