Building on someone else's DefaultButton Silverlight work...

Posted by KyleBurns on Geeks with Blogs See other posts from Geeks with Blogs or by KyleBurns
Published on Fri, 18 Feb 2011 10:53:17 GMT Indexed on 2011/02/18 15:26 UTC
Read the original article Hit count: 336

Filed under:

This week I was handed a "simple" requirement - have a search screen execute its search when the user pressed the Enter key instead of having to move hands from keyboard to mouse and click Search.  That is a reasonable request that has been met for years both in Windows and Web apps.  I did a quick scan for code to pilfer and found Patrick Cauldwell's Blog posting "A 'Default Button' In Silverlight".  This posting was a great start and I'm glad that the basic work had been done for me, but I ran into one issue - when using bound textboxes (I'm a die-hard MVVM enthusiast when it comes to Silverlight development), the search was being executed before the textbox I was in when the Enter key was pressed updated its bindings.  With a little bit of reflection work, I think I have found a good generic solution that builds upon Patrick's to make it more binding-friendly.  Also, I wanted to set the DefaultButton at a higher level than on each TextBox (or other control for that matter), so the use of mine is intended to be set somewhere such as the LayoutRoot or other high level control and will apply to all controls beneath it in the control tree.  I haven't tested this on controls that treat the Enter key special themselves in the mix.

The real change from Patrick's solution here is that in the KeyUp event, I grab the source of the KeyUp event (in my case the textbox containing search criteria) and loop through the static fields on the element's type looking for DependencyProperty instances.  When I find a DependencyProperty, I grab the value and query for bindings.  Each time I find a binding, UpdateSource is called to make sure anything bound to any property of the field has the opportunity to update before the action represented by the DefaultButton is executed.

Here's the code:

public class DefaultButtonService
{
public static DependencyProperty DefaultButtonProperty = DependencyProperty.RegisterAttached("DefaultButton",
typeof (Button),
typeof (DefaultButtonService),
new PropertyMetadata
(null,
DefaultButtonChanged));

private static void DefaultButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uiElement = d as UIElement;
var button = e.NewValue as Button;
if (uiElement != null && button != null)
{
uiElement.KeyUp += (sender, arg) =>
{
if (arg.Key == Key.Enter)
{
var element = arg.OriginalSource as FrameworkElement;
if (element != null)
{
UpdateBindings(element);
}

if (button.IsEnabled)
{
button.Focus();
var peer = new ButtonAutomationPeer(button);
var invokeProv =
peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
if (invokeProv != null) invokeProv.Invoke();
arg.Handled = true;
}
}
};
}
}

public static DefaultButtonService GetDefaultButton(UIElement obj)
{
return (DefaultButtonService) obj.GetValue(DefaultButtonProperty);
}

public static void SetDefaultButton(DependencyObject obj, DefaultButtonService button)
{
obj.SetValue(DefaultButtonProperty, button);
}

public static void UpdateBindings(FrameworkElement element)
{
element.GetType().GetFields(BindingFlags.Public | BindingFlags.Static).ForEach(field =>
{
if (field.FieldType.IsAssignableFrom(typeof(DependencyProperty)))
{
try
{
var dp = field.GetValue(null) as DependencyProperty;
if (dp != null)
{
var binding = element.GetBindingExpression(dp);
if (binding != null)
{
binding.UpdateSource();
}
}
}
// ReSharper disable EmptyGeneralCatchClause
catch (Exception)
// ReSharper restore EmptyGeneralCatchClause
{
// swallow exceptions
}
}
});
}
}

© Geeks with Blogs or respective owner