Handling events in an MVVM WPF application
Posted: June 30, 2013 | Filed under: MVVM, WPF, XAML |1 Comment
In a WPF application that uses the MVVM (Model-View-ViewModel) design pattern, the view model is the component that is responsible for handling the application’s presentation logic and state. This means that the view’s code-behind file should contain no code to handle events that are raised from any user interface (UI) element such as a Button or a ComboBox nor should it contain any domain specific logic.
Ideally, the code-behind of a view – typically a Window or a UserControl – contains only a constructor that calls the InitializeComponent method and perhaps some additional code to control or interact with the view layer that is difficult or inefficient to express in XAML, e.g. complex animations.
In other words, an MVVM application should not have any code like this where a button’s click event is handled in the code-behind of the view:
<
Button
Content
=
"Click here!"
Click
=
"btn_Click"
/>
protected
void
btn_Click(
object
sender, RoutedEventArgs e)
{
/* This is not MVVM! */
}
Commands
Instead, in addition to providing properties to expose data to be displayed or edited in the view, the view model defines actions that can be performed by the user and typically expose these as commands. A command is an object that implements theSystem.Windows.Input.ICommand interface and encapsulates the code for the action to be performed. It can be data bound to a UI control in the view to be invoked as a result of a mouse click, key press or any other input event. As well as the command being invoked as the user interacts with the UI, a UI control can be automatically enabled or disabled based on the command.
The Execute method of the ICommand interface encapsulates the operation itself while the CanExecute method indicates whether the command can be invoked at a particular time or not. The interface also defines a CanExecuteChanged event that is raised on the UI thread to cause every invoking control to requery to check if the command can execute.
WPF provides two implementations of the ICommand interface; the System.Windows.Input.RoutedCommand andSystem.Windows.Input.RoutedUICommand where the latter is a subclass of the former that simply adds a Text property that describes the command. However, neither of these implementations are especially suited to be used in a view model as they search the visual tree from the focused element and up for an element that has a matchingSystem.Windows.Input.CommandBinding object in its CommandBindings collection and then executes the Execute delegate for this particular CommandBinding. Since the command logic should reside in the view model, you don’t want to setup aCommandBinding in the view in order to connect the command to a visual element. Instead, you can create your own command by creating a class that implements the ICommand. The below implementation is a common one that invokes delegates for the Execute and CanExecute methods. If you are using Prism, the framework for building composite WPF and Silverlight applications from the Microsoft Patterns and Practices Team, there is a similar Microsoft.Practices.Prism.Commands.DelegateCommand class available.
public
class
DelegateCommand<T> : System.Windows.Input.ICommand
{
private
readonly
Predicate<T> _canExecute;
private
readonly
Action<T> _execute;
public
DelegateCommand(Action<T> execute)
:
this
(execute,
null
)
{
}
public
DelegateCommand(Action<T> execute, Predicate<T> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public
bool
CanExecute(
object
parameter)
{
if
(_canExecute ==
null
)
return
true
;
return
_canExecute((parameter ==
null
) ?
default
(T) : (T)Convert.ChangeType(parameter,
typeof
(T)));
}
public
void
Execute(
object
parameter)
{
_execute((parameter ==
null
) ?
default
(T) : (T)Convert.ChangeType(parameter,
typeof
(T)));
}
public
event
EventHandler CanExecuteChanged;
public
void
RaiseCanExecuteChanged()
{
if
(CanExecuteChanged !=
null
)
CanExecuteChanged(
this
, EventArgs.Empty);
}
}
The view model will then expose properties of this type for the view to bind to. Below is a sample implementation of a view model that exposes a ButtonClickCommand that will disable any UI control that is bound to it if the private string field _input, which is in turn exposed through a string property called Input, is empty. If you only pass a single delegate to the constructor of the DelegateCommand<T> class, it will assume that the command should always by available and the CanExecute method will always return true. Also note that when the value of the string property changes, a RaiseCanExecuteChanged method is called to raise the CanExecuteChanged event in order to update the status of any control in the view that is bound to the command.
public
class
ViewModel
{
private
readonly
DelegateCommand<
string
> _clickCommand;
public
ViewModel()
{
_clickCommand =
new
DelegateCommand<
string
>(
(s) => {
/* perform some action */
},
//Execute
(s) => {
return
!
string
.IsNullOrEmpty(_input); }
//CanExecute
);
}
public
DelegateCommand<
string
> ButtonClickCommand
{
get
{
return
_clickCommand; }
}
private
string
_input;
public
string
Input
{
get
{
return
_input; }
set
{
_input = value;
_clickCommand.RaiseCanExecuteChanged();
}
}
}
public
partial
class
MainWindow : Window
{
public
MainWindow()
{
InitializeComponent();
this
.DataContext =
new
ViewModel();
}
}
<
StackPanel
Margin
=
"10"
>
<
TextBox
Text
=
"{Binding Input, UpdateSourceTrigger=PropertyChanged}"
/>
<
Button
Content
=
"Click here!"
Command
=
"{Binding ButtonClickCommand}"
Margin
=
"0 5 0 0"
/>
</
StackPanel
>
With this approach you have now moved the presentation logic from the view to the view model. Instead of hooking up the button’s click handler, its Command property is now bound to the command defined in the view model and when the user clicks on the button the command’s Execute method will be invoked.
EventTriggers
There is an alternative way of associating a control in the view with a command object exposed by the view model. Only some controls can actually bind to a command through the Command property, notably those derived fromSystem.Windows.Controls.Primitives.ButtonBase or System.Windows.Controls.MenuItem. If you want to attach a command to some other control or when you want to invoke a command on an event other than the click event for a button, you can use Expression Blend interaction triggers and the System.Windows.Interactivity.InvokeCommandAction class. Below is an example on how you would execute a command object called MouseEnterCommand in the view model when the user moves the mouse pointer over a Rectangle element in the view. You specify the event for which the command will be executed in the EventName property of the EventTrigger. Remember to add a reference to System.Windows.Interactivity.dll for this to compile.
Also note that using the InvokeCommandAction doesn’t automatically enable or disable the control based on the command’s CanExecute method, unlike controls that have a Command property and can be bound directly to a command.
<
Window
x:Class
=
"Mm.HandlingEventsMVVM.MainWindow"
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i
=
"clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title
=
"MainWindow"
Height
=
"350"
Width
=
"525"
>
<
StackPanel
>
<
Rectangle
Fill
=
"Yellow"
Stroke
=
"Black"
Width
=
"100"
Height
=
"100"
>
<
i:Interaction.Triggers
>
<
i:EventTrigger
EventName
=
"MouseEnter"
>
<
i:InvokeCommandAction
Command
=
"{Binding MouseEnterCommand}"
/>
</
i:EventTrigger
>
</
i:Interaction.Triggers
>
</
Rectangle
>
</
StackPanel
>
</
Window
>
CommandParameters
If you wish to pass a parameter to a command from the view you do so by using the CommandParameter property. The type argument of the generic DelegateCommand<T> class specifies the type of the command parameter that gets passed to the Execute and CanExecute methods. The CommandParameter property exists in both the ButtonBase and MenuItem derived controls as well as in the InvokeCommandAction class:
<
Button
Content
=
"Click here!"
Command
=
"{Binding ButtonClickCommand}"
CommandParameter
=
"some string to be passed..."
Margin
=
"0 5 0 0"
/>
<
i:InvokeCommandAction
Command
=
"{Binding MouseEnterCommand}"
CommandParameter
=
"some string to be passed..."
/>
CallMethodAction
Besides the InvokeCommandAction class, there is also another class named CallMethodAction that can be used to invoke a method in the view model from the view without using commands. It has a MethodName property for specifying the name of the method to call and a TargetObject property that needs to be bound to an instance of the class containing the method, i.e. the view model:
<
Window
x:Class
=
"Mm.HandlingEventsMVVM.MainWindow"
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i
=
"clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ei
=
"http://schemas.microsoft.com/expression/2010/interactions"
Title
=
"MainWindow"
Height
=
"350"
Width
=
"525"
>
<
StackPanel
>
<
Rectangle
Fill
=
"Yellow"
Stroke
=
"Black"
Width
=
"100"
Height
=
"100"
>
<
i:Interaction.Triggers
>
<
i:EventTrigger
EventName
=
"MouseEnter"
>
<!-- Execute a method called 'SomeMethod' defined in the view model -->
<
ei:CallMethodAction
TargetObject
=
"{Binding}"
MethodName
=
"SomeMethod"
/>
</
i:EventTrigger
>
</
i:Interaction.Triggers
>
</
Rectangle
>
</
StackPanel
>
</
Window
>
public
void
SomeMethod()
{
/* do something ... */
}
Note that the CallMethodAction class is defined in another assembly and namespace and you will need to add a reference to Microsoft.Expressions.Interactions.dll to be able to use it. Also note that it doesn’t support parameters.
Handling events in an MVVM WPF application的更多相关文章
- 每天翻译一点点: WPF Application Framework (WAF)
ps:http://waf.codeplex.com/wikipage?title=Model-View-ViewModel%20Pattern&referringTitle=Document ...
- 【转】How to view word document in WPF application
How to view word document in WPF application (CSVSTOViewWordInWPF) Introduction The Sample demonstra ...
- Merging a WPF application into a single EXE(WPF应用程序合并成单个Exe文件)
I always dislike handing off little applications to people. Not because I can’t, but because of the ...
- C# WPF Application 下的文件操作
好气哦,电脑好烂,每天花大把的时间在等电脑反应上. 没有钱买新电脑,连组台式机的钱都没有.好气哦. 啊啊啊啊文件操作是什么鬼???C++下我都懵了,C#下好多东西要学!!!我不会!我不会!我不会!!! ...
- (4)事件处理——(1)事件处理(Handling Events)
JavaScript has several built-in ways of reacting to user interaction and other events. To make a pag ...
- WPF Application 类介绍以及怎样修改启动方式
因为想要修改wpf的启动方式,所以研究了下Application类,现把一些有用的属性与大家分享下: 属性: Current 获取当前 AppDomain的 Appl ...
- WPF——Application
Application类处于WPF应用程序的最顶端,main函数就在这个类中. Application类的作用: 截图连接 https://docs.microsoft.com/zh-cn/dotne ...
- WPF Application
Application类作为启动的入口,在VS中,通常自动代码为我们继承了Application类,这样做的有点,我还没有理解到,但是我们先学到这个知识点. 为了能够更好的控制整个启动过程,包括得到A ...
- Eloquent JavaScript #12# Handling Events
索引 Notes onclick removeEventListener Event objects stopPropagation event.target Default actions Key ...
随机推荐
- 20145206邹京儒《Java程序设计》第7周学习总结
20145206 <Java程序设计>第7周学习总结 教材学习内容总结 第十三章 时间与日期 13.1.1 时间的度量 ·即使标注为GMT(格林威治时间),实际上谈到的的是UTC(Unix ...
- Delphi的TThread中的FreeOnTerminate成员
类 Create 了就要 Free; 但 TThread(的子类) 有特殊性, 很多时候我们不能确定新建的线程什么时候执行完(也就是什么时候该释放); 如果线程执行完毕自己知道释放就好了, 所以 ...
- python中的monkey-patching
这个技巧我很少用过. 但知道无防. 在运行时改变函数或类的行为, 一般用猴子补丁,原类,装饰器都可以实现. #!/usr/bin/env python # -*- coding: utf-8 -*- ...
- GMap.Net开发之技巧小结
1.在GMap地图上,如果要让添加的图标(Marker)有个高亮(highlight)的效果,可以在MouseOver到Marker的时候设置Marker外观效果. 如果要让图标有个报警闪烁的效果,可 ...
- MS SQL数据批量备份还原(适用于MS SQL 2005+) 分类: SQL Server 数据库 2015-03-10 14:32 103人阅读 评论(0) 收藏
我们知道通过Sql代理,可以实现数据库的定时备份功能:当数据库里的数据库很多时,备份一个数据库需要建立对应的定时作业,相对来说比较麻烦: 还好,微软自带的osql工具,比较实用,通过在命令行里里输入命 ...
- (译)【Unity教程】使用Unity开发Windows Phone上的横版跑酷游戏
译者注: 目前移动设备的跨平台游戏开发引擎基本都是采用Cocos2d-x或者Unity.一般而言2d用cocos2d-x 3d用unity,但是对于Windows Phone开发者, cocos2d- ...
- Accelerating Matlab
Matlab is a very useful programming environment, but it also has many inefficiencies. You might thin ...
- mageView图片显示出来 ()
ImageView图片显示出来: igSign 是 ImageView的实例 igSign.setImageDrawable(getResources().getDrawable(R.drawable ...
- C语言基本点初探
1,对于int a=10++;此语句错误,为什么呢,对于i++来说,i是一个变量,是把i加1然后赋值给i,然而10不是一个变量所以无法执行加加的语法; 2,运算符的优先级: 赋值运算符<逻辑运算 ...
- 转载 linux内核 asmlinkage宏
转载http://blog.chinaunix.net/uid-7390305-id-2057287.html 看一下/usr/include/asm/linkage.h里面的定义:#define a ...