How do I handle DependencyProperty overflow situations? - c#

I have a UserControl and an int DependencyProperty called Value. This is bound to a text input on the UserControl.
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(QuantityUpDown), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged, CoerceValue));
public int Value
{
get { return (int) GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static object CoerceValue(DependencyObject d, object basevalue)
{
//Verifies value is not outside Minimum or Maximum
QuantityUpDown upDown = d as QuantityUpDown;
if (upDown == null)
return basevalue;
if ((int)basevalue <= 0 && upDown.Instrument != null)
return upDown.Minimum;
//Stocks and ForEx can have values smaller than their lotsize (which is assigned to Minimum)
if (upDown.Instrument != null &&
upDown.Instrument.MasterInstrument.InstrumentType != Cbi.InstrumentType.Stock &&
upDown.Instrument.MasterInstrument.InstrumentType != Cbi.InstrumentType.Forex)
return Math.Max(Math.Min(upDown.Maximum, (int)basevalue), upDown.Minimum);
if (upDown.Instrument == null)
return Math.Max(Math.Min(upDown.Maximum, (int)basevalue), upDown.Minimum);
if (upDown.Instrument.MasterInstrument.InstrumentType == Cbi.InstrumentType.Stock ||
upDown.Instrument.MasterInstrument.InstrumentType == Cbi.InstrumentType.Forex)
return Math.Min(upDown.Maximum, (int)basevalue);
return basevalue;
}
If a user enters a value greater than int.MaxValue in the text box, when the value comes into CoerceValue, the baseValue argument is 1. The same occurs if I provide a validation value callback on the DependencyProperty.
I'd like to handle this situation myself, such as setting the incoming value to int.MaxValue. Is there a way to do this?

A int property can never be set to something else than an int value. The type of the Text property of a TextBox is however string and the error occurs when when the runtime is trying to set your int property to a string value that doesn't represent a valid integer.
Your dependency property cannot do much about this as it never gets set. As ##Ed Plunkett suggests in his comment you could use a ValidationRule to do something before the value conversion occurs and present an error message to the user if the conversion fails:
public class StringToIntValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
int i;
if (int.TryParse(value.ToString(), out i))
return new ValidationResult(true, null);
return new ValidationResult(false, "Please enter a valid integer value.");
}
}
<TextBox>
<TextBox.Text>
<Binding Path="Value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:StringToIntValidationRule ValidationStep="RawProposedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Please refer to the following blog post about data validation in WPF for more information: https://blog.magnusmontin.net/2013/08/26/data-validation-in-wpf/

Related

C# WPF OnPropertyChange doesn't work when using ValidationRules

