在我们的项目中经常要用到数据模板,最近做的一个项目中在数据模板中要放一些RadioButton,其中每一个RadioButton设置了Checked事件,如果直接在View层写Checked事件的话不符合MVVM的设计思想,View层尽量只做和界面相关的绑定,而把所有的逻辑都写在ViewModel层中,但是如何才能把我们常见的.net事件绑定到Command上面呢?在该项目中我们使用了System.Windows.Interactivity下面的EventTriggers来进行相关的命令绑定,System.Windows.Interactivity.dll
中的 Interaction
可以帮助我们实现对命令的绑定,所以我们需要引用该文件到项目中,这个文件是微软的Blend中提供的。引用的方式包括两种:第一种是使用 xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 来引用相关的命名空间,另外一种是通过xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"来引用该命名空间,记得在引用之前要添加对相关的DLL的引用,这个是第一步,后面就是对具体的内容的引用,这里贴出相关的核心的代码。
<ListBox x:Name="radioButtonListBox" ItemsSource="{Binding AllRadioButtonModels,Mode=TwoWay}"> <ListBox.Template> <ControlTemplate TargetType="ListBox"> <ScrollViewer VerticalScrollBarVisibility="Auto"> <ItemsPresenter></ItemsPresenter> </ScrollViewer> </ControlTemplate> </ListBox.Template> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter> <Setter Property="VerticalContentAlignment" Value="Stretch"></Setter> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Rows="2" Columns="2" IsItemsHost="True"> </UniformGrid> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate> <Border Background="#eee" BorderBrush="Blue" BorderThickness="1"> <StackPanel Orientation="Horizontal"> <Button Content="qqq" Command="{Binding DataContext.RadioButtonCheckedCommand,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" CommandParameter="{Binding Content,RelativeSource={RelativeSource Mode=Self}}"></Button> <RadioButton GroupName="GroupOne" Content="{Binding BindingContent,Mode=TwoWay}" IsChecked="{Binding IsSelected,Mode=TwoWay}"> <i:Interaction.Triggers> <i:EventTrigger EventName="Checked"> <i:InvokeCommandAction Command="{Binding DataContext.MyCommandInstance,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" CommandParameter="{Binding Content,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=RadioButton}}"> </i:InvokeCommandAction> </i:EventTrigger> </i:Interaction.Triggers> </RadioButton> </StackPanel> </Border> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
我们再看一下具体绑定的DataContext,在我们的MainWindow.cs 中 this.DataContext = RadioButtonViewModels.Instance;直接将一个RadioButtonViewModels的一个静态实例作为Window的DataContext,这里需要特别注意的地方是在模板中我们必须通过相关资源来找到这个DataContext,这里 Command="{Binding DataContext.MyCommandInstance,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"这个写法非常重要,MyCommandInstance是我们定义的一个命令,这个命令属于DataContext,另外还必须通过RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}来找到这个Window,如果我们定义的控件直接是在Grid中而不是在模板中的话,我们直接通过下面的方式就能够找到DataContext,这个特别需要注意,如果不是定义在模板中,那么程序会通过逻辑树一步步向上查找最终找到这个DataContext,但是在模板中不行,这个需要特别引起注意。
<RadioButton Content="测试程序"> <i:Interaction.Triggers> <i:EventTrigger EventName="Checked"> <i:InvokeCommandAction Command="{Binding RadioButtonCheckedCommand}" CommandParameter="{Binding Content,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=RadioButton}}"> </i:InvokeCommandAction> </i:EventTrigger> </i:Interaction.Triggers> </RadioButton>
另外一部分很重要的部分就是ViewModel层,在该层中我们通过三种不同的方式来进行命令绑定,这里首先介绍一下通过Microsoft.Practices.Prism.dll下面的DelegateCommand方法,首先定义public DelegateCommand<object> MyCommandRadioButtonCheckedInstance { get; private set; }属性,定义好属性之后,我们通过MyCommandRadioButtonCheckedInstance = new DelegateCommand<object>(MyCommandRadioButtonCheckedMethod,CanExecute);来为这个属性赋值,在我们的构造函数中,前面一个是一个委托执行的方法是带object类型的参数的,在定义好这些函数之后我们就可以将该命令绑定到View层中。第二种是通过 GalaSoft.MvvmLight.dll下面的RelayCommand类来进行命令的绑定,这个原理也差不多,具体方法请参考下面的文档。
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using GalaSoft.MvvmLight.Command; using Microsoft.Practices.Prism.Commands; using TestRadioButton.GetDataSource; using TestRadioButton.Models; namespace TestRadioButton.ViewModels { public class RadioButtonViewModels { public RadioButtonViewModels() { allRadioButtonModels = DataSources.GetRadioButtonBindingSource(); MyCommandInstance = new MyCommand(MyCommandMethod); MyCommandRadioButtonCheckedInstance = new DelegateCommand<object>(MyCommandRadioButtonCheckedMethod,CanExecute); RadioButtonCheckedCommand = new RelayCommand<object>(RadioButtonCheckedCommandMethod); } //自定义命令 public MyCommand MyCommandInstance { get; private set; } private void MyCommandMethod(object sender) { if (sender is string) { MessageBox.Show("Hello," + sender.ToString()); } } //自定义命令 public DelegateCommand<object> MyCommandRadioButtonCheckedInstance { get; private set; } public bool CanExecute(object parameter) { return true; } private void MyCommandRadioButtonCheckedMethod(object sender) { if (sender is string) { MessageBox.Show(sender.ToString()+ " is Checked!"); } } public RelayCommand<object> RadioButtonCheckedCommand { get; private set; } private void RadioButtonCheckedCommandMethod(object sender) { if (sender != null) { MessageBox.Show(sender.ToString()+" is Checked!"); } } public static RadioButtonViewModels instance = null; public static RadioButtonViewModels Instance { get { if (instance != null) { return instance; } else { instance = new RadioButtonViewModels(); return instance; } } set { if (value != instance) { instance = value; } } } private ObservableCollection<RadioButtonModels> allRadioButtonModels = new ObservableCollection<RadioButtonModels>(); public ObservableCollection<RadioButtonModels> AllRadioButtonModels { get { return allRadioButtonModels; } set { if (value != allRadioButtonModels) { allRadioButtonModels = value; } } } } }
下面着重介绍第三种通过继承ICommand接口的方式来实现命令的绑定,这里首先贴出相关代码。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; namespace TestRadioButton { public class MyCommand:ICommand { public MyCommand(Action<object> action) { if (action == null) { throw new ArgumentNullException(); } _action = action; } private readonly Action<object> _action; private bool _isEnabledExecute = true;//默认为启用状态 public bool IsEnabledExecute { get { return _isEnabledExecute; } set { if (value != _isEnabledExecute) { _isEnabledExecute = value; if (CanExecuteChanged != null) { CanExecuteChanged(this,new EventArgs()); } } } } #region ICommand 接口 public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) { return _isEnabledExecute; } public void Execute(object parameter) { _action(parameter); } #endregion } }
这里我们来实现接口中的Execute和CanExecute方法,在ViewModel层中,首先定义 public MyCommand MyCommandInstance { get; private set; }属性,紧接着为这个属性来赋值,MyCommandInstance = new MyCommand(MyCommandMethod);然后再写一些委托函数以及回调方法,在我们的主窗体中我们实现了这一方法。
<Button Content="我的未来不是梦!" Height="30" Command="{Binding MyCommandInstance}" CommandParameter="{Binding Content,RelativeSource={RelativeSource Self}}"></Button> <ToggleButton Height="23" Content="启动/禁用" IsChecked="{Binding MyCommandInstance.IsEnabledExecute}"></ToggleButton>
这里通过点击ToggleButton就能控制Button是否有效。这里不再赘述。通过上面的几种命令绑定的方式我们就能实现在如何在DataTemplate中绑定RadioButton的Checked事件,重点是开始讲到的如何在DataTemplate中找到Datacontext,这点需要引起注意。
下面贴出整个View层的代码:
<Window x:Class="TestRadioButton.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:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity" Title="MainWindow" Height="350" Width="525"> <Window.Resources> </Window.Resources> <Grid> <StackPanel> <Button Content="我的未来不是梦!" Height="30" Command="{Binding MyCommandInstance}" CommandParameter="{Binding Content,RelativeSource={RelativeSource Self}}"></Button> <ToggleButton Height="23" Content="启动/禁用" IsChecked="{Binding MyCommandInstance.IsEnabledExecute}"></ToggleButton> <Button Content="2" Height="30" Command="{Binding MyCommandRadioButtonCheckedInstance}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"> </Button> <Button x:Name="third" Content="3" Height="30" Command="{Binding MyCommandRadioButtonCheckedInstance}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"> </Button> <Button Content="4" Height="30"></Button> <Button Content="qqq" Command="{Binding RadioButtonCheckedCommand}" CommandParameter="{Binding Content,RelativeSource={RelativeSource Self}}"></Button> <RadioButton Content="测试程序"> <i:Interaction.Triggers> <i:EventTrigger EventName="Checked"> <i:InvokeCommandAction Command="{Binding RadioButtonCheckedCommand}" CommandParameter="{Binding Content,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=RadioButton}}"> </i:InvokeCommandAction> </i:EventTrigger> </i:Interaction.Triggers> </RadioButton> <ListBox x:Name="radioButtonListBox" ItemsSource="{Binding AllRadioButtonModels,Mode=TwoWay}"> <ListBox.Template> <ControlTemplate TargetType="ListBox"> <ScrollViewer VerticalScrollBarVisibility="Auto"> <ItemsPresenter></ItemsPresenter> </ScrollViewer> </ControlTemplate> </ListBox.Template> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter> <Setter Property="VerticalContentAlignment" Value="Stretch"></Setter> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Rows="2" Columns="2" IsItemsHost="True"> </UniformGrid> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate> <Border Background="#eee" BorderBrush="Blue" BorderThickness="1"> <StackPanel Orientation="Horizontal"> <Button Content="qqq" Command="{Binding DataContext.RadioButtonCheckedCommand,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" CommandParameter="{Binding Content,RelativeSource={RelativeSource Mode=Self}}"></Button> <RadioButton GroupName="GroupOne" Content="{Binding BindingContent,Mode=TwoWay}" IsChecked="{Binding IsSelected,Mode=TwoWay}"> <i:Interaction.Triggers> <i:EventTrigger EventName="Checked"> <i:InvokeCommandAction Command="{Binding DataContext.MyCommandInstance,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" CommandParameter="{Binding Content,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=RadioButton}}"> </i:InvokeCommandAction> </i:EventTrigger> </i:Interaction.Triggers> </RadioButton> </StackPanel> </Border> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel> </Grid> </Window>