TOC

This article is currently in the process of being translated into Polish (~35% done).

Polecenia:

Używanie poleceń (commands) w WPF

W poprzednim artykule, omawialiśmy sporo teorii na temat czym są polecenia i jak działają. W tym rozdziale przyjrzymy się jak właściwie używać poleceń poprzez przypisanie ich do interfejsu użytkownika oraz tworzenia poleceń wiązania(bindings) które pozwolą na połączenie ich razem.

A więc zacznijmy od bardzo prostego przykładu:

<Window x:Class="WpfTutorialSamples.Commands.UsingCommandsSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="UsingCommandsSample" Height="100" Width="200">
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.New" Executed="NewCommand_Executed" CanExecute="NewCommand_CanExecute" />
    </Window.CommandBindings>

    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button Command="ApplicationCommands.New">New</Button>
    </StackPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;

namespace WpfTutorialSamples.Commands
{
	public partial class UsingCommandsSample : Window
	{
		public UsingCommandsSample()
		{
			InitializeComponent();
		}

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

		private void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			MessageBox.Show("The New command was invoked");
		}
	}
}

Zdefiniowaliśmy polecenie wiązania(binding) w oknie poprzez dodanie do zbioru CommandBindings. Określamy przez to, że polecenie które chcemy użyć(nowe polecenie z ApplicationCommands), a także dwie metody obsługi zdarzeń. Interfejs użytkownika składa się z pojedyńczego przycisku który jest podłączony do polecenia używającego właściwości Command

W module kodu przechwytujemy obsługę dwóch zdarzeń. Obsługa zdarzenia CanExecute, które jest wywoływane przez WPF, kiedy aplikacja przechodzi w stan bezczynności, po to by sprawdzić, czy konkretne polecenie jest aktualnie dostępne, jest bardzo prosta w tym przykładzie, ponieważ chcemy, aby to polecenie było dostępne cały czas. Uzyskujemy to poprzez ustawienie właściwości zdarzenia CanExecute na true.

The Executed handler simply shows a message box when the command is invoked. If you run the sample and press the button, you will see this message. A thing to notice is that this command has a default keyboard shortcut defined, which you get as an added bonus. Instead of clicking the button, you can try to press Ctrl+N on your keyboard - the result is the same.

Użycie metody CanExecute

W pierwszym przykładzie zaimplementujemy zdarzenie CanExecute które zwróci wartość True więc przycisk będzie dostępny cały czas. Jednakże to nie dotyczy wszystkich przycisków - w wielu przypadkach będziesz chciał aby przycisk był włączony lub wyłączony w zależności od stanu aplikacji.

Bardzo powszechnym rozwiązaniem jest przełączanie przycisków do korzystania ze schowka systemu Windows. Gdy chcesz żeby przycisk Kopiuj i Wytnij były aktywne tylko wtedy gdy tekst jest zaznaczony natomiast przycisk Wklej był aktywny gdy w schowku znajduje się tekst. To jest dokładnie to co chcemy zrealizować w tym przykładzie:

<Window x:Class="WpfTutorialSamples.Commands.CommandCanExecuteSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="CommandCanExecuteSample" Height="200" Width="250">
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Cut" CanExecute="CutCommand_CanExecute" Executed="CutCommand_Executed" />
        <CommandBinding Command="ApplicationCommands.Paste" CanExecute="PasteCommand_CanExecute" Executed="PasteCommand_Executed" />
    </Window.CommandBindings>
    <DockPanel>
        <WrapPanel DockPanel.Dock="Top" Margin="3">
            <Button Command="ApplicationCommands.Cut" Width="60">_Cut</Button>
            <Button Command="ApplicationCommands.Paste" Width="60" Margin="3,0">_Paste</Button>
        </WrapPanel>
        <TextBox AcceptsReturn="True" Name="txtEditor" />
    </DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;

namespace WpfTutorialSamples.Commands
{
	public partial class CommandCanExecuteSample : Window
	{
		public CommandCanExecuteSample()
		{
			InitializeComponent();
		}

		private void CutCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = (txtEditor != null) && (txtEditor.SelectionLength > 0);
		}

		private void CutCommand_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			txtEditor.Cut();
		}

		private void PasteCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = Clipboard.ContainsText();
		}

		private void PasteCommand_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			txtEditor.Paste();
		}
	}
}

Więc mamy ten prosty interfejs z parą przycisków i kontrolką TextBox. Pierwszy przycisk wycina tekst do schowka a drugi wkleja ze schowka.

In Code-behind, we have two events for each button: One that performs the actual action, which name ends with _Executed, and then the CanExecute events. In each of them, you will see that I apply some logic to decide whether or not the action can be executed and then assign it to the return value CanExecute on the EventArgs.

The cool thing about this is that you don't have to call these methods to have your buttons updated - WPF does it automatically when the application has an idle moment, making sure that you interface remains updated all the time.

Default command behavior and CommandTarget

As we saw in the previous example, handling a set of commands can lead to quite a bit of code, with a lot of being method declarations and very standard logic. That's probably why the WPF team decided to handle some it for you. In fact, we could have avoided all of the Code-behind in the previous example, because a WPF TextBox can automatically handle common commands like Cut, Copy, Paste, Undo and Redo.

WPF does this by handling the Executed and CanExecute events for you, when a text input control like the TextBox has focus. You are free to override these events, which is basically what we did in the previous example, but if you just want the basic behavior, you can let WPF connect the commands and the TextBox control and do the work for you. Just see how much simpler this example is:

<Window x:Class="WpfTutorialSamples.Commands.CommandsWithCommandTargetSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="CommandsWithCommandTargetSample" Height="200" Width="250">
    <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>
</Window>

No Code-behind code needed for this example - WPF deals with all of it for us, but only because we want to use these specific commands for this specific control. The TextBox does the work for us.

Notice how I use the CommandTarget properties on the buttons, to bind the commands to our TextBox control. This is required in this particular example, because the WrapPanel doesn't handle focus the same way e.g. a Toolbar or a Menu would, but it also makes pretty good sense to give the commands a target.

Podsumowanie

Dealing with commands is pretty straight forward, but does involve a bit extra markup and code. The reward is especially obvious when you need to invoke the same action from multiple places though, or when you use built-in commands that WPF can handle completely for you, as we saw in the last example.

This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!