65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 WPF/Silverlight EventBroker 实现松耦合

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (12投票s)

2010年6月7日

CPOL

5分钟阅读

viewsIcon

34592

downloadIcon

182

在 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 命名空间的引用。

    Bobeck_EventBroker_Image1.png

  • 修改 XAML 以包含一个矩形。我们将处理矩形的 MouseLeftButtonDown 事件。
  • 向矩形添加 MouseLeftButtonDown 附加属性。其值可以是您想要的任何 string(稍后将详细介绍)。

    Bobeck_EventBroker_Image2.png

  • 找到一个您想接收事件通知的类。在本例中,我将选择 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);
    }

    Bobeck_EventBroker_Image3.png

就是这样!很简单,对吧!当这个应用程序运行时,只要您点击红色矩形内部,上述事件处理程序就会执行。请注意参数。eventType 将指示事件的类型(在这种情况下是 EventBroker.EventType.MouseLeftButtonDown)。eventKey 参数将是您在 XAML 中分配给附加属性的 string。(在这种情况下是“Click!”)。您可以使用其中任何一个(或两者)值进行过滤。除非您将 EventBroker 用于事件日志记录之类的用途,否则您可能会想在响应之前限定 eventKey,因为可能有许多其他控件会触发相同的 MouseLeftButtonDown 事件,而您可能希望以不同的方式响应每个事件。

所以,这引出了我想要强调的一点……当您以这种方式附加到 EventBrokerEventHandler 时,您实际上是在订阅一个广播频道。EventBroker 处理的所有事件都会广播给所有已附加的客户端。

但是,EventBroker 也提供了一个 API,允许客户端“Register”(注册)事件。这使得 EventBroker 负责将事件路由到适当的处理程序。要注册一个事件,代码看起来像这样……

EventBroker.Register(EventBroker.EventType.MouseLeftButtonDown, 
	"Click!", EventBroker_EventHandler);

当您完成监听事件后,您可以“Unregister”(注销)(确保每个参数与注册时使用的参数相同)

EventBroker.Unregister(EventBroker.EventType.MouseLeftButtonDown, 
	"Click!", EventBroker_EventHandler);

因此,广播还是路由……您可以选择最适合您需求的实现。以下列表显示了当前支持的事件。

  • KeyUpFrameworkElement
  • KeyDownFrameworkElement
  • MouseEnterFrameworkElement
  • MouseLeaveFrameworkElement
  • MouseLeftButtonUpFrameworkElement
  • MouseLeftButtonDownFrameworkElement
  • MouseRightButtonUpFrameworkElement
  • MouseRightButtonDownFrameworkElement
  • MouseMoveFrameworkElement
  • MouseWheelFrameworkElement
  • SelectionChangedSelector & TextBox
  • ClickButtonBase
  • CheckedChangedToggleButton
  • ValueChangedRangeBase

幕后

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 事件时,EventBrokerProxy_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 的公共方法,传递 eventTypeeventKey

    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 事件仅适用于 ListBoxComboBox 控件,这两个控件在 WPF 和 Silverlight 中都很常见,而不适用于 TabControlListViewDataGrid

好吧,我认为这就差不多了。我希望您觉得 EventBroker 有用。如果没有,也许您在文章中找到了某个有教育意义的亮点。暂时就这样,祝您编码愉快!

历史

  • 2010年6月6日 创建文章 - DAB
  • 2010年6月8日 更新源代码以支持 RangeBaseToggleButton - DAB
© . All rights reserved.