Silverlight开发中的疑难杂症-如何为Silverlight添加默认按钮
在开发中发现Silverlight中没有WPF的DefaultButton和CancelButton属性,于是就准备自己实现一个。DefaultButton要实现的是在按下Enter键的时候触发对应的按钮事件,而CancelButton要实现的是在按下Esc键的时候触发对应的按钮事件。本以为是个很简单的事情,只需要在对应的keyup事件中进行按键判断,然后调用对应的按钮事件即可。结果在实现时发现,Silverlight中并没有WPF中常用的RaiseEvent方法,一时间无从下手,最后只好再次求助谷歌(没有它我怎么活啊~)。最终再几种方案中选择了一种比较合适的跟大家分享一下。它通过UI自动化中的ButtonAutomationPeer类来完成了按钮的模拟点击,为了能够方便以后的使用,这里还是将其实现成了一个扩展方法,具体的代码如下:
/// 按钮扩展类
/// </summary>
public static class ButtonExtension
{
/// <summary>
/// 点击按钮
/// </summary>
/// <param name="clickBtn"></param>
public static void PerfomClick(this Button clickBtn)
{
//这里必须按判断是否处于Enable状态
if (clickBtn != null && clickBtn.IsEnabled)
{
//通过代码点击按钮
var peer = new ButtonAutomationPeer(clickBtn);
var invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
if (invokeProv != null)
{
invokeProv.Invoke();
//调用后同时设置焦点为按钮
clickBtn.Focus();
}
}
}
}
完成了最为关键的部分,接下来就是默认按钮以及撤销按钮的实现,这里还是通过附加属性来实现这一个功能,关于附加属性的具体使用请见我的 这篇文章 。实现的思路就是注册属性所附加到的元素的KeyUp事件,在Enter和Esc键按下时调用按钮的点击事件。完整的代码如下:
/// SilverLight按钮服务
/// </summary>
public class ButtonServices
{
#region Dependency Properties
#region 默认按钮
#region DefaultButton
public static Button GetDefaultButton(DependencyObject obj)
{
return (Button)obj.GetValue(DefaultButtonProperty);
}
public static void SetDefaultButton(DependencyObject obj, Button value)
{
obj.SetValue(DefaultButtonProperty, value);
}
public static readonly DependencyProperty DefaultButtonProperty =
DependencyProperty.RegisterAttached("DefaultButton", typeof(Button), typeof(ButtonServices), new PropertyMetadata(OnDefaultButtonChanged));
#endregion
private static void OnDefaultButtonChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UIElement scopeElement = obj as UIElement;
if (scopeElement != null)
{
scopeElement.KeyUp += new KeyEventHandler(Element_EnterKeyUp);
}
}
static void Element_EnterKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
UIElement scopeElement = sender as UIElement;
if (scopeElement != null)
{
Button defaultButton = scopeElement.GetValue(ButtonServices.DefaultButtonProperty) as Button;
//通过代码点击按钮
defaultButton.PerfomClick();
}
}
}
#endregion
#region 取消按钮
#region CancelButton
public static Button GetCancelButton(DependencyObject obj)
{
return (Button)obj.GetValue(CancelButtonProperty);
}
public static void SetCancelButton(DependencyObject obj, Button value)
{
obj.SetValue(CancelButtonProperty, value);
}
public static readonly DependencyProperty CancelButtonProperty =
DependencyProperty.RegisterAttached("CancelButton", typeof(Button), typeof(ButtonServices), new PropertyMetadata(OnCancelButtonChanged));
#endregion
private static void OnCancelButtonChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UIElement scopeElement = obj as UIElement;
if (scopeElement != null)
{
scopeElement.KeyUp += new KeyEventHandler(Element_EscKeyUp);
}
}
static void Element_EscKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
UIElement scopeElement = sender as UIElement;
if (scopeElement != null)
{
Button clickBtn = scopeElement.GetValue(ButtonServices.DefaultButtonProperty) as Button;
//通过代码点击按钮
clickBtn.PerfomClick();
}
}
}
#endregion
#endregion
}
接下来只要将这个属性附加到要作用的元素下就可以实现默认按钮和撤销按钮的效果啦,使用时先添加如下别名,如下:
xmlns:MyExtensions="clr-namespace:YQL.Core.Extensions;assembly=YQL.Core.Silverlight"
<Grid x:Name="LayoutRoot" MyExtensions:ButtonServices.DefaultButton="{Binding ElementName=btnLogin}"/>
PS:做完之后想到一个问题,在附加属性中注册事件,很难找到合适的时机取消事件的注册,这样就可能会引起内存泄露,不知道这里除了WeakEvent Pattern外还有什么方法能够简单的防治内存的泄露。关于WeakEvent Pattern大家可以参考MSDN的文章 http://msdn.microsoft.com/en-us/library/aa970850.aspx 。