I've searched all over google and am still trying to come up with the correct answer. The problem is as follows when I clear this text field, the bound value does not get triggered.
So, the problem is that the bounded value of this text field is not getting changed if it is made empty, but my validation rule does detect it and sends out the warning.
Underneath, you can find my XAML snippet, the belonging validation rule, and the mentioned property.
The XAML-Snippet
<TextBox Style="{StaticResource TextBoxErrorStyle}"
Margin="8 0 0 0"
Visibility="{Binding AdditionalSurfaceTreatmentInfoVisibility,
Converter={StaticResource BoolToVisConverter}}"
FontSize="12" MaxLength="4" Width="40"
Height="25">
<TextBox.Text>
<Binding Path="AdditionalSurfaceTreatmentInfo"
UpdateSourceTrigger="PropertyChanged"
NotifyOnSourceUpdated="True" Mode="TwoWay"
NotifyOnValidationError="True">
<Binding.ValidationRules>
<classes:StandardValidationRule ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
The StandardValidationRule:
public class StandardValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var valueToValidate = value as string;
if (string.IsNullOrEmpty(valueToValidate))
{
return new ValidationResult(false, "Field is mandatory.");
}
return new ValidationResult(true, null);
}
}
The property:
private string _additionalSurfaceTreatmentInfo;
public string AdditionalSurfaceTreatmentInfo
{
get => _additionalSurfaceTreatmentInfo;
set
{
_additionalSurfaceTreatmentInfo = value;
OnPropertyChanged();
SetOrModifySurfaceTreatment();
Console.WriteLine(string.IsNullOrEmpty(_additionalSurfaceTreatmentInfo).ToString());
}
}
Thank you in advance for your efforts. Any help is much appreciated!
The code above does work as I prefer. I already tried everything regarding the different properties I can fill in within the ValidationRule. The only thing that needs to be changed is that when the textbox is empty, it must trigger the OnPropertyChanged() method. This way, I can later validate the property when I, for example, submit a save command.
Use the ValidationStep property.
Example:
<local:StandardValidationRule ValidationStep="UpdatedValue"
ValidatesOnTargetUpdated="True"/>
But to validate the source property, more complex logic is needed, since the validator does not receive the value of the property, but the binding expression from the target property.
public class StandardValidationRule : ValidationRule
{
private bool gettingValue = false;
private bool isValueReceived = false;
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (gettingValue)
{
isValueReceived = true;
return ValidationResult.ValidResult;
}
string? valueToValidate = value as string;
ValidationResult? result = null;
if (valueToValidate is null && value is not null)
{
if (value is BindingExpressionBase bindingExpression)
{
gettingValue = true;
isValueReceived = false;
DependencyObject target = bindingExpression.Target;
var gettingValueExpression = BindingOperations.SetBinding(target, SourceValueProperty, bindingExpression.ParentBindingBase);
if (!isValueReceived)
{
gettingValueExpression.UpdateTarget();
}
valueToValidate = target.GetValue(SourceValueProperty)?.ToString();
target.ClearValue(SourceValueProperty);
gettingValue = false;
}
else
{
result = unvalid;
}
}
if (result is null)
{
result = string.IsNullOrEmpty(valueToValidate)
? unvalid
: ValidationResult.ValidResult;
}
return result;
}
private static readonly ValidationResult unvalid = new ValidationResult(false, "Field is mandatory.");
// Using a DependencyProperty as the backing store for SourceValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceValueProperty =
DependencyProperty.RegisterAttached("SourceValue", typeof(object), typeof(StandardValidationRule), new PropertyMetadata(null));
}
There is another method for getting the source value.
It is based on using the internal method through reflection.
Which is considered by many to be a «bad» way.
But it works much more efficiently.
And I think it is unlikely that someone will make changes to the internal method, which is already used in many places.
public static class BindingExpressionHelper
{
private static readonly Func<BindingExpressionBase, DependencyObject, DependencyProperty, object> GetValueOfBindingExpression;
static BindingExpressionHelper()
{
Type beType = typeof(BindingExpressionBase);
var beMethod = beType
.GetMethod("GetValue", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, new Type[] { typeof(DependencyObject), typeof(DependencyProperty) })
?? throw new Exception("GetValue method not found.");
var beFunc = (Func<BindingExpressionBase, DependencyObject, DependencyProperty, object>)
beMethod.CreateDelegate(typeof(Func<BindingExpressionBase, DependencyObject, DependencyProperty, object>));
GetValueOfBindingExpression = beFunc;
}
/// <summary>Returns the source value of this binding expression.</summary>
/// <param name="bindingExpression">The binding expression whose value to get.</param>
/// <returns>The value of the binding expression received.</returns>
public static object? GetSourceValue(this BindingExpressionBase bindingExpression)
{
DependencyObject target = bindingExpression.Target;
DependencyProperty targetProperty = bindingExpression.TargetProperty;
var value = GetValueOfBindingExpression(bindingExpression, target, targetProperty);
return value;
}
}
public class StandardValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string? valueToValidate = value as string;
ValidationResult? result = null;
if (valueToValidate is null && value is not null)
{
if (value is BindingExpressionBase bindingExpression)
{
valueToValidate = bindingExpression.GetSourceValue()?.ToString();
}
else
{
result = unvalid;
}
}
if (result is null)
{
result = string.IsNullOrEmpty(valueToValidate)
? unvalid
: ValidationResult.ValidResult;
}
return result;
}
private static readonly ValidationResult unvalid = new ValidationResult(false, "Field is mandatory.");
}

Force a dependencyproperty to always call PropretyChangedCallback

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.

Bind TextBox to decimal-property

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);

MultiBinding doesn't pick up the second Property

I have a problem with the MultiBinding. It seems the latest value of the second property is not picked up when the first property changes.
<Image Width="16" Source="../Images/YellowScales.png" Grid.Column="1" >
<Image.Visibility>
<MultiBinding Converter="{Converters:GoldScaleConverter}">
<Binding Path="IsFavourite"/>
<Binding Path="MemoryUsageLevel"/>
</MultiBinding>
</Image.Visibility>
</Image>
In the ViewModel:
public bool IsFavourite
{
get { return _isFavourite; }
set
{
if (_isFavourite == value)
return;
_isFavourite = value;
RaisePropertyChanged("IsFavourite");
UnloadBookCommmand.RaiseCanExecuteChanged();
}
}
public double MemoryUsageLevel
{
get
{
return GetMemoryUsageLevel(this);
}
}
Initially when I start the app, both properties are hit from the Converter and it works as expected.
However once the app is running and I change the IsFavourite property, it does trigger the multibinding and I can see withing the Converter that IsFavourite has flipped but the second value that is MemoryUsageLevel is always 0.0. The getter is not hit again.
But why I thought the MultiBinding is meant to check the latest value of both Bindings?
This is the converter:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
bool isFavourite = (bool) values[0];
double memoryWarningLevel = (double) values[1];
if(isFavourite && (memoryWarningLevel >= 50.00 && memoryWarningLevel < 75.00))
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
You probably have to raise PropertyChanged notification for MemoryUsageLevel as well. You can raise this in the setter of IsFavourite
public bool IsFavourite
{
get { .. }
set {
...
RaisePropertyChanged("IsFavourite");
RaisePropertyChanged("MemoryUsageLevel");
}
}

DependencyProperty ValidateValueCallback question

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);
}

Categories

Resources