【WPF學習】第六十章 創建控制項模板

来源:https://www.cnblogs.com/Peter-Luo/archive/2020/04/03/12628679.html
-Advertisement-
Play Games

經過數十天的忙碌,今天終於有時間寫博客。 前面一章通過介紹有關模板工作方式相關的內容,同時介紹了FrameWorkElement下所有控制項的模板。接下來將介紹如何構建一個簡單的自定義按鈕,併在該過程中學習有關控制項模板的一些細節。 通過上一章內容,基本Button控制項使用ButtonChrome類繪製 ...


  經過數十天的忙碌,今天終於有時間寫博客。

  前面一章通過介紹有關模板工作方式相關的內容,同時介紹了FrameWorkElement下所有控制項的模板。接下來將介紹如何構建一個簡單的自定義按鈕,併在該過程中學習有關控制項模板的一些細節。

  通過上一章內容,基本Button控制項使用ButtonChrome類繪製其特殊的背景和邊框。Button類使用ButtonChrome類而不使用WPF繪圖圖元的一個原因是,標準按鈕的外觀依賴於幾個明顯的特征(是否被禁用、是否具有焦點以及是否正在被單擊)和其他一些更微妙的因素(如當前Windows主題)。只使用觸發器實現這類邏輯是笨拙的。

  然而,當構建自定義控制項時,可以不用擔心標準化和主題集成(實際上,WPF不像以前的用戶界面技術那樣強調用戶界面標準化)。反而能更需要關註如何創建富有吸引力的新穎控制項,並將他們混合到用戶界面的其他部分。因此,可能不需要創建諸如ButtonChrome的類,而可使用以及學過的元素,設計自給自足的不使用代碼的控制項模板。

一、簡單按鈕

  為應用自定義控制項模板,只需要設置控制項的Template屬性。儘管可定義內聯模板(通過在控制項標簽內部嵌入控制項模板標簽),但這種方法基本沒有意義。這是因為幾乎總是希望為同一控制項的多個皮膚實例重用模板。為適應這種設計,需要將控制項模板定義為資源,並使用StaticResource引用該資源,如下所示:

<Button  Margin="10" Padding="5" Template="{StaticResource ButtonTemplate}"  >
            A Simple Button with a Custom Template
</Button>

  通過這種方法,不僅可以較容易地創建許多自定義按鈕,在以後還可以很靈活地修改控制項模板,而不會擾亂應用程式用戶界面的其餘部分。

  在這個特定示例中,ButtonTemplate資源放在包含視窗的Resource集合中。然而,在實際應用程式中,可能更喜歡使用應用程式資源,具體原因在下一章介紹的“組織模板資源”中進行討論。

  下麵是控制項模板的基本框架:

<Window.Resources>
        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            ...
        </ControlTemplate>
</Window.Resources>

  在上面的控制項模板中設置了TargetType屬性,以明確指示該模板是為按鈕設計的。與樣式類似,這總是一個可以遵循的好約定。在內容控制項(如按鈕)中也需要使用該約定,否則ContentPresenter元素就不能工作。

  要為基本按鈕創建模板,需要自己繪製邊框和背景,然後在按鈕中放置內容。繪製邊框的兩種可能的候選方法是使用Rectangle類和Border類。下麵的示例使用Border類,將具有圓角的桔色輪廓與引入註目的紅色背景和白色文本結合在一起:

 <Window.Resources>
        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2"
                    Background="Red" TextBlock.Foreground="White" Name="Border">
            ...
            </Border>
        </ControlTemplate>
</Window.Resources>

  在此主要關註背景,但仍需要一種方法顯示按鈕內容。在以前的學習中,可能還記得Button類在其他控制項模板中包含了一個ContentPresenter元素。所有內容控制項都需要ContentPresenter元素——它是標示“在此插入內容”的標記器,告訴WPF在何處保存內容:

<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2"
                    Background="Red" TextBlock.Foreground="White" Name="Border">
                  <ContentPresenter RecognizesAccessKey="True"></ContentPresenter>
            </Border>
</ControlTemplate>

  在ContentPresenter元素將RecognizesAccessKey屬性設置為true。儘管這不是必需的,但可確保按鈕支持訪問鍵——具有下劃線的字母,可以使用該字母快速觸發按鈕。對於這種情況,如果按鈕具有文本Click_Me,那麼當用戶按下Alt+M組合鍵時會觸發按鈕(在標準的Windows設置中,下劃線是隱藏的,並且只要按下Alt鍵,訪問鍵(在此是M鍵)就會具有下劃線)。如果為將RecongnizesAccessKey屬性設置為true,就會忽略該細節,並且任何下劃線都將被視為普通的下劃線,並作為按鈕內容的一部分進行顯示。

