使用 WPF/Silverlight EventBroker 实现松耦合






4.74/5 (12投票s)
在 WPF 或 Silverlight 中使用附加属性将 UI 元素的事件与任意代码进行松耦合。
引言
您是否在使用 MVVM 模式?您是否将 ICommand
绑定到基于按钮的控件,并在 ViewModel 中处理事件?您是否见过 RelayCommand
?它非常棒,我喜欢它可以通过实现 CanExecute
谓词来启用/禁用按钮。虽然我不是在劝阻您不要使用 ICommand
方法进行松耦合,但您可能仍然想使用(或者仅仅是了解)另一种选择。
这篇文章是我对社区的第一次贡献。我创建了一个我认为很巧妙的类,并想与大家分享。也许有人会觉得它有用。
注意
我将要讨论的 EventBroker
类使用了附加属性。如果您不熟悉它们,或者只是需要复习一下,可以在 MSDN 这里阅读。
Using the Code
首先,我们将看到如何使用 EventBroker
。它非常简单。之后,对于那些坚持看下去的人,我将展示代码的样子。
那么,让我们开始吧……
- 创建一个新的 WPF 或 Silverlight 应用程序,并将 EventBroker.cs 包含到项目中。
- 如果您选择了 WPF,请打开 MainWindow.xaml 文件。如果您选择了 Silverlight,请打开 MainPage.xaml 文件。
- 在 XAML 文件的顶部,添加对
EventBroker
命名空间的引用。 - 修改 XAML 以包含一个矩形。我们将处理矩形的
MouseLeftButtonDown
事件。 - 向矩形添加
MouseLeftButtonDown
附加属性。其值可以是您想要的任何string
(稍后将详细介绍)。 - 找到一个您想接收事件通知的类。在本例中,我将选择
MainWindow
类。(当然,将EventBroker
处理程序放在视图的代码隐藏中并不能带来任何好处,因为您总是可以那样做。EventBroker
的精妙之处在于您可以处理任意代码中的事件!) - 在文件顶部包含
EventBroker
的命名空间。using Bobeck.Windows.Events;
- 在
MainWindow
类的构造函数中(或其他合适的位置),附加到EventBroker
发布的唯一事件。EventBroker.EventHandler += new EventBroker.EventHandlerDelegate(EventBroker_EventHandler);
- 为了处理事件,我们必须提供一个事件处理程序,其签名与
EventBroker
的委托匹配……void EventManager_EventHandler(object sender, EventArgs e, EventBroker.EventType eventType, string eventKey) { MessageBox.Show("eventKey = " + eventKey); }
就是这样!很简单,对吧!当这个应用程序运行时,只要您点击红色矩形内部,上述事件处理程序就会执行。请注意参数。eventType
将指示事件的类型(在这种情况下是 EventBroker.EventType.MouseLeftButtonDown
)。eventKey
参数将是您在 XAML 中分配给附加属性的 string
。(在这种情况下是“Click!”)。您可以使用其中任何一个(或两者)值进行过滤。除非您将 EventBroker
用于事件日志记录之类的用途,否则您可能会想在响应之前限定 eventKey
,因为可能有许多其他控件会触发相同的 MouseLeftButtonDown
事件,而您可能希望以不同的方式响应每个事件。
所以,这引出了我想要强调的一点……当您以这种方式附加到 EventBroker
的 EventHandler
时,您实际上是在订阅一个广播频道。EventBroker
处理的所有事件都会广播给所有已附加的客户端。
但是,EventBroker
也提供了一个 API,允许客户端“Register
”(注册)事件。这使得 EventBroker
负责将事件路由到适当的处理程序。要注册一个事件,代码看起来像这样……
EventBroker.Register(EventBroker.EventType.MouseLeftButtonDown,
"Click!", EventBroker_EventHandler);
当您完成监听事件后,您可以“Unregister
”(注销)(确保每个参数与注册时使用的参数相同)
EventBroker.Unregister(EventBroker.EventType.MouseLeftButtonDown,
"Click!", EventBroker_EventHandler);
因此,广播还是路由……您可以选择最适合您需求的实现。以下列表显示了当前支持的事件。
KeyUp
(FrameworkElement
)KeyDown
(FrameworkElement
)MouseEnter
(FrameworkElement
)MouseLeave
(FrameworkElement
)MouseLeftButtonUp
(FrameworkElement
)MouseLeftButtonDown
(FrameworkElement
)MouseRightButtonUp
(FrameworkElement
)MouseRightButtonDown
(FrameworkElement
)MouseMove
(FrameworkElement
)MouseWheel
(FrameworkElement
)SelectionChanged
(Selector
&TextBox
)Click
(ButtonBase
)CheckedChanged
(ToggleButton
)ValueChanged
(RangeBase
)
幕后
EventBroker
是一个 static
类,它实现了许多附加属性——实际上是为每个事件都实现了一个。这些属性的底层实现看起来很典型。这是 MouseLeftButtonDown
附加属性的实现。我们还必须提供 CLR get()
& set()
方法,以便我们可以在 XAML 中使用这些属性。
public static readonly DependencyProperty MouseLeftButtonDownProperty =
DependencyProperty.RegisterAttached("MouseLeftButtonDown",
typeof(string),
typeof(EventBroker),
new PropertyMetadata(String.Empty, OnMouseLeftButtonDownChanged)
);
public static string GetMouseLeftButtonDown(DependencyObject obj)
{
return (string)obj.GetValue(MouseLeftButtonDownProperty);
}
public static void SetMouseLeftButtonDown(DependencyObject obj, string value)
{
obj.SetValue(MouseLeftButtonDownProperty, value);
}
对于每个附加属性的定义,我们都提供了一个回调,让我们有机会挂接到FrameworkElement元素的实际事件接口。这段代码看起来是这样的……
private static void OnMouseLeftButtonDownChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs e)
{
FrameworkElement target = obj as FrameworkElement;
if (target == null) return;
string newValue = e.NewValue as string;
string oldValue = e.OldValue as string;
if (!String.IsNullOrEmpty(newValue) && String.IsNullOrEmpty(oldValue))
{
target.MouseLeftButtonDown +=
new MouseButtonEventHandler(Proxy_MouseLeftButtonDown);
target.Unloaded += new RoutedEventHandler(Proxy_UnLoaded);
}
else if (String.IsNullOrEmpty(newValue) && !String.IsNullOrEmpty(oldValue))
{
target.MouseLeftButtonDown -=
new MouseButtonEventHandler(Proxy_MouseLeftButtonDown);
target.Unloaded -= new RoutedEventHandler(Proxy_UnLoaded);
}
}
首先,我们将 DependencyObject obj
强制转换为 FrameworkElement
,这是支持鼠标事件的最底层通用控件类。如果正在设置附加属性,那么我们将挂接到控件的 MouseLeftButtonDown
事件。如果正在清除附加属性,那么我们将从控件的 MouseLeftButtonDown
事件解除挂接。我们还利用这个机会挂接/解除挂接控件的 Unloaded 事件。监视此事件是为了在控件卸载时解除挂接我们的处理程序。如果我们不这样做,那么控件将无法被垃圾回收。糟糕!
正如您从上面的代码中可以看到的,每当发生 MouseLeftButtonDown
事件时,EventBroker
的 Proxy_MouseLeftButtonDown
处理程序就会执行。每个事件类型都有自己的 Proxy_XXX
处理程序。下面是一个示例……
private static void Proxy_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
string eventKey = GetMouseLeftButtonDown(sender as DependencyObject);
Fire_EventHandler(sender, e, EventType.MouseLeftButtonDown, eventKey);
}
每个 Proxy_XXX
处理程序然后调用一个名为 Fire_EventHandler
的公共方法,传递 eventType
和 eventKey
。
private static void Fire_EventHandler
(object sender, EventArgs e, EventType eventType, string eventKey)
{
// Step 1: Broadcast to all those connected to our EventHandler
if (EventHandler != null)
{
EventHandler(sender, e, eventType, eventKey);
}
// Step 2: Send to matching registered handlers
List contracts = _Contracts.Where(c => (c.EventType == eventType) &&
(c.EventKey == eventKey)).ToList();
foreach (EventContract contract in contracts)
{
if (contract.EventHandler != null)
{
contract.EventHandler(sender, e, eventType, eventKey);
}
}
}
Fire_EventHandler
方法负责将事件数据广播给所有客户端/侦听器,并将事件数据仅路由给那些感兴趣(已注册)的方。在内部,EventBroker
维护一个合同列表,将已注册的客户端处理程序映射到事件类型和事件键(在 XAML 中设置的那些 string
)。
注意:在撰写本文时,SelectionChanged
事件仅适用于 ListBox
和 ComboBox
控件,这两个控件在 WPF 和 Silverlight 中都很常见,而不适用于 TabControl
、ListView
或 DataGrid
。
好吧,我认为这就差不多了。我希望您觉得 EventBroker
有用。如果没有,也许您在文章中找到了某个有教育意义的亮点。暂时就这样,祝您编码愉快!
历史
- 2010年6月6日 创建文章 - DAB
- 2010年6月8日 更新源代码以支持
RangeBase
和ToggleButton
- DAB