0

I'm learning WPF.

In one of the exercises, I have a TextBox and buttons Cut and Paste. The following is enough to implement Cut and Paste functionality:

XAML:

<DockPanel>
    <WrapPanel DockPanel.Dock="Top" Margin="3">
        <Button Command="ApplicationCommands.Cut"
                CommandTarget="{Binding ElementName=txtEditor}"
                Width="60">
                _Cut
        </Button>
        <Button Command="ApplicationCommands.Paste"
                CommandTarget="{Binding ElementName=txtEditor}"
                Width="60" Margin="3,0">
                _Paste<
        /Button>
    </WrapPanel>
    <TextBox AcceptsReturn="True" Name="txtEditor" />
    </DockPanel>

When pressed, the button Cut executes the ApplicationCommands.Cut on the TextBox with name txtEditor. When needed, the button will ask the TextBox with name textEditor if it can execute a Cut command, and when pressed it will order the textEditor to execute the Cut command.

Fairly straightforward. It works fine.

Just for Fun, I'd like to implement another button: Clear. When pressed it should clear the TextBox. The Textbox class has a method Clear.

<Button Command="ApplicationCommands.Clear"
                CommandTarget="{Binding ElementName=txtEditor}"
                Width="60">
                Clear
        </Button>

Alas, this won't work. ApplicationCommands doesn't have a Clear. Should I implement a custom command, as suggested in this example?

I tried the following:

I implemented CanExecute and Executed methods in my window:

public partial class CustomCommandSample : Window
{
    public CustomCommandSample()
    {
        InitializeComponent();
    }

    private void ClearCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    private void ClearCommand_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        txtEditor.Clear();
    }
}

A static CustomCommands class:

public static class CustomCommands
{
    public static RoutedUICommand Clear => new RoutedUICommand (
        "Clear",
        "Clear",
        typeof(CustomCommands));
}

Finally the XAML: (Note: the classes in this project are in namespace WpfCommandDemo. Xaml refers to it as Local)

<Window x:Class="WpfTutorialSamples.Commands.UsingCommandsSample"
    xmlns="...
    xmlns:local="clr-namespace:WpfCommandDemo"
    Title="UsingCommandsSample" Height="100" Width="200">

    <Window.CommandBindings>
        <CommandBinding Command="CustomCommands.Clear"
                        CanExecute="ClearCommand_CanExecute"
                        Executed="ClearCommand_Executed" />
    </Window.CommandBindings>

 <DockPanel>
    <WrapPanel DockPanel.Dock="Top" Margin="3">
        <Button Command="CustomCommands.Clear"
                CommandTarget="{Binding ElementName=txtEditor}"
                Width="60">
                Clear
        </Button>
        ... (other buttons: cut / paste, as above
    </WrapPanel>
        <TextBox AcceptsReturn="True" Name="txtEditor" />
    </DockPanel>

Although this compiles, The constructor of CustomCommandSample throws an XamlParseException:

Type reference cannot find type named 
'{http://schemas.microsoft.com/winfx/2006/xaml/presentation}CustomCommands'.

Should I solve the problem using Custom Commands? What should I change? Or am I completely wrong, and should I solve this differently

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • 1
    You can implement your own relay command for that, have a look at this [thread](https://stackoverflow.com/questions/22285866/why-relaycommand) – Pavel Anikhouski Jan 13 '20 at 10:45

2 Answers2

1

To use CustomCommands in XAML, you'll need to add a reference to it. In the element, add a line:

xmlns:custom="clr-namespace:MyApplication.NamespaceWithCustomInIt"

Replacing the namespace value as appropriate. Then you should be able to reference CustomCommands anywhere in XAML as custom:CustomCommands (may have to bind, I'll check later).

pete the pagan-gerbil
  • 3,136
  • 2
  • 28
  • 49
0

Should I solve the problem using Custom Commands?

Yes. This is how to solve this using the Model-View-ViewModel (MVVM) design pattern which is the recommended design pattern to use when developing XAML based UI applications.

From this blog post:

WPF provides two implementations of the ICommand interface; the System.Windows.Input.RoutedCommand and System.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 matching System.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 a CommandBinding 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:

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

    public DelegateCommand(Action<object> execute)
        : this(execute, null) { }

    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute(parameter);

    public void Execute(object parameter) => _execute(parameter);

    public event EventHandler CanExecuteChanged;

    public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

Once you have an implementation of the ICommand interface, it's easy to use in your view models:

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        ClearCommand = new DelegateCommand(Clear);
    }

    private string _text;
    public string Text
    {
        get { return _text; }
        set { _text = value; NotifyPropertyChanged(); }
    }

    public ICommand ClearCommand { get; }

    private void Clear(object parameter)
    {
        Text = string.Empty;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

In the view, you simply bind to the properties of the view model:

<TextBox AcceptsReturn="True" Name="txtEditor" Text="{Binding Text}" />
<Button Content="Clear" Command="{Binding ClearCommand}" />

Just remember to set the DataContext of the view to an instance of your view model for the bindings to work:

public MainWindow()
{
    InitializeComponent();
    DataContext = new ViewModel();
}
mm8
  • 163,881
  • 10
  • 57
  • 88