MultiBinding doesn't pick up the second Property - c#

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

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

How do I handle DependencyProperty overflow situations?

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/

Check if Hex string is correct in Converter

I made a text box to input and show color Hex value.The binding is twoway to a color property of a parent.
Everything is working but, I need to make sure, in case I enter manually a Hex in the text box, and if this a not correct string, then use and display the current Hex value of the color, rather than trying to change it.
Here is what I tried but obviously it's not working, I'm a beginner and I have only a little experience with converter and WPF. If I write anything but not a valid Hex string, at the moment the textbox gets a red outline, but I wish that in this case, the Hex previous string reappears.
[ValueConversion(typeof(Color), typeof(String))]
public class ColorToStringConverter : IValueConverter
{
public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
{
Color colorValue = (Color)value;
return ColorNames.GetColorName(colorValue);
}
public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ColorHexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var hexCode = System.Convert.ToString(value);
//if (string.IsNullOrEmpty(hexCode))
// return null;
try
{
var color = (Color)ColorConverter.ConvertFromString(hexCode);
return color;
}
catch
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var hexCode = System.Convert.ToString(value);
Regex myRegex = new Regex("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
bool isValid = false;
if (string.IsNullOrEmpty(hexCode))
{
isValid = false;
}
else
{
isValid = myRegex.IsMatch(hexCode);
}
try
{
return hexCode;
}
catch
{
return null;
}
}
}
And the C# class for the TextBox
public class ColorHex : TextBox
{
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter)
{
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(this, TextProperty);
if (bindingExpression != null)
bindingExpression.UpdateSource();
}
}
}
And its xaml in Generic.xaml
<local:ColorHex x:Name="PART_ColorHex" Style="{StaticResource ColorPickerTextBox}" Text="{Binding SelectedColor, Converter={StaticResource ColorToHexConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ColorPicker}}}" />
Any idea ?
thank you
A valid hex color has the form '#nnn' or '#nnnnnn'.
So, the regex for this case would be: (^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)
NOw, you could add these lines of code:
var regex = #"(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)"
var match = Regex.Match(inputYouWantToCheck, regex,RegexOptions.IgnoreCase);
if (!match.Success)
{
// Color is not valid
}
Hope this helps.
What if you did something where you add a label next to the textbox to show an example of what color has been entered. You would just need to change the label color change each time.
I'd do it differently, using property to validate the color:
public Color Color { get; set; } = Colors.Red;
public string ColorText
{
get { return (new ColorConverter()).ConvertToString(Color); }
set
{
Color = (Color)ColorConverter.ConvertFromString(value);
OnPropertyChanged();
}
}
Bind to ColorText, it will throw in case it's wrong hex, and you can use ExceptionValidationRule to display it (as a red border):
<TextBox Text="{local:ExceptionBinding Path=ColorText}" />
where
public class ExceptionBinding : Binding
{
public ExceptionBinding() : base()
{
ValidationRules.Add(new ExceptionValidationRule());
}
}
As a bonus you can enter known colors as text, e.g. "Red":

An issue when TextBox is binding to double and enter negative number that little than -1

I have an issue when i binding a textbox to Propery and enter a negative number that is less than -1 - for example -0.45:
the textbox:
<TextBox Text="{Binding Txt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
and the property:
double txt;
public double Txt
{
get { return txt; }
set { txt = value; OnPropertyChanged("Txt"); }
}
it seems when i try to enter -0.54 it changes immediatly to 0, why?
Here is the convertor that does the job ( So leave your view model as it is- You can use it for both decimal and double). We just need to hold the decimal and -ve position initially:
public class DecimalConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value !=null)
{
return value.ToString();
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string data = value as string;
if (data == null)
{
return value;
}
if (data.Equals(string.Empty))
{
return 0;
}
if (!string.IsNullOrEmpty(data))
{
decimal result;
//Hold the value if ending with .
if (data.EndsWith(".") || data.Equals("-0"))
{
return Binding.DoNothing;
}
if (decimal.TryParse(data, out result))
{
return result;
}
}
return Binding.DoNothing;
}
}
So we hold the values or do nothing on binding
when you enter the decimal value it becomes 0 again so the best way to do this is using the lostfocus trigger:
<TextBox Text="{Binding Txt, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" Grid.Row="0"/>
Also you need to do this in the view model:
public double Txt
{
get { return txt; }
set
{
if (!txt.Equals(value))
{
txt = value;
OnPropertyChanged("Txt");
}
}
}

Bind Flag enums to listbox containing checkboxes