二、模板綁定

  該例還存在一個小問題。現在為按鈕添加的標簽將Margin屬性的值指定為10,並將Padding屬性的值指定為5。StackPanel控制項關註的是按鈕的Margin屬性,但忽略了Padding屬性,使按鈕的內容和側邊擠壓在一起。此處的問題是Padding屬性不起作用,除非在模板中特別註意它。換句話說,模板負責檢索內邊距值並使用該值在內容周圍插入額外的空白。

  幸運的是,WPF專門針對該目的設計了一個工具:模板綁定。頭能改過使用綁定模板,模板可從應用模板的控制項中提取一個值。在本例中,可使用模板綁定檢索Padding屬性的值,並使用該屬性值在ContentPresenter元素周圍創建外邊距:

<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2"
                    Background="Red" TextBlock.Foreground="White" Name="Border">
                  <ContentPresenter RecognizesAccessKey="True"  Margin="{TemplateBinding Padding}"></ContentPresenter>
            </Border>
</ControlTemplate>

  這樣就會得到所期望的效果,在邊框和內容之間添加了一些空白。如下圖顯示了新的簡單按鈕。

   模板綁定和普通的數據綁定類似,但它們的量級更輕,因為它們是專門針對在控制項模板中使用而設計的。它們只支持單向數據banding(換句話說,它們是從控制項向模板傳遞信息,但不能從模板向控制項傳遞信息),並且不能用於從Freezable類的派生類的屬性中提取信息。如果遇到模板綁定不生效的情形,可改用具有完整功能的數據綁定。

  預計需要哪些模板綁定的唯一方法是檢查預設控制項模板。如果查看Button類的控制項模板,就會發現在模板綁定的使用方法上與自定義模板是完全相同的——獲取為按鈕指定的內邊距,並將它轉換成ContentPresenter元素周圍的外邊距。還會發現標準按鈕模板包含另外幾個模板綁定,如HorizontalAlignment、VerticalAlignment以及Background,這個簡單的自定義模板中沒有使用這些模板綁定。這意味著如果為控制項設置了這些屬性,對於這個簡單的自定義模板來說,這些設置是沒有效果。

  在許多情況下,可不考慮模板綁定。實際上,如果不准備使用屬性或者不希望修改模板,就不必綁定屬性。例如,當前得簡單按鈕將用於文本的Foreground屬性設置為白色並忽略為Background屬性設置的任何值是合理的,因為前景色和背景色是該該按鈕可視化外觀的固有部分。

  可能選擇避免模板綁定的另一個原因是——控制項不能橫好地支持它們。例如,如果為按鈕設置了Background屬性,可能註意到當按鈕被按下時不會連貫地處理該背景色(實際上,這時該背景色消失了,並且被按下的預設外觀替換了)。該例中的自定義模板與此類似,儘管還沒有任何滑鼠懸停和滑鼠單擊行為,但一旦添加這些細節,就會希望完全控制按鈕的顏色以及在不同狀態下它們的變化。

三、改變屬性的觸發器

  如果測試上一節創建的按鈕,就會發現它令人十分失望。本質上,它不過是一個紅色的圓角矩形——當在它上面移動滑鼠或單擊滑鼠時,其外觀沒有任何反應。按鈕只是無動於衷,呆在那兒不動。

  可通過為控制項模板添加觸發器來方便地解決這個問題。當一個屬性發生變化時,可使用觸發器改變另一個或多個屬性。在按鈕中至少希望響應IsMouseOver和IsPressed屬性。下麵的標記是控制項模板的修改版本。當這些屬性發生變化時,會改變控制項的顏色:

<Window.Resources>
        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2"
                    Background="Red" TextBlock.Foreground="White" Name="Border">
                <ContentPresenter RecognizesAccessKey="True" 
                                      Margin="{TemplateBinding Padding}"></ContentPresenter>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="Border" Property="Background" Value="DarkRed"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="Border" Property="Background" Value="IndianRed"/>
                    <Setter TargetName="Border" Property="BorderBrush" Value="DarkKhaki"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Resources>

  為使該模板能夠工作,還要進行另一項修改。已為Border元素指定一個名稱,並且該名稱被用於設置每個設置其的TargetName屬性。通過這種方法,設置器能更新在模板中指定的Border元素的Background和BorderBrush屬性。使用名稱是確保更新模板特定部分的最容易方法。可創建一條元素類型規則來影響所有Border元素(原因是已經知道在按鈕模板中只有一個邊框),但如果在以後改變模板,這種方法更清晰,也跟更靈活。

  在所有按鈕(以及其他大部分控制項)中還需要另一個元素——焦點指示器。雖然無法改變現有的邊框以天津焦點效果,但是可以很容易地天津另一個元素以顯示是否具有焦點,並且可以簡單地使用觸發器根據Button.IsKeyboardFocused屬性顯示或隱藏該元素。儘管可使用許多方法創建焦點效果,但下麵的示例值只天津了一個具有虛線邊框的透明的Rectangle元素。Rectangle元素不能包含子內容,從而需要確保Rectangle元素和其餘內容相互重疊。完成該操作最容易得方法是,使用只有一個單元格的Grid空哦關鍵來封裝Rectangle元素和ContentPresenter元素,這兩個元素位於同一個單元格中。

  下麵是修改後的支持焦點的的模板:

