《深入淺出WPF》筆記——命令篇


 一、認識命令

1.1命令的特點

  提到“命令”,我們應該想到命令的發出者,命令的接受者,命令的內容,准備工作,完成任務,回報工作。。。與事件中的發送者,接受者,消息,處理,處理,處理一一對應,如果是單純的幾個對應關系,的確用事件是能夠代替的,不過,命令相對事件有其自己的特點的。比如,古時候,如果兩個部落發動戰爭,經常在出軍之前,做了充分的准備,才可能一聲令下,沖啊!相反,如果沒有准備好的話,一定會限制,軍隊不能隨意出軍,所以命令具有限制性。除此之外,命令一旦下達是不經常更改的。如在軟件里面,一般Ctr+C命令是復制,沒有哪個軟件用這個命令表示粘貼的呢?所以說命令具有普遍性,除此之外,命令有自己的元素,這讓程序員定義命令的時間,有章可依,所以命令更具有規范性。上面的特點純屬個人理解,可能這些性質不是非常嚴格。除此之外,很多網友對命令的使用褒貶不一,此文重在理解命令,所以上面特點僅供用於理解命令。

1.2WPF中命令的組成元素以及元素之間的關系

  由上面的介紹,應該可以猜出個大概了。下面直接給出其組成元素:

  • 命令(Command)實現了ICommand接口的類,使用比較多的是RoutedCommand。
  • 命令源(Command Source)命令的發送者,現實了ICommandSource接口的類,實現此類的元素主要有ButtonBase,Hyperlink,MenuItem、ListBoxItem等
  • 命令目標(Command Target)命令的接受者,實現了IInputElement接口的類。
  • 命令關聯(Command Binding)負責把外圍的邏輯與命令關聯起來。

  相對事件的元素來說,命令元素之間的關系還是會復雜一些,具體的關系會通過命令的使用來說明。下面先簡單介紹一下自定義命令的步驟。 

  a、創建命令類

  如果命令沒有涉及到業務邏輯的話,一般使用WPF類庫的RoutedCommand類即可,如果要聲明相對邏輯復雜一些的類,可以實現RouteCommand類的繼承或者是ICommand的接口。

  b、聲明命令實例

  由於命令的普遍性,一般情況下程序中某類命令只需要一個命令實例即可(單件模式)。

  c、指明命令的源

  通常是可以點擊的控件,命令還有個好處就是,沒有准備好的命令,這個控件不可用。如果把命令看做炮彈,那么命令源相當於火炮,這個火炮還是防走火的。

  d、指明命令的目標

  目標是命令的作用對象。如果指定了目標,無論是否有焦點,都會受到這個命令。如果沒有指定目標的話,擁有焦點的對象默認為命令目標。還有一個要注意的是設置目標是通過命名的源來設置的。格式為:命令源控件.CommandTarget = 目標控件;

   e、設置命令關聯

   關於設置命令關聯還是在實例中好好的體會一下吧。下面就通過一個例子來說明。

二、小試命令

   下面的例子實現的是點擊按鈕時,清除文本框里面的內容。由於代碼注釋寫的比較詳細,直接給代碼,然后具體再解釋:

XAML代碼:

<Window x:Class="Chapter_07.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Background="LightBlue" Title="MainWindow" Height="230" Width="260">
    <StackPanel x:Name="stackPanel">
        <Grid x:Name="grid" Margin="10">
            <Button x:Name="button1" Content="Clear Commend"  Height="50" Margin="0,10,0,440"/>
            <TextBox x:Name="textBoxA" Margin="0,65,0,325" />
        </Grid>
    </StackPanel>
</Window>