I have flag enum say this -
[Flags]
public enum Department
{
None = 0,
A = 1,
B = 2,
C = 4,
D = 8
}
I want to show values of this enum on view. I thought of creating a listbox and binding its source to the collection of this enum List<Department> Departments.
All works so good until i thought of having a checkbox which binds to a property on my Viewmodel -
public Department SelectedDepartments { get; set; }
The solution here http://compilewith.net/2008/12/wpf-flagsenumvalueconverter.html provides elegant solution for binding enum values to checkboxes but its have one limitation of creating checkboxes equal to the number of enum values in list.
But, in my case i can't afford of having so many checkboxes lying on my UI since my Enum contains 20 values (so that means having 20 checkboxes on UI).
I tried using MultiBindingConverter but that fails in ConvertBack Method.
I want to bind the state of checkboxes with property SelectedDepartments. Say if property value is "A | B" then A and B checkbox should be checked whereas C and D should remain unchecked.
I don't think there's a way of doing this without using some code-behind.
I took the sample solution you linked to above, removed all of the CheckBoxes from MainWindow.xaml, added the following method to MainWindow.xaml.cs and called it from the MainWindow constructor:
private void AddCheckBoxes()
{
var converter = new FlagsEnumValueConverter();
foreach (Department dept in Enum.GetValues(typeof(Department)))
{
if (dept != Department.None)
{
var binding = new Binding()
{
Path = new PropertyPath("Department"),
Converter = converter,
ConverterParameter = dept
};
var checkBox = new CheckBox() { Content = dept.ToString() };
checkBox.SetBinding(CheckBox.IsCheckedProperty, binding);
DepartmentsPanel.Children.Add(checkBox);
}
}
}
This method does the work of creating all of the checkboxes, one for each named enum constant apart from None. I could then add further departments to the Department enum, rerun the solution and see additional checkboxes for the newly-added departments.
There were a few further minor changes that I had to make to this solution to get it working completely. You may or may not need to make these changes to your code. Firstly, I made the DataObject class implement INotifyPropertyChanged. Secondly, I rewrote the XAML in MainWindow.xaml as follows:
<StackPanel>
<StackPanel x:Name="DepartmentsPanel" />
<TextBlock Margin="5,20,0,0">
<TextBlock Text="Raw Value:" FontWeight="Bold" />
<TextBlock Text="{Binding Department}" />
</TextBlock>
</StackPanel>
(Basically, I wrapped the existing DepartmentsPanel in another StackPanel and moved the 'Raw Value' display into this outer StackPanel.) Finally, I set the DataContext of the whole MainWindow, rather than the DataContext of the DepartmentsPanel, to the DataObject created. This step was necessary to make the 'Raw Value' display work.
I have created an IValueConverter that supports binding to the enum directly without codebehind or helper classes. It has only two restrictions:
One converter instance has to be used for each source property. If the model contains more properties of the same enum type, each of them needs to use a separate converter instance. This can be done by instantiating the converters in the XAML.
When the enum type is a flags enum it has to be an Integer.
The solution is based on the empirical fact that there is always a Convert first before a ConvertBack. And there is always a Convert if a ConvertBack has changed the value. This one only works when the INotifyPropertyChanged is properly implemented on the model. So between the two calls the last known value can be stored in the converter and used in the ConvertBack method.
The converter instance should get the Type of the enum and whether it is a Flags enum or not.
<EnumToCheckedConverter x:Key="InstanceName" Type="{x:Type MyEnum}" Flags="True" />
Then the checkboxes can be bound using this converter.
<CheckBox Content="ValueText" IsChecked="{Binding Source, Converter={StaticResource InstanceName}, ConverterParameter=Value}"/>
Radio buttons can be bound by the same mechanism using an instance with Flags="False"
The source code of the converter
public class EnumToCheckedConverter : IValueConverter
{
public Type Type { get; set; }
public int? LastValue { get; private set; }
public bool Flags { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.GetType() == Type)
{
try
{
var parameterValue = Enum.Parse(Type, parameter as string);
if (Flags == true)
{
var intParameter = (int)parameterValue;
var intValue = (int)value;
LastValue = intValue;
return (intValue & intParameter) == intParameter;
}
else
{
return Equals(parameterValue, value);
}
}
catch (ArgumentNullException)
{
return false;
}
catch (ArgumentException)
{
throw new NotSupportedException();
}
}
else if (value == null)
{
return false;
}
throw new NotSupportedException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value is bool check)
{
if (check)
{
try
{
if (Flags == true && LastValue.HasValue)
{
var parameterValue = Enum.Parse(Type, parameter as string);
var intParameter = (int)parameterValue;
return Enum.ToObject(Type, LastValue | intParameter);
}
else
{
return Enum.Parse(Type, parameter as string);
}
}
catch (ArgumentNullException)
{
return Binding.DoNothing;
}
catch (ArgumentException)
{
return Binding.DoNothing;
}
}
else
{
try
{
if (Flags == true && LastValue.HasValue)
{
var parameterValue = Enum.Parse(Type, parameter as string);
var intParameter = (int)parameterValue;
return Enum.ToObject(Type, LastValue ^ intParameter);
}
else
{
return Binding.DoNothing;
}
}
catch (ArgumentNullException)
{
return Binding.DoNothing;
}
catch (ArgumentException)
{
return Binding.DoNothing;
}
}
}
throw new NotSupportedException();
}
}

Categories

Resources