<Window.Resources>
        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2"
                    Background="Red" TextBlock.Foreground="White" Name="Border">
                <Grid>
                    <Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Black"
                               StrokeThickness="1" StrokeDashArray="1 2"
                               SnapsToDevicePixels="True"></Rectangle>
                    <ContentPresenter RecognizesAccessKey="True" 
                                      Margin="{TemplateBinding Padding}"></ContentPresenter>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="Border" Property="Background" Value="DarkRed"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="Border" Property="Background" Value="IndianRed"/>
                    <Setter TargetName="Border" Property="BorderBrush" Value="DarkKhaki"/>
                </Trigger>
                <Trigger Property="IsKeyboardFocused" Value="True">
                    <Setter TargetName="FocusCue" Property="Visibility" Value="Visible"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Resources>

  設置器在此使用TargetName屬性茶盅需要改變的元素。

  下圖顯示了使用修改版模板的三個按鈕。第二個按鈕當前具有焦點(通過虛線矩形表示),而滑鼠正好懸停在第三個按鈕上。

   為了潤色該按鈕,還需要另一個觸發器。當按鈕的IsEnable屬性變為false是,該觸發器改變按鈕的背景色(也可改變文本的前景色):

<Window.Resources>
        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2"
                    Background="Red" TextBlock.Foreground="White" Name="Border">
                <Grid>
                    <Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Black"
                               StrokeThickness="1" StrokeDashArray="1 2"
                               SnapsToDevicePixels="True"></Rectangle>
                    <ContentPresenter RecognizesAccessKey="True" 
                                      Margin="{TemplateBinding Padding}"></ContentPresenter>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="Border" Property="Background" Value="DarkRed"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="Border" Property="Background" Value="IndianRed"/>
                    <Setter TargetName="Border" Property="BorderBrush" Value="DarkKhaki"/>
                </Trigger>
                <Trigger Property="IsKeyboardFocused" Value="True">
                    <Setter TargetName="FocusCue" Property="Visibility" Value="Visible"/>
                </Trigger>
                <Trigger Property="IsEnabled" Value="False">
                    <Setter TargetName="Border" Property="TextBlock.Foreground" Value="Gray" />
                    <Setter TargetName="Border" Property="Background" Value="MistyRose" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Resources>

  為確保該規則優先與其他相衝突的觸發器設置,應當在觸發器列表的末尾定義它。這樣,不管IsMouseOver屬性是否為true,IsEnabled屬性觸發器都具有優先權,並且按鈕保持未激活狀態的外觀。

  下圖設置按鈕不可用時所示的圖片:

四、使用動畫的觸發器

  觸發器並非局限於設置屬性。當特定屬性發生變化時,還可以使用事件觸發器運行動畫。

  乍一看,這好像有些曲折,但除了最簡單的WPF控制項外,觸發器實際上時其他所有WPF控制項的關鍵要素。例如,考慮到目前位置研究過的按鈕。目前,當滑鼠移到按鈕上時,該按鈕立即從一種顏色切換到另一種顏色。然而,更時髦的按鈕可能使用一個非常短暫的動畫從一種顏色昏倒到其他顏色,從而創建微妙但優雅的效果。類似地,按鈕可使用動畫改變焦點提示矩形的透明度,當按鈕獲取焦點時將快速淡入到試圖中,而不是驟然顯示。換句話說,事件觸發器允許控制項更通暢地一點點從一個狀態改變到另一個狀態,從而進一步潤色其外觀。

  下麵是重新設計的按鈕模板,當滑鼠懸停在按鈕上時,該模板使用觸發器實現按鈕顏色脈衝效果(在紅色和藍色之間不斷切換)。當滑鼠離開時,使用一個單獨的持續1秒得動畫,將按鈕背景返回到其正常顏色:

