I have written a control with a bindable property. This control also has a method to modify that property's value:
public class MyControl : ContentView // WPF: inherited from UserControl
{
// Xamarin:
public static readonly BindableProperty MyValueProperty = ...
// WPF:
// public static readonly DependencyProperty MyValueProperty = ...
public int MyValue
{
get { return (int) GetValue(MyValueProperty); }
set { SetValue(MyValueProperty, value); }
}
public void Reset()
{
MyValue = 0;
}
}
I am using that control in a normal XAML page and update MyValue via binding:
<local:MyControl x:Name="TheControl"
MyValue="{Binding MyValueSource, Mode=OneWay}" />
The binding initially propagates changes from MyValueSource to MyValue. But as soon as I call the Reset() method once, the binding is overwritten by the 0 and updates to MyValueSource are no longer pulled.
I suppose any direct assignment of MyValue is intended to replace a OneWay binding. With a TwoWay binding, the change is just propagated back to MyValueSource and the binding remains functional.
If Reset() was in the view model, I could do this:
public void Reset()
{
// TheControl.MyValue = 0; // Bad practice, destroys the binding
MyValueSource = 0; // Good practice, preserves the binding
}
I don't want to implement the reset logic (which is more complex than in this reduced example) in every VM though, so it's located in the view/control.
So I wonder - can you assign a bindable property's value from the control's code behind and still preserve a possible OneWay binding? I know this means the VM does not get the changed value; binding OneWay is likely not correct if the control updates the property as well; you should rather use a TwoWay binding then.
But if someone says OneWay in XAML, I'd rather have it behave that way down to the wire than implement some "OneWay until you call Reset()" behavior.
Side note: I am working in Xamarin, but I guess the behavior is the same for WPF.
Taken and fleshed out from #Clemens' comment:
WPF
You can use the SetCurrentValue method on an DependencyObject (i. e. the control) to change the current effective value of a DependencyProperty. Unlike SetValue, with SetCurrentValue any triggers, data bindings and styles to that property remain intact.
public void Reset()
{
// this.SetValue(MyValueProperty, 0); // Replaces the binding
this.SetCurrentValue(MyValueProperty, 0); // Keeps the binding
}
Remember that if you defined a OneWay binding, the view model will not be notified about the changed value, and that any change to the VM's MyValueSource property will override the control's value again (if the property is implemented correctly).
Xamarin
There is currently no proper way to assign a BindableProperty's value without replacing a OneWay binding attached to it. BindableObject (the control's base class) does not have any method comparable to WPF's SetCurrentValue and SetValue will allways replace the binding.
However, if you change the binding to BindingMode.TwoWay, the internal value change is propagated back to the view model. You should probably do this anyway to keep the control and the VM synchronized.
public void Reset()
{
// Replaces any OneWay bindings
// Updates MyValueSource for TwoWay bindings
this.SetValue(MyValueProperty, 0);
}
Here is the Hacky WPF equivalent for Xamarin, for OneWay binding:
public static class BindingObjectExtensions
{
public static Binding GetBinding(this BindableObject self, BindableProperty property)
{
if (self == null)
{
throw new ArgumentNullException(nameof(self));
}
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}
var methodInfo = typeof(BindableObject).GetTypeInfo().GetDeclaredMethod("GetContext");
var context = methodInfo?.Invoke(self, new object[] { property });
var propertyInfo = context?.GetType().GetTypeInfo().GetDeclaredField("Binding");
return propertyInfo?.GetValue(context) as Binding;
}
public static void SetCurrentValue(this BindableObject self, BindableProperty property, object value)
{
if (self == null)
{
throw new ArgumentNullException(nameof(self));
}
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}
var backupBinding = self.GetBinding(property);//backup binding
var backupConverter = backupBinding.Converter;//backup orig. converter
self.SetValue(property,value);//removes the binding.
backupBinding.Converter = new DefaultValueConverter {DefaultValue = value};//change the converter
self.SetBinding(property, backupBinding);//target should be updated to the default value
var converterField = backupBinding.GetType().GetTypeInfo().GetDeclaredField("_converter");
converterField.SetValue(backupBinding, backupConverter);//restore the converter
}
}
//the default value converter class
[ContentProperty(nameof(DefaultValue))]
public class DefaultValueConverter : BindableObject, IValueConverter, IMarkupExtension<DefaultValueConverter>
{
public object DefaultValue
{
get => GetValue(DefaultValueProperty);
set => SetValue(DefaultValueProperty, value);
}
public static readonly BindableProperty DefaultValueProperty =
BindableProperty.Create(nameof(DefaultValue), typeof(object), typeof(DefaultValueConverter));
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return DefaultValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DefaultValue;
}
public DefaultValueConverter ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return ((IMarkupExtension<DefaultValueConverter>) this).ProvideValue(serviceProvider);
}
}
Related
My question is not about serialization I am getting the serialization part to work just fine, but for context I am trying to serialize some properties set by the user in a WPF form, and therefore require a two-way binding (which I can only get to work in one direction: target-to-source updates).
Let's say I have defined an application property that is a container for my properties I want to be serializable:
public SerializableApplicationProperties ThisAppsSerializableProperties { get; set; }
The SerializableApplicationProperties class looks like this:
[Serializable]
public class SerializableApplicationProperties
{
public SerializableApplicationProperties()
{
UserSelectedPreFillFilePath = new ObservableString();
}
public SerializableApplicationProperties(string defaultFilePath)
{
UserSelectedPreFillFilePath = new ObservableString(defaultFilePath);
}
public ObservableString UserSelectedPreFillFilePath { get; set; }
}
I have one property defined so far that I need to establish a two way-binding for in code (yes, in code). Please do not suggest how I can do this in XAML markup, unless you also explain how to do it entirely in code.
The property is "UserSelectedPreFillFilePath", which you can see is of type "ObservableString".
ObservableString Class looks like this:
[Serializable]
public class ObservableString : INotifyPropertyChanged
{
private string _stringValue;
public string StringValue
{
get { return _stringValue; }
set
{
_stringValue = value;
OnPropertyChanged("StringValue");
}
}
public ObservableString() { }
public ObservableString(string value)
{
this._stringValue = value;
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
A very simple class that holds a "StringValue" property and implements INotifyChange.
So then, I try to establish a two-way binding between this "UserSelectedPreFillFileLocation" and a text box on the main window.
I want a two-way binding so that before I close down the application I can serialize the UserSelectedPreFillFileLocation property (which should be automatically updated based on what the user puts in the textbox in the window).
Then when the application loads from a file (deserializes) that textbox is auto-populated with the UserSelectedPreFillFileLocation value via the binding.
Second to last thing, my converter class definition:
[ValueConversion(typeof(ObservableString), typeof(string))]
class ObservableStringToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ObservableString myVar = (ObservableString)value;
return myVar.StringValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string myVar = (string)value;
return new ObservableString(myVar);
}
}
Lastly here is the definition of the binding. The binding works target-to-source but not the other way around. I.E. when the user updates the textbox, UserSelectedPreFillFileLocation is updated, but not the other way around.
Binding UserSelectedPreFillFilePathBinding = new Binding();
UserSelectedPreFillFilePathBinding.Path = new PropertyPath(nameof(ThisAppsSerializableProperties.UserSelectedPreFillFilePath));
UserSelectedPreFillFilePathBinding.Source = ThisAppsSerializableProperties;
UserSelectedPreFillFilePathBinding.Converter = new ObservableStringToStringConverter();
UserSelectedPreFillFilePathBinding.Mode = BindingMode.TwoWay;
UserSelectedPreFillFilePathBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
ThisAppInstanceMainWindow.TxtbxPrefillFileLocation.SetBinding(TextBox.TextProperty, UserSelectedPreFillFilePathBinding);
I discovered what I was doing wrong.
I have indeed defined an ObservableString and the observable property that raises the property changed event is the StringValue within the class.
So, in this case I did not even need a converter since the source property I should have defined for the binding was the StringValue, and the source object should have been the UserSelectedPreFillFilePath.
Corrected code that is now working great with the two-way binding:
Binding UserSelectedPreFillFilePathBinding = new Binding();
UserSelectedPreFillFilePathBinding.Path = new PropertyPath(nameof(ThisAppsSerializableProperties.UserSelectedPreFillFilePath.StringValue));
UserSelectedPreFillFilePathBinding.Source = ThisAppsSerializableProperties.UserSelectedPreFillFilePath;
UserSelectedPreFillFilePathBinding.Mode = BindingMode.TwoWay;
UserSelectedPreFillFilePathBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
ThisAppInstanceMainWindow.TxtbxPrefillFileLocation.SetBinding(TextBox.TextProperty, UserSelectedPreFillFilePathBinding);
I have a WPF control that is based on the TextBox control:
public class DecimalTextBox : TextBox
I have a dependency property that is bound to, which manages the numeric value, and is responsible for setting the Text property:
public decimal NumericValue
{
get { return (decimal)GetValue(NumericValueProperty); }
set
{
if (NumericValue != value)
{
SetValue(NumericValueProperty, value);
SetValue(TextProperty, NumericValue.ToString());
System.Diagnostics.Debug.WriteLine($"NumericValue Set to: {value}, formatted: {Text}");
}
}
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (decimal.TryParse(Text, out decimal num))
{
SetValue(NumericValueProperty, num);
}
}
This works well when entering a value into the textbox itself (it updates the underlying values, etc...). However, when the bound property of NumericValue is changed, despite updating the NumericValue DP, the Text property is not updated. In the tests that I've done, it would appear that the reason for this is that the set method above is not called when the bound value is updated. The binding in question looks like this:
<myControls:DecimalTextBox NumericValue="{Binding Path=MyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Can anyone point me in the right direction as to why this property setter is not firing, or is there a better way to approach this?
As explained in Custom Dependency Properties and XAML Loading and Dependency Properties, you should not call anything else than GetValue and SetValue in the CLR wrapper of a dependency property:
Because the current WPF implementation of the XAML processor behavior for property setting bypasses the wrappers entirely, you should not put any additional logic into the set definitions of the wrapper for your custom dependency property. If you put such logic in the set definition, then the logic will not be executed when the property is set in XAML rather than in code.
In order to get notified about value changes, you'll have to register a PropertyChangedCallback with the dependency property metadata.
public static readonly DependencyProperty NumericValueProperty =
DependencyProperty.Register(
"NumericValue", typeof(decimal), typeof(DecimalTextBox),
new PropertyMetadata(NumericValuePropertyChanged));
public decimal NumericValue
{
get { return (decimal)GetValue(NumericValueProperty); }
set { SetValue(NumericValueProperty, value); }
}
private static void NumericValuePropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var textBox = (DecimalTextBox)obj;
textBox.Text = e.NewValue.ToString();
}
The WPF binding is not actually using your getter and setter, but instead directly interacts with the dependency property NumericValueProperty. In order to update the text, subscribe to the PropertyChanged event of the NumericValueProperty instead of trying to do anything special in the setter.
Subscribe to the change in your DependencyProperty definition, similar to the following:
// Using a DependencyProperty as the backing store for NumericValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NumericValueProperty =
DependencyProperty.Register("NumericValue", typeof(decimal), typeof(DecimalTextBox), new FrameworkPropertyMetadata(0.0m, new PropertyChangedCallback(OnNumericValueChanged)));
private static void OnNumericValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var self = d as DecimalTextBox;
// if the new numeric value is different from the text value, update the text
}
I have a custom textbox that has a dependencyProperty called valueProperty of type double nullable. My problem is that the property is binded to doubles nullables and no nullables on the model, and when I try to put a null and the binding value is no nullable, this obviously fails, showing the red rectangle. I want to detect the binding fail and assign a 0 when this occurs. So my question is: is there any way to detect that binding fail?
I know that I can fix it using 2 diferent customsTextbox for nullables and no nullables, and other ways, just wondering if there is a way to check the success of the binding. Thanks in advance.
EDIT >>>>>
Model:
private double _Temperature;
public double Temperature
{
get { return _Temperature; }
set { SetProperty(ref this._Temperature, value); }
}
private double? _Density;
public double? Density
{
get { return _Density; }
set { SetProperty(ref this._Density, value); }
}
View(simplified):
<local:customTextBox Value="{Binding Temperature}"/>
<local:customTextBox Value="{Binding Density}"/>
customTextBox dependencyProperty:
public static readonly DependencyProperty valueProperty =
DependencyProperty.RegisterAttached(
"Value",
typeof(double?),
typeof(customTextBox),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValuePropertyChanged)
);
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
customTextBox ctb = d as customTextBox;
ntb.Value = (double?)e.NewValue;
//Here I can check the binding fail.
}
EDIT WITH SOLUTION >>>>>
There was diferent solutions to my problem, I'll enumerate them:
#blindmeis solution. This is the easiest one, but the less potent to:
<local:customTextBox Value="{Binding Temperature, TargeNullValue=0}"/>
#Gary H solution. This is the one I selected as the solution, because it answer exactly what I was asking and also the easier to implement in my current app:
private static void OnValuePropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
customTextBox ctb = d as customTextBox;
ntb.Value = (double?)e.NewValue;
if (Validation.GetHasError(d))
{
//The binding have failed
}
}
#tomab solution. I think using converters is a good solution to(maybe better), but I still need keeping the customTextBox class because of other dependency properties, and I will need to refactor so much code. I will keep in mind that way for future implementations.
Thank you all for the help.
Yes this is a validation error.
You can query for the attached property:
var errors=Validation.GetErrors(myTextbox);
For handling and customizing validation see:
Data validation in WPF
Here is a way to use a Converter and its advantage is that you can control how you want the output value (which will be displayed) based on any logic you need.
public class DoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
// if you want to handle `double` too you can uncomment following lines but this is an ugly hack
// if (value != null && value.GetType() == typeof(double))
// {
// return value;
// }
var nullableDouble = (double?)value;
if (nullableDouble.HasValue)
{
return nullableDouble.Value;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
And the xaml may look like this:
<UserControl.Resources>
<converters:DoubleConverter x:Key="DoubleConverter"/>
</UserControl.Resources>
<TextBox Text="{Binding SomeValue, Converter={StaticResource DoubleConverter}}" />
SomeValue must be of type double?.
I have created a custom UserControl with some Dependency properties.
This custom control is hosted on a Window.
When I try to get a value from a DependecyProperty in code behind it doesn't work.
public static readonly DependencyProperty ValueDp = DependencyProperty.Register("Value", typeof(string), typeof(MyCustomUserControl), new FrameworkPropertyMetadata(string.Empty, OutputHandler));
public string Value
{
get { return (string)GetValue(ValueDp); }
set { SetValue(ValueDp, value); }
}
private static void OutputHandler(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var temp= dependencyObject as MyCustomUserControl;
if (temp!= null)
{
dependencyObject.SetValue(ValueDp,temp._conversionValue);
}
}
On the host I have put a button and when I click on it, I want to read the value stored in the DP, but I will always get the default value set in DP.
Any ideas what I`m doing wrong here?
Regards
I think that in the OutputHandler method you are always discarding the new value assigned to the property (dependencyPropertyChangedEventArgs.NewValue)
As #Alberto has said the OldValue and NewValue are the properties which hold the value of the DependencyProperty. The above properties are found in dependencyPropertyChangedEventArgs. In your Handler the member dependencyObject and temp refer to the same object.
I have a custom MarkupExtension that simulates binding. It works well in normal assignments but not when used in Style Setters, for example:
<Setter Property="Content" Value="{local:MyExtension}" />
results in a XamlParseException:
A 'Binding' cannot be set on the 'Value' property of type 'Setter'.
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
This is implementation of the extension:
public class MyExtension : MarkupExtension
{
public MyExtension()
{
Value = 123;
}
public object Value
{
get;
set;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var binding = new Binding("Value")
{
Source = this,
};
return binding.ProvideValue(serviceProvider);
}
}
What's the problem?!
Kind of guessing, but it's likely because the XAML compiler has special built-in support for the Binding class, allowing its usage in this scenario (and others). The Binding class is also a MarkupExtension, but unfortunately it seals its implementation of ProvideValue().
That said, you might just get away with this:
public class MyBinding : Binding
{
private object value;
public object Value
{
get { return this.value; }
set
{
this.value = value;
this.Source = value;
}
}
}
Since ProvideValue will return the Binding instance anyway.
From the documentation, it looks like the object must be freezable (so they can be shared between various interested parties)
http://msdn.microsoft.com/en-us/library/system.windows.setter.value.aspx
"Data binding and dynamic resources within the object is supported if the specified value is a Freezable object. See Binding Markup Extension and DynamicResource Markup Extension."
why don't you
return Value
inside the ProvideValue??
else
You can bind to only DependencyProperty. make a dependency property for Value in your MyExtension Class!
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(Object), typeof(MyContentControl), new UIPropertyMetadata());