CoerceValue not working correctly with POCO objects - wpf

I've run into an issue which seems pretty strange and I'm not sure if this is a bug or a design feature of WPF.
I have a Dependency Object with a Dependency Property with constraints enforced via value coercion and everything works as expected when using this property in bindings with other dependency properties.
But things break down when trying to use the property in a binding against a POCO object. In this case the POCO is updated when the dependency property changes, but before value coercion is executed, leaving the POCO with an invalid/out-of-sync value.
Here's an example:
public class TestDO : DependencyObject
{
public static DependencyProperty NumberProperty =
DependencyProperty.Register("Number", typeof(int), typeof(TestDO),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.None, OnNumberChanged, OnNumberCoerce));
public int Number
{
get { return (int)GetValue(NumberProperty); }
set { SetValue(NumberProperty, value); }
}
private static void OnNumberChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("DepObj: NumberChanged {0} --> {1}", e.OldValue, e.NewValue);
}
private static object OnNumberCoerce(DependencyObject d, object value)
{
int cval = (int)value;
if (cval > 10)
cval = 10;
else if (cval < -10)
cval = 10;
Console.WriteLine("DepObj: OnNumberCoerce: {0} --> {1}", value, cval);
return cval;
}
}
public class TestPOCO
{
private int _pocoNumber = 0;
public int PocoNumber
{
get { return _pocoNumber; }
set { _pocoNumber = value; }
}
}
Now, we can create two instances of the types above and a binding and test setting the Number property. The binding updates the POCO object but it does so before value coercion.
// create a POCO object
var testPoco = new TestPOCO();
// create a dependency object
var testObj = new TestDO();
// create a binding between the NumberProperty of the Dependency Object and the
// PocoNumber of the POCO object
BindingOperations.SetBinding(testObj, TestDO.NumberProperty, new Binding("PocoNumber")
{
Source = testPoco,
Mode = BindingMode.TwoWay
});
// try setting a value of 100, to trigger value coercion
testObj.Number = 100;
Console.WriteLine("Poco Number = {0}", testPoco.PocoNumber); // displays 100
Console.WriteLine("DP Number = {0}", testObj.Number); // displays 10
The same is not true if the binding exists between two DependencyObjects. In that case everything works as expected. SetValue --> ValueCoercion --> PropertyChanged --> SetValue

Related

WPF: TwoWay Binding doesn't update back

I have the following custom control:
<abc:MyControl MyProperty="{Binding FieldInMyModel, Mode=TwoWay}">
And in my custom control I have
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(double), typeof(MyControl),
new PropertyMetadata(0.0, OnMyPropertyChanged));
public double MyProperty
{
get { return (double)GetValue(MyPropertyProperty ); }
set { SetValue(MyPropertyProperty , value); }
}
private static void OnMyPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (d is MyControl mc)
{
// here I check if the new value is valid for my scope.
// if it is not I update it here to be valid
var v = (double)e.NewValue;
if (!mc.IsValidValue(v))
{
v = mc.MakeValidValue(v);
mc.MyProperty = v;
}
}
}
In my model I change the value of FieldInMyModel to be not valid for my scope. OnMyPropertyChanged is called, and after making a valid value from received invalid value I expect that the FieldInMyModel will be updated to have the new value of MyProperty but actually nothing happens. Any thought?
Setting MyProperty from code will break your TwoWay Binding.
Instead you can use SetCurrentValue method.

How to get value from Binding object in Code as a string (WPF)

I'd like to get value from Binding in my code and use it as a string. How Can I do that?
Binding b = new Binding("MyProperty")
{
Source = myobject
};
//[...]
string value = b //HOW TO GET VALUE FROM b ?
BTW:
I would like the Converter attached to Binding to be called when retrieving this value.
I found that the solution could be an auxiliary class with DependencyProperty.
Auxiliary class
public class TestClass : FrameworkElement
{
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", typeof(string), typeof(TestClass), new PropertyMetadata(""));
}
Binding conversion to String
Binding b = new Binding("MyProperty") { Source = myobject };
TestClass tc = new TestClass { DataContext = b };
BindingOperations.SetBinding(tc, TestClass.MyPropertyProperty, b);
string txt = tc.MyProperty;
Advantages:
You can use Binding and MultiBinding
You can use Converters
Disadvantages:
Each time we create a class that inherits from the FrameworkElement, which means that we do unnecessary operations.

