środa, 28 lipca 2010

Commands WPF

Komendy to jedna z nowości w WPFie. Oferują całkiem sporo:
- oddzielenie obiektu wywołującego od logiki przetwarzającej polecenie
- pozwalają na wywoływanie tej samej logiki w wielu miejscach programu (jak i zmianę jej w zależności od wymagań)
- sygnalizowanie użytkownikowi możliwość lub jej brak wykonania konkretnej komendy

Oczywiście jak wszystko istnieją także i ciemne strony komend, które być może w przyszłości zostaną naprawione:
- brak historii wywołanych komend
- brak możliwości cofnięcia komendy
- komendy nie mogą mieć wielu stanów

Po tym krótkim wstępie wad i zalet czas na sam model:
Command - reprezentuje zadanie (np. wytnij, wklej), nie zawiera żadnego kodu odpowiadającego za wykonanie zadania
Command Bindings - łączy polecenie z logiką
Command Sources - obiekt wywołujący polecenie (np. Button, MenuItem)
Command Targets - definiuje element w którym otrzymamy efekt działania polecenia.

Definiując kontrolkę, która ma korzystać poleceń definiujemy następujące właściwości:
Command - wskazuje jakie polecenie ma zostać wywołane (np. Cut, Paste)
CommandParameter - przekazuje dowolny parametr do komendy (nieobowiązkowo)
CommandTarget - wskazuje element w którym zostanie wykonane polecenie (nieobowiązkowo)

Bindując polecenia definiujemy trzy rzeczy:
- co ma się stać gdy zostanie wywołana komenda
- jak zdecydować kiedy jest możliwość wywołania komendy
- zasięg (jeden przycisk czy np. całe okno)

Zobaczmy na przykład:

<Window x:Class="WpfApplication1.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:local="clr-namespace:WpfApplication1"
       Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="New" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed" />
    </Window.CommandBindings>
    <Grid>
        <Button Command="New" Content="New" Width="50" Height="25"/>
    </Grid>
</Window>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

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

        private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("New document");
        }
    }
}


Prosty przykład. Po kliknięciu użytkownik zobaczy MessageBox-a. Teraz dodamy menu i tam też metodę New:
<Window x:Class="WpfApplication1.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:local="clr-namespace:WpfApplication1"
       Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="New" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed" />
    </Window.CommandBindings>
    <Grid>
        <Menu VerticalAlignment="Top">
            <MenuItem Command="New" />
        </Menu>
        <Button Command="New" Content="New" Width="50" Height="25"/>
    </Grid>
</Window>

Dodaliśmy 3 linijki kodu. Należy zauważyć, że nie nadaliśmy żadnego Headera elementowi menu. MenuItem jest na tyle sprytny, że pobierze sobie zawartość pola Text polecenia New. Wygodne i łatwe w użyciu.


Teraz zobaczymy przykład, w którym dzięki polecenią, program sam dokonuje oceny, czy dana komenda jest możliwa do wykonania czy też nie. Na początek kod:
<Window x:Class="WpfApplication1.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:local="clr-namespace:WpfApplication1"
       Title="MainWindow" Height="350" Width="525">
    <StackPanel Orientation="Vertical">
        <Menu>
            <MenuItem Command="Copy" />
            <MenuItem Command="Cut" />
            <MenuItem Command="Paste" />
        </Menu>

        <TextBox Name="txt" />

        <StackPanel Orientation="Horizontal" Margin="0,20,0,0" >
            <Button Margin="20" Content="Copy" Command="Cut" CommandTarget="{Binding ElementName=txt}" />
            <Button Margin="20" Content="Paste" Command="Paste" CommandTarget="{Binding ElementName=txt}" />
        </StackPanel>
    </StackPanel>
</Window>

Po uruchomieniu zobaczymy:

Co należy zauważyć to to, że na starcie mamy coś w buforze schowka do wklejenia. Kolejnym aspektem jest aktywność klawisza Paste nawet po straceniu focusu z TextBoxa (w przypadku Menu klawisz Paste od razu traci aktywność). Dzieje się tak, gdyż w przypadku Buttona ustawiliśmy właściwość CommandTarget. Istotną rzeczą jest także, że możliwość Copy i Cut otrzymujemy dopiero po zaznaczeniu jakiegoś fragmentu tekstu w TextBoxie. Jak więc widać na starcie otrzymaliśmy niezły kawałek gotowej funkcjonalności.


Własne komendy
Oczywiście każdy programista chce mieć możliwość dodawania własnych poleceń. Nie jest to trudne. Zobaczmy na przykład implementacji:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

namespace WpfApplication2
{
    public class DataCommands
    {
        private static RoutedUICommand requery;
        static DataCommands()
        {
            InputGestureCollection inputs = new InputGestureCollection();
            inputs.Add(new KeyGesture(Key.R, ModifierKeys.Control, "Ctrl+R"));
            requery = new RoutedUICommand("Requery", "Requery", typeof(DataCommands), inputs);
        }

        public static RoutedUICommand Requery
        {
            get { return requery; }
        }
    }
}


<Window x:Class="WpfApplication2.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:l="clr-namespace:WpfApplication2"
       Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="l:DataCommands.Requery" Executed="CommandBinding_Executed" />
    </Window.CommandBindings>
    <Grid>
        <Button Name="button1" Command="l:DataCommands.Requery" Content="Click me!" />
    </Grid>
</Window>


        private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("You create new command! Congratulations!");
        }


Jak widać nic trudnego.

Brak komentarzy:

Prześlij komentarz