MVVM之旅-給任意的事件綁定命令(Adventures in MVVM – Binding Commands to ANY Event)

 原文標題:Adventures in MVVM – Binding Commands to ANY Event

當我實現MVVM模式時,令我最爲頭疼一件事是需要給事件綁定命令。當我使用Prism框架時,我得到一個Button.Click的命令綁定,但是每一個其他的時間都需要單獨的進行處理。做這些的時候,需要很多的容易出錯的樣板代碼。在我過去的工作崗位上,我發表一些代碼來減輕疼痛。然而,仍需要你針對每一個你想進行綁定的事件寫一個新的行爲和附加內容。

有一段時間了,我的想法只是直接綁定命令到時間。在這期間我遇到很多困難。例如,每一個事件處理都有一個同的事件參數類型。這需要所有的處理都是動態的。我仍然不能創建一個內置的命令綁定--我將確信對每一個單一控件綁定到超過1個的事件。因此我需要創建一個綁定的集合。創建結構體數據就是給自己創造麻煩---綁定僅工作在VisualTree的FrameWorkElements上。這需要我來寫自己的綁定基於我的通用的行爲。

以下是非常鬆散的基礎下Chinch MVVM框架。我測試了這些代碼在Silverlight和WPF,並且運行真的不錯!

假定我有一個像如下的ViewMode:

public class MainPageViewModel : INotifyPropertyChanged
{
    ...
    public ICommand MouseLeaveCommand { get; private set; }
    public ICommand MouseEnterCommand { get; private set; }
    public ICommand ClickCommand { get; private set; }
    ...
}

我能綁定命令到一個控件的時間上,以一Button爲例:

Button Content="Click Me">
    <Behaviors:Events.Commands>
        <Behaviors:EventCommandCollection>
            <Behaviors:EventCommand CommandName="MouseEnterCommand" EventName="MouseEnter" />
            <Behaviors:EventCommand CommandName="MouseLeaveCommand" EventName="MouseLeave" />
            <Behaviors:EventCommand CommandName="ClickCommand" EventName="Click" />
        </Behaviors:EventCommandCollection>
    </Behaviors:Events.Commands>
</Button>

我不再需寫任何的額外的代碼,無論何時我想附加命令到我的事件上!下面是砂鍋麪代碼的警告:

 

  1. the XAML requires the EventCommandCollection to be declared in the XAML.  I struggled to figure out how to eliminate this but gave up.  Someone smarter than me might be able to tell me what I am doing wrong.
  2. This code does not consider command properties.  Every command assumes a null parameter.  If you need parameters (like data context), then you’ll have to do something differently (either use the old-school mechanism or extend this code to handle some special event types).
  3. You don’t bind directly to the command.  Instead, you declare the name of the command (Notice CommandName is not bound).  The behavior binds for you using a primitive mechanism.

下面給出命令的行爲,它可以完成所有的工作:

public class Events
{
    private static readonly DependencyProperty EventBehaviorsProperty =
        DependencyProperty.RegisterAttached(
        "EventBehaviors",
        typeof(EventBehaviorCollection),
        typeof(Control),
        null);

    private static readonly DependencyProperty InternalDataContextProperty =
        DependencyProperty.RegisterAttached(
        "InternalDataContext",
        typeof(Object),
        typeof(Control),
        new PropertyMetadata(null, DataContextChanged));

    private static void DataContextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var target = dependencyObject as Control;
        if (target == null) return;