后台代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 Chapter_07
{
    /// <summary>
    /// MainWindow.xaml 的交互邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            //初始化命令
            InitializeCommand();
        }

        //聲明並定義命令
        private RoutedCommand clearCmd = new RoutedCommand("Clear",typeof(MainWindow));
        
        //初始化命令方法
        private void InitializeCommand()
        { 
            //把命令賦值給命令源,並指定快捷鍵
            this.button1.Command = this.clearCmd;
            this.clearCmd.InputGestures.Add(new KeyGesture(Key.C,ModifierKeys.Alt));
            //為命令設置目標
            this.button1.CommandTarget = this.textBoxA;
            
            //創建命令關聯
            CommandBinding cb = new CommandBinding();
            //指定關聯的命令
            cb.Command = this.clearCmd;
            //確定此命令是否可以在其當前狀態下執行
            cb.CanExecute += cb_CanExecute;
            //調用此命令
            cb.Executed += cb_Executed;
            
            //把命令關聯到外圍控件上
            this.stackPanel.CommandBindings.Add(cb);
        }
        
        //執行命令,要做的事情
        void cb_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            this.textBoxA.Clear();
            e.Handled = true;
        }

        //在執行命令之前,檢查命令是否可以執行對應的處理器
        void cb_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if (string.IsNullOrEmpty(this.textBoxA.Text))
            {
                e.CanExecute = false;
            }
            else
            {
                e.CanExecute = true;
            }
            e.Handled = true;
        }
    }
}

效果是:文本框沒有輸入內容的話,按鈕是不可用的,當文本框里面有文字的話,按鈕可用,點擊按鈕清除文本框內容。效果如圖1:

圖1

   通過效果圖,應該基本上了解路由命令的,我在看書的時間,想起了一個問題,為什么叫路由命令,應該是與路由事件有關,后來又翻了一遍書,發現路由事件在下面圖中有提及到,在上面的代碼中,站在高處的元素StackPanel作為CommandBinding的載體(由於路由事件的傳播是在可視樹上傳播的,所以CommandBinding的載體一定為命令目標的外圍控件上面),CommandBinding來指定監聽命令是否准備好的事件和命令的處理事件,然后告訴命令,由於命令源擁有命令的實例,命令源就會根據命令接到的信息,作出相應的反應,在此處,命令只是捕捉信息和通知命令源,真正其作用的是CommandBinding指定的處理器。

   圖2

 三、WPF的命令庫

   在WPF中微軟提供了一些便捷的命令庫: ApplicationCommandsMediaCommandsComponentCommandsNavigationCommandsEditingCommands。下面通過一個例子來說明調用ApplicationCommands的Copy命令。點擊按鈕,把文本框內容拷貝到另外一個文本框里面。 

XAML代碼如下:

<Window x:Class="Chapter_07.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300" Background="Azure">
    <StackPanel x:Name="stackpanel">
        <TextBox x:Name="txt1" Margin="10" />
        <TextBox x:Name="txt2" Margin="10" />
        <Button x:Name="button1" Content="按鈕一" Height="50" Margin="10" Command="ApplicationCommands.Copy"/>
    </StackPanel>
    <Window.CommandBindings>
        <CommandBinding Command="Copy" CanExecute="Copy_CanExecute" Executed="Copy_Executed"/>
    </Window.CommandBindings>
</Window>

后台代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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.Shapes;

namespace Chapter_07
{
    /// <summary>
    /// Window1.xaml 的交互邏輯
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Copy_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            this.txt2.Text = this.txt1.Text;
        }

        private void Copy_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            //檢查是否能發出命令
             e.CanExecute = !string.IsNullOrEmpty(this.txt1.Text);
            e.Handled = false;
        }
    }
}

  上面的例子是把Window作為CommandBinding的載體,而且是在XAML中實現的,注意與在后台代碼上的區別,簡單的一個Copy功能就實現了。雖然說微軟提供了WPF類庫,但是處理函數還是要我們自己去寫的。還有一點要注意的是:可以用轉到定義查看ApplicationCommands類與其類里面的屬性,他們都是靜態的,所以都以單件模式出現。如果有兩個命令源,同使用一個“命令”,那么應該怎么區別?原來命令源除了含有Command屬性,還含有CommandParmeter屬性,用來區分不同的命令源。在處理的時間,根據e.Parameter(如圖3)來獲取是哪個源發出的命令,來采取相應的命令。關於其他的WPF類庫如果有用到的話再查msdn了。

