WPF Binding and Dynamically Assigning StringFormat Property - wpf

I have a form that is generated based on several DataTemplate elements. One of the DataTemplate elements creates a TextBox out of a class that looks like this:
public class MyTextBoxClass
{
public object Value { get;set;}
//other properties left out for brevity's sake
public string FormatString { get;set;}
}
I need a way to "bind" the value in the FormatString property to the "StringFormat" property of the binding. So far I have:
<DataTemplate DataType="{x:Type vm:MyTextBoxClass}">
<TextBox Text="{Binding Path=Value, StringFormat={Binding Path=FormatString}" />
</DataTemplate>
However, since StringFormat isn't a dependency property, I cannot bind to it.
My next thought was to create a value converter and pass the FormatString property's value in on the ConverterParameter, but I ran into the same problem -- ConverterParameter isn't a DependencyProperty.
So, now I turn to you, SO. How do I dynamically set the StringFormat of a binding; more specifically, on a TextBox?
I would prefer to let XAML do the work for me so I can avoid playing with code-behind. I'm using the MVVM pattern and would like to keep the boundaries between view-model and view as un-blurred as possible.
Thanks!

This is a solution from Andrew Olson that uses attached properties and thus can be used in various situations.
Used like this:
<TextBlock
local:StringFormatHelper.Format="{Binding FormatString}"
local:StringFormatHelper.Value="{Binding Value}"
Text="{Binding (local:StringFormatHelper.FormattedValue)}"
/>
The required helper: (source Gist)
public static class StringFormatHelper
{
#region Value
public static DependencyProperty ValueProperty = DependencyProperty.RegisterAttached(
"Value", typeof(object), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null, OnValueChanged));
private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
RefreshFormattedValue(obj);
}
public static object GetValue(DependencyObject obj)
{
return obj.GetValue(ValueProperty);
}
public static void SetValue(DependencyObject obj, object newValue)
{
obj.SetValue(ValueProperty, newValue);
}
#endregion
#region Format
public static DependencyProperty FormatProperty = DependencyProperty.RegisterAttached(
"Format", typeof(string), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null, OnFormatChanged));
private static void OnFormatChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
RefreshFormattedValue(obj);
}
public static string GetFormat(DependencyObject obj)
{
return (string)obj.GetValue(FormatProperty);
}
public static void SetFormat(DependencyObject obj, string newFormat)
{
obj.SetValue(FormatProperty, newFormat);
}
#endregion
#region FormattedValue
public static DependencyProperty FormattedValueProperty = DependencyProperty.RegisterAttached(
"FormattedValue", typeof(string), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null));
public static string GetFormattedValue(DependencyObject obj)
{
return (string)obj.GetValue(FormattedValueProperty);
}
public static void SetFormattedValue(DependencyObject obj, string newFormattedValue)
{
obj.SetValue(FormattedValueProperty, newFormattedValue);
}
#endregion
private static void RefreshFormattedValue(DependencyObject obj)
{
var value = GetValue(obj);
var format = GetFormat(obj);
if (format != null)
{
if (!format.StartsWith("{0:"))
{
format = String.Format("{{0:{0}}}", format);
}
SetFormattedValue(obj, String.Format(format, value));
}
else
{
SetFormattedValue(obj, value == null ? String.Empty : value.ToString());
}
}
}