<Window.Resources>
        <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
            <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2"
                    Background="Red" TextBlock.Foreground="White" Name="Border">
                <Grid>
                    <Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Black"
                               StrokeThickness="1" StrokeDashArray="1 2"
                               SnapsToDevicePixels="True"></Rectangle>
                    <ContentPresenter RecognizesAccessKey="True" 
                                      Margin="{TemplateBinding Padding}"></ContentPresenter>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <EventTrigger RoutedEvent="MouseEnter">
                    <BeginStoryboard>
                        <Storyboard>
                            <ColorAnimation Storyboard.TargetName="Border"
                                Storyboard.TargetProperty="Background.Color" 
                                  To="Blue" AutoReverse="True" RepeatBehavior="Forever" Duration="0:0:1"></ColorAnimation>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
                <EventTrigger RoutedEvent="MouseLeave">
                    <BeginStoryboard>
                        <Storyboard>
                            <ColorAnimation Storyboard.TargetName="Border"
                                            Storyboard.TargetProperty="Background.Color"
                                            Duration="0:0:0.5"></ColorAnimation>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
                <Trigger Property="IsPressed" Value="True">
                    <Setter TargetName="Border" Property="Background" Value="IndianRed" />
                    <Setter TargetName="Border" Property="BorderBrush" Value="DarkKhaki" />
                </Trigger>
                <Trigger Property="IsKeyboardFocused" Value="True">
                    <Setter TargetName="FocusCue" Property="Visibility" Value="Visible" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Resources>

  可使用兩種等價的方法添加滑鼠懸停動畫——創建響應MouseEnter和MouseLeave事件的事件觸發器,或創建當IsMouseOver屬性發生變化時添加進入和退出動作的屬性觸發器。最終效果圖如下所示:

  該例使用兩個ColorAnimation對象來改變按鈕。下麵是可能希望使用EventTrigger驅動的動畫只需的其他一些任務:

  •   顯示或隱藏元素。為此,需要改變控制項模板中元素的Opacity屬性。
  •   改變形狀或位置。可使用TranslateTransform對象調整元素的位置(例如,稍偏移元素使按鈕具有已被按下的感覺)。當用戶將滑鼠移到元素上時,可使用ScaleTransform或RotateTransform對象稍微旋轉元素的外觀。
  •   改變光照或著色。為此,需使用改變繪製背景色畫刷的動畫。可使用ColorAnimation動畫改變SolidBrush畫刷中的顏色,也可動態顯示更複雜的畫刷以得到更高級的效果。例如,使用LinearGradientBrush畫刷中的一種顏色(這是預設按鈕控制項模板執行的操作),也可改變RadialGradientBrush畫刷的中心點。

 


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

-Advertisement-
Play Games
更多相關文章
  • 我的LeetCode:https://leetcode cn.com/u/ituring/ 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 36. 有效的數獨 題目 判斷一個 9x9 的數獨是否有效。只 ...
  • 時間 java8以前使用的時間很多方法都已經廢棄了,而且不是線程安全的,java8提供了一系列的時間類,這些時間類都是線程安全的 LocalDate、LocalTime、LocalDateTime 這三個關於時間的類在使用上都類似 時間戳 Duration獲取時間間隔 Peroid獲取日期間隔 Te ...
  • 一、什麼是異常處理 異常處理從字面的意思來講就是一種發生在 java 程式中的錯誤並對其處理,但對程式員而言,異常的處理不單單的簡單去處理異常,就 ok 了,還有眾多的概念和使用異常的方式方法需要掌握 異常在 java 中分有三種: 1、編譯時異常(受檢異常) > 這種異常發生的概率很高; 2、運行 ...
  • 今天給大家分享五個開源的博客系統,可用於免費創建自己的博客,也有大量精美的模板使用,也就是說你不懂技術,用了這五個開源系統也能創建自己的博客,至於創建博客的好處,想必大家都知道,可用戶記錄生活,分享技術,也能鍛煉一下自己的文筆。 一.wordpress wordpress老牌博客系統,開源免費,有大 ...
  • 1、 Docker虛擬化鏡像製作實戰一 1)Docker commit可以實現容器提交為新的鏡像,提交的鏡像自動進入當前系統的鏡像列表(容器|鏡像內容是完整的); docker commit 7ec01484db55 centos7:v1 docker images 2)Docker export可 ...
  • 我的LeetCode:https://leetcode cn.com/u/ituring/ 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 8. 字元串轉換整數 (atoi) 題目 請你來實現一個 at ...
  • tensorflow入門 tensorflow是由谷歌人工智慧團隊谷歌大腦(Google Brain)開發和維護的一個深度學習框架。 入門案例 以下是一個加法運算的示例,我們通過tensorflow構建一個tensorflow graph ,然後通過session 會話去運行該graph,我們通過a ...
  • title: Java基礎語法(11) 面向對象之關鍵字 blog: "CSDN" data: "Java學習路線及視頻" 1.this 1. this是什麼 它在方法內部使用,即這個方法所屬對象的引用; 它在構造器內部使用,表示該構造器正在初始化的對象。 this可以作為一個類中構造器相互調用的特 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...