要開始使用命令,必須做三件事:
一:定義一個命令
二:定義命令的實現
三:為命令創建一個觸發器
WPF中命令系統的基礎是一個相對簡單的ICommand的接口,代碼如下:
public interface ICommand
{ event EventHandler CanExecuteChanged; bool CanExecute(object parameter); void Execute(object parameter); }
CanExecute用於確定命令是否處於可執行的狀態。典型的,UI控件能使用CanExecute來啟用或禁用自己。也就是說,在相關的命令從CanExecute中返回False的時候,按鈕將變得不可用。
Execute是命令的關鍵,當被調用時,它將觸發命令的執行。
要定義一個新命令,可以實現ICommand接口。如希望ICommand在被調用后關閉應用程序,代碼如下:
public class Exit : ICommand {
event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Application.Current.Shutdown();
}
}
要把一個菜單項綁定到應用程序關閉這個命令上,可以把他們的Command屬性掛到Exit命令上,代碼如下:
<MenuItem Header="_File"> <MenuItem Header="_Exit"> <MenuItem.Command> <local:Exit/> </MenuItem.Command> </MenuItem> </MenuItem>
由於把命令用於多個位置比較常見,所以創建一個存儲命令的靜態字段也常見:
public static readonly ICommand ExitCommand = new Exit();
這樣做的好處是,通過這個類型為ICommand的字段,可以讓Exit命令的實現完全私有化。現在,可以把Exit標記為私有類,並把標記轉化為綁定到靜態字段,代碼如下:
<MenuItem Header="_File">
<MenuItem Header="_Exit" Command="{x:Static local:WinCommand.ExitCommand}"/>
</MenuItem>
下面我們通過添加一個和Close命令掛接的按鈕,可以為窗口編寫一個模板,以實現關閉窗口的功能,代碼如下:
<Window.Style>
<Style TargetType="Window">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Window">
<DockPanel>
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<Button
Command="{x:Static ApplicationCommands.Close}">Close</Button>
</StatusBarItem>
</StatusBar>
<ContentPresenter/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Style>
我們接着要通過把命令綁定添加到窗口中讓窗口關閉。
/// <summary>
/// WinCommand.xaml 的交互邏輯
/// </summary>
public partial class WinCommand : Window
{
public static readonly ICommand ExitCommand = new Exit();
public WinCommand()
{
InitializeComponent();
CommandBindings.Add(
new CommandBinding(
ApplicationCommands.Close,
CloseExecuted));
}
void CloseExecuted(object sender, ExecutedRoutedEventArgs e)
{
this.Close();
}
}
使用命令可以清晰地把顯示和行為分開。通過使用單一的名稱為所需的語義動作簽名,在嘗試把多個控件和單個事件處理過程掛接起來的時候,可以避免很多由此引發的緊耦合問題。通常,應用程序邏輯應該總是通過命令的方式來實現的,而不是事件處理程序。對於很多需要直接掛接到事件處理過程上的常見例子,用觸發器來處理更好。
命令與數據綁定
使用命令的一個令人振奮和強大的特性 就是和數據綁定集成。由於Command和CommandParameter都是元素上的屬性,所以他們都能被設置為一些綁定到他們的數據。因此,可以使用綁定的數據內容來確定應該發生的動作。
為了演示他們是如何融合到一起的,將以C:\下面的文件的應用程序來開頭。首先,定義一個顯示內容的ListBox,和一個顯示了每個文件名的數據模板,代碼如下:
<ListBox Margin="2" Name="lbFile">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
在后台,把ItemSource屬性設置為文件列表:
public WinCommandAndBinding()
{
InitializeComponent();
FileInfo[] fileList = new DirectoryInfo("C:\\").GetFiles("*.*");
lbFile.ItemsSource = fileList;
}
現在,再添加一個按鈕用來顯示文件,但不希望任何文件都被打開。所以,要在加載的文件上提供某種類型的過濾器。現實現兩個命令Open和Blocked並為他們提供某種類型的處理過程,代碼如下:
public static readonly RoutedCommand OpenCommand =
new RoutedCommand("Open", typeof(WinCommandAndBinding));
public static readonly RoutedCommand BlockedCommand =
new RoutedCommand("Blocked", typeof(WinCommandAndBinding));
public WinCommandAndBinding()
{
InitializeComponent();
CommandBindings.Add(new CommandBinding (OpenCommand,
delegate(object sender,ExecutedRoutedEventArgs e){
Process.Start("notepad.exe",(string)e.Parameter);}));
CommandBindings.Add(new CommandBinding(BlockedCommand,
delegate(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show((string)e.Parameter, "Blocked");
}));
FileInfo[] fileList = new DirectoryInfo("C:\\").GetFiles("*.*");
lbFile.ItemsSource = fileList;
}
}
在定義好兩個命令后,就可以更新文件的數據模板來包含按鈕了。在命令參數(文件名)中使用數據綁定。對應命令本身,由於希望某些條目用OpenCommand,而其他條目用BlockedCommand,所以將使用IValueConvert把文件名轉換為ICommand,代碼如下:
<ListBox Margin="2" Name="lbFile">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Path=Name}"/>
<Button Margin="5" CommandParameter="{Binding Path=FullName}">
<Button.Command>
<Binding>
<Binding.Converter>
<local:FileToCommandConverter/>
</Binding.Converter>
</Binding>
</Button.Command> Show
</Button>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
下面是轉換器:
public class FileToCommandConverter : IValueConverter
{
public object Convert(object value ,Type targetType,object parameter,CultureInfo culture)
{
string ext = ((FileInfo)value).Extension.ToLowerInvariant();
if (ext == ".txt")
return WinCommandAndBinding.OpenCommand;
else
return WinCommandAndBinding.BlockedCommand;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
運行結果:
這個例子雖然有點微不足道,不過可以使用CanExecute方法輕松地完成類似的行為,並針對“壞”文件禁用這個命令。然而,這里最重要的一點是,可以返回任何命令。可以使用任何基於數據的邏輯來確定任何元素的行為。
另外我們可以考慮下能不能用數據觸發器實現呢?呵呵,可以的,這等於把命令、數據綁定和觸發器三者融合到一起了?是不是很強大,呵呵下面是代碼:
<ListBox Margin="2" Name="lbFile2">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Path=Name}"/>
<Button x:Name="btnShow" Margin="5" CommandParameter="{Binding Path=FullName}"
Command="{x:Static local:WinCommandAndBinding.BlockedCommand}"
Content=" Block"/>
</WrapPanel>
<DataTemplate.Triggers>
<DataTrigger Value=".txt">
<DataTrigger.Binding>
<Binding Path='Extension'>
<Binding.Converter>
<local:ToLowerInvariantConvert/>
</Binding.Converter>
</Binding>
</DataTrigger.Binding>
<Setter TargetName="btnShow"
Property="Command"
Value="{x:Static local:WinCommandAndBinding.OpenCommand}"/>
<Setter TargetName="btnShow" Property="Content" Value="Show"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
轉換器:
public class ToLowerInvariantConvert : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return ((string)value).ToLowerInvariant(); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