        foreach (var behavior in GetOrCreateBehavior(target))
            behavior.Bind();
    }

    public static readonly DependencyProperty CommandsProperty =
        DependencyProperty.RegisterAttached(
        "Commands",
        typeof(EventCommandCollection),
        typeof(Events),
        new PropertyMetadata(null, CommandsChanged));

    public static EventCommandCollection GetCommands(DependencyObject dependencyObject)
    {
        return dependencyObject.GetValue(CommandsProperty) as EventCommandCollection;
    }

    public static void SetCommands(DependencyObject dependencyObject, EventCommandCollection eventCommands)
    {
        dependencyObject.SetValue(CommandsProperty, eventCommands);
    }

    private static void CommandsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var target = dependencyObject as Control;
        if (target == null) return;

        var behaviors = GetOrCreateBehavior(target);
        foreach (var eventCommand in e.NewValue as EventCommandCollection)
        {
            var behavior = new EventBehavior(target);
            behavior.Bind(eventCommand);
            behaviors.Add(behavior);
        }

    }

    private static EventBehaviorCollection GetOrCreateBehavior(FrameworkElement target)
    {
        var behavior = target.GetValue(EventBehaviorsProperty) as EventBehaviorCollection;
        if (behavior == null)
        {
            behavior = new EventBehaviorCollection();
            target.SetValue(EventBehaviorsProperty, behavior);
            target.SetBinding(InternalDataContextProperty, new Binding());
        }

        return behavior;
    }
}

public class EventCommand
{
    public string CommandName { get; set; }
    public string EventName { get; set; }
}

public class EventCommandCollection : List<EventCommand>
{
}

public class EventBehavior : CommandBehaviorBase<Control>
{
    private EventCommand _bindingInfo;

    public EventBehavior(Control control)
        : base(control)
    {

    }

    public void Bind(EventCommand bindingInfo)
    {
        ValidateBindingInfo(bindingInfo);

        _bindingInfo = bindingInfo;

        Bind();
    }

    private void ValidateBindingInfo(EventCommand bindingInfo)
    {
        if(bindingInfo == null) throw new ArgumentException("bindingInfo");
        if (string.IsNullOrEmpty(bindingInfo.CommandName)) throw new ArgumentException("bindingInfo.CommandName");
        if (string.IsNullOrEmpty(bindingInfo.EventName)) throw new ArgumentException("bindingInfo.EventName");
    }

    public void Bind()
    {
        ValidateBindingInfo(_bindingInfo);
        HookPropertyChanged();
        HookEvent();
        SetCommand();
    }

    public void HookPropertyChanged()
    {
        var dataContext = TargetObject.DataContext as INotifyPropertyChanged;
        if (dataContext == null) return;

        dataContext.PropertyChanged -= DataContextPropertyChanged;
        dataContext.PropertyChanged += DataContextPropertyChanged;
    }

    private void DataContextPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _bindingInfo.CommandName)
            SetCommand();
    }

    private void SetCommand()
    {
        var dataContext = TargetObject.DataContext;
        if (dataContext == null) return;

        var propertyInfo = dataContext.GetType().GetProperty(_bindingInfo.CommandName);
        if (propertyInfo == null) throw new ArgumentException("commandName");

        Command = propertyInfo.GetValue(dataContext, null) as ICommand;
    }

    private void HookEvent()
    {
        var eventInfo = TargetObject.GetType().GetEvent(
            _bindingInfo.EventName, BindingFlags.Public | BindingFlags.Instance);
        if (eventInfo == null) throw new ArgumentException("eventName");

        eventInfo.RemoveEventHandler(TargetObject, GetEventMethod(eventInfo));
        eventInfo.AddEventHandler(TargetObject, GetEventMethod(eventInfo));
    }

    private Delegate _method;
    private Delegate GetEventMethod(EventInfo eventInfo)
    {
        if (eventInfo == null) throw new ArgumentNullException("eventInfo");
        if (eventInfo.EventHandlerType == null) throw new ArgumentException("EventHandlerType is null");

        if (_method == null)
        {
            _method = Delegate.CreateDelegate(
                eventInfo.EventHandlerType, this,
                GetType().GetMethod("OnEventRaised",
                BindingFlags.NonPublic | BindingFlags.Instance));
        }

        return _method;
    }

    private void OnEventRaised(object sender, EventArgs e)
    {
        ExecuteCommand();
    }
}

public class EventBehaviorCollection : List<EventBehavior>
{ }

翻譯完畢,本人將繼續製作有關WPF和WCF相關文章的翻譯,如果有不錯的文章可以推薦

轉載請註明文章出處,表示對作者的尊重,謝謝……

發佈了35 篇原創文章 · 獲贊 28 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章