教程5:指令
指令指令
命令使您可以在一处定义动作,然后从所有用户界面控件(如MenuItems,ToolBars或Buttons)中引用它们。命令示例包括在许多应用程序中发现的Copy,Cut和Paste操作。应用程序通常通过多种机制同时公开这些动作:菜单中的MenuItems,ContextMenu中的 MenuItems ,工具栏上的按钮,键盘快捷键等。
命令有几个目的。第一个目的是将语义和调用命令的对象与执行命令的逻辑分开。这允许多个不同的源调用相同的命令逻辑,并且允许针对不同目标定制命令逻辑。例如,在许多应用程序中找到的编辑操作Copy,Cut和Paste可以通过使用不同的用户操作(如果使用命令来实现)来调用。应用程序可能允许用户通过单击按钮,选择菜单中的项目或使用组合键(例如CTRL + X)来剪切选定的对象或文本。。通过使用命令,可以将每种类型的用户操作绑定到相同的逻辑。
命令还是任何MVVM(模型-视图-视图模型)应用程序的重要组件之一。MVVM体系结构有助于解耦应用程序的组件,从而使应用程序更易于单元测试,维护和扩展。
命令绑定
命令实际上并不自己执行任何操作。从根本上讲,它们由ICommand接口组成,该接口仅定义一个事件和两个方法:Execute()和CanExecute()。第一个用于执行实际动作,而第二个用于确定该动作当前是否可用。要执行命令的实际动作,您需要在命令和代码之间建立链接,而CommandBinding正是在此起作用。
一的CommandBinding通常在定义窗口或用户控件,并持有到参考命令其处理,以及为应付实际的事件处理程序执行()和CanExecute()的事件命令。
命令的语义在应用程序和类之间可以保持一致,但是操作的逻辑特定于所作用的特定对象。例如,组合键CTRL + X在文本类,图像类和Web浏览器中调用Cut命令,但是执行Cut操作的实际逻辑是由执行剪切的应用程序定义的。一的RoutedCommand使客户能够实现逻辑。文本对象可以将所选文本剪切到剪贴板中,而图像对象可以剪切所选图像。当应用程序处理Executed事件时,它可以访问命令的目标并可以采取适当的措施。 动作取决于目标的类型。
内置命令
诸如Button,CheckBox和MenuItem之类的控件具有代表您与任何命令进行交互的逻辑。它们公开了一个简单的Command属性。设置后,这些控件在引发Click事件时会自动调用命令的Execute方法(当CanExecute返回true时)。此外,它们通过利用CanExecuteChanged事件自动将其IsEnabled的值与CanExecute的值保持同步。通过简单的属性分配支持所有这些功能,XAML即可提供所有这些逻辑。
ApplicationCommands提供了一组可供使用的内置命令。
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel Width="300" Height="300" Background="#505860">
<Menu DockPanel.Dock="Top">
<MenuItem Header="File"/>
<MenuItem Header="Edit">
<MenuItem Header="Copy" Command="ApplicationCommands.Copy"/>
<MenuItem Header="Cut" Command="ApplicationCommands.Cut"/>
<MenuItem Header="Paste" Command="ApplicationCommands.Paste"/>
</MenuItem>
<MenuItem Header="Help"/>
</Menu>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox Width="200"/>
<TextBox Width="200" Margin="0,10,0,0"/>
</StackPanel>
</DockPanel>
</Grid>
自定义命令
创建自定义命令的最简单方法是实现ICommand接口。例如,调用委托的命令的实现如下:
注意
请有关扩展noesisGUI的更多信息,请参见我们的扩展NoesisGUI教程。
C ++
class DelegateCommand final: public Noesis::BaseCommand
{
public:
typedef Noesis::Delegate<void (BaseComponent*)> ExecuteFunc;
typedef Noesis::Delegate<bool (BaseComponent*)> CanExecuteFunc;
DelegateCommand(const ExecuteFunc& execute): _execute(execute) {}
DelegateCommand(const ExecuteFunc& execute, const CanExecuteFunc& canExecute):
_execute(execute), _canExecute(canExecute) {}
bool CanExecute(BaseComponent* param) const override
{
return _canExecute.Empty() || _canExecute(param);
}
void Execute(BaseComponent* param) const override
{
_execute(param);
}
private:
ExecuteFunc _execute;
CanExecuteFunc _canExecute;
};
C#
public class DelegateCommand: ICommand
{
public DelegateCommand(Action<object> execute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
}
public DelegateCommand(Func<object, bool> canExecute, Action<object> execute)
{
if (canExecute == null)
{
throw new ArgumentNullException("canExecute");
}
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, System.EventArgs.Empty);
}
}
private Func<object, bool> _canExecute;
private Action<object> _execute;
}
MVVM示例
MVVM模式可以通过使用命令和ViewModel类在NoesisGUI中实现。该视图模型是负责将数据绑定到所述XAML和执行将由命令调用的委托的类。例如:
注意
在此示例中,未按堆方式分配DelegateCommand来减少分配,如《C ++体系结构指南》中所述。这是可以安全执行且不需要引用计数的情况之一。
C ++
class ViewModel: public NotifyPropertyChangedBase
{
public:
ViewModel()
{
_command.SetExecuteFunc(MakeDelegate(this, &ViewModel::SayHello));
}
const char* GetInput() const
{
return _input;
}
void SetInput(const char* input)
{
String::Copy(_input, sizeof(_input), value);
}
const char* GetOutput() const
{
return _output;
}
void SetOutput(const char* output)
{
if (!String::Equals(_output, value))
{
String::Copy(_output, sizeof(_output), value);
OnPropertyChanged("Output");
}
}
DelegateCommand* GetSayHelloCommand() const
{
return &_command;
}
private:
void SayHello(BaseComponent* param_)
{
if (Boxing::CanUnbox<NsString>(param_))
{
const char* param = Boxing::Unbox<NsString>(param_).c_str();
char text[512];
snprintf(text, sizeof(text), "Hello, %s (%s)", _input, param);
SetOutput(text);
}
}
private:
DelegateCommand _command;
char _input[256] = "";
char _output[256] = "";
NS_IMPLEMENT_INLINE_REFLECTION(ViewModel, NotifyPropertyChangedBase)
{
NsMeta<TypeId>("Commands.ViewModel");
NsProp("Input", &ViewModel::GetInput, &ViewModel::SetInput);
NsProp("Output", &ViewModel::GetOutput, &ViewModel::SetOutput);
NsProp("SayHelloCommand", &ViewModel::GetSayHelloCommand);
}
};
C#
public class ViewModel : NotifyPropertyChangedBase
{
public string Input { get; set; }
private string _output = string.Empty;
public string Output
{
get { return _output; }
set
{
if (_output != value)
{
_output = value;
OnPropertyChanged("Output");
}
}
}
public Noesis.Samples.DelegateCommand SayHelloCommand { get; private set; }
public ViewModel()
{
SayHelloCommand = new Noesis.Samples.DelegateCommand(SayHello);
}
private void SayHello(object parameter)
{
string param = (string)parameter;
Output = System.String.Format("Hello, {0} ({1})", Input, param);
}
}
以下XAML显示了如何使用以前的ViewModel。将其设置为根元素的DataContext。面板内部的按钮获取从ViewModel分配的命令。每当按下按钮时,就会触发该命令。
![](https://img.haomeiwen.com/i20475064/29f0fcc04e3870fb.jpg)
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Viewbox>
<Border x:Name="myBorder" DataContext="{StaticResource ViewModel}" Width="400" Margin="50"
Background="#801C1F21" BorderThickness="1" CornerRadius="5" BorderBrush="#D0101611"
Padding="5" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Vertical">
<TextBox MinWidth="300" Margin="3" Text="{Binding Input, Mode=TwoWay}" FontSize="28"/>
<TextBox x:Name="Param" MinWidth="300" Margin="3" Text="" FontSize="24"/>
<Button Content="Say Hello" Margin="3" Command="{Binding SayHelloCommand}"
CommandParameter="{Binding Text, ElementName=Param}" FontSize="28" />
<Viewbox Margin="5" Height="50">
<TextBlock Margin="5" Padding="0" TextAlignment="Center" Text="{Binding Output}"
FontSize="28" Foreground="White"/>
</Viewbox>
</StackPanel>
</Border>
</Viewbox>
</Grid>