WPF命令綁定 自定義命令


WPF的命令系統是wpf中新增加的內容,在以往的winfom中並沒有。為什么要增加命令這一塊內容。在winform里面的沒有命令只使用事件的話也可以實現程序員希望實現的功能。這個問題在很多文章中都提到了。但大家都是引用深入淺出wpf里面的概述。沒有用自己的話來闡述。當然你仔細理解一下的話也很容易理解。但在這里我還是用我自己的話來說一下什么情況下使用命令。

命令具有約束力

事件的作用是發布、傳播一些消息,消息傳達到了接收者,事件的指令也就算完成了,至於如何響應事件送來的消息事件並不做任何限制,每個接收者可已用自己的行為來響應事件。也就是說,事件不具有約束力。命令和事件的區別就在於命令具有約束力。

事件是一種很自由散漫的調用。就像我們平時的自由職業者一樣,不受約束,想做什么就做什么。仔細運用的話雖然可以什么功能都實現,但是總是有些凌亂。但是命令不同,命令具有約束力,也就是上班族一樣。有一個制度來約束你。必須按照流程來實現功能。所說這樣做略顯麻煩,但是對於一些復雜特定的情況卻能夠避免程序員出錯。

比如保存事件的處理器,程序員可以寫Save()、SaveHandle()、SaveDocument()... 這些都符合代碼規范。但遲早有一天整個項目會變的讓人無法讀懂,新來的程序員或修改bug的程序員會很抓狂。如果使用命令,情況就會好很多----當Save命令到達某個組件的時候,命令會自動去調用組件的Save方法。而這個方法可能定義在基類或者接口里(即保證了這個方法是一定存在的),這就在代碼結構和命名上做了約束。

這里所謂的約束力就是指命令系統的幾大要素

  • 命令(Command):WPF的命令實際上就是實現了ICommand接口的類,平時使用最多的就是RoutedCommand類。我們還會學習使用自定義命令。
  • 命令源(Command Source):即命令的發送者,是實現了ICommandSource接口的類。很多界面元素都實現了這個接口,其中包括Button,ListBoxItem,MenuItem等。
  • 命令目標(Command Target):即命令發送給誰,或者說命令作用在誰的身上。命令目標必須是實現了IInputElement接口的類。
  • 命令關聯(Command Binding):負責把一些外圍邏輯和命令關聯起來,比如執行之前對命令是否可以執行進行判斷、命令執行之后還有哪些后續工作等。

要想使用命令,就必須要實現這幾大要素。而程序員如果實現了這幾大要素,那么程序的邏輯就很明了。也就是讓程序員想犯錯都難。

命令最常見的使用情況是例如工具欄里面綁定命令,右鍵命令,菜單綁定命令,保存,撤銷,打開文件夾等情境。

命令實現例1

命令的實現可以自定義命令,但對於一些簡單的情況我們可以使用wpf內置的已經實現了ICommand接口的RoutedCommand或者RoutedUICommand

注意RoutedUICommand只是比RoutedCommand多了一個說明性的文本參數。

xaml

<Window x:Class="WpfApplication1.Window28"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window28" Height="300" Width="300" WindowStyle="ToolWindow">
    <StackPanel Background="LightBlue" x:Name="sp1">
        <Button Content="Send Command" x:Name="btn1" Margin="5"></Button>
        <TextBox x:Name="txtA" Margin="5,0" Height="200"></TextBox>
    </StackPanel>
</Window>

后台cs

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.Shapes;

namespace WpfApplication1
{
    /// <summary>
    /// Window28.xaml 的交互邏輯
    /// </summary>
    public partial class Window28 : Window
    {
        public Window28()
        {
            InitializeComponent();
            InitializeCommand();
        }
        //聲明並定義命令
        private RoutedCommand rouutedCommand = new RoutedCommand("Clear",typeof(Window28));
        private void InitializeCommand()
        {
            //把命令賦值給命令源,並定義快捷鍵
            this.btn1.Command = rouutedCommand;
            //定義快捷鍵 gesture 手勢
            this.rouutedCommand.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt));
            //指定命令目標
            this.btn1.CommandTarget = txtA;

