如何实现干净的代码隐藏文件?

时间:2010-02-22 08:21:24

标签: .net wpf mvvm code-behind

使用WPF最好将文件后面的xaml.cs代码保持小而干净。 MVVM pattern通过数据绑定和命令绑定帮助实现此目标,其中任何业务逻辑在ViewModel类中处理。

我正在使用MVVM模式的原则,我的代码隐藏文件非常干净。使用命令绑定处理任何按钮单击事件,还有一些控件也支持命令绑定。但是,控件上有几个事件没有Command和CommandParameter属性,因此我看不到使用绑定的直接方法。在这些事件的代码隐藏文件中摆脱逻辑的最佳方法是什么?例如。处理控件内的鼠标事件。

3 个答案:

答案 0 :(得分:4)

一个很好的方法是使用attached behaviour。行为本身可以挂钩您需要的事件,并使用您想要的参数激活相应的命令。它本质上是相同的代码,但它只是从你的代码中删除它,让你纯粹在XAML中表达你的意图。

CodePlex上有几个示例行为,或者这是执行命令的基本示例:

using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;

/// <summary>
/// Trigger action to execute an ICommand command
/// </summary>
public class ExecuteCommand : TriggerAction<FrameworkElement>
{
    #region Dependency Properties

    /// <summary>
    /// Command parameter
    /// </summary>
    public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(ExecuteCommand), new UIPropertyMetadata(null, OnCommandParameterChanged));

    /// <summary>
    /// Command to be executed
    /// </summary>
    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommand), new UIPropertyMetadata(null, OnCommandChanged));

    #region Public Properties

    /// <summary>
    /// Gets or sets the command
    /// </summary>
    public ICommand Command
    {
        get
        {
            return (ICommand)this.GetValue(CommandProperty);
        }

        set
        {
            this.SetValue(CommandProperty, value);
        }
    }

    /// <summary>
    /// Gets or sets the command parameter
    /// </summary>
    public object CommandParameter
    {
        get
        {
            return (object)this.GetValue(CommandParameterProperty);
        }

        set
        {
            this.SetValue(CommandParameterProperty, value);
        }
    }
    #endregion

    /// <summary>
    /// Executes the command if it is not null and is able to execute
    /// </summary>
    /// <param name="parameter">This argument not used</param>
    protected override void Invoke(object parameter)
    {
        if (this.Command != null && this.Command.CanExecute(this.CommandParameter))
        {
            this.Command.Execute(this.CommandParameter);
        }
    }

    /// <summary>
    /// Called on command change
    /// </summary>
    /// <param name="oldValue">old ICommand instance</param>
    /// <param name="newValue">new ICommand instance</param>
    protected virtual void OnCommandChanged(ICommand oldValue, ICommand newValue)
    {
    }

    /// <summary>
    /// Called on command parameter change
    /// </summary>
    /// <param name="oldValue">old ICommand instance</param>
    /// <param name="newValue">new ICommand instance</param>
    protected virtual void OnCommandParameterChanged(object oldValue, object newValue)
    {
    }

    /// <summary>
    /// Called on command parameter change
    /// </summary>
    /// <param name="o">Dependency object</param>
    /// <param name="e">Dependency property</param>
    private static void OnCommandParameterChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        ExecuteCommand invokeCommand = o as ExecuteCommand;
        if (invokeCommand != null)
        {
            invokeCommand.OnCommandParameterChanged((object)e.OldValue, (object)e.NewValue);
        }
    }

    /// <summary>
    /// Called on command change
    /// </summary>
    /// <param name="o">Dependency object</param>
    /// <param name="e">Dependency property</param>
    private static void OnCommandChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        ExecuteCommand invokeCommand = o as ExecuteCommand;
        if (invokeCommand != null)
        {
            invokeCommand.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);
        }
    }        
    #endregion
}

}

然后,您可以使用System.Windows.Interactivity命名空间(程序集包含在Blend 3中)来挂钩事件并触发命令,如下所示:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="MouseLeftButtonUp">
        <triggers:ExecuteCommand Command="{Binding MyCommand}" CommandParameter="MyParameter" />
    </i:EventTrigger>
</i:Interaction.Triggers>

对于具有多个参数的更复杂事件,您可能需要创建特定的行为,而不是像上面的示例那样使用通用行为。我通常更喜欢创建自己的类型来存储参数,并映射到它,而不是在我的ViewModel中具有特定的EventArgs要求。

*我应该补充的一件事是,我肯定不是“Code Behind”中的“0代码”,我认为强制执行这一点在某种程度上忽略了MVVM。只要背后的代码包含 no logic ,因此没有真正需要测试的东西,那么我可以使用一些小代码来弥补View和ViewModel之间的“差距”。如果你有一个“智能视图”,有时我也称之为浏览器控件,或者你需要与ViewModel进行交流,这有时也是必要的。有些人会因为提出这样的事情而得到干草叉,这就是为什么我把这一点留到最后并先回答你的问题: - )*

答案 1 :(得分:1)

我的建议会引起争议。 不要这样做。即这样做,但要非常小心。 MVVM的主要目标之一是简化开发。如果它让你编写代码,那就很难理解和支持 - 你选择了错误的道路。

拜托,别误会我的意思。我不是说“在代码中写下所有内容”。我说 - 如果这段代码简化了理解,那么在代码背后隐藏一些代码是完全没问题的。并且不要以为你打破模式。模式只是建议......

答案 2 :(得分:0)

我已经通过以下策略解决了这个问题,但我不知道这是否是理想的解决方案。

对于不支持命令绑定的事件,我在代码隐藏文件中处理事件本身,但我实际上并没有在那里做任何业务逻辑。然后相关的ViewModel类有一个处理事件的函数,因此代码隐藏事件处理程序调用其ViewModel类的相应函数。

E.g。我有一些鼠标事件。让我们看一下Canvas上的MouseDown事件。这将在代码隐藏中触发事件处理程序,事件处理程序将简单地将调用传递给ViewModel。在这种情况下,我需要知道的是按下了哪个鼠标按钮,当前的位置是什么,所以我不传递MouseEventArgs:

private void CanvasMouseDown(object sender, MouseButtonEventArgs e)
{
    var mousePositionX = e.GetPosition(_myCanvas).X;
    var mousePositionY = e.GetPosition(_myCanvas).Y;
    _vm.MouseDown(e.ChangedButton, mousePositionX, mousePositionY);
}

然后ViewModel有一个MouseDown函数,这是我实际处理事件的地方:

public void MouseDown(MouseButton button, double mousePositionX, mousePositionY)
{
    switch (button)
    {
        case MouseButton.Left:
            // Do something 
            break;
        case MouseButton.Right:
            // Do something 
            break;
        case MouseButton.Middle:
            // Do something 
            break;
    }

    _mousePositionX = mousePositionX;
    _mousePositionY = mousePositionY;
}

这听起来像是从代码中获取代码的合理方式吗?更好的解决方案?最佳实践?