Most user interface frameworks have the concept of a default button. The default button is the button which is activated when the Enter key is pressed. This is an important aspect for usability. For example, on a search form, most users expect to be able to perform a search by pressing Enter on any form element. If this does not happen as expected, the user gets frustrated as they need to either tab onto the button and press Enter, or use the mouse to click. I think the web has pushed this standard as HTML forms automatically post themselves when the user presses Enter within them. WPF offers the ability to have both Default and Cancel buttons. The Cancel button is activated by pressing the ESC key, although in my opinion this is not quite as common as the Enter button as most users don’t expect to be able to press ESC within forms – only dialog windows maybe. How do default buttons work? There is nothing particularly special going on under the covers when you set a button to be the default button. When the IsDefault property is set, AccessKeyManager.Register is called, passing in “\r” as the access key. Similarly, the act of setting IsCancel calls AccessKeyManager.Register passing in the character code for ESC - “\x001b”. The only special thing that setting IsDefault will do is ensure that the IsDefaulted property is updated correctly. This allows, for example, buttons to be styled differently if they are defaulted. How do I get multiple default buttons per page? In ASP.NET a common problem was that because you are forced to have a single server-side form, it was not possible to have multiple logical forms on the page and have the Enter button behave as expected without a lot of hacking. For example, you might want the template for all your pages to have a small search form at the top with a search button, and also have the search performed when the user presses enter within the search box. When the user is on a page with its own form on it, you would want that form to be submitted when the user pressed enter. This was not possible natively. This issue was solved in ASP.NET2 by allowing any Panel or Table to have set a DefaultButton property. Behind the scenes this would set up an event handler in JavaScript which would activate the button when enter was pressed. In WPF, there is no such property. If you define two panels on a page, and into each panel place a TextBox and a Button with IsDefault="True", you will see that pressing enter in either TextBox always activates the first button. This is because that button registered itself first so is at the top of the invocation list of the Enter access key:
<Window x:Class="WindowsApplication7.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WindowsApplication7"
Title="WindowsApplication7" Height="300" Width="300"
>
<StackPanel>
<StackPanel Margin="5" Background="Yellow">
<TextBox Margin="5" />
<Button Margin="5" IsDefault="True" Content="Yellow" />
</StackPanel>
<StackPanel Margin="5" Background="Green">
<TextBox Margin="5" />
<Button Margin="5" IsDefault="True" Content="Green" />
</StackPanel>
</StackPanel>
</Window>
<StackPanel Margin="5" Background="Yellow" local:AccessKeyScoper.IsAccessKeyScope="True">
<TextBox Margin="5" />
<Button Margin="5" IsDefault="True" Content="Yellow" />
</StackPanel>
<StackPanel Margin="5" Background="Green" local:AccessKeyScoper.IsAccessKeyScope="True">
<TextBox Margin="5" />
<Button Margin="5" IsDefault="True" Content="Green" />
</StackPanel>
using System;
using System.Windows;
using System.Windows.Input;
namespace WindowsApplication7
{
/// <summary>
/// Class used to manage generic scoping of access keys
/// </summary>
public static class AccessKeyScoper
{
/// <summary>
/// Identifies the IsAccessKeyScope attached dependency property
/// </summary>
public static readonly DependencyProperty IsAccessKeyScopeProperty =
DependencyProperty.RegisterAttached("IsAccessKeyScope", typeof(bool), typeof(AccessKeyScoper), new PropertyMetadata(false, HandleIsAccessKeyScopePropertyChanged));
/// <summary>
/// Sets the IsAccessKeyScope attached property value for the specified object
/// </summary>
/// <param name="obj">The object to retrieve the value for</param>
/// <param name="value">Whether the object is an access key scope</param>
public static void SetIsAccessKeyScope(DependencyObject obj, bool value)
{
obj.SetValue(AccessKeyScoper.IsAccessKeyScopeProperty, value);
}
/// <summary>
/// Gets the value of the IsAccessKeyScope attached property for the specified object
/// </summary>
/// <param name="obj">The object to retrieve the value for</param>
/// <returns>The value of IsAccessKeyScope attached property for the specified object</returns>
public static bool GetIsAccessKeyScope(DependencyObject obj)
{
return (bool) obj.GetValue(AccessKeyScoper.IsAccessKeyScopeProperty);
}
private static void HandleIsAccessKeyScopePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue.Equals(true))
{
AccessKeyManager.AddAccessKeyPressedHandler(d, HandleScopedElementAccessKeyPressed);
}
else
{
AccessKeyManager.RemoveAccessKeyPressedHandler(d, HandleScopedElementAccessKeyPressed);
}
}
private static void HandleScopedElementAccessKeyPressed(object sender, AccessKeyPressedEventArgs e)
{
if (!Keyboard.IsKeyDown(Key.LeftAlt) && !Keyboard.IsKeyDown(Key.RightAlt) && GetIsAccessKeyScope((DependencyObject)sender))
{
e.Scope = sender;
e.Handled = true;
}
}
}
}