            //創建命令關聯
            CommandBinding commandBinding = new CommandBinding();
            //把命令rouutedCommand和具體的邏輯事件綁定起來。
            commandBinding.Command = rouutedCommand;//只關注與rouutedCommand相關的命令
            commandBinding.CanExecute += new CanExecuteRoutedEventHandler(cb_CanExecute);
            commandBinding.Executed += new ExecutedRoutedEventHandler(cb_Execute);
            //把命令關聯安置在外圍控件上
            this.sp1.CommandBindings.Add(commandBinding);
        }
        //當命令到達目標之后,此方法被調用
        private void cb_Execute(object sender, ExecutedRoutedEventArgs e)
        {
            txtA.Clear();
            //避免事件繼續向上傳遞而降低程序性能
            e.Handled = true;
        }
        //當探測命令是否可執行的時候該方法會被調用
        private void cb_CanExecute(object sender,CanExecuteRoutedEventArgs e)
        {
            if (string.IsNullOrEmpty(txtA.Text))
            {
                e.CanExecute = false;
            }
            else
            {
                e.CanExecute = true;
            }
            //避免事件繼續向上傳遞而降低程序性能
            e.Handled = true;
        }

    }
}
View Code

上面這個例子代碼寫的比較詳細,簡單些我們也可以這樣寫

<Window x:Class="WpfApplication1.Window65"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="Window64" Height="159" Width="461">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="100" />
        </Grid.RowDefinitions>
        <Menu>
            <MenuItem Header="文件">
                <MenuItem Header="打開文件" Command="{x:Static Member=local:Commands.NewProject}"></MenuItem>
                <MenuItem Header="另存為"></MenuItem>
                <MenuItem Header="關閉文件"></MenuItem>
            </MenuItem>
        </Menu>
    </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.Shapes;
using System.Windows.Media.Animation;

namespace WpfApplication1
{
    public partial class Window65 : Window
    {
        public Window65()
        {
            InitializeComponent();
            this.CommandBindings.Add(new CommandBinding(Commands.NewProject, new ExecutedRoutedEventHandler(ExecuteNewProject)));
        }

        private void ExecuteNewProject(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("自定義命令");
        }
    }
    public static class Commands
    {
        public static ICommand NewProject = new RoutedUICommand("newproject", "NewProject", typeof(Commands));
    }
}
View Code

自定義命令(深入淺出wpf原版例30)

關於自定義命令,園子里大多文章都是取自深入淺出理解wpf里面的例子。我們也從這個例子開始分析,然后一點一點把這個例子改為我們自己掌握的例子。

xaml窗體

<Window x:Class="WpfApplication1.Window30"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window30" Height="300" Width="300" xmlns:my="clr-namespace:WpfApplication1">
    <StackPanel>
        <my:MyCommandSource x:Name="myCommandSource1">
            <TextBlock Text="清除" Width="80" FontSize="16" TextAlignment="Center" Background="LightGreen"></TextBlock>
        </my:MyCommandSource>
        <my:MniView x:Name="mniView1" />
    </StackPanel>
</Window>
View Code
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.Shapes;
using WpfApplication1.Command;

namespace WpfApplication1
{
    /// <summary>
    /// Window30.xaml 的交互邏輯
    /// </summary>
    public partial class Window30 : Window
    {
        public Window30()
        {
            InitializeComponent();
            ClearCommand clearCommand = new ClearCommand();
            this.myCommandSource1.Command = clearCommand;
            this.myCommandSource1.CommandTarget = mniView1;
        }
    }
}
View Code

 

命令源:

<UserControl x:Class="WpfApplication1.MyCommandSource"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
</UserControl>
View Code
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>
    /// MyCommandSource.xaml 的交互邏輯
    /// </summary>
    public partial class MyCommandSource : UserControl,ICommandSource
    {
        
        /// <summary>
        /// 繼承自ICommand的3個屬性
        /// </summary>
        public ICommand Command
        {
            get;
            set;
        }

        public object CommandParameter
        {
            get;
            set;
        }

        public IInputElement CommandTarget
        {
            get;
            set;
        }
        //在命令目標上執行命令,或者說讓命令作用於命令目標
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            if(this.CommandTarget!=null)
            {
                this.Command.Execute(CommandTarget);
            }
        }
        protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
        {
            base.OnMouseDoubleClick(e);
        }
    }
}
View Code

 

命令目標:

<UserControl x:Class="WpfApplication1.MniView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Border CornerRadius="5" BorderBrush="GreenYellow" BorderThickness="2">
        <StackPanel>
            <TextBox Margin="5" x:Name="txt1"></TextBox>
            <TextBox Margin="5" x:Name="txt2"></TextBox>
            <TextBox Margin="5" x:Name="txt3"></TextBox>
            <TextBox Margin="5" x:Name="txt4"></TextBox>
        </StackPanel>
    </Border>
</UserControl>
View Code
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;
using WpfApplication1.InterFace;

namespace WpfApplication1
{
    /// <summary>
    /// MniView.xaml 的交互邏輯
    /// </summary>
    public partial class MniView : UserControl,IView
    {
        //構造器
        public MniView()
        {
            InitializeComponent();
        }
        //繼承自IView的成員們
        public bool IsChanged
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public void SetBinding()
        {
            throw new NotImplementedException();
        }

        public void Refresh()
        {
            throw new NotImplementedException();
        }
        /// <summary>
        /// 用於清除內容的業務邏輯
        /// </summary>
        public void Clear()
        {
            this.txt1.Clear();
            this.txt2.Clear();
            this.txt3.Clear();
            this.txt4.Clear();
        }

        public void Save()
        {
            throw new NotImplementedException();
        }
    }
}
View Code

自定義命令:

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

namespace WpfApplication1.Command
{
    /// <summary>
    ///自定義命令
    /// </summary>
    public class ClearCommand : ICommand
    {
        //用來判斷命令是否可以執行
        public bool CanExecute(object parameter)
        {
      throw new NotImplementedException();
        }
        //當命令可執行狀態發送改變時,應當被激發
        public event EventHandler CanExecuteChanged;

        //命令執行時,帶有與業務相關的Clear邏輯
        public void Execute(object parameter)
        {
            IView view = parameter as IView;
            if (view != null)
            {
                view.Clear();
            }
        }
    }
}
View Code

定義的接口

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

namespace WpfApplication1.InterFace
{
    public interface IView
    {
        //屬性
        bool IsChanged { get; set; }
        //方法
        void SetBinding();
        void Refresh();
        void Clear();
        void Save();
    }
}
View Code

原版命令改

原例子中首先對於命令源。:即命令的發送者,是實現了ICommandSource接口的類。很多界面元素都實現了這個接口,其中包括Button,ListBoxItem,MenuItem等。因為wpf中大部分控件都已經實現了這個接口,所以我們可以把例子弄簡單些,暫時不用實現自定義的MyCommandSource

我們把我們主窗體中的xaml修改成下面這樣

<Window x:Class="WpfApplication1.Window30"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window30" Height="300" Width="300" xmlns:my="clr-namespace:WpfApplication1">
    <StackPanel>
        <!--<my:MyCommandSource x:Name="myCommandSource1">
            <TextBlock Text="清除" Width="80" FontSize="16" TextAlignment="Center" Background="LightGreen"></TextBlock>
        </my:MyCommandSource>-->
        <Button Name="myCommandSource1">
            <TextBlock Text="清除" Width="80" FontSize="16" TextAlignment="Center" Background="LightGreen"></TextBlock>
        </Button>
        <my:MniView x:Name="mniView1" />
    </StackPanel>
</Window>

用一個button來作為命令源。調試程序,會報錯如下

在原版代碼里面是沒有return true 的,所以在給空間綁定命令的時候會報錯。這說明當給wpf自帶的控件作為命令源綁定時會初始化執行CanExecute,而在我們自己實現的命令源MyCommandSource並沒有實現這一邏輯。也就是當我們初始化命令綁定的時候並沒有去執行CanExecute這個方法。當然在這個例子中這無關緊要,但當我們自定義控件的時候最好實現這一邏輯,所以在初始化時候可以首先檢查命令是否可以執行。我們現在先把canExecute  return true;繼續我們的改造。

此時我們點擊清除,會發現命令並沒有被執行。檢查代碼我們會發現 public void Execute(object parameter)方法的parameter為null.

 我們看一下原版中當我們左擊的時候,源命令源執行了上面這個方法。此時感覺有些奇怪。CommandTarget命令目標是這樣使用的么?

我們查看一下msdn,解釋如下。

在 Windows Presentation Foundation (WPF) 命令系統中,僅當 ICommand 為 RoutedCommand 時,ICommandSource 上的 CommandTarget 屬性才適用。 如果 CommandTarget 是在 ICommandSource 上設置的,並且對應的命令不是 RoutedCommand,則會忽略該命令目標。

命令目標是只針對RoutedCommand實現的。我們看一下我們最開始的例子,該例使用了wpf內置的 RoutedCommand 命令。

 private void ExecuteNewProject(object sender, ExecutedRoutedEventArgs e)

與該命令綁定的方法實現有兩個參數,其中sender 就是指的我們的CommandTarget

與 RoutedCommand 一起使用時,命令目標為引發 Executed 和 CanExecute 事件的對象。 如果未設置 CommandTarget 屬性,則會將具有鍵盤焦點的元素用作目標。

所以原版demo中命令目標的使用有些不恰當。此時我們可以將主窗體的代碼改為如下。向目標源傳遞參數

this.myCommandSource1.CommandParameter = mniView1;此時我們發現命令被正確執行。

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.Shapes;
using WpfApplication1.Command;

namespace WpfApplication1
{
    /// <summary>
    /// Window30.xaml 的交互邏輯
    /// </summary>
    public partial class Window30 : Window
    {
        public Window30()
        {
            InitializeComponent();
            ClearCommand clearCommand = new ClearCommand();
            this.myCommandSource1.Command = clearCommand;
            this.myCommandSource1.CommandParameter = mniView1;
            //this.myCommandSource1.CommandTarget = mniView1;
        }
    }
}

通過這個例子的改造我們發現,很多情況下我們如果不需要實現自定義控件是不需要自定義命令源的,而命令目標很多情況下也不需要。我們只需要向命令綁定傳遞參數就可以實現很多的應用。

通過構造函數傳遞參數

另外我們也可以重新命令的構造函數來實現參數的傳遞。改造后代碼如下。

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.Shapes;
using WpfApplication1.Command;

namespace WpfApplication1
{
    /// <summary>
    /// Window30.xaml 的交互邏輯
    /// </summary>
    public partial class Window30 : Window
    {
        public Window30()
        {
            InitializeComponent();
            ClearCommand clearCommand = new ClearCommand(mniView1);
            this.myCommandSource1.Command = clearCommand;
            //this.myCommandSource1.CommandParameter = mniView1;
            //this.myCommandSource1.CommandTarget = mniView1;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using WpfApplication1.InterFace;

namespace WpfApplication1.Command
{
    /// <summary>
    ///自定義命令
    /// </summary>
    public class ClearCommand : ICommand
    {
        IView _view;
        public ClearCommand(IView view)
        {
            this._view = view;
        }
        //用來判斷命令是否可以執行
        public bool CanExecute(object parameter)
        {
            return true;
            throw new NotImplementedException();
        }
        //當命令可執行狀態發送改變時,應當被激發
        public event EventHandler CanExecuteChanged;

        //命令執行時,帶有與業務相關的Clear邏輯
        public void Execute(object parameter)
        {
            //IView view = parameter as IView;
            IView view = _view as IView;
            if (view != null)
            {
                view.Clear();
            }
        }
    }
}

源版代碼下載鏈接:http://download.csdn.net/detail/fwj380891124/4778376(例30)

本文地址:http://www.cnblogs.com/santian/p/4385220.html

博客地址:一天兩天三天

轉載請以超鏈接形式標明文章原始出處。


免責聲明!

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



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