This code (inspired from DefaultValueConverter.cs # referencesource.microsoft.com) works for a two way binding to a TextBox or similar control, as long as the FormatString leaves the ToString() version of the source property in a state that can be converted back.
(i.e. format like "#,0.00" is OK because "1,234.56" can be parsed back, but FormatString="Some Prefix Text #,0.00" will convert to "Some Prefix Text 1,234.56" which can't be parsed back.)
XAML:
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource ToStringFormatConverter}"
ValidatesOnDataErrors="True" NotifyOnValidationError="True" TargetNullValue="">
<Binding Path="Property" TargetNullValue="" />
<Binding Path="PropertyStringFormat" Mode="OneWay" />
</MultiBinding>
</TextBox.Text>
</TextBox>
Note duplicate TargetNullValue if the source property can be null.
C#:
/// <summary>
/// Allow a binding where the StringFormat is also bound to a property (and can vary).
/// </summary>
public class ToStringFormatConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 1)
return System.Convert.ChangeType(values[0], targetType, culture);
if (values.Length >= 2 && values[0] is IFormattable)
return (values[0] as IFormattable).ToString((string)values[1], culture);
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
var targetType = targetTypes[0];
var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType);
if (nullableUnderlyingType != null) {
if (value == null)
return new[] { (object)null };
targetType = nullableUnderlyingType;
}
try {
object parsedValue = ToStringFormatConverter.TryParse(value, targetType, culture);
return parsedValue != DependencyProperty.UnsetValue
? new[] { parsedValue }
: new[] { System.Convert.ChangeType(value, targetType, culture) };
} catch {
return null;
}
}
// Some types have Parse methods that are more successful than their type converters at converting strings
private static object TryParse(object value, Type targetType, CultureInfo culture)
{
object result = DependencyProperty.UnsetValue;
string stringValue = value as string;
if (stringValue != null) {
try {
MethodInfo mi;
if (culture != null
&& (mi = targetType.GetMethod("Parse",
BindingFlags.Public | BindingFlags.Static, null,
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider) }, null))
!= null) {
result = mi.Invoke(null, new object[] { stringValue, NumberStyles.Any, culture });
}
else if (culture != null
&& (mi = targetType.GetMethod("Parse",
BindingFlags.Public | BindingFlags.Static, null,
new[] { typeof(string), typeof(IFormatProvider) }, null))
!= null) {
result = mi.Invoke(null, new object[] { stringValue, culture });
}
else if ((mi = targetType.GetMethod("Parse",
BindingFlags.Public | BindingFlags.Static, null,
new[] { typeof(string) }, null))
!= null) {
result = mi.Invoke(null, new object[] { stringValue });
}
} catch (TargetInvocationException) {
}
}
return result;
}
}

One way may be to create a class that inherits TextBox and in that class create your own dependency property that delegates to StringFormat when set. So instead of using TextBox in your XAML you will use the inherited textbox and set your own dependency property in the binding.

Just bind the textbox to the instance of a MyTextBoxClass instead of MyTextBoxClass.Value and use a valueconverter to create a string from the value and formatstring.
Another solution is to use a multivalue converter which would bind to both Value and FormatString.
The first solution don't support changes to properties, that is if value or formatstring changes the value converter will not be called like it would be if you are using a multivalueconverter and binding directly to the properties.

One could create an attached behavior that could replace the binding with one that has the FormatString specified. If the FormatString dependency property then the binding would once again be updated. If the binding is updated then the FormatString would be reapplied to that binding.
The only two tricky things that I can think that you would have to deal with. One issue is whether you want to create two attached properties that coordinate with each other for the FormatString and the TargetProperty on which the binding exist that the FormatString should be applied (ex. TextBox.Text) or perhaps you can just assume which property your dealing with depending on the target control type. The other issue may be that it may be non-trivial to copy an existing binding and modifying it slightly given the various types of bindings out there which might also include custom bindings.
It's important to consider though that all of this only achieves formatting in the direction from your data to your control. As far as I can discover using something like a MultiBinding along with a custom MultiValueConverter to consume both the original value and the FormatString and produce the desired output still suffers from the same problem mainly because the ConvertBack method is only given the output string and you would be expected to decipher both the FormatString and the original value from it which at that point is almost always impossible.
The remaining solutions that should work for bidirectional formatting and unformatting would be the following:
Write a custom control that extends TextBox that has the desired formatting behavior like Jakob Christensen suggested.
Write a custom value converter that derives from either DependencyObject or FrameworkElement and has a FormatString DependencyProperty on it. If you want to go the DependencyObject route I believe you can push the value into the FormatString property using the OneWayToSource binding with a "virtual branch" technique. The other easier way may to instead inherit from FrameworkElement and place your value converter into the visual tree along with your other controls so that you can just bind to it when needed by ElementName.
Use an attached behavior similar to the one I mentioned at the top of this post but instead of setting a FormatString instead have two attached properties, one for a custom value converter and one for the parameter that would be passed to the value converter. Then instead of modifying the original binding to add the FormatString you would be adding the converter and the converter parameter to the binding. Personally I think this option would result in the most readable and intuitive result because attached behaviors tend to be more clean yet still flexible enough to use in a variety of situations other than just a TextBox.

