使用WPF最好将文件后面的xaml.cs代码保持小而干净。 MVVM pattern通过数据绑定和命令绑定帮助实现此目标,其中任何业务逻辑在ViewModel类中处理。
我正在使用MVVM模式的原则,我的代码隐藏文件非常干净。使用命令绑定处理任何按钮单击事件,还有一些控件也支持命令绑定。但是,控件上有几个事件没有Command和CommandParameter属性,因此我看不到使用绑定的直接方法。在这些事件的代码隐藏文件中摆脱逻辑的最佳方法是什么?例如。处理控件内的鼠标事件。
答案 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;
}
这听起来像是从代码中获取代码的合理方式吗?更好的解决方案?最佳实践?