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?.
Related
I am trying to enable a button only if text is present in two text boxes in a WinForm Application.
My Question is -
Can I achieve this using Data Binding?
If so how?
Edit
Please give reasons for downvote.
Update: Since OP want's to work with DataBinding only here is a solution with the desired technique.
You'll need to use a MultiBinding. Add the two Binding instances (one for each TextBox). Then you'll need to implement an IMultiValueConverter that will accept the values produced by the two binding objects and convert them into a single value.
The binding setup would look something like this:
var multiBinding = new MultiBinding();
multiBinding.Bindings.Add(new Binding("Enabled", textBox1, "Text", true));
multiBinding.Bindings.Add(new Binding("Enabled", textBox2, "Text", true));
multiBinding.Converter = new MyMultiValueConverter();
button1.DataBindings.Add(multiBinding);
And the converter implementation would look something like:
public class MyMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// perform your conversion here and return the final value
// so which value both textBoxes need to have that you return `true` so
// that `button1.Enabled` gets set to `true`
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
That you can use those classes MultiBinding and IMultiValueConverter you have to add a reference of the PresentationFramework lib to your project. Furthermore I suggest you to add:
using System.Windows.Data;
To shorten your code.
Since I already posted an answer and it's a legit working solution in the way OP desired it I don't edit the question, but rather show in a new question another approach.
You could create a computed property and bind the button.1.Enabled property to it. For example, create a textBoxesCorrect property that returns the value, and bind button.1.Enabled to it. The textBoxesCorrect property gets set in the TextChanged() events of those TextBoxes.
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (textBox1.Text == "") //desired text that the textBoxes shell contain
MyData.textBox1Correct = true;
else
MyData.textBox1Correct = false;
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
if (textBox2.Text == "") //desired text that the textBoxes shell contain
MyData.textBox2Correct = true;
else
MyData.textBox2Correct = false;
}
public class MyData
{
public static bool textBox1Correct { get; set; }
public static bool textBox2Correct { get; set; }
public bool textBoxesCorrect
{
get
{
if (textBox1Correct && textBox2Correct)
return true;
else
return false;
}
}
}
So can still work with your DataBinding, but it's an easier solution to implement to work with multiple sources for the binding.
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
}
Where this is the clr way of writing a property:
public byte Value
{
get{
return GetByteData();
}
set{
SetByteData(value);
}
}
I've read up on how to do the same the dependency property way, and this is all I could do by myself:
public byte Value
{
get { return (byte)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value", typeof(byte), typeof(MyControl),
new FrameworkPropertyMetadata((byte)0, ValueChanged));
public static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
byte r = (byte)e.NewValue;
MyControl v = (MyControl)d;
v.SetByteData(r);
}
With the examples I've looked at, from which I've made the above snippet, I can't find a place to put the GetByteData(), which calculates an output value for the current UI-state when the user interacts, so as to update the 'Value'.
Until previously I was putting it in the getter anyway like with clr, but I get the feeling that it's the wrong approach, but I could be wrong. Where should I be putting it? If not, what should be my approach? Is it at all possible to have a programmatic getter for a dependency property?
It's possible that I've been using the wrong keywords to look for a solution. Any help putting me in the right direction would be much appreciated. Thanks in advance.
As long as it is only the source (and not the target) property of a Binding, as in
{Binding Value, ElementName=MyControlName}
and you don't want to apply a value by a Style Setter, or animate the value, the property does not need to be a dependency property.
Just implement INotifyPropertyChanged like this:
public partial class MyControl : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public byte Value
{
get { return GetByteData(); }
set
{
SetByteData(value);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
...
}
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);
}
}
<!-- View -->
<TextBox Text="{Binding str, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
// View Model
private string _str;
public string str
{
get { return _str; }
set
{
if (!value.Contains("a"))
_str = value;
OnPropertyChanged(nameof(str));
}
}
When typing in the TextBox I want it to throw out any invalid characters (in this sample case the letter 'a', but it could really be for anything). For example:
User types 'fds' followed by an 'a'
str detects a, so it doesn't set _str to 'fdsa', keeping it at 'fds' but raises the event anyway to indicate to the view to throw out the 'a'.
In WPF, this results in the textbox containing 'fds'. In UWP, this results in the textbox incorrectly containing 'fdsa' still.
It appears that in UWP when a control has focus, it will not respect the TwoWay binding.
I can create a button that has a Click event that when pressed will update my TextBox correctly.
private void btn_Click(object sender, RoutedEventArgs e)
{
OnPropertyChanged(nameof(str));
}
We have many ViewModels that we need to use in both WPF and UWP views, and we have this required behavior all over the place. What is a good solution to this problem?
* EDIT *
Came back to the problem after the weekend and it seems to have fixed itself. I have no idea why. I am closing the question for now.
You could use a converter to solve your problem, you could elaborate a better converter, in my example I just use a silly converter to demonstrate my idea.
Converter:
public class Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
var someString = value.ToString();
return someString.Replace("a", "");
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
XAML
<TextBox Text="{Binding Str, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource converter}}"/>
You could use an attached behavior also.