Related

Possible to use constant/static value inside a binding in XAML?

We are using the following mechanism/syntax to bind commands in XAML:
Command="{Binding CommandAggregator[FooCmd], Mode=OneTime}"
Here, CommandAggregator is an object that you can use an indexer (with string parameter) on to get back the actual command.
The command registrations with the aggregator are bugging me a bit, because we are still using magic strings for the command names like this:
this.CommandAggregator.SetCommand("FooCmd", new RelayCommand(execute, canExecute));
While I don't necessarily like this whole process, I cannot change much. The one thing I would like to do for now is quit using magic strings by making them constants or static readonly string objects inside a static CommandName class.
But is it possible to define the binding inside the XAML and reference the constant (say CommandName.Foo)? I thought about using {x:Static ...}, but I don't know how to get the returned value into the indexer.
you can implement an IValueConverter which will return a command from CommandAggregator based on converter Parameter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var aggregator = value as CommandAggregator;
var cmd = parameter as string;
if (aggregator != null && cmd != null)
return aggregator[cmd];
return null;
}
and pass the parameter from xaml:
Command="{Binding Path=CommandAggregator,
Converter={StaticResource MyConverter},
ConverterParameter={x:Static Constants.FooCmd},
Mode=OneTime}"
Not sure if this is the best, but it works for me. I didn't like so many CmdWhatEvers for my Relay commands showing up in my debugger so moved them to a dictionary, which seems similar to your CommandAggregator, then indexed them with a static string (magic strings have caused so many binding failures for me.)
Anyway, my string constants live in a static class
internal static class Str
{
public static readonly string CmdReset = "CmdReset";
}
In the View Model
internal class CtrlVm : ViewModelBase
{
public Dictionary<string, IRelayCommand> Commands { get; }
public CtrlVm()
{
Commands = new Dictionary<string, IRelayCommand>()
{
// My relay command class takes, Execute, CanExecute
// and a Header parameter, that I use in binding
Str.CmdReset, new RelayCommand(Reset, CanReset, "Reset");
},
}
private bool CanReset(object parameter)
{
return bHasChanges; // or whatever
}
private void Reset(object parameter)
{
// do the reset work
}
}
Then in the Xaml
<Button Command= "{Binding Commands[CmdReset]}"
Content= "{Binding Commands[CmdReset].Header}"/>

String interpolation in XAML

I'm thinking about ways of getting advance of C# 6 string interpolation in XAML, such as using them instead of value converters in some simple scenarios like replacing a zero by an empty string when binding to numbers.
From its design discussions:
An interpolated string is a way to construct a value of type String
(or IFormattable) by writing the text of the string along with
expressions that will fill in "holes" in the string. The compiler
constructs a format string and a sequence of fill-in values from the
interpolated string.
However, as I suspected, it seems that they can't be used from XAML since it uses a different compiler to generate the BAML and I find no trace of the strings in the generated .g.i.cs files.
Are string interpolations not supported in XAML?
What workarounds could there be? Maybe using markup extensions to dynamically compile the string interpolations?
This sounds a lot like the StringFormat attribute introduced in .Net 3.5. As you quote, "writing the text of the string along with expressions that will fill in 'holes' in the string", this can be performed within a XAML binding like this:
<TextBlock Text="{Binding Amount, StringFormat=Total: {0:C}}" />
Since you can use any of the custom string formats, there's a lot of power under the hood here. Or are you asking something else?
This is tricky to support due to the way that Binding works in WPF. String interpolations in the C# code can be compiled directly to string.Format calls and basically just provide a convenient syntactic sugar. To make this work with Binding, though, it's necessary to do some work at runtime.
I've put together a simple class that can do this, though it has a few limitations. In particular, it doesn't support passing through all the binding parameters and it's awkward to type in the XAML since you have to escape the curly braces (maybe worth using a different character?) It should handle multi-path bindings and arbitrarily complex format strings, though, as long as they are properly escaped for use in XAML.
In reference to one particular point in your question, this doesn't allow you to embed arbitrary expressions like you can do in interpolated strings. If you wanted to do that, you'd have to get a bit fancier and do something like on-the-fly code compilation in terms of the bound values. Most likely you'd need to emit a function call that takes the parameter values, then call that as a delegate from the value converter and have it execute the embedded expressions. It should be possible, but probably not easy to implement.
Usage looks like this:
<TextBlock Text="{local:InterpolatedBinding '\{TestString\}: \{TestDouble:0.0\}'}"/>
And here is the markup extension that does the work:
public sealed class InterpolatedBindingExtension : MarkupExtension
{
private static readonly Regex ExpressionRegex = new Regex(#"\{([^\{]+?)(?::(.+?))??\}", RegexOptions.Compiled);
public InterpolatedBindingExtension()
{
}
public InterpolatedBindingExtension(string expression)
{
Expression = expression;
}
public string Expression { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
//Parse out arguments captured in curly braces
//If none found, just return the raw string
var matches = ExpressionRegex.Matches(Expression);
if (matches.Count == 0)
return Expression;
if (matches.Count == 1)
{
var formatBuilder = new StringBuilder();
//If there is only one bound target, can use a simple binding
var varGroup = matches[0].Groups[1];
var binding = new Binding();
binding.Path = new PropertyPath(varGroup.Value);
binding.Mode = BindingMode.OneWay;
formatBuilder.Append(Expression.Substring(0, varGroup.Index));
formatBuilder.Append('0');
formatBuilder.Append(Expression.Substring(varGroup.Index + varGroup.Length));
binding.Converter = new FormatStringConverter(formatBuilder.ToString());
return binding.ProvideValue(serviceProvider);
}
else
{
//Multiple bound targets, so we need a multi-binding
var multiBinding = new MultiBinding();
var formatBuilder = new StringBuilder();
int lastExpressionIndex = 0;
for (int i=0; i<matches.Count; i++)
{
var varGroup = matches[i].Groups[1];
var binding = new Binding();
binding.Path = new PropertyPath(varGroup.Value);
binding.Mode = BindingMode.OneWay;
formatBuilder.Append(Expression.Substring(lastExpressionIndex, varGroup.Index - lastExpressionIndex));
formatBuilder.Append(i.ToString());
lastExpressionIndex = varGroup.Index + varGroup.Length;
multiBinding.Bindings.Add(binding);
}
formatBuilder.Append(Expression.Substring(lastExpressionIndex));
multiBinding.Converter = new FormatStringConverter(formatBuilder.ToString());
return multiBinding.ProvideValue(serviceProvider);
}
}
private sealed class FormatStringConverter : IMultiValueConverter, IValueConverter
{
private readonly string _formatString;
public FormatStringConverter(string formatString)
{
_formatString = formatString;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(string))
return null;
return string.Format(_formatString, values);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(string))
return null;
return string.Format(_formatString, value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}
I've done very limited testing, so I recommend more thorough testing and hardening before using this in production. Should hopefully be a good starting point for someone to make something useful, though.

Force Propagation of Coerced Value

tl;dr: Coerced values are not propagated across data bindings. How can I force the update across the data binding when code-behind doesn't know the other side of the binding?
I'm using a CoerceValueCallback on a WPF dependency property and I'm stuck at the issue that coerced values don't get propagated through to bindings.
Window1.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace CoerceValueTest
{
public class SomeControl : UserControl
{
public SomeControl()
{
StackPanel sp = new StackPanel();
Button bUp = new Button();
bUp.Content = "+";
bUp.Click += delegate(object sender, RoutedEventArgs e) {
Value += 2;
};
Button bDown = new Button();
bDown.Content = "-";
bDown.Click += delegate(object sender, RoutedEventArgs e) {
Value -= 2;
};
TextBlock tbValue = new TextBlock();
tbValue.SetBinding(TextBlock.TextProperty,
new Binding("Value") {
Source = this
});
sp.Children.Add(bUp);
sp.Children.Add(tbValue);
sp.Children.Add(bDown);
this.Content = sp;
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
typeof(int),
typeof(SomeControl),
new PropertyMetadata(0, ProcessValueChanged, CoerceValue));
private static object CoerceValue(DependencyObject d, object baseValue)
{
if ((int)baseValue % 2 == 0) {
return baseValue;
} else {
return DependencyProperty.UnsetValue;
}
}
private static void ProcessValueChanged(object source, DependencyPropertyChangedEventArgs e)
{
((SomeControl)source).ProcessValueChanged(e);
}
private void ProcessValueChanged(DependencyPropertyChangedEventArgs e)
{
OnValueChanged(EventArgs.Empty);
}
protected virtual void OnValueChanged(EventArgs e)
{
if (e == null) {
throw new ArgumentNullException("e");
}
if (ValueChanged != null) {
ValueChanged(this, e);
}
}
public event EventHandler ValueChanged;
public int Value {
get {
return (int)GetValue(ValueProperty);
}
set {
SetValue(ValueProperty, value);
}
}
}
public class SomeBiggerControl : UserControl
{
public SomeBiggerControl()
{
Border parent = new Border();
parent.BorderThickness = new Thickness(2);
parent.Margin = new Thickness(2);
parent.Padding = new Thickness(3);
parent.BorderBrush = Brushes.DarkRed;
SomeControl ctl = new SomeControl();
ctl.SetBinding(SomeControl.ValueProperty,
new Binding("Value") {
Source = this,
Mode = BindingMode.TwoWay
});
parent.Child = ctl;
this.Content = parent;
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
typeof(int),
typeof(SomeBiggerControl),
new PropertyMetadata(0));
public int Value {
get {
return (int)GetValue(ValueProperty);
}
set {
SetValue(ValueProperty, value);
}
}
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
}
Window1.xaml
<Window x:Class="CoerceValueTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CoerceValueTest" Height="300" Width="300"
xmlns:local="clr-namespace:CoerceValueTest"
>
<StackPanel>
<local:SomeBiggerControl x:Name="sc"/>
<TextBox Text="{Binding Value, ElementName=sc, Mode=TwoWay}" Name="tb"/>
<Button Content=" "/>
</StackPanel>
</Window>
i.e. two user controls, one nested inside the other, and the outer one of those in a window. The inner user control has a Value dependency property that is bound to a Value dependency property of the outer control. In the window, a TextBox.Text property is bound to the Value property of the outer control.
The inner control has a CoerceValueCallback registered with its Value property whose effect is that this Value property can only be assigned even numbers.
Note that this code is simplified for demonstration purposes. The real version doesn't initialize anything in the constructor; the two controls actually have control templates that do everything that's done in the respective constructors here. That is, in the real code, the outer control doesn't know the inner control.
When writing an even number into the text box and changing the focus (e.g. by focusing the dummy button below the text box), both Value properties get duly updated. When writing an odd number into the text box, however, the Value property of the inner control doesn't change, while the Value property of the outer control, as well as the TextBox.Text property, show the odd number.
My question is: How can I force an update in the text box (and ideally also in the outer control's Value property, while we're at it)?
I have found an SO question on the same problem, but doesn't really provide a solution. It alludes to using a property changed event handler to reset the value, but as far as I can see, that would mean duplicating the evaluation code to the outer control ... which is not really viable, as my actual evaluation code relies on some information basically only known (without much effort) to the inner control.
Moreover, this blogpost suggests invoking UpdateTarget on the binding in TextBox.Text in the CoerceValueCallback, but first, as implied above, my inner control cannot possibly have any knowledge about the text box, and second, I would probably have to call UpdateSource first on the binding of the Value property of the inner control. I don't see where to do that, though, as within the CoerceValue method, the coerced value has not yet been set (so it's too early to update the binding), while in the case that the value is reset by CoerceValue, the property value will just remain what it was, hence a property changed callback will not get invoked (as also implied in this discussion).
One possible workaround I had thought of was replacing the dependency property in SomeControl with a conventional property and an INotifyPropertyChanged implementation (so I can manually trigger the PropertyChanged event even if the value has been coerced). However, this would mean that I cannot declare a binding on that property any more, so it's not a really useful solution.
I have been looking for an answer to this rather nasty bug myself for a while.
One way to do it, without the need to force an UpdateTarget on the bindings is this:
Remove your CoerceValue callback.
Shift the logic of the CoerceValue callback into your ProcessValueChanged callback.
Assign your coerced value to your Value property, when applicable (when the number is odd)
You will end up with the ProcessValueChanged callback being hit twice, but your coerced value will end up being effectively pushed to your binding.
Base on your code, your dependency property declaration would become this:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value",
typeof(int),
typeof(SomeControl),
new PropertyMetadata(0, ProcessValueChanged, null));
And then, your ProcessValueChanged would become this:
private static void ProcessValueChanged(object source, DependencyPropertyChangedEventArgs e)
{
int baseValue = (int) e.NewValue;
SomeControl someControl = source as SomeControl;
if (baseValue % 2 != 0)
{
someControl.Value = DependencyProperty.UnsetValue;
}
else
{
someControl.ProcessValueChanged(e);
}
}
I slightly modified your logic, to prevent raising the event when the value needs to be coerced. As mentionned before, assigning to someControl.Value the coerced value will cause your ProcessValueChanged to be called twice in a row. Putting the else statement would only raise the events with valid values once.
I hope this helps!

How to use PropertyChanged to pass trough DataTemplate?

Question is simple: how can I trigger a change on the dataObject without acutaly changing the dataObject, and see this change on the visual?
DataObject:
ProductData : INotifyPropertyChanged
{
private ProductPartData myProductPartData;
public ProductPartData ProductPartData
{
get
{
return myProductPartData;
}
set
{
if (value != myProductPartData)
{
myProductPartData = value;
OnNotifyPropertyChanged("ProductPartData");
}
}
}
}
DataTemplate:
<DataTemplate
DataType="{x:Type ProductData}"
>
<VisualProduct
ProductPartData="{Binding Path=ProductPartData, Mode=OneWay}"
/>
</DataTemplate>
And now in a VM I have:
product.OnNotifyPropertyChanged("ProductPartData");
Problem:
Even if the getter for ProductPart is called when I execute OnNotifyPropertyChanged, the visual is not notified, because is the same instance of the ProductPartData.
How do I trigger a change seen by the Visual without changing the instance?
Thank you,
Daniel,
A solution is to use UpdateTarget() method of the BindingExpression class, this way the target of the binding gets refreshed no matter what; of course, your converter will also be hit - if any. Since I'm guessing you don't have access to your visual in the Product, you could use an attached property and in its callback, you can get the BindingExpression and call UpdateTarget() on it.
Note that I'm using a simple TextBlock as the visual of the data object.
public class BindingHelper
{
public static bool GetRefreshBinding(DependencyObject obj)
{
return (bool) obj.GetValue(RefreshBindingProperty);
}
public static void SetRefreshBinding(DependencyObject obj, bool value)
{
obj.SetValue(RefreshBindingProperty, value);
}
// Using a DependencyProperty as the backing store for RefreshBinding. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RefreshBindingProperty =
DependencyProperty.RegisterAttached("RefreshBinding", typeof(bool), typeof(BindingHelper), new UIPropertyMetadata(false, OnRefreshBindingPropertyChanged));
static void OnRefreshBindingPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs ea)
{
TextBlock elem = o as TextBlock;
if (elem != null)
{
BindingExpression bEx = elem.GetBindingExpression(TextBlock.TextProperty);
if (bEx != null)
{
bEx.UpdateTarget();
}
}
}
}
Also, in your data object that you can create a new bool property(let's name it ShouldRefresh) that is bound to the attached property within the template - this will trigger the AP's property changing:
<DataTemplate DataType="{x:Type local:ProductData}">
<TextBlock Text="{Binding Path=Name, Converter={StaticResource BlankConverter}}"
local:BindingHelper.RefreshBinding="{Binding Path=ShouldRefresh}"/>
</DataTemplate>
So, this way, whenever you want to update the target through binding, you can set:
ShouldRefresh = !ShouldRefresh
in your data class.
HTH.
If you raise a PropertyChanged event and the new value of the property is equal to the value WPF already has, it will simply ignore you. You have a couple of options:
The "fast" way is to set the property to null and then back to the correct value again, ensuring PropertyChanged events are raised each time. It's dirty but it works every time.
The "right" way is to force a binding refresh as discussed in this post by Jaime Rodriguez. Because your visual is data-templated though getting the "dependencyObject" to pass into the call in that post is a little tricky. You may end up needing to use the template's FindName method as discussed in this post by Josh Smith.
We encountered this kind of issue with data coming from a database and converted to a DTO (data transfert object).
Our base class for DTO override Object's method such as Equals() and GetHashCode() as follow:
public override Boolean Equals(Object obj)
{
// Null reference
if (null == obj)
return false;
// Same reference
if (Object.ReferenceEquals(this, obj))
return true;
EntityDTOBase<TEntity> entiteObj = obj as EntityDTOBase<TEntity>;
if (null == entiteObj)
return false;
else
return Equals(entiteObj);
}
public Boolean Equals(EntityDTOBase<TEntity> other)
{
// Null reference
if (null == other)
return false;
// Same reference
if (Object.ReferenceEquals(this, other))
return true;
// No Id: cannot be compared, return false
if (this.id == TypeHelper.DefaultValue<long>())
return false;
// Id comparison
if (this.id != other.id)
return false;
return true;
}
public override Int32 GetHashCode()
{
return this.id.GetHashCode();
}
So the problem was when we load again the same entity from the database, since the ID is the same, some binding were not properly updated.
This particular issue was circumvented by adding an additional virtual EqualsExtended() method which default implementation simply returns true:
protected virtual Boolean EqualsExtended(EntityDTOBase<TEntity> other)
{
return true;
}
public Boolean Equals(EntityDTOBase<TEntity> other)
{
/// Same code as before (except last line):
return EqualsExtended(other);
}
Now in any implementation of our DTO class we can add some logic to make Equals() returning false in some situations, for example by adding a timestamp when data is retrieved from the database :
protected override Boolean EqualsExtended(EntityDTOBase<Act> other
{
if (this.Timestamp != other.Timestamp)
{
return false;
}
return true;
}
Long story short, one way to workaround this issue is to make your class instance look different whenever you want the GUI to update accordingly.
The problem might be that you are returning GuiProductPartData typed myProductPartData with ProductPartData typed ProductPartData? But in any case this shouldn't be like this :)
Also it's not a great practice to have the variable name same as the type, so you shouldn't have a ProductPartData ProductPartData property.
Naming conventions aside (and assuming just typos on the typing) the problem probably resides inside your ProductPartData class. Does it implement INotifyPropertyChanged as well?

Strong Validation in WPF

I have a databound TextBox in my application like so: (The type of Height is decimal?)
<TextBox Text="{Binding Height, UpdateSourceTrigger=PropertyChanged,
ValidatesOnExceptions=True,
Converter={StaticResource NullConverter}}" />
public class NullableConverter : IValueConverter {
public object Convert(object o, Type type, object parameter, CultureInfo culture) {
return o;
}
public object ConvertBack(object o, Type type, object parameter, CultureInfo culture) {
if (o as string == null || (o as string).Trim() == string.Empty)
return null;
return o;
}
}
Configured this way, any non-empty strings which cannot be converted to decimal result in a validation error which will immediately highlight the textbox. However, the TextBox can still lose focus and remain in an invalid state. What I would like to do is either:
Not allow the TextBox to lose focus until it contains a valid value.
Revert the value in the TextBox to the last valid value.
What is the best way to do this?
Update:
I've found a way to do #2. I don't love it, but it works:
private void TextBox_LostKeyboardFocus(object sender, RoutedEventArgs e) {
var box = sender as TextBox;
var binding = box.GetBindingExpression(TextBox.TextProperty);
if (binding.HasError)
binding.UpdateTarget();
}
Does anyone know how to do this better? (Or do #1.)
You can force the keyboard focus to stay on the TextBox by handling the PreviewLostKeyBoardFocus event like this:
<TextBox PreviewLostKeyboardFocus="TextBox_PreviewLostKeyboardFocus" />
private void TextBox_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) {
e.Handled = true;
}
It sounds to me that you'll want to handle two events:
GotFocus: Will trigger when the textbox gains focus. You can store the initial value of the box.
LostFocus: Will trigger when the textbox loses focus. At this point you can do your validation and decide if you want to roll back or not.

Resources