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.
Related
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.");
}
I usually extend controls by creating bindable properties, in this form:
public static readonly BindableProperty OnTextProperty = BindableProperty.Create(nameof(OnText),
typeof(string), typeof(TextSwitch), defaultValue: string.Empty, defaultBindingMode: BindingMode.TwoWay,
propertyChanged: HandleOnTextPropertyChanged);
private static void HandleOnTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
(bindable as TextSwitch)?.Rebuild();
}
public string OnText
{
get { return (string)GetValue(OnTextProperty); }
set { SetValue(OnTextProperty, value); }
}
for me, since I did some WPF, bindable properties consists of two parts: the static readonly BindableProperty field, and a corresponding property with GetValue in getter and SetValue in setter. But I stumbled over this: https://github.com/adamped/NavigationMenu/blob/master/NavigationMenu/NavigationMenu/NavigationItem.xaml.cs
which just fire the PropertyChanged event :
public static readonly BindableProperty TextProperty = BindableProperty.Create(
nameof(Text),
typeof(string),
typeof(NavigationItem),
string.Empty,
propertyChanging: (bindable, oldValue, newValue) =>
{
var ctrl = (NavigationItem)bindable;
ctrl.Text = (string)newValue;
},
defaultBindingMode: BindingMode.OneWay);
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged();
}
}
How can this incorporate with the bindable properties to get it working without GetValue and SetValue?, and in which cases we need to use one approach over another?
EDIT
Apparently I'm not accustomed to the notion of the self binding reusable control.. but isn't calling GetValue and SetValue essential for binding properties?
These two implementations are the same.
GetValue(BindableProperty) and SetValue are used to access the values of properties that are implemented by a BindableProperty. That is, application developers typically provide an interface for a bound property by defining public property whose get accessor casts the result of GetValue(BindableProperty) to the appropriate type and returns it, and whose set accessor uses SetValue to set the value on the correct property.
Your achievement
private static void HandleOnTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
(bindable as TextSwitch)?.Rebuild();
}
is same with
propertyChanging: (bindable, oldValue, newValue) =>
{
var ctrl = (NavigationItem)bindable;
ctrl.Text = (string)newValue;
}
Used
OnPropertyChanged(); in setValuemethod
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);
}
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.
Consider the following scenario:
I want to bind the TextElement.FontWeight property to an xml attribute. The xml looks somewhat like this and has arbitrary depth.
<text font-weight="bold">
bold text here
<inlinetext>more bold text</inlinetext>
even more bold text
</text>
I use hierarchical templating to display the text, no problem there, but having a Setter in the template style like:
<Setter Property="TextElement.FontWeight" Value="{Binding XPath=#font-weight}"/>
sets the fontweight correctly on the first level, but overwrites the second level with null (as the binding can't find the xpath) which reverts to Fontweight normal.
I tried all sorts of things here but nothing quite seems to work.
e.g. i used a converter to return UnsetValue, which didn't work.
I'm currently trying with:
<Setter Property="custom:AttributeInserter.Wrapper" Value="{custom:AttributeInserter Property=TextElement.FontWeight, Binding={Binding XPath=#font-weight}}"/>
Codebehind:
public static class AttributeInserter
{
public static AttributeInserterExtension GetWrapper(DependencyObject obj)
{
return (AttributeInserterExtension)obj.GetValue(WrapperProperty);
}
public static void SetWrapper(DependencyObject obj, AttributeInserterExtension value)
{
obj.SetValue(WrapperProperty, value);
}
// Using a DependencyProperty as the backing store for Wrapper. This enables animation, styling, binding, etc...
public static readonly DependencyProperty WrapperProperty =
DependencyProperty.RegisterAttached("Wrapper", typeof(AttributeInserterExtension), typeof(AttributeInserter), new UIPropertyMetadata(pcc));
static void pcc(DependencyObject o,DependencyPropertyChangedEventArgs e)
{
var n=e.NewValue as AttributeInserterExtension;
var c = o as FrameworkElement;
if (n == null || c==null || n.Property==null || n.Binding==null)
return;
var bex = c.SetBinding(n.Property, n.Binding);
bex.UpdateTarget();
if (bex.Status == BindingStatus.UpdateTargetError)
c.ClearValue(n.Property);
}
}
public class AttributeInserterExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public DependencyProperty Property { get; set; }
public Binding Binding { get; set; }
}
which kinda works, but can't track changes of the property
Any ideas? Any links?
thx for the help
You're on the right track. Using an attached property is the way to go.
Simplest solution
If you're willing to code for each inherited property (there aren't very many of them) it can be much simpler:
<Setter Property="my:OnlyIfSet.FontWeight" Value="{Binding XPath=#font-weight}"/>
with code
public class OnlyIfSet : DependencyObject
{
public static FontWeight GetFontWeight(DependencyObject obj) { return (FontWeight)obj.GetValue(FontWeightProperty); }
public static void SetFontWeight(DependencyObject obj, FontWeight value) { obj.SetValue(FontWeightProperty, value); }
public static readonly DependencyProperty FontWeightProperty = DependencyProperty.RegisterAttached("FontWeight", typeof(FontWeight), typeof(Object), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
if(e.NewValue!=null)
obj.SetValue(TextElement.FontWeightProperty, e.NewValue);
else
obj.ClearValue(TextElement.FontWeightProperty);
}
});
}
Generalizing for multiple properties
Generalizing this can be done in several ways. One is to create a generalized property registering method, but still use multiple attached properties:
public class OnlyIfSet
{
static DependencyProperty CreateMap(DependencyProperty prop)
{
return DependencyProperty.RegisterAttached(
prop.Name, prop.PropertyType, typeof(OnlyIfSet), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
if(e.NewValue!=null)
obj.SetValue(prop, e.NewValue);
else
obj.ClearValue(prop);
}
});
}
public static FontWeight GetFontWeight(DependencyObject obj) { return (FontWeight)obj.GetValue(FontWeightProperty); }
public static void SetFontWeight(DependencyObject obj, FontWeight value) { obj.SetValue(FontWeightProperty, value); }
public static readonly DependencyProperty FontWeightProperty =
CreateMap(TextElement.FontWeightProperty);
public static double GetFontSize(DependencyObject obj) { return (double)obj.GetValue(FontSizeProperty); }
public static void SetFontSize(DependencyObject obj, double value) { obj.SetValue(FontSizeProperty, value); }
public static readonly DependencyProperty FontSizeProperty =
CreateMap(TextElement.FontSizeProperty);
...
}
Allowing arbitrary properties
Another approach is to use two attached properties:
<Setter Property="my:ConditionalSetter.Property" Value="FontWeight" />
<Setter Property="my:ConditionalSetter.Value" Value="{Binding XPath=#font-weight}"/>
with code
public class ConditionalSetter : DependencyObject
{
public string GetProperty( ... // Attached property
public void SetProperty( ...
public static readonly DependencyProperty PropertyProperty = ...
{
PropertyChangedCallback = Update,
});
public object GetValue( ... // Attached property
public void SetValue( ...
public static readonly DependencyProperty ValueProperty = ...
{
PropertyChangedCallback = Update,
});
void Update(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var property = GetProperty(obj);
var value = GetValue(obj);
if(property==null) return;
var prop = DependencyPropertyDescriptor.FromName(property, obj.GetType(), typeof(object)).DependencyProperty;
if(prop==null) return;
if(value!=null)
obj.SetValue(prop, value);
else
obj.ClearValue(prop);
}
}
if anyone comes across this, my final solution was this:
<DataTrigger Binding="{Binding XPath=#font-weight,Converter={converters:IsNullConverter}}" Value="false">
<Setter Property="TextElement.FontWeight" Value="{Binding XPath=#font-weight}" />
</DataTrigger>
with IsNullConverter being a simple Valueconverter with this body:
return value == null;