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);
}
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 was confronted with a StackOverflowException that made me discover that DependencyObject does not handle equality correctly ?!
When the DependencyProperty is of Type Object it will allways use Reference.Equals. This causes to fire PropertyChanged everytime for strings and valuetypes when the same value gets applied.
If you take a look at
DependencyObject.Equals(DependencyProperty dp, object value1, object
value2)
https://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/DependencyObject.cs,3453
/// <summary>
/// Helper method to compare two DP values
/// </summary>
private bool Equals(DependencyProperty dp, object value1, object value2)
{
if (dp.IsValueType || dp.IsStringType)
{
// Use Object.Equals for Strings and ValueTypes
return Object.Equals(value1, value2);
}
else
{
// Use Object.ReferenceEquals for all other ReferenceTypes
return Object.ReferenceEquals(value1, value2);
}
}
A simple way to reproduce:
public static readonly DependencyProperty ObjValueProperty = DependencyProperty.Register(nameof(ObjValue), typeof(object), typeof(MainWindow), new PropertyMetadata(default(object)));
public object ObjValue
{
get
{
return GetValue(ObjValueProperty);
}
set
{
SetValue(ObjValueProperty, value);
}
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if(e.Property == ObjValueProperty) { /*Breakpoint here*/ }
}
...
ObjValue = 7;
ObjValue = 7;
So, is this desired behaviour or a bug?
I don't think this is a bug. Its just the way dotnet handles objects. It is same same if you try it without the context of DependencyProperties:
I created a small console application:
object v1 = 7;
object v2 = 7;
if (v1 == v2) Console.Write("are the same"); else Console.Write("are different");
I read "are different" in console. So generally, if objects are evaluated for equality then always by ReferenceEquals (even if they are boxed value types).
In xaml in Xamarin.Forms, I have a custom control, I want to add property of type int. I think I have to use Bindable properties, so later I can bind a property from ViewModel.
I found this topic, but I'm not sure how to use it.. there is:
BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(BindablePicker), null,
propertyChanged: OnItemsSourcePropertyChanged);
what's "BindablePicker"? Is it the view where property is declared?
Here's my try:
public int WedgeRating
{
get
{
return (int)GetValue(WedgeRatingProperty);
}
set
{
try
{
SetValue(WedgeRatingProperty, value);
}
catch (ArgumentException ex)
{
// We need to do something here to let the user know
// the value passed in failed databinding validation
}
}
}
public static readonly BindableProperty WedgeRatingProperty =
BindableProperty.Create(nameof(WedgeRating), typeof(int), typeof(GameCocosSharpView), null, propertyChanged: OnItemsSourcePropertyChanged);
private static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
}
I didn't even use it in xaml, and it already doesn't work. No particular exception. Only the page where the custom control is initialized doesn't loead. When I comment line I pasted here, it works.
Your code is good, just change your default value from null to 0 or default(int). You have it as null but an int property could never be null. This was the reason of the "crash".
public static readonly BindableProperty WedgeRatingProperty =
BindableProperty.Create (nameof (WedgeRating), typeof (int), typeof (GameCocosSharpView), default(int), propertyChanged: OnItemsSourcePropertyChanged);
Hope this helps!
Here is the example for Bindable Property
public class GameCocosSharpView : View
{
public int WedgeRating
{
get { return (int)GetValue(WedgeRatingProperty); }
set { SetValue(WedgeRatingProperty, value); }
}
public static void WedgeRatingChanged(BindableObject bindable, object oldValue, object newValue)
{
}
public static readonly BindableProperty WedgeRatingProperty =
BindableProperty.Create("WedgeRating", typeof(int), typeof(GameCocosSharpView), 1, BindingMode.Default, null, WedgeRatingChanged);
}
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);
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));