[转] 使用模板自定义 WPF 控件
?????????????????????????????????????????????????????????????????????????????????????????????????????????????????? Charles Petzold
Demo
隨著 Windows Vista? 和 Microsoft? .NET Framework 3.0 的發布,出現了許多可供開發人員學習、討論和使用的新技術。新的工具、庫和范例將改變構建托管應用程序的方法,帶來了巨大的可能性。 我們推出的這一新的每月專欄將介紹用于開發應用程序的基本技術。您所熟知的業內專家將輪番與您探討 Windows? Presentation Foundation、Windows Communication Foundation 和 Windows Workflow Foundation。我們開始吧。
在 Windows 中自定義現有控件通常需要四個步驟。首先需要有靈感。然后需要進行研究和探索。這一過程難免會有困難。而最終發現需要完全重寫。由于很難訪問到將控件的可視部分與其功能相關聯的代碼,因而通常無法自定義控件。此代碼對于控件至關重要,因此必須完全接受它,或者完全跳過并替換它。
Windows Presentation Foundation(作為 .NET Framework 3.0 的一部分提供)的開發人員已經不可避免地感受到了自定義控件的艱辛。他們提出了一個令人耳目一新的強大解決方案,我們稱之為“模板”。
Windows Presentation Foundation 模板不僅簡單,而且功能強大,讓我能迅速理解其概念。我很快就理解了 Windows Presentation Foundation 樣式(通常容易與模板混淆),但是模板需要我們花費更多的時間去了解。
Windows Presentation Foundation 中的每個具有可視外觀的預定義控件也都具有一個完全定義了其外觀的模板。此模板是類型 ControlTemplate(設置為由 Control 類定義的 Template 屬性)的一個對象。
在應用程序中使用 Windows Presentation Foundation 控件時,您可以用自己設計的模板替換該默認模板。您可以保留控件的基本功能(包括所有鍵盤和鼠標操作的處理),但您可以為其設置完全不同的外觀。這就是在提到 Windows Presentation Foundation 控件時所說的“變臉”(不太文雅)的含義。控件都有默認外觀,但是此外觀并不是固定地對應控件的內部功能。
用代碼編寫模板不太合適。而使用可擴展應用程序標記語言 (XAML) 進行編寫會更容易,因為模板完全可以用 XAML 來表示,從而能夠借助于可視化設計工具進行設計。 如果您要編寫像現有 Windows Presentation Foundation 控件一樣工作但外觀不同的自定義控件,請立即停止!很可能僅使用一個模板即可得到這樣的控件。
可下載的源代碼包含七個獨立的 XAML 文件,我將在整個專欄中進行討論。在制作此專欄的過程中,未編譯任何 C# 代碼!如果您已安裝 .NET Framework 3.0 SDK,則可以使用我的《Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation》一書中的類似程序 XAMLPad 或 XAML Cruncher 編輯這些文件。
Windows Presentation Foundation 支持用于顯示控件內容的其他類型的模板,但本專欄將僅討論類型 ControlTemplate 的對象。
轉儲默認值
Windows Presentation Foundation 中的每個具有可視外觀的預定義控件都有一個默認模板。如果您對編寫自定義模板感興趣,請研究這些默認值。
我的書中第 25 章的 DumpControlTemplate 顯示了控件的簡單 XAML 格式的默認 Template 屬性。(您可以下載源代碼。)如果希望自己編寫模板轉儲程序,以下是執行關鍵步驟的代碼:
XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true; settings.IndentChars = new string(' ', 4); settings.NewLineOnAttributes = true; StringBuilder strbuild = new StringBuilder(); XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings); XamlWriter.Save(ctrl.Template, xmlwrite);此代碼假設 ctrl 是 Control 派生類的實例,并且已在屏幕上呈現 ctrl。(這是我的個人經驗,否則 Template 屬性將為 null。)如果 Template 屬性為 null,XamlWriter.Save 調用將引發異常,因為該值將用于某些無可視外觀的控件。在此代碼的末尾,對 strbuild 調用 ToString 可以提供包含模板的完整 XAML 文檔。
此默認模板中的 XAML 可能比您編寫的 XAML 冗長一些。例如,您可能會編寫:
<Trigger Property="IsEnabled" Value="False">但是在由 XamlWriter.Save 生成的 XAML 文件中,您將看到如下標記:
<Trigger Property="UIElement.IsEnabled"><Trigger.Value><s:Boolean>False</s:Boolean></Trigger.Value>s 前綴是用 .NET 系統命名空間的 xmlns 命名空間聲明定義的。
可視樹
讓我們首先來看一下一個非常簡單但是很有用的控件模板:CheckBox 模板。假設您希望得到外觀比標準 CheckBox 更生動一些的控件。您可能希望用戶所選擇的選項顯示為大的綠色復選標記或大的紅色 X,并顯示在 CheckBox 的全部內容上。文件 BigCheckCheckBox.xaml 顯示了一個這樣的 CheckBox 模板。
在 XAML 文件中,模板是類型 ControlTemplate 的元素。在小型的 Windows Presentation Foundation 程序中,通常在 XAML 文件根元素的 Resources 部分定義 ControlTemplate 元素。對于大型應用程序或定義多個應用程序共用的模板時,ControlTemplate 元素位于自己的具有 ResourceDictionary 根元素的 XAML 文件中。
無論是哪種情況,ControlTemplate 元素通常均包含三個部分。首先是 Resources 部分(可選),定義了模板所使用的樣式或畫筆。(BigCheckCheckBox.xaml 的模板沒有 Resources 部分。)然后,模板定義了模板的可視樹。此樹以元素或其他控件的布局的形式描述所需要的控件外觀。模板包含一個 Triggers 部分,用于指明在響應控件屬性更改時,可視樹元素應如何變化。圖 1 和圖 2 顯示了自定義 CheckBox 的大部分 ControlTemplate 元素。圖 1 中的代碼顯示了可視樹,圖 2 接著顯示了 ControlTemplate 元素的 Triggers 部分。
圖 1 顯示了 BigCheckBox.xaml 的摘要,說明了 ControlTemplate 開始標記和可視樹的使用。由于此模板是資源,因此它必須包含一個帶有其資源名稱的 x:Key 屬性。您會注意到其中使用了 TargetType 屬性,盡管不是必需使用,但這么做便無需在每個屬性前面加上類名,從而簡化了模版的其余部分。
此可視樹的頂級元素為一個 Border(邊框),后者可以作為單個子元素的父元素。在本示例中,此 Border 的三個屬性均使用 TemplateBinding 標記擴展進行指定。TemplateBinding 用于將可視樹中的元素屬性綁定到控件屬性。
ContentPresenter 元素中的 TemplateBinding 表達式更有趣,它可以將按鈕和 ContentControl 派生而來的其他控件的內容格式化。正是 ContentPresenter 允許在 Button 或 CheckBox 內顯示幾乎任何內容。請注意,TemplateBinding 用于將 ContentPresenter 的 Margin 屬性設置為正在模板化的 CheckBox 控件的 Padding 屬性。Margin(邊緣)是元素外部的額外空間;Padding(留白)是控件中未被其內容占據的空間,據此您可以看出此綁定所隱含的原理。
ContentPresenter 與另一個邊框一起顯示在單個單元格的網格面板內。網格單元格通常用于容納必須分層顯示的多個子項。因此,第二個邊框顯示在 ContentPresenter 頂部。為此邊框指定包含 50% 不透明畫筆(其中包含紅色 X 標記)的背景。請注意,應為呈現紅色 X 標記的 Path 元素指定一個路徑名。模板的 Triggers 部分會引用此名稱。
模板觸發器
ControlTemplate 的最后部分通常專用于 Trigger 元素。對可視樹的組成元素的這些屬性更改是基于控件的屬性更改進行的。圖 2 顯示了自定義 CheckBox 模板的 Triggers 部分的很大部分。
默認情況下,CheckBox 的 IsChecked 屬性為 false 并且 CheckBox 顯示紅色 X 標記。第一個 Trigger 元素指示在 IsChecked 變為 true 時,名為 path 的元素的 Data 屬性和 Stroke 屬性應設置為顯示綠色復選標記。下一個 Trigger 元素通過顯示一個藍色問號來反映 IsChecked 為 null 值時的情況(在 CheckBox 設置為三態操作時會發生)。當 IsEnabled 屬性為 false 時,最后一個 Trigger 元素將控件的 Foreground 屬性更改為灰色畫筆。(BigCheckCheckBox.xaml 文件具有一個額外的 Trigger 元素,此元素在控件獲得輸入焦點時將在控件內容周圍顯示虛線。)
模板的 Triggers 部分可以非常寬泛。通常還會在其中包含 IsMouseOver 屬性的 Trigger 元素,因此當鼠標從控件上面經過時,控件會做出響應。
然后您可以在創建 CheckBox 的元素中引用模板,例如:
<CheckBox Template="{StaticResource templateBigCheck}" ...圖 3 顯示了由 BigCheckCheckBox.xaml 文件顯示的三個 CheckBox 控件。兩個包含文本內容,第三個(其 IsThreeState 屬性設置為 true,IsChecked 屬性設置為 null)包含位圖。
圖 3?使用自定義模板的 CheckBox 控件 (單擊該圖像獲得較小視圖)
圖 3?使用自定義模板的 CheckBox 控件 (單擊該圖像獲得較大視圖)
改進
您可能會懷疑模板概念應用于較復雜控件的能力。畢竟,某些控件具有可動部分并更多地涉及與用戶的交互。為了消除您的此類疑慮,我們將在本專欄的其余部分中介紹一下從 RangeBase 派生而來的三個控件的模板。它們是 ProgressBar(進度條)、ScrollBar(滾動條)和 Slider(滑塊)。
要正確運行,更復雜控件的模板需要具有特定名稱的某些類型的元素。例如,ProgressBar 可視樹必須具有類型 FrameworkElement(或由 FrameworkElement 派生)的兩個元素,名為 PART_Track 和 PART_Indicator。這兩個元素被稱為模板的“已命名部件”。這些名稱可以在 ProgressBar 類的 SDK 文檔中找到,它們是 TemplatePart 屬性。如果您的模板不包括具有這些名稱的元素,控件將無法正確運行。
如果 ProgressBar 以其默認的水平方向顯示,則控件的內部邏輯將 PART_Indicator 元素的 Width 屬性設置為 PART_Track 元素的 ActualWidth 屬性的一個分數。該分數取決于 ProgressBar 的 Minimum、Maximum 和 Value 屬性。如果 ProgressBar 為豎直方向,則使用這兩個元素的 Height 和 ActualHeight 屬性。
實際上,兩種不同方向的 ProgressBar 控件對應兩個默認模板。(對于 ScrollBar 和 Slider,也是如此。)如果希望您的新 ProgressBar 對兩個方向均支持,則應單獨編寫兩個模板,并在同樣為 ProgressBar 定義的 Style 元素的 Triggers 部分中選擇這兩個模板。
圖 4 是 BareBonesProgressBar.xaml 文件的摘要,該文件顯示了一個完整的 ProgressBar 元素,其中 ControlTemplate 對象作為該元素的屬性。該文件還包含一個 ScrollBar,用于通過綁定來測試 ProgressBar。在操作 ScrollBar 時,藍色矩形(PART_Indicator 元素)的寬度可以變化,范圍為零到紅色矩形(PART_Track 元素)的寬度。請注意,盡管 BareBonesProgressBar.xaml 為 PART_Track 矩形指定了明確的寬度,但實際上您應在任何可能的情況下避免使用明確的尺寸。
指示元素通常在跟蹤元素之內,而且您可能會考慮將 Border 元素用于跟蹤。但是請注意:如果為該 Border 指定的 BorderThickness 不為零,則其粗細度將屬于 Border 元素總寬度的一部分,邊框內的寬度將會略微小一些。如果出于顯示目的而采用了非零邊框厚度的 Border,則應為跟蹤元素指定位于第一個邊框內的粗細度為零的 Border,并將其他內容放入第二個 Border 內以用作指示器。(如果使用 Border 元素而不使用其邊框或背景屬性,請考慮使用 Decorator,Decorator 是 Border 的無邊框、無背景的祖先類。)
圖 5 顯示了 ThermometerProgressBar.xaml 文件中的一個更廣泛的 ControlTemplate 對象。此模板具有 Resources 部分但沒有 Triggers 部分。盡管可視樹將一些明確坐標用于邊框和角,但未定義 ProgressBar 的總尺寸。這一職責落到了任何使用如下模板定義 ProgressBar 的標記的身上,例如:
<ProgressBar Template="{StaticResourcetemplateThermometer}" Orientation="Vertical" Minimum="0" Maximum="100"Width="50" Height="350" ...得到的 ProgressBar 如圖 6 所示。請注意,此 ProgressBar 的 Orientation(方向)屬性必須設置為 Vertical(豎直),否則將無法正常運行。您可以記住在使用此模板時再設置 Orientation 屬性,也可以為 ProgressBar 定義一個既能設置 Orientation 又能引用模版的樣式。(稍后即向您介紹此方法的示例。)
圖 6?
本專欄的可下載代碼中還包括 SpeedometerProgressBar.xaml,用于產生更多如圖 7 所示的基本 ProgressBar。
圖 7?速度計進度條
這需要一點技巧。此模板包括兩個不可見矩形,它們沒有高度、填充顏色或筆畫顏色,如下所示:
<Rectangle Name="PART_Track" Width="180" /><Rectangle Name="PART_Indicator" />PART_Track 元素的寬度設置為半圓的度數。紅色指針的 Polygon 元素取決于 RotateTransform,RotateTransform 的 Angle 屬性與 PART_Indicator 的 ActualWidth 綁定在一起,例如:
<RotateTransform Angle="{Binding ElementName=PART_Indicator, Path=ActualWidth}" />剖析 ScrollBar
ProgressBar 和 ScrollBar 均派生自抽象的 RangeBase 類。RangeBase 的全部內容包括:Value 屬性、ValueChanged 事件以及 Minimum、Maximum、SmallChange 和 LargeChange 的定義。ProgressBar 只采用了 Minimum、Maximum 和 Value 屬性。
ScrollBar 比 ProgressBar 更復雜,它以多種方式響應用戶輸入。此行為分布在標準 ScrollBar 的五個子控件中。可移動的縮略圖是名為 Thumb 的控件。直接位于縮略圖兩側的是兩個通常用于執行 Page Up 和 Page Down 命令的 RepeatButton 控件。(RepeatButton 類似于常規的 Button,不同的是,它以多次 Click 事件響應持續的鼠標按壓。)在 ScrollBar 的 Value 屬性更改時,這兩個 RepeatButton 控件的大小也會發生更改,有時,其中的一個會縮小為零。在 ScrollBar 的兩個端點還有兩個標有箭頭、大小固定的 RepeatButton 控件,通常用于執行 Line Up 和 Line Down 命令。
Thumb 和中間的 RepeatButton 控件是 Track 元素的子項,Track 元素負責它們的交互、移動和大小更改。ScrollBar 的大部分核心功能和復雜的邏輯都由 Track 處理。
在為 ScrollBar 定義 ControlTemplate 時,可視樹需要包含一個名為 PART_Track 的 Track 元素。這是 ScrollBar 模板唯一需要命名的部分。
Track 不是控件,而是從 FrameworkElement 派生而來,并且由于 Template 屬性是由 Control 定義的,因此 Track 沒有 Template 屬性。盡管不能為 Track 提供模板,但可以為組成 Track 元素的三個控件提供模板。
Track 定義了與它的三個子項相對應的三個屬性:Thumb(類型為 Thumb)、DecreaseRepeatButton(類型為 RepeatButton)和 IncreaseRepeatButton(類型也為 RepeatButton)。默認情況下,這三個屬性為 null,這意味著模板的可視樹應包含這三個控件的明確定義。如果您希望在 ScrollBar 的兩端提供兩個按鈕,可視樹應包括用于這兩個按鈕的元素。您可以為這五個子控件指定它們自己的模板,或者使用現有模板而僅分配某些屬性。
按鈕命令
標準 ScrollBar 模板中的四個 RepeatButton 控件必須能夠與 ScrollBar 的內部邏輯進行通信。通過使用被 ScrollBar 類定義為靜態只讀字段的預定義 RoutedCommand 對象可實現通信。ScrollBar 定義了至少 17 個這樣的靜態只讀字段,這些字段對 ScrollBar 可以執行的各種操作做出響應。其中的某些命令與 ScrollBar 上下文菜單一起使用;某些命令用于被用作 ScrollViewer 控件的一部分的 ScrollBar。還有一些命令,特別是以單詞 Line 和 Page 開頭的命令,用于模板。
圖 8 顯示了 NoFrillsScrollBar.xaml 文件中的 ScrollBar 模板。 Track 元素和位于端點處的兩個 RepeatButton 控件可在 Grid 面板中進行組織。Track 元素的三個屬性設置為兩個其他 RepeatButton 控件和一個 Thumb。
這是全功能的 ScrollBar,但是正如您在圖 9 所見的那樣,這四個 RepeatButton 控件看起來像常規按鈕。兩端的兩個按鈕原來很小,所以我增大了它們的尺寸,方法是將它們的內容設置為 Wingdings 字體中的指示方向的手形字符。此外,五個控件的所有屬性中,除了 Command 屬性和 Grid.Column 附加屬性之外,其他屬性均未設置。
圖 9?基于圖 8 的 ScrollBar (單擊該圖像獲得較小視圖)
圖 9?基于圖 8 的 ScrollBar (單擊該圖像獲得較大視圖)
圖 8 中的模板用于水平 ScrollBar。豎直方向將需要網格中的三個行而不是三個列,并將引用包括字 Up 和 Down 而非 Left 和 Right 的 ScrollBar 類中的 RoutedCommand 字段。
默認的 ScrollBar 模板使用 Microsoft.Windows.Themes 命名空間的 ScrollChrome 類來顯示它的某些控件。您還會注意到,模板看起來好像比需要的長度要長出許多,這是因為可視樹中控件的很多屬性是通過樣式而非特性定義的。例如,在可以使用如下代碼的地方:
<RepeatButton IsFocusable="False" ... 您會看到以下代碼: <RepeatButton ... ><RepeatButton.Style><Style TargetType="RepeatButton">...<Setter Property="UIElement.IsFocusable"><Setter.Value><s:Boolean>False</s:Boolean></Setter.Value></Setter>...我想默認的模板是包含此類標記的,因為這些屬性最初是在模板的 Resources 部分的 Style 元素中定義的。但是,在編寫自己的模板時,您可以直接在元素中設置屬性,知道這一點很重要。
默認模板將每個 RepeatButton 的 IsFocusable 和 IsTabStop 屬性設置為 false,在嘗試使用 NoFrillsScrollBar.xaml 時,您將明白為什么。單擊最左側的按鈕。現在按幾次 Tab 鍵。您將發現輸入焦點從 RepeatButton 轉移到了 RepeatButton,這當然是不應該發生的。
由于模板中有四個 RepeatButton 控件,最簡單的方法可能是使用一個 Style 元素為它們設置統一屬性。您可以將以下標記添加到 NoFrillsScrollBar.xaml 中緊跟 ControlTemplate 開始標記的位置,以解決焦點問題:
<ControlTemplate.Resources><Style TargetType="{x:Type RepeatButton}"><Setter Property="Focusable" Value="False" /><Setter Property="IsTabStop" Value="False" /></Style> </ControlTemplate.Resources>您還可以使用 ControlTemplate 的 Resources 部分,來為組成 ScrollBar 可視樹的子控件定義屬性。我在 SpringLoadedScrollBar.xaml 文件中進行了此操作。圖 10 顯示了該模板的可視樹部分。
請注意,模板中的四個 RepeatButton 元素將 Foreground 屬性設置為 ScrollBar 本身的 Foreground 屬性,并設置之前在 Resources 部分定義的 Template。其他模板(圖 10 中未顯示)使用 RepeatButton 的 Foreground 屬性為其可視化元素著色。圖 11 顯示了兩個使用此模板但具有不同 Foreground 設置的 ScrollBar 控件。
圖 11?使用不同的 Foreground 設置 (單擊該圖像獲得較小視圖)
圖 11?使用不同的 Foreground 設置 (單擊該圖像獲得較大視圖)
我將 DropShadowBitmapEffect 元素添加到了這四個按鈕。實際上,我非常喜歡這個效果,因此決定添加一個 Triggers 部分,以便在鼠標經過按鈕時出現一個額外的陰影。
假設這個像是安裝了彈簧的 ScrollBar 的模板為水平方向,當高度為與設備無關的 50 個單位時,效果最佳。這些設置并不是模板自身的一部分,模板中實在沒有它們的位置。您可能會考慮將它們放在 Style 定義中,我稍后會對這種做法進行說明。
ScrollBar 中 Thumb 的大小取決于 ScrollBar ViewportSize 屬性,通常用于顯示當前查看的文檔的比例。我沒有找到不通過設置 ViewportSize 而更改 Thumb 大小的方法。如果需要較大的 Thumb,您應為 Slider 編寫模板,實際上您可以編寫模板,使 Slider 看起來更像 ScrollBar。
創建三維滑塊
與 ScrollBar 不同,默認的 Slider 控件的兩端并沒有兩個小的更改按鈕。但是您可以將此類控件添加至 Slider 模板。Slider 將六個 RoutedCommand 對象定義為只能獲取的靜態屬性,包括 DecreaseSmall、IncreaseSmall、DecreaseLarge 和 IncreaseLarge。Slider 也可以在一側或兩側顯示刻度線(可選)。
查看默認 Slider 模板時首先需會注意到的可能是,模板長度超過一千行,這是默認 ScrollBar 模板長度的三倍還多。部分原因是由于 Slider 模板并不依賴于 Microsoft.Windows.Themes 中的類。而是在模板內部生成所有對象。
此模板定義了不同的縮略圖形狀,以用于刻度線只顯示在 Slider 的一側時的情況,此模板包含很多用于為此縮略圖著色的漸變畫筆。如果要將可選的 TickBar 元素包括在 Slider 模板中,應將其 Visibility 屬性設置為 Visibility.Collapsed。ControlTemplate 的 Triggers 部分應包含根據 Slider 的 TickPlacement 屬性將 Visibility 屬性設置為 Visibility.Visible 的 Trigger 元素。因此,有必要為 TickBar 元素指定名稱,但不必是特殊名稱。
對于我自定義的豎直 Slider,我希望 Thumb 像混音板上的滑桿。目標是使滑桿像一個具有三維外觀的突出的塑料塊。要模擬三維透視,則需要在上下移動時改變形狀。推至 Slider 頂部時,您應可以看到操縱桿的底部;而在位于 Slider 底部時,應可以看到其頂部。
Windows Presentation Foundation 具有某些三維圖形功能,是處理此類任務的理想選擇。Slider3D.xaml 文件包含該模板。圖 12 顯示了其中五個設置在不同位置的三維 Slider 控件。請注意,滑桿的形狀變化取決于其位置。
圖 12?三維 Slider 控件 (單擊該圖像獲得較小視圖)
圖 12?三維 Slider 控件 (單擊該圖像獲得較大視圖)
豎直 Slider 開始處通常是一個三列的 Grid 面板;外側的兩個列用于兩個 TickBar 元素。在默認的豎直 Slider 模板中,中間的 Grid 包含一個提供凹陷圖像的 Border 元素以及一個包含 Thumb 和兩個 RepeatButton 控件的 Track 元素。我在自己的模板中使用同樣的方法,并將兩個 RepeatButton 控件定義為具有 Transparent(透明)背景的 Border 元素。它們不會使凹陷變暗,但仍會響應鼠標。
我確定 Thumb 應為 50 個像素高(由于 Grid 中的 ColumnDefinition,其寬度已為 50 個像素)。可視樹包含另一個具有透明背景的 Border。此時,盡管您無法看到縮略圖,但 Slider 運行完全正常。此透明 Border 的子項是一個 Viewport3D 元素,這正是其有趣之處。
我認為,三維圖形編程基本上包含兩個部分:單調枯燥和輕松自在。單調枯燥的工作主要涉及定義 MeshGeometry3D 元素,這些元素是在三維坐標空間中完整定義為一組互相連接的三角形的可視對象。對于 Thumb,我定義了一個對象,是切掉頂部的矩形棱錐:
<MeshGeometry3DPositions="-2 -1 0, -1 -0.25 4, -2 1 0, -1 0.25 4, 2 -1 0, 1 -0.25 4, 2 1 0, 1 0.25 4"TriangleIndices="0 1 2, 1 3 2, 0 2 4, 2 6 4, 0 4 1, 1 4 5, 1 5 7, 1 7 3, 4 6 5, 7 5 6, 2 3 6, 3 7 6"TextureCoordinates="0 1, 0.2 0.6, 0 0, 0.2 0.4,1 1, 0.8 0.6, 1 0, 0.8 0.4" />八個頂點中每個頂點的 Positions 屬性都由各自的三維坐標組成。圖形底部(Z 等于零)的跨度為從 X 值 -2 到 +2 和從 Y 值 -1 到 +1。頂部矩形(Z 等于 4)的跨度為從 X 值 -1 到 +1 和從 Y 值 -0.25 到 +0.25。
TriangleIndices 屬性中的每個三元組確定了圖的一個面,并對應儲存在 Positions 數組中的編號。例如,第一個 TriangleIndices 的三元組為 0、1 和 2,是指 Positions 集合中的第一、第二和第三個頂點。TextureCoordinates 集合包含圖中每個三維頂點的一個二維點。這些點對應于基于 GeometryDrawing(用于包括三維對象外側)的畫筆中的點:
<GeometryDrawing Brush="LightGray"Geometry="F 1 M 0 0 L 1 0 L 1 1 L 0 1 ZM 0.2 0.4 L 0.8 0.4 0.8 0.6 0.2 0.6 ZM 0 0 L 0.2 0.4 M 1 0 L 0.8 0.4M 1 1 L 0.8 0.6M 0 1 L 0.2 0.6"><GeometryDrawing.Pen><Pen Brush="DarkGray"Thickness=".05" /></GeometryDrawing.Pen> </GeometryDrawing>此畫筆只是用淡灰色覆蓋整個對象,并以深灰色使對象的邊緣顏色加重。
我所說的三維圖形編程中輕松自在的部分是指使用照明和照相機。通常,使用照明都希望獲得 DirectionalLight 和 AmbientLight 結合的效果。前者本身太生硬;而后者會沖淡所有對象。模板中的照相機最初設置是在滑桿從 Z 軸向下看,但也可以進行旋轉變換從上面和下面查看滑桿。我無法通過對 Slider 的 Value 屬性執行 TemplateBinding 而達到上述目的,因此改用常規綁定:
<AxisAngleRotation3D Axis="1 0 0"Angle="{Binding RelativeSource={RelativeSource AncestorType={x:Type Slider}}, Path=Value}" />由于 Value 屬性決定了照相機的旋轉角度,我需要分別硬性地將 Minimum 和 Maximum 屬性設置為 -25 和 25。這使得照相機的最大擺動角度為 50 度,如果更大,滑桿將卡住。小于 50 度也可以,但是三維效果會變弱。Slider3D.xaml 文件使用 Style 設置這兩個屬性以及 Orientation 屬性和 Template 屬性,如下所示:
<Style x:Key="styleSlider3D" TargetType="Slider"><Setter Property="Orientation" Value="Vertical" /><Setter Property="Minimum" Value="-25" /><Setter Property="Maximum" Value="25" /><Setter Property="Template" Value="{StaticResource templateSlider3D}" /> </Style>使用此模板的 Slider 實際上引用的是此樣式而非此模板。
記住要適度
正如您所見,只需要對 XAML 進行少許編碼就可以極大地改變標準控件的外觀。如果您像我認識的多數編程人員一樣,您可能會琢磨著編寫打破所有邏輯和風格的模板。
在您對模板著迷之前,請記住 Windows 的重要價值之一就是用戶界面的一致性。如果界面看起來相似,則用戶便能猜測到下一步的操作。因此,要完全控制好您的創新欲望并適度使用模板。(《MSDN? 雜志》的編輯非要我加上這一段。)
將您想詢問的問題和提出的意見發送至 ?mmnet30@microsoft.com.
Charles Petzold是《MSDN 雜志》的一名特約編輯,也是《Applications = Code + Markup:A Guide to the Microsoft Windows Presentation Foundation》(Microsoft Press,2006)的作者。其網站為 www.charlespetzold.com。
?摘自?January 2007?期刊?MSDN Magazine.
Figure 1 描述 CheckBox 的可視樹<ControlTemplate x:Key="templateBigCheck" TargetType="{x:Type CheckBox}"><Border BorderBrush="{TemplateBinding BorderBrush}"BorderThickness="{TemplateBinding BorderThickness}"Background="{TemplateBinding Background}"><Grid><!-- ContentPresenter displays content of CheckBox --><ContentPresenterContent="{TemplateBinding Content}"ContentTemplate="{TemplateBinding ContentTemplate}"Margin="{TemplateBinding Padding}"HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"VerticalAlignment="{TemplateBindingVerticalContentAlignment}" /><!-- This Border displays a semi-transparent red X --><Border><Border.Background><VisualBrush Opacity="0.5"><VisualBrush.Visual><Path Name="path"Data="M 0 0 L 10 10 M 10 0 L 0 10"Stroke="Red" StrokeStartLineCap="Round"StrokeEndLineCap="Round"StrokeLineJoin="Round" /></VisualBrush.Visual></VisualBrush></Border.Background></Border></Grid></Border>
Figure 2 Triggers 部分<ControlTemplate.Triggers><Trigger Property="IsChecked" Value="True"><Setter TargetName="path"Property="Data"Value="M 0 5 L 3 10 10 0" /><Setter TargetName="path"Property="Stroke"Value="Green" /></Trigger><Trigger Property="IsChecked" Value="{x:Null}"><Setter TargetName="path"Property="Data"Value="M 0 2.5 A 5 2.5 0 1 1 5 5 L 5 8 M 5 10 L 5 10" /><Setter TargetName="path"Property="Stroke"Value="Blue" /></Trigger><Trigger Property="IsEnabled" Value="False"><Setter Property="Foreground" Value="{DynamicResource{x:Static SystemColors.GrayTextBrushKey}}" /></Trigger>... </ControlTemplate.Triggers>
Figure 4 ProgressBar 模板骨架<ProgressBar Margin="50" HorizontalAlignment="Center" Value="{Binding ElementName=scroll, Path=Value}"><ProgressBar.Template><ControlTemplate><StackPanel><Rectangle Name="PART_Track"Height="20" Width="300" Fill="Red" /><Rectangle Name="PART_Indicator"Height="20" Fill="Blue" /></StackPanel></ControlTemplate></ProgressBar.Template> </ProgressBar>
Figure 5 溫度計進度條的 ControlTemplate<ControlTemplate x:Key="templateThermometer"TargetType="{x:Type ProgressBar}"><!-- Define two brushes for the thermometer liquid --><ControlTemplate.Resources><LinearGradientBrush x:Key="brushStem"StartPoint="0 0" EndPoint="1 0"><GradientStop Offset="0" Color="Red" /><GradientStop Offset="0.3" Color="Pink" /><GradientStop Offset="1" Color="Red" /></LinearGradientBrush><RadialGradientBrush x:Key="brushBowl"GradientOrigin="0.3 0.3"><GradientStop Offset="0" Color="Pink" /><GradientStop Offset="1" Color="Red" /> </RadialGradientBrush></ControlTemplate.Resources><!-- Two-row Grid divides thermometer into stem and bowl --><Grid><Grid.RowDefinitions><RowDefinition Height="*" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><!-- Second grid divides stem area in three columns --><Grid Grid.Row="0"><Grid.ColumnDefinitions><ColumnDefinition Width="25*" /><ColumnDefinition Width="50*" /><ColumnDefinition Width="25*" /></Grid.ColumnDefinitions><!-- This border displays the stem --><Border Grid.Column="1" BorderBrush="SteelBlue" BorderThickness="3 3 3 0"CornerRadius="6 6 0 0" ><!-- Track and Indicator elements --><Decorator Name="PART_Track"><Border Name="PART_Indicator"CornerRadius="6 6 0 0"VerticalAlignment="Bottom"Background="{StaticResource brushStem}" /></Decorator></Border></Grid><!-- The bowl outline goes in the main Grid second row --><Ellipse Grid.Row="1"Width="{TemplateBinding Width}"Height="{TemplateBinding Width}"Stroke="SteelBlue" StrokeThickness="3" /><!-- Another grid goes in the same cell --><Grid Grid.Row="1" ><Grid.RowDefinitions><RowDefinition Height="50*" /><RowDefinition Height="50*" /></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="25*" /><ColumnDefinition Width="50*" /><ColumnDefinition Width="25*" /></Grid.ColumnDefinitions><!-- This is to close up the gap between bowl and stem --><Border Grid.Row="0" Grid.Column="1"BorderBrush="SteelBlue"BorderThickness="3 0 3 0"Background="{StaticResource brushStem}" /></Grid><!-- Another ellipse to fill up the bowl --><Ellipse Grid.Row="1"Width="{TemplateBinding Width}"Height="{TemplateBinding Width}"Stroke="Transparent" StrokeThickness="6"Fill="{StaticResource brushBowl}" /></Grid> </ControlTemplate>
Figure 8 一個簡單的 ScrollBar 模板<ControlTemplate x:Key="templateNoFrillsScroll"TargetType="{x:Type ScrollBar}"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition Width="1*" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions><RepeatButton Grid.Column="0" Command="ScrollBar.LineLeftCommand"FontFamily="Wingdings" Content="E" /><Track Grid.Column="1" Name="PART_Track"><Track.DecreaseRepeatButton><RepeatButton Command="ScrollBar.PageLeftCommand" /></Track.DecreaseRepeatButton><Track.IncreaseRepeatButton><RepeatButton Command="ScrollBar.PageRightCommand" /></Track.IncreaseRepeatButton><Track.Thumb><Thumb /></Track.Thumb></Track><RepeatButton Grid.Column="2" Command="ScrollBar.LineRightCommand"FontFamily="Wingdings" Content="F" /></Grid> </ControlTemplate>
Figure 10 對其他模板進行引用的可視樹<Grid><Grid.ColumnDefinitions><ColumnDefinition Width="50" /><ColumnDefinition Width="1*" /><ColumnDefinition Width="50" /></Grid.ColumnDefinitions><!-- Line-left button on left side --><RepeatButton Grid.Column="0" Command="ScrollBar.LineLeftCommand"Foreground="{TemplateBinding Foreground}"Template="{StaticResource templateArrow}" /><!-- Named track occupies most of the ScrollBar --><Track Grid.Column="1" Name="PART_Track" ><Track.DecreaseRepeatButton><RepeatButton Command="ScrollBar.PageLeftCommand"Foreground="{TemplateBinding Foreground}"Template="{StaticResource templateSpring}" /></Track.DecreaseRepeatButton><Track.IncreaseRepeatButton><RepeatButton Command="ScrollBar.PageRightCommand"Foreground="{TemplateBinding Foreground}"Template="{StaticResource templateSpring}" /></Track.IncreaseRepeatButton><Track.Thumb><Thumb Background="{TemplateBinding Foreground}" /></Track.Thumb></Track><!-- Line-right button on right side --><RepeatButton Grid.Column="2" Command="ScrollBar.LineRightCommand"Foreground="{TemplateBinding Foreground}"Template="{StaticResource templateArrow}" LayoutTransform="-1 0 0 1 0 0" /> </Grid>
轉載于:https://www.cnblogs.com/zhouyinhui/archive/2007/03/26/689001.html
總結
以上是生活随笔為你收集整理的[转] 使用模板自定义 WPF 控件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: javax.servlet.Generi
- 下一篇: 移动的验证码安全问题告诉移动网站后...