圖3

 四、自定義命令

   在小試命令的例子中,記錄了自定義RoutedCommand,路由命令中真正做事情的是CommandBinding。下面主要記錄一下自定義直接實現ICommand接口的命令。在介紹之前,先看一下ICommand接口的原型:

  • event EventHandler CanExecuteChanged;
  • bool CanExecute(object parameter);
  • void Execute(object parameter);

  其中第一個事件為,當命令可執行狀態發生改變時,可以激化此事件來通知其他對象。另外兩個方法在上面已經用過同名的,在此不做重復說明。下面開始實現一個自定義直接實現ICommand接口的命令,同樣實現點擊源控件,清除目標控件的內容:

a.准備工作:

    //為了使目標控件,含有Clear()方法,所以在此一個定義接口
    public interface IView
    {
        void Clear();
    }
    
    //定義命令
    public class ClearCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
            throw new System.NotImplementedException();
        }

        public void Execute(object parameter)
        {
            IView view = parameter as IView;
            if (view != null)
            {
                view.Clear();
            }
        }
    }

    //自定義命令源
    public class MyCommandSource : System.Windows.Controls.UserControl, ICommandSource
    {
        public ICommand Command { get; set; }

        public object CommandParameter { get; set; }

        public IInputElement CommandTarget { get; set; }

        //重寫點擊處理函數,注意由於事件的優先級不同,如果命令源是button的話,下面的函數不起作用
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);

            if (this.CommandTarget != null)
            {
                this.Command.Execute(this.CommandTarget);
            }
        }
    }

b.制作一個userControl控件。

XAML
<UserControl x:Class="Chapter_07.MiniView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Height="140"  Width="200">
    <Border CornerRadius="5" BorderBrush="LawnGreen" BorderThickness="2" >
        <StackPanel >
            <TextBox x:Name="textBox1" Margin="5"/>
            <TextBox x:Name="textBox2" Margin="5"/>
            <TextBox x:Name="textBox3" Margin="5"/>
            <TextBox x:Name="textBox4" Margin="5"/>
        </StackPanel>
    </Border>
</UserControl>
MiniView.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 Chapter_07
{
    /// <summary>
    /// MiniView.xaml 的交互邏輯
    /// </summary>
    public partial class MiniView : UserControl,IView
    {
        public MiniView()
        {
            InitializeComponent();
        }

        public void Clear()
        {
            this.textBox1.Clear();
            this.textBox2.Clear();
            this.textBox3.Clear();
            this.textBox4.Clear();
        }
    }
}

c.制作自定義命令界面。

<Window x:Class="Chapter_07.DefineCommand"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Chapter_07"
        Title="DefineCommand" Height="300" Width="300">
    <StackPanel>
        <local:MyCommandSource x:Name="ctrClear" Margin="10">
            <TextBlock Text="清除" FontSize="16" TextAlignment="Center" Background="LightGreen" Width="80"/>
        </local:MyCommandSource>
        <local:MiniView x:Name="miniView"/>
    </StackPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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.Shapes;

namespace Chapter_07
{
    /// <summary>
    /// DefineCommand.xaml 的交互邏輯
    /// </summary>
    public partial class DefineCommand : Window
    {
        public DefineCommand()
        {
            InitializeComponent();
            //下面的聲明命令方式,僅作為練習使用,由於命令
            //具有"全局性",所以一般聲明在靜態全局的地方,供全局使用
            ClearCommand clearCmd = new ClearCommand();
            this.ctrClear.Command = clearCmd;
            this.ctrClear.CommandTarget = this.miniView;
        }
    }
}

終於黏貼完了,弱弱的看一下效果吧!

 圖4

  本例純屬筆記,但是涉及到的東西還是比較多的,包括接口、自定義控件以及命令的幾個組成元素,對於菜鳥的自己,如果細心的看幾遍還是能小有所獲的。

 五、總結

   本文主要通過簡單的例子,認識了RoutedCommand和ICommand的自定義命令以及微軟的WPF提供的命令庫,雖然上面的例子比較簡單,但是里面涉及的東西還是很有必要時常查閱的。本文是讀書筆記,里面難免有理解不對的地方,歡迎討論!下一篇:《深入淺出WPF》筆記——資源篇。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM