If you’ve done WPF or Silverlight programming, you may have found an occasion where
using the Binding property
UpdateSourceTrigger
set to PropertyChanged was extremely useful. (I know I have!)
It may have looked something like this:
<TextBox Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}" />
The key feature was the live updating of the property through the binding. For
example, as the user typed in the TextBox above, the property
Value in the bound object would have been updated as the user
typed. The default behavior in Silverlight 5 and WPF is to use LostFocus: the
Value is only updated when the TextBox loses focus. Sometimes,
that’s OK.
In WinRT/Metro however, this option was completely removed, and no decent
replacement was provided unfortunately. In searching for a reasonable work-around, I
discovered a few other related missing pieces from WinRT/XAML:
-
BindingOptions.GetBinding: This allowed the WPF programmer a method for retrieving a Binding object. I
was interested in this option as I wanted to clone the original Binding, make it
two-way, and attach to the TextChanged event so that a new binding could be
updated when the text changed, with the new binding pointing to the same
property as the Text property. (My solution uses the basic idea as this).
-
FrameworkElement.GetBindingExpression: Again, gone. Same basic logic as above. (Probably uses same underlying
support that just isn’t there).
-
XamlWriter.Save: XamlWriter is completely gone. I thought maybe I’d save an
instance of the TextBox to Xaml, and create a new one with adjusted bindings
from the Xaml. XamlReader does exist.
You can start to feel how Xaml really is just a thin wrapper on the Windows 8 run
time as you start to look around to see what remains from WPF. Even traditional
Silverlight features are gone unfortunately. Maybe Windows 9?
So, I took a round-about approach to solving this problem.
public class UpdateSourceHelper : FrameworkElement
{
public static string GetUpdateSourceText(DependencyObject obj)
{
return (string)obj.GetValue(UpdateSourceTextProperty);
}
public static void SetUpdateSourceText(DependencyObject obj, string value)
{
obj.SetValue(UpdateSourceTextProperty, value);
}
// Using a DependencyProperty as the backing store for UpdateSourceText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty UpdateSourceTextProperty =
DependencyProperty.RegisterAttached("UpdateSourceText", typeof(string), typeof(UpdateSourceHelper), new PropertyMetadata(""));
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
// Using a DependencyProperty as the backing store for IsEnabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(UpdateSourceHelper),
new PropertyMetadata(false,
// property changed
(obj, args)=>
{
if (obj is TextBox)
{
TextBox tb = (TextBox)obj;
if ((bool)args.NewValue)
{
tb.TextChanged += AttachedTextBoxTextChanged;
}
else
{
tb.TextChanged -= AttachedTextBoxTextChanged;
}
}
}
));
static void AttachedTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
if (sender is TextBox)
{
var tb = (TextBox)sender;
tb.SetValue(UpdateSourceHelper.UpdateSourceTextProperty, tb.Text);
}
}
}
And here it is in use:
xmlns:local="using:WiredPrairie.Converter"
(The above namespace is needed to provide context to the Xaml parser for the
attached properties. Change it to whatever you need).
<TextBox Height="Auto" Margin="0,6" Grid.Row="1" TextWrapping="Wrap" TabIndex="0"
Text="{Binding Value}"
local:UpdateSourceHelper.IsEnabled="True"
local:UpdateSourceHelper.UpdateSourceText="{Binding Value, Mode=TwoWay}"/>
A classic pattern in WPF for extending built in controls without subclassing was to
create an attached behavior. Thankfully, this pattern still works.
The core concept is to attach to the original instance of the TextBox by using an
enabling property. In this case, an
attached property called IsEnabled is added to the TextBox.
When the value of the IsEnabled property changes from the default of false, the
PropertyChanged
callback is called. It’s here that the simple magic happens. As long as the attached
property is attached to an instance of TextBox, it wires up to the TextChanged
event.
In the TextChanged event handler, the value of the attached TextBox (Text property)
is set into the second binding’s value. By doing this, it then sets the datasource’s
object’s bound property to the new value immediately.
The annoying part about this solution really is the extra binding that is required.
As there isn’t a documented/known API for reading an existing binding, this
technique requires that the developer specify the binding
twice.
local:UpdateSourceHelper.UpdateSourceText="{Binding Value, Mode=TwoWay}"
In WPF, it would have been possible to declare the UpdateSourceText as defaulting to
TwoWay binding. But, again, that feature has been removed. Without the TwoWay
binding mode, the SetValue call in the TextChanged event handler will not update the
object’s bound property. It’s one way.
I looked low and high for a more elegant solution, but couldn’t find one. If you
find something better, please leave a comment!