WPF Data Binding: BindingOperations.SetBinding() do not use the value of the Dependency Property

I have a own dependency property Target.Height bound to a normal property Source.Height using BindingOperations.SetBinding(). Updating the Target.Height property should update the Source.Height property. But not the actual value of the dependency property is used rather the default value of the dependency property. Is this the intended behavior?
Thanks for any hints. Code I use:
public class Source
{
private int m_height;
public int Height
{
get { return m_height; }
set { m_height = value; }
}
}
public class Target : DependencyObject
{
public static readonly DependencyProperty HeightProperty;
static Target()
{
Target.HeightProperty =
DependencyProperty.Register("Height", typeof(int), typeof(Target),
new PropertyMetadata(666)); //the default value
}
public int Height
{
get { return (int)GetValue(Target.HeightProperty); }
set { SetValue(Target.HeightProperty, value); }
}
}
Source source = new Source();
Target target = new Target();
target.Height = 100;
Binding heightBinding = new Binding("Height");
heightBinding.Source = source;
heightBinding.Mode = BindingMode.OneWayToSource;
BindingOperations.SetBinding(target, Target.HeightProperty, heightBinding);
//target.Height and source.Height is now 666 instead of 100 ....
WPF puts Binding as values of dependency properties. When you setting up a binding you actually replaces your current property value with a new one. At the end of the DependencyObject.SetValueCommon you may find a code that did it. There we can see that WPF gets a default value, then set it as a current property value with expression marker, and then attach BindingExpression which updates the source using the current property value - the default value.
this.SetEffectiveValue(entryIndex, dp, dp.GlobalIndex, metadata, expression, BaseValueSourceInternal.Local);
object defaultValue = metadata.GetDefaultValue(this, dp);
entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);
this.SetExpressionValue(entryIndex, defaultValue, expression);
DependencyObject.UpdateSourceDependentLists(this, dp, array, expression, true);
expression.MarkAttached();
expression.OnAttach(this, dp);
entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);
effectiveValueEntry = this.EvaluateExpression(entryIndex, dp, expression, metadata, valueEntry, this._effectiveValues[entryIndex.Index)]);
entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);

WPF Get real value from a custom validation rule

I use custom validation rule to validate my data. But I can't access/determine the property value.
here is my code
public class MandatoryRule: ValidationRule
{
public MandatoryRule()
{
ValidationStep = System.Windows.Controls.ValidationStep.UpdatedValue;
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
BindingExpression exp = value as BindingExpression;
if (value == null)
return new ValidationResult(true, null);
return new ValidationResult(true, null);
}
}
I need to set the ValidationStep to UpdatedValue (for further business logic)
Then comes the problem: I don't know what's the property value? Because:
It is a generic validator, can't bound to a specific model
The value in parameter of Validate method is a BindingExpression
So how can I read the real value?
Thanks
At last, I come up with this idea.
Create a class DummyObject : DependencyObject.
Create a public static DependencyProperty DummyProperty.
Then create a new databinding, copy the source, binding path, element name, converter, etc from the (value as BindingExpression).ParentBinding.
Set the new databinding target to the dummyobject.
Then use the binding to UpdateTarget()
And now you can access the value from the dummyproperty.
Had the same issue and came accross this question, Gary's answer seems to be the way to go, but it lacked the source code. So here's my interpretation.
public class BindingExpressionEvaluator : DependencyObject
{
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("ValueProperty", typeof(object),
typeof(BindingExpressionEvaluator), new UIPropertyMetadata(null));
public static object Evaluate(BindingExpression expression)
{
var evaluator = new BindingExpressionEvaluator();
var binding = new Binding(expression.ParentBinding.Path.Path);
binding.Source = expression.DataItem;
BindingOperations.SetBinding(evaluator, BindingExpressionEvaluator.ValueProperty, binding);
var value = evaluator.Value;
BindingOperations.ClearBinding(evaluator, BindingExpressionEvaluator.ValueProperty);
return value;
}
}

