Thursday, May 28, 2009

Databinding and LostFocus (HOWTO Force SourceUpdate for any WPF FrameworkElement)

Today I faced a bug that was not so easy to handle. While editing a value within a WPF textbox I pressed CTRL+S to save my work. Unfortunately my edited data was not saved, but the previous data was instead. How did that happen.

Well my WPF textbox was data bound to my dataobject. The dataobject (source) normally gets updated upon a Lostfocus of the textbox (that’s the default). However pressing CTRL+S (or even selecting the save menuitem under the file menu) does not result in the textbox losing focus.

OK. The short way to handle this problem is to get the binding expression of the textbox on de Text Property. Upon this binding expression it’s possible to call UpdateSource() explicitly and the value gets stored in the bound dataobject.

However I searched for a more generic approach because I do not have textboxes only. And it’s not always the Text Property that is being bound. So searching on the internet, I found a reaction on Nigel Spencer's blog: http://blog.spencen.com/2008/05/02/how-to-get-a-list-of-bindings-in-wpf.aspx#comment-1015806[^].

I modified the code a little bit to fit my needs and here is the result:

using System; 
using System.Windows;
using System.Windows.Data;
namespace MyNamespace
{
internal static class WPFHelper
{
internal static void ActionAllBindings(FrameworkElement targetElement, Action<BindingExpression> action)
{
WalkElementTree(targetElement, action);
}

private static void WalkElementTree(object obj, Action<BindingExpression> action)
{
var dependencyObject
= obj as FrameworkElement;
// Sometimes leaf nodes aren’t DependencyObjects (e.g. strings)
if (dependencyObject == null)
{
return;
}

// Recursive call for each logical child
ActionBindings(dependencyObject, action);
foreach (var child in LogicalTreeHelper.GetChildren(dependencyObject))
{
WalkElementTree(child, action);
}
}

private static void ActionBindings(DependencyObject target, Action<BindingExpression> action)
{
var localValueEnumerator
= target.GetLocalValueEnumerator();
while (localValueEnumerator.MoveNext())
{
var current
= localValueEnumerator.Current;
var binding
= BindingOperations.GetBindingExpression(target, current.Property);
if (binding != null)
{
action(binding);
}
}
}
}
}


Using the helper is extremely simple:



public void ForceSourceUpdateActiveElement()    
{
var focusedElement
= Keyboard.FocusedElement as FrameworkElement;
if (focusedElement != null)
{
MyNamespace.ActionAllBindings(focusedElement, bindingExpression
=> bindingExpression.UpdateSource());
}
}

No comments: