【WPF學習】第三十二章 執行命令

来源:https://www.cnblogs.com/Peter-Luo/archive/2020/02/08/12274329.html
-Advertisement-
Play Games

前面章節已經對命令進行了深入分析,分析了基類和介面以及WPF提供的命令庫。但尚未例舉任何使用這些命令的例子。 如前所述,RoutedUICommand類沒有任何硬編碼的功能,而是只表達命令,為觸發命令,需要有命令源(也可使用代碼)。為響應命令,需要有命令綁定,命令綁定將執行轉發給普遍的事件處理程式。 ...


  前面章節已經對命令進行了深入分析,分析了基類和介面以及WPF提供的命令庫。但尚未例舉任何使用這些命令的例子。

  如前所述,RoutedUICommand類沒有任何硬編碼的功能,而是只表達命令,為觸發命令,需要有命令源(也可使用代碼)。為響應命令,需要有命令綁定,命令綁定將執行轉發給普遍的事件處理程式。

一、命令源

  命令庫中的命令始終可用。觸發他們的最簡單的方法是將它們關聯到實現了ICommandSource介面的控制項,其中包括繼承自ButtonBase類的控制項(Button和CheckBox等)、單獨的ListBoxItem對象、HyperLink以及MenuItem。

  ICommandSource介面定義了三個屬性,如下表所示。

表 ICommandSource介面的屬性

 

   例如,下麵的按鈕使用Command屬性連接到ApplicationCommands.New命令:

 <Button Command="ApplicationCommands.New">New</Button>

  WPF的智能程度足夠高,它能查找前面介紹的所有5個命令容器類,這意味著可使用下麵的縮寫的形式:

 <Button Command="New">New</Button>

  然而,由於沒有指明包含命令的類,這種語法不夠明確、不夠清晰。

二、命令綁定

  當將命令關聯到命令源時,會看到一些有趣的現象。命令源將會被自動禁用。

  例如,如果創建上一節提到的New按鈕,該按鈕的顏色就會變淺並且不能被單擊,就像將IsEnabled屬性設置為false那樣(如下圖所示)。這是因為按鈕已經查詢了命令的狀態,而且由於命令還沒有與其關聯的綁定,所以按鈕被認為是禁用的。

 

 

   為改變這種狀態,需要為命令創建綁定以明確以下三件事:

  當命令被觸發時執行什麼操作。

  如何確定命令是否能夠被執行(這是可選的。如果未提供這一細節,只要提供了關聯的事件處理程式,命令總是可用)。

  命令在何處起作用。例如,命令可被限制在單個按鈕中使用,或在整個視窗中使用(這種情況更常見)。

  下麵的代碼片段為New命令創建綁定。可將這些代碼添加到視窗的構造函數中:

//Create the binding
CommandBinding binding=new CommandBinding(ApplicationCommands.New);

//Attach the event handler
binding.Executed+=NewCommand_Executed;

//Register the binding
this.CommandBinding.Add(binding);

  註意,上面創建的CommandBinding對象唄添加到包含視窗的CommandBindings集合中,這通過事件冒泡進行工作。實際上,當單擊按鈕時,CommandBinding.Executed事件從按鈕冒泡到包含元素。

  儘管習慣上為視窗添加所有綁定,但CommandBindings屬性實際是在UIElement基類中定義的。這意味著任何元素都支持該屬性。例如,如果將命令綁定直接添加到使用它的按鈕中,這個示例仍工作的很好(儘管不能在將該綁定重用與其他高級元素)。為得到最大的靈活性,命令綁定通常被添加到頂級視窗。如果希望在多個視窗中使用相同相同的命令,需要在這些視窗中分別創建命令綁定。

  上面的代碼假定在同一個類中已有名為NewCommand_Executed的事件處理程式,該處理程式已經準備好接收命令。下麵是一個示例,該例包含一些顯示命令源的簡單代碼:

private void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("New command triggered by " + e.Source.ToString());
        }

  現在,如果允許應用成功需,按鈕處於啟用狀態。如果單擊按鈕,就會觸發Executed事件,該事件冒泡至視窗,並被上面給出的NewCommand_Executed()事件處理程式程式處理。這是,WPF會告知事件源(按鈕)。通過ExecutedRoutedEventArgs對象還可獲得被調用的命令的引用(ExecutedRoutedEventArgs.Command),以及所有同時傳遞的額外數據(ExecutedRoutedEventArgs.Parameter)。在該例中,因為沒有傳遞任何額外的數據,所以參數為null(如果希望傳遞附加數據,贏設置命令源的CommandParameter屬性;並且如果希望傳遞一些來自另一個控制項的信息,還需要使用數據綁定表達式設置CommandParameter屬性)。

  在上面的示例中,使用代碼生成了命令綁定。然而,如果希望精簡代碼隱藏文件,使用XAML以生命方式關聯命令同樣容易。下麵是所需的標記:

<Window x:Class="Commands.TestNewCommand"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TestNewCommand" Height="300" Width="300">
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.New" Executed="NewCommand"></CommandBinding>
    </Window.CommandBindings>
    <StackPanel>
        <Button Margin="5" Padding="5" Command="ApplicationCommands.New">New</Button>
    </StackPanel>
</Window>

  使用Visual Studio沒有為定義命令綁定提供任何設計時支持。對連接控制項和命令的支持也較弱。可使用Properties視窗設置控制項的Command屬性,但需要輸入正確的命令名稱——由於並未提供包含命令的下拉列表,因此不能方便地從列表中選擇命令。

三、使用多命令源

  上面示例中觸發普通事件的方式看起來不那麼直接。然而,當添加使用相同命令的更多控制項時,額外命令層的意義就提現出出來了。例如,可添加如下也使用New命令的菜單項:

        <Menu >
            <MenuItem Header="File">
                <MenuItem Command="New"></MenuItem>
            </MenuItem>
        </Menu>

  註意,New命令的這個MenuItem對象沒有設置Header屬性。這是因為MenuItem類足夠智能,如果沒有設置Header屬性,它將從命令中提取文本(Button控制項不具有這一特性)。雖然該特性帶帶來的便利看起來不大,但如果計劃使用不同的語言本地化應用程式,這一特性就很重要了。在這種情況下,只需要在一個地方修改文本即可(通過設置命令的Text屬性)。這比在整個視窗中進行跟蹤更容易。

  MenuItem類還有一項功能:能自動提取Command.InputBinding集合中的第一個快捷鍵(如果存在的話)。對於ApplicationCommands.New命令對象,這意味著在菜單文本的旁邊會顯示Ctrl+N快捷鍵(如下圖所示)。

 

 四、微調命令文本

  既然菜單具有自動提取命令項文本的功能,肯恩改回好奇其他ICommandSource類是否也具有類似功能,如Button控制項。

  可以使用兩種技術重用命令文本。一種選擇是直接從靜態命令對象中提取文本。XAML可使用Static標記擴展完成這一任務。下麵的示例獲取命令名New,並將它作為按鈕的文本:

<Button Margin="5" Padding="5" Command="New" Content="{x:Static ApplicationCommands.New}"></Button>

  該方法的問題在於,它指示調用命令對象命令對象的ToString()方法。因此,得到的是命令名,而不是命令的文本(對於哪些名稱中包含多個單詞的命令,使用命令文本更好些,因為命令文本包含空格)。雖然解決這一問題,但需要完成更多工作。這種方法還存在一個問題,一個按鈕將同一個命令使用兩次,可能會無意間從錯誤的命令獲取文本)。

  更好的解決方案是使用數據綁定表達式。在此使用的數據綁定有些不尋常,因為他綁定到當前元素嗎,獲取正在使用的Command對象,並提取Text屬性。下麵是非常複雜的語法:

 <Button Margin="5" Padding="5" Command="New" Content="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}"></Button>

  可通過另一種更具想象力的方式使用該技術。例如,可使用一幅小圖像設置按鈕的內容,而在按鈕的工具提示中使用數據綁定表達式顯示命令名:

<Button Margin="5" Padding="5" Command="ApplicationCommands.New" ToolTip="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}">
     <Image Source="redx.jpg"  Stretch="None"></Image>
</Button>

  按鈕的內容可以是形狀,也可以是顯示為縮略圖的點陣圖。

  顯然,這種方法比直接在標記中放置命令文本更麻煩些。然而,如果準備使用不同的語言本地化應用程式,使用這個方法是值得的。當應用程式啟動時,只需要為所有命令設置命令文本即可(如果在創建了命令綁定後改變命令文本,不會產生任何效果。因為Text屬性不是依賴項屬性,所以沒有自動的更改通知來更新用戶界面)。

五、直接調用命令

  並非只能使用實現了ICommandSource介面的類來觸發希望執行的命令。也可以用Execute()方法直接調用來自任何事件處理程式的方法。這時需要傳遞參數值(或null引用)和對目標元素的引用:

ApplicationCommands.New.Execute(null,targetElement);

  目標元素是WPF開始查找命令綁定的地方。可使用包含視窗(具有命令綁定)或嵌套的元素(例如,實際引發事件的元素)。

  也可在關聯的CommandBinding對象中調用Execute()方法。在這種情況下,不需要提供目標元素,因為會自動將公開正在使用的CommandBindings集合的元素設置為目標元素。

this.CommandBindings[0].Command.Execute(null);

  這種方法只使用了半個命令模型。雖然也觸發命令,但不能響應命令的狀態變化。如果希望實現該特性,當命令變為啟用或禁用時,也可能希望處理RoutedCommand.CanExecuteChanged事件進行響應。當引發CanExecuteChanged事件時,需要調用RoutedCommand.CanExecute()方法檢查命令是否處於可用狀態。如果命令不可用。可禁用或改變用戶界面中的部分內容。

六、禁用命令

  如果想要創建狀態在啟用和禁用之間變化的命令,你將體會到命令模型的真正優勢。例如,分析下圖中顯示的單視窗應用程式,它是有菜單、工具欄以及大文本框構成的簡單文本編輯器。該應用程式可以打開文件,創建新的(空白)文檔,以及保存所執行的操作。

 

   在該應用程式中,保持New、Open、Save、SaveAs以及Close命令一直可用是非常合理的。但還有一種設計,只有當某些操作使文本相對於原來的文件發生了變化時才啟用Save命令。根據約定,可在代碼中使用簡單的Boolean值來跟蹤這一細節:

private bool isDirty = false;

  然後當文本發生變化時設置該標誌:

private void txt_TextChanged(object sender, RoutedEventArgs e)
        {
            isDirty = true;
        }

  現在需要從視窗命令綁定傳遞信息,使鏈接的控制項可根據需要進行更新。技巧是處理命令綁定的CanExecute事件。可通過下麵的代碼為該事件關聯事件處理程式:

CommandBinding binding = new CommandBinding(ApplicationCommands.Save);
            binding.Executed += SaveCommand_Executed;
            binding.CanExecute += SaveCommand_CanExecute;
            this.CommandBindings.Add(binding);

  或使用聲明方式:

<Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Save" 
                        Executed="SaveCommand_Executed" 
                        CanExecute="SaveCommand_CanExecute"></CommandBinding>
    </Window.CommandBindings>

  在事件處理程式中,只需要檢查isDirty變數,並相應地設置CanExecuteRoutedEventArgs.CanExecute屬性:

private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = isDirty;
        }

  如果isDirty的值時false,就禁用命令。如果isDirty的值為true,就啟用命令(如果沒有設置CanExecute標誌,就會保持最近的值)。

  當使用CanExecute事件時,還需要理解一個問題,由WPF負責調用RoutedCommand.CanExecute()方法來觸發事件處理程式,並確定命令的狀態。當WPF命令管理器探測到某個確信十分重要的變化——例如,當焦點從一個控制項移到另一個控制項上時,或執行了某個命令後,WPF命令管理器就會完成該工作。控制項還能引發CanExecuteChanged事件以通知WPF重新評估命令——例如,當用戶在文本框中按下一個鍵時會發生該事件。總之,CanExecute事件會被頻繁地觸發,並且不應在該事件的處理程式中使用耗時的代碼。

  然而,其他因素可能影響命令狀態。在當前示例中,為響應其他操作,可能會修改isDirty標誌。如果發現命令狀態未在正確的時間被更新,可強制WPF為所有正在使用的命令調用CanExecute()方法。通過調用靜態方法CommandManager.InvalidateRequerySuggested()完成該工作。然後命令管理器觸發RequerySuggested事件,通知視窗中的命令源(按鈕、菜單項等)。此後命令源會重新查詢它們鏈接的命令並相應地更新它們的狀態。

七、具有內置命令的控制項

  一個輸入控制項可自行處理命令事件。例如,TextBox類處理Cut、Copy以及Paste命令(還有Undo、Redo命令,以及一些來自EditingCommd類的用於選擇文本以及將游標移到不同位置的命令)。

  當控制項具有自己的硬編碼命令邏輯時,為使命令工作不需要做其他任何事情。例如,對於上節示例的簡單編輯器,添加如下工具欄按鈕,就會自動獲取對剪切、複製和粘貼文本的支持:

<ToolBar>
     <Button Command="Cut">Cut</Button>
     <Button Command="Copy">Copy</Button>
     <Button Command="Paste">Paste</Button>
 </ToolBar>

  現在淡季這些按鈕中的任意一個(當文本框具有焦點時),就可以複製、剪切或從剪貼板粘貼文本。有趣的是,文本框還處理CanExecute事件。如果當前未在文本框中選中任何內容,就會禁用剪切和複製命令。當焦點移到其他不支持這些命令的控制項時,會自動禁用所有這三個命令(除非關聯自己的CanExecute事件處理程式以啟動這些命令)。

  該例有一個有趣的細節。Cut、Copy和Paste命令被具有焦點的文本框處理。然而,由工具欄上的按鈕觸發的命令時完全獨立的元素。在該例中,這個過程之所以能夠無縫工作,是因為按鈕被放到工具欄上,ToolBar類提供了一些內置邏輯,可將其子元素的CommandTarget屬性動態設置為當前具有焦點的控制項(從技術角度看,ToolBar控制項一直在關註著其父元素,即視窗,併在上下文中查找最近具有焦點的控制項,即文本框。ToolBar控制項有單獨的焦點範圍(focus scope),並且在其上下文中按鈕是具有焦點的)。

如果在不同容器(不是ToolBar或Menu控制項)中放置按鈕,就不會獲得這些優勢。這意味著除非手動設置CommanTarget屬性,否則按鈕不能工作。為此,必須使用命令目標元素的綁定的表達式。例如,如果文本框被命名為txtDocument,就應該像下麵這樣定義按鈕:

<Button Command="Cut" CommandTarget="{Binding ElementName=txtDocument}">Cut</Button>
<Button Command="Copy" CommandTarget="{Binding ElementName=txtDocument}">Copy</Button>
<Button Command="Paste" CommandTarget="{Binding ElementName=txtDocument}">Paste</Button>

  另一個較簡單的選擇是使用附加屬性FocusManager.IsFocusScope創建新的焦點範圍。當觸發命令時,該焦點範圍會通知WPF在父元素的焦點範圍內查找元素:

<StackPanel FocusManager.IsFocusScope="True">
    <Button Command="Cut">Cut</Button>
    <Button Command="Copy">Copy</Button>
    <Button Command="Paste">Paste</Button>
</StackPanel>

  該方法還有一個附加優點,即相同的命令可應用於多個控制項,不像上個示例那樣對CommandTarget進行硬編碼。此外,Menu和ToolBar控制項預設將FocusManager.IsFocusScope屬性設置為true,但如果希望簡化命令路由行為,不在父元素上下文中查找具有焦點的元素,也可將該屬性設為false。

  在極少數情況下,你可能發現控制項支持內置命令,而你並不想啟用它。在這種情況下,可以採用三種方法禁用命令。

  理想情況下,控制項提供用於關閉命令支持的屬性,從而確保控制項移除這些特性並連貫地調整自身。例如,TextBox控制項提供了IsUndoEnabled屬性,為阻止Undo特性,可將該屬性設置為false(如果IsUndoEnabled屬性為true,Ctrl+Z組合鍵將觸發Undo命令)。

  如果這種做法行不通,可為希望禁用的命令添加新的命令綁定。然後該命令綁定可提供新的CanExecute事件處理程式,並總是響應false。下麵舉一個使用該技術刪除文本框Cut特性支持的示例:

CommandBinding commandBinding=new CommandBinding(ApplicationCommands.Cut,null,SuppressCommand);
txt.CommandBindings.Add(commandBinding);

  而且該事件處理程式設置CanExecute狀態:

private void SupressCommand(object sender,CanExecuteRoutedEventArgs e)
{
  e.CanExecute=false;
  e.Handled=false;    
}

  註意,上面的代碼設置了Handled標誌以阻止文本框自我執行計算,而文本框可能將CanExecute屬性設置為true。

  該方法並不完美。它可成功地為文本框禁用Cut快捷鍵(Ctrl+X)和上下文菜單中的Cut命令。然而,仍會在上下文菜單中顯示處理禁用狀態的該選項。

  最後一種選擇是,使用InputBinding集合刪除觸發命令的輸入。例如,可使用帶阿媽禁用觸發TextBox控制項中的Copy命令的Ctrl+C組合鍵,如下所示:

KeyBinding keyBinding=new KeyBinding(ApplicationCommands.NotACommand,Key.C,ModifierKeys.Control);
txt.InputBinding.Add(keyBinding);

  技巧是使用特定的ApplicationCommands.NotACommand值,該命令什麼都不做,它專門用於禁用輸入綁定。

  當使用這種方法時,仍啟用Copy命令。可通過自己創建的按鈕觸發該命令(或使用文本框的上下文菜單觸發命令,除非也通過將ContextMenu屬性設置為null刪除了上下文菜單)。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 集成MyBatis (1)在pom.xml中添加依賴 <!-- mybatis的起步依賴。包含了mybatis、mybatis-spring、spring-jdbc(事務要用到)的坐標 --> <dependency> <groupId>org.mybatis.spring.boot</groupI ...
  • 慕課網-bennyhuo-新版Kotlin從入門到精通 筆記 微雲:https://share.weiyun.com/81aa12bb98016e200add31fb8e191cdf百度網盤:鏈接:https://pan.baidu.com/s/1IiClTkQwFJgBL2NqlptKbA 提取碼 ...
  • 查詢緩存 一級緩存:同一個sqlSession對象 MyBatis預設開啟一級緩存,如果用同樣的sqlSession對象查詢相同的數據,則會在第一次查詢時向資料庫發送SQL語句,並將查詢的結果放入到SQLSESSION中,後續再次查詢該同樣的對象時,則直接從緩存中查詢該對象即可(即忽略了資料庫的訪問 ...
  • 數據類型(二) 今日內容 1、列表 2、元組 內容回顧和補充 1、電腦基礎 ①硬體:cpu,記憶體,硬碟,主板,網卡 ②操作系統:linux,centos, Ubuntu,redhat windows mac ③解釋器,編譯器 補充:編譯型語言和解釋型語言 編譯型:寫完代碼後,編譯器一次性編譯交給計 ...
  • 在頁面進行後臺資料庫操作的時候,不想 用戶再進行 頁面上的 其他操作,這時候就要 將頁面 遮罩。 1]使用ScreenMask函數 2]JS調用 1]使用ScreenMask函數 ScreenMask.Color:=clGreen; // 顏色 ScreenMask.Enabled:=True; / ...
  • Eclipse是Java的“集成開發環境”(IDE).當然也可作為其它語言的集成開發環境,如C,C++,PHP,和 Ruby 等。 一、Windows下安裝Eclipse 安裝Eclipse前一定要確保電腦上已經裝了JDK。如果未安裝,請前往:http://www.oracle.com/techne ...
  • 更新記錄 【1】2020.02.08 16:37 1.完善內容 正文 我正在看內部類與介面的時候,突然萌生出一個想法:抽象類中能不能嵌套介面呢? 於是我準備試一試: 沒想到,這種寫法竟然被認可 經過一番分析後覺得是有道理的 那麼問題來了:怎麼實現介面呢? 其實這和內部類很像,只要分別實現抽象類與介面 ...
  • 前言: 圖論乃noip之重要知識點,但有點難理解 本人因此吃過不少虧 因為本人實在太弱,所以此篇乃正宗<蒟蒻專屬文章> 正文:(本文僅介紹圖論中的重點、難點,其餘部分略將或不講) 圖一般來說有二種存儲方法:鄰接矩陣和鄰接表 鄰接矩陣: 存儲:拿二維數組來存 for(int i=1;i<=n;++i) ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...