NoesisGUI

Commands

github Tutorial Data

Commands allows you to define actions in one place and then refer to them from all your user interface controls like menu items, toolbar or buttons. Examples of commands are the Copy, Cut, and Paste operations found on many applications. Applications often expose these actions through many mechanisms simultaneously: MenuItems in a Menu, MenuItems on a ContextMenu, Buttons on a ToolBar, keyboard shortcuts and so on.

Commands have several purposes. The first purpose is to separate the semantics and the object that invokes a command from the logic that executes the command. This allows for multiple and disparate sources to invoke the same command logic, and it allows the command logic to be customized for different targets. For example, the editing operations Copy, Cut, and Paste, which are found in many applications, can be invoked by using different user actions if they are implemented by using commands. An application might allow a user to cut selected objects or text by either clicking a button, choosing an item in a menu, or using a key combination, such as CTRL+X. By using commands, you can bind each type of user action to the same logic.

Command bindings

Commands don't actually do anything by themselves. At the root, they consist of the ICommand interface, which only defines an event and two methods: Execute() and CanExecute(). The first one is for performing the actual action, while the second one is for determining whether the action is currently available. To perform the actual action of the command, you need a link between the command and your code and this is where the CommandBinding comes into play.

A CommandBinding is usually defined on a Window or UserControl, and holds a reference to the Command that it handles, as well as the actual event handlers for dealing with the Execute() and CanExecute() events of the Command.

The semantics of a command can be consistent across applications and classes, but the logic of the action is specific to the particular object acted upon. The key combination CTRL+X invokes the Cut command in text classes, image classes, and Web browsers, but the actual logic for performing the Cut operation is defined by the application that performs the cut. A RoutedCommand enables clients to implement the logic. A text object may cut the selected text into the clipboard, while an image object may cut the selected image. When an application handles the Executed event, it has access to the target of the command and can take appropriate action depending on the target's type.

Built-In Commands

Controls such as Button, CheckBox, and MenuItem have logic to interact with any command on your behalf. They expose a simple Command property. When set, these controls automatically call the command's Execute method (when CanExecute returns true) whenever their Click event is raised. In addition, they automatically keep their value for IsEnabled synchronized with the value of CanExecute by leveraging the CanExecuteChanged event. By supporting all this via a simple property assignment, all of this logic is available from XAML.

The following built-in commands are available in the ApplicationCommands class:

  • CancelPrintCommand
  • CloseCommand
  • ContextMenuCommand
  • CopyCommand
  • CorrectionListCommand
  • CutCommand
  • DeleteCommand
  • FindCommand
  • HelpCommand
  • NewCommand
  • OpenCommand
  • PasteCommand
  • PrintCommand
  • PrintPreviewCommand
  • PropertiesCommand
  • RedoCommand
  • ReplaceCommand
  • SaveCommand
  • SaveAsCommand
  • SelectAllCommand
  • StopCommand
  • UndoCommand
<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>

Custom Commands

The easiest way to create custom commands is implementing the ICommand interface. For example, the implementation of a Command that invokes a Delegate whenever it executes could be as follows:

NOTE

Please, refer to our Extending NoesisGUI Tutorial for more information about extending noesisGUI.

C++

class DelegateCommand: public BaseCommand
{
public:
    typedef Delegate<void (BaseComponent*)> Action;

    DelegateCommand(const Action& action): BaseCommand(NsSymbol::Null(), 0), _action(action) {}

    NsBool CanExecute(BaseComponent* param) const
    {
        return true;
    }

    void Execute(BaseComponent* param) const
    {
        _action(param);
    }

private:
    Action _action;

    NS_IMPLEMENT_INLINE_REFLECTION(DelegateCommand, BaseCommand)
    {
        NsMeta<TypeId>("DelegateCommand");
    }
};

C#

public class DelegateCommand : System.Windows.Input.ICommand
{
    private readonly Action<object> _action;

    public DelegateCommand(Action<object> action)
    {
        _action = action;
    }

    public event EventHandler CanExecuteChanged
    {
        add { }
        remove { }
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _action(parameter);
    }
}

MVVM example with Commands

MVVM pattern can be implemented in noesisGUI by using commands and a ViewModel class. The ViewModel is the class in charge of binding data to the XAML and implementing the delegate that will be invoked by the command. For example:

C++

class ViewModel: public BaseComponent, public INotifyPropertyChanged
{
public:
    ViewModel()
    {
        _command = *new DelegateCommand(MakeDelegate(this, &ViewModel::SayHello));
    }

    ~ViewModel()
    {
        _destroyed(this);
    }

    const NsChar* GetInput() const
    {
        return _input.c_str();
    }

    void SetInput(const NsChar* input)
    {
        _input = input;
    }

    const NsChar* GetOutput() const
    {
        return _output.c_str();
    }

    void SetOutput(const NsChar* output)
    {
        if (_output != output)
        {
            _output = output;
            _propertyChanged(this, NSS(Output));
        }
    }

    DelegateCommand* GetSayHelloCommand() const
    {
        return _command.GetPtr();
    }

    PropertyChangedEventHandler& PropertyChanged()
    {
        return _propertyChanged;
    }

    DestroyedEventHandler& Destroyed()
    {
        return _destroyed;
    }

    void Serialize(Noesis::Core::SerializationData* data) const
    {
        data->Serialize("Input", _input);
        data->Serialize("Output", _output);
    }

    void Unserialize(Noesis::Core::UnserializationData* data, NsUInt32 version)
    {
        data->Unserialize("Input", _input);
        data->Unserialize("Output", _output);
    }

private:
    void SayHello(BaseComponent* param)
    {
        if (Boxing::CanUnbox<NsString>(param))
        {
            char text[256];
            sprintf(text, "Hello, %s (%s)", _input.c_str(), Boxing::Unbox<NsString>(param).c_str());
            SetOutput(text);
        }
    }

private:
    Ptr<DelegateCommand> _command;

    NsString _input;
    NsString _output;

    PropertyChangedEventHandler _propertyChanged;
    DestroyedEventHandler _destroyed;

    NS_IMPLEMENT_INLINE_REFLECTION(ViewModel, BaseComponent)
    {
        NsMeta<TypeId>("Noesis.Samples.ViewModel");
        NsImpl<INotifyPropertyChanged>();

        NsProp("Input", &ViewModel::GetInput, &ViewModel::SetInput);
        NsProp("Output", &ViewModel::GetOutput, &ViewModel::SetOutput);
        NsProp("SayHelloCommand", &ViewModel::GetSayHelloCommand);
    }
};

C#

public class NotifyPropertyChangedBase : System.ComponentModel.INotifyPropertyChanged
{
    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new System.ComponentModel.PropertyChangedEventArgs(name));
        }
    }
}

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);
    }
}

The following XAML shows how you might use the previous ViewModel. It is set as the DataContext of the root element. The button inside the panel gets the command assigned from the ViewModel. Whenever the button is pressed the command is fired.

CommandsTutorialImg1.jpg
<Grid
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:noesis="clr-namespace:Noesis.Samples">

    <Grid.Resources>
        <noesis:ViewModel x:Key="ViewModel"/>
    </Grid.Resources>

    <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>
© 2017 Noesis Technologies