Bound Property Setter not getting Called

I have a problem with the following scenario (code cut for brevity). Basically the Setter of my User Control Property isn't being called when the dependency property is set and I need to get around this.
I have the following code in my View.xaml
<Filter:Filter x:Name="ProductFilter" PrimaryItemSource="{Binding CarrierProducts}" />
In the View.xaml.cs
public ProductPricing()
{
InitializeComponent();
ViewModel.Filter.ProductPricing vm = new ViewModel.Filter.ProductPricing();
this.DataContext = vm;
}
In my ViewModel I expose a property
public ObservableCollection<Model.FilterItem> _carrierProducts;
public ObservableCollection<Model.FilterItem> CarrierProducts
{
get
{
return _carrierProducts;
}
set
{
if (_carrierProducts != value)
{
_carrierProducts = value;
RaisePropertyChanged("CarrierProducts");
}
}
}
Finally the Filter User control is defined like so.
public static readonly DependencyProperty PrimaryItemSourceProperty =
DependencyProperty.Register("PrimaryItemSource", typeof(ObservableCollection<Model.FilterItem>), typeof(Filter), new PropertyMetadata(null));
public ObservableCollection<Model.FilterItem> PrimaryItemSource
{
get
{
return (ObservableCollection<Model.FilterItem>)GetValue(PrimaryItemSourceProperty);
}
set
{
SetValue(PrimaryItemSourceProperty, value);
ComboBox combo = _filters.ElementAt(0);
FilterSourceChange(combo, value);
}
}
For some reason the PrimaryItemSource property is set but the Setter doesn't get called. Do I have to add a PropertyChange event to the PropertyMetadata object to handle this as that seems like a lot of code for something simple.
This is how a Dependency property that requires additional code to be run on set should be written:-
public ObservableCollection<Model.FilterItem> PrimaryItemSource
{
get { return (ObservableCollection<Model.FilterItem>)GetValue(PrimaryItemSourceProperty); }
set { SetValue(PrimaryItemSourceProperty , value); }
}
public static readonly DependencyProperty PrimaryItemSourceProperty =
DependencyProperty.Register(
"PrimaryItemSource",
typeof(ObservableCollection<Model.FilterItem>),
typeof(Filter), new PropertyMetadata(null, OnPrimaryItemSourceChanged));
private static void OnPrimaryItemSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Filter filter = (Filter)d;
var oldValue = (ObservableCollection<Model.FilterItem>)e.OldValue;
var newValue = (ObservableCollection<Model.FilterItem>)e.NewValue;
filter.OnPrimaryItemSourceChanged(oldValue, newValue);
}
protected virtual void OnPrimaryItemSourceChanged(
ObservableCollection<Model.FilterItem> oldValue,
ObservableCollection<Model.FilterItem> newValue)
{
ComboBox combo = _filters.ElementAt(0);
FilterSourceChange(combo, newValue);
}
You use place a static DependencyPropertyChanged handler in the class that will cast down the dependency object to the correct type and then call an instance method to alert that instance of the change.
This change handler will get called whenever the underlying dependency property is changed be that via the SetValue call in the property Set method or by binding or any other means.
Yes, always use the callback if you need additional logic for the setter. This is a must in Silverlight and WPF.
As far as I know, the Setter would only be called when actually used from code. When you do Binding, things happen using the DependencyProperty framework.
You should also wrap your ComboBox combo = ... code into a this.Dispatcher.BeginInvoke(() => ... );, because that ensures the visual tree is initialized.
The Last parameter of the DependencyProperty.Register() method takes a PropertyMetaData where you're passing null. One of the overloads of the constructor takes a PropertyChangedCallback. Use this overload to define a callback function that will be called when your property is modified.
static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Filter filter = d as Filter;
ComboBox combo = filter._filters.ElementAt(0);
filter.FilterSourceChange(combo, filter.PrimaryItemSource);
}

Resources