【WPF學習】第六十一章 組織模板資源

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

為表達全國各族人民對抗擊新冠肺炎疫情鬥爭犧牲烈士和逝世同胞的深切哀悼,國務院今天發佈公告,決定2020年4月4日舉行全國性哀悼活動。 當使用控制項模板時,需要決定如何更廣泛地共用模板,以及是否希望自動地或明確地位用模板。 第一個問題是關於希望在何處使用模板的問題。例如,是將它們限制在特定視窗中嗎?大多 ...


  為表達全國各族人民對抗擊新冠肺炎疫情鬥爭犧牲烈士和逝世同胞的深切哀悼,國務院今天發佈公告,決定2020年4月4日舉行全國性哀悼活動。

  當使用控制項模板時,需要決定如何更廣泛地共用模板,以及是否希望自動地或明確地位用模板。

  第一個問題是關於希望在何處使用模板的問題。例如,是將它們限制在特定視窗中嗎?大多數情況下,控制項模板應用於多個視窗,甚至可能應用於整個應用程式。為避免多次定義模板,可在Application類的Resources集合中定義模板資源。

  然而,為此需要考慮另一個事項。通常,控制項模板在多個應用程式之間共用。單個應用程式很有可能使用單獨開發的模板。然而,一個應用程式只能有一個App.xaml文件和一個Application.Resources集合。因此,在單獨資源字典中定義資源是一個更好的主意。這樣,可靈活地再特定視窗或在整個應用程式中使用資源。而且還可以結合使用樣式,因為任何應用程式都可以包含多個資源字典。為在Visual Studio中添加資源字典,在Solution Explorer視窗中右擊項目,選擇Add|New Item菜單項,然後選擇Resources Dictionary(WPF)模板。

  在前面章節中介紹了資源字典,使用它們很容易,只需要為應用程式添加一個新的具有如下內容的XAML文件即可:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    >
    <ControlTemplate x:Key="GradientButtonTemplate" TargetType="{x:Type Button}">
        ...
    </ControlTemplate>
</ResourceDictionary>

  雖然可將所有模板組合到單個資源字典文件中,但富有經驗的開發人員更願意為每個控制項模板創建單獨的資源字典。這是因為控制項模板可能很快地變得過於複雜,並可能需要使用其他相關資源。將它們保存在一個單獨的地方,並與其他控制項相隔離,是一種很好的組織方式。

  為使用資源字典,只需要將他們添加到特定視窗或應用程式(這種情況更常見)的Resources集合中。可使用MergedDictionaries集合完成該工作。例如,如果按鈕模板在項目文件夾的Resources子文件夾下的Button.xaml文件中,就可以在App.xaml文件中使用以下標記:

<Application x:Class="ControlTemplates.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Menu.xaml"
    >
    <Application.Resources>
            <ResourceDictionary>
               <ResourceDictionary.MergedDictionaries>
                  <ResourceDictionary Source="Resources\Button.xaml"/>
               </ResourceDictionary.MergedDictionaries>
             </ResourceDictionary>
    </Application.Resources>
</Application> 

一、分解按鈕控制項模板

  當完善或擴展控制項模板時,可發現其中封裝了大量的不同細節,包括特定的形狀、幾何圖形和畫刷。從控制項模板中提取這些細節並將他們定義為單獨的資源是一個好主意。一個原因是通過該步驟,可以更方便地再一組相關的控制項中重用這些畫刷。例如,可能會決定創建使用相同的自定義Button、CheckBox和RadioButton控制項。為使該工作更加容易,可為畫刷(名為Brushes.xaml)創建一個單獨的資源字典,並將該資源字典合併到每個控制項(如Button.xaml、CheckBox.xaml和RadioButton.xaml)的資源字典中。

  為查看這種技術的工作情況,分析下嗎的標記。這些標記代表了一個按鈕的完整資源字典,包括控制項模板使用的資源、控制項模板,以及為應用程式中每個按鈕應用控制項模板的樣式規則。始終需要遵循這一順序,因為資源需要在使用之前先定義(如果在模板之後定義畫刷,將收到錯誤信息,因為模板找不到所需的畫刷)。

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!-- Resources used by the template. -->
    <RadialGradientBrush RadiusX="1" RadiusY="5" GradientOrigin="0.5,0.3"
                         x:Key="HighlightBackground">
        <GradientStop Color="White" Offset="0"/>
        <GradientStop Color="Blue" Offset="0.4"/>
    </RadialGradientBrush>
    <RadialGradientBrush RadiusX="1" RadiusY="5" GradientOrigin="0.5,0.3"
                         x:Key="PressedBackground">
        <GradientStop Color="White" Offset="0"/>
        <GradientStop Color="Blue" Offset="1"/>
    </RadialGradientBrush>

    <SolidColorBrush Color="Blue" x:Key="DefaultBackground"></SolidColorBrush>
    <SolidColorBrush Color="Gray" x:Key="DisabledBackground"></SolidColorBrush>

    <RadialGradientBrush RadiusX="1" RadiusY="5" GradientOrigin="0.5,0.3"
                         x:Key="Border">
        <GradientStop Color="White" Offset="0"/>
        <GradientStop Color="Blue" Offset="1"/>
    </RadialGradientBrush>

    <!-- The button control template. -->
    <ControlTemplate x:Key="GradientButtonTemplate" TargetType="{x:Type Button}">
        <Border Name="Border" BorderBrush="{StaticResource Border}" BorderThickness="2"
       CornerRadius="2" Background="{StaticResource DefaultBackground}"
       TextBlock.Foreground="White">
            <Grid>
                <Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Black"
           StrokeThickness="1" StrokeDashArray="1 2" SnapsToDevicePixels="True">
                </Rectangle>
                <ContentPresenter Margin="{TemplateBinding Padding}"
           RecognizesAccessKey="True"></ContentPresenter>
            </Grid>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter TargetName="Border" Property="Background"
           Value="{StaticResource HighlightBackground}" />
            </Trigger>
            <Trigger Property="IsPressed" Value="True">
                <Setter TargetName="Border" Property="Background"
           Value="{StaticResource PressedBackground}" />
            </Trigger>
            <Trigger Property="IsKeyboardFocused" Value="True">
                <Setter TargetName="FocusCue" Property="Visibility"
            Value="Visible"></Setter>
            </Trigger>
            <Trigger Property="IsEnabled" Value="False">
                <Setter TargetName="Border" Property="Background"
           Value="{StaticResource DisabledBackground}"></Setter>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
</ResourceDictionary>

  下圖顯示了該模板定義的按鈕。在該例中,當用戶滑鼠移到按鈕上時,使用漸變填充。然而,漸變中心位於按鈕中央。如果希望創建更新穎的效果,例如跟隨滑鼠位置的漸變,就需要使用動畫或者編寫代碼。

二、通過樣式應用模板

  這種設計存在局限性,控制項模板本質上硬編碼了一些細節,如顏色方案。這意味著如果希望在按鈕中使用相同的元素組合(Border、Grid、Rectangle和ContentPresenter)並採用相同的方式安排它們,但希望提供不同的顏色方案,就必須創建應用不同畫刷資源的新模板副本。

  這未必是個問題(畢竟,佈局和格式化細節可能緊密相關,以至於不希望以任何方式隔離它們)。但這確實限制了重用控制項模板的能力。如果模板使用了元素的複合排列方式,並且希望重用這些具有各種不同格式化細節(通常是顏色和字體)的元素,可從模板中將這些細節提取出來,並將它們放到樣式中。

  為此,需要重新編寫模板。這次不能使用硬編碼的顏色,而需要使用模板綁定從控制項屬性中提取出信息。下麵的示例為前面看到的特殊按鈕定義了一個精簡模板。控制項模板將一些細節作為基礎的固定要素——焦點框和兩個單位寬的圓角邊框。背景和邊框畫刷是可配置的。唯一需要保留的觸發器是顯示焦點框的那個觸發器:

<ControlTemplate x:Key="GradientButtonTemplate" TargetType="{x:Type Button}">
        <Border Name="Border" BorderThickness="2" CornerRadius="2"
              Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}">
            <Grid>
                <Rectangle Name="FocusCue" Visibility="Hidden" Stroke="Black"
           StrokeThickness="1" StrokeDashArray="1 2" SnapsToDevicePixels="True">
                </Rectangle>
                <ContentPresenter Margin="{TemplateBinding Padding}"
           RecognizesAccessKey="True"></ContentPresenter>
            </Grid>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="IsKeyboardFocused" Value="True">
                <Setter TargetName="FocusCue" Property="Visibility"
            Value="Visible"></Setter>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

  關聯的樣式應用這個控制項模板,設置邊框和背景顏色,並添加觸發器以便根據按鈕的狀態改變背景色:

<!-- The style that applies the button control template. -->
    <Style TargetType="{x:Type Button}">
        <Setter Property="Control.Template"
       Value="{StaticResource GradientButtonTemplate}"></Setter>
        <Setter Property="BorderBrush"
       Value="{StaticResource Border}"></Setter>
        <Setter Property="Background"
         Value="{StaticResource DefaultBackground}"></Setter>
        <Setter Property="TextBlock.Foreground"
           Value="White"></Setter>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background"
           Value="{StaticResource HighlightBackground}" />
            </Trigger>
            <Trigger Property="IsPressed" Value="True">
                <Setter Property="Background"
           Value="{StaticResource PressedBackground}" />
            </Trigger>
            <Trigger Property="IsEnabled" Value="False">
                <Setter Property="Background"
           Value="{StaticResource DisabledBackground}"></Setter>
            </Trigger>
        </Style.Triggers>
    </Style>

  理想情況下,應能在控制項模板中保留所有觸發器,因為它們代表控制項的行為,並使用樣式簡單設置基本屬性。但在此如果希望樣式能夠設置顏色方案,是不可能實現的。

  為使用這個新模板,需要設置按鈕的Style屬性而不是Template屬性:

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

  現在可創建一些新樣式,這些樣式使用相同的模板,但為了應用新的顏色方案,應將模板綁定到不同的畫刷。

  使用這種方法存在的重要限制。在該樣式中不能使用Setter.TargetName屬性,因為樣式不包含控制項模板(只是引用模板而已)。因此,樣式和觸發器有一定的限制。它們不能深入到可視化樹中來改變嵌套的元素的這個方面。反而,樣式需要設置控制項的屬性,而且控制項中的元素需要使用模板來綁定來綁定 。

三、自動應用模板

  在當前示例中,每個按鈕負責使用Template或Style屬性將自身關聯到適當模板。如果使用控制項模板,在應用程式中的特定位置創建特殊效果,這是合理的。但如果希望在具有自定義外觀的整個應用程式中改變每個按鈕的皮膚,這就不是很方便了。對於這種情況,可能會更希望應用程式中的所有按鈕自動請求新的模板。為實現該功能,需要通過樣式應用控制項模板。

  技巧是使用類型樣式,這種樣式會自動影響響應的元素類型並設置Template屬性。下麵是一個樣式示例,應將該樣式放到資源字典的資源集合中,從而為按鈕提供新外觀:

<Style TargetType="{x:Type Button}">
        <Setter Property="Control.Template"
       Value="{StaticResource GradientButtonTemplate}"></Setter>
    </Style>

  上面的代碼可以工作,原因是樣式沒有指定鍵名,這意味著改用元素類型(Button)。

  請記住,仍可通過創建一個按鈕並將其Style屬性明確設置為null值,退出該樣式:

<Button Style="{x:Null}" ...></Button>

  包含基於類型的樣式的組合的資源字典通常(非正式地)被稱為主題(theme)。主題能夠實現非凡的效果。通過主題可為已有應用程式的所有控制項重新應用皮膚,而根本不需要改用戶界面標記。需要做的全部工作就是為項目添加資源字典,並將其合併到App.xaml文件的Application.Resources集合中。

  如果在Web上搜索,可找到許多能用於為WPF應用程式換膚的主題,例如,可下載WPF Futures版本中的幾個示例主題。

  為使用主題,為項目添加包含資源字典的.xaml文件。例如,WPF Futuers提供了一個名為ExpressionDark.xaml的主題文件。然後,需要在應用程式中激活樣式。可逐個視窗地完成該工作,但更快捷的方法是通過添加如下所示的標記在應用程式級別導入他們:

<Application ...>
    <Application.Resources>
        <ResourceDictionary Source="ExpressionDark.xaml"/>
    </Application.Resources>
</Application>

  現在將全面實施資源字典中基於類型的樣式,並將自動改變應用程式每個視窗的每個通用控制項的外觀。如果是一位正在搜索熱門用戶界面的應用程式開發人員,但不具備自己構建這類用戶界面的設計技能,那麼使用該技巧幾乎不需要付出努力就能很容易地插入第三方的精彩界面。

四、由用戶選擇的皮膚

  在一些應用程式中,可能希望動態改變模板,通常是根據用戶的個人愛好加以改變。這很容易發現,但文檔沒有對比進行詳細說明。基本技術是在運行時載入新的資源字典,並使用新載入的資源字典代替當前的資源字典(不需要替換所有資源,字需要替換那些勇於皮膚的資源)。

  訣竅在於檢索ResourceDictionary對象,該對象經過編譯並作為資源嵌入到應用程式中。最簡單的方法是使用ResourceManager類來載入所需資源。

  例如,假定已創建用於定義同一個按鈕控制項模板的替代版本的兩個資源。其中一個保存在GradientButton.xaml文件中,而另一個保存在GradientButtonVariant.xaml文件中。為了更好地組織資源,這兩個文件都位於當前項目的Resources子文件夾中。

  現在可創建一個簡單的視窗,通過Resources集合使用這些資源中的一個,如下所示:

 <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/GradientButton.xaml"></ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

  現在可通過如下代碼使用不同的資源字典:

ResourceDictionary resourceDictionary = new ResourceDictionary();
            resourceDictionary.Source = new Uri(
              "Resources/GradientButtonVariant.xaml", UriKind.Relative);
            this.Resources.MergedDictionaries[0] = resourceDictionary;

  上面的代碼載入GradientButtonVariant資源字典,並將它放置到MergedDictionaries集合的第一個位置。在此沒有清空MergedDictionaries集合或其他任何視窗的資源,因為可能鏈接到了其他席位繼續使用的資源字典。也沒有為MergedDictionaries集合添加新條目,因為這可能與位於不同集合中的同名資源發生衝突。

  如果正在為整個應用程式改變皮膚,可使用相同的方法,但應使用應用程式的資源字典,可使用如下代碼更新這個資源字典:

Application.Current.Resources.MergedDictionaries[0]=newDictionary;

  還可使用pack URI語法載入在另一個程式集合中的資源字典:

ResourceDictionary resourceDictionary = new ResourceDictionary();
            resourceDictionary.Source = new Uri(
              "ControlTemplates;component/GradientButtonVariant.xaml", UriKind.Relative);
            this.Resources.MergedDictionaries[0] = resourceDictionary;

  當載入新的資源字典時,會自動使用新模板更新所有按鈕。如果當修改控制項時不需要完全改變皮膚,還可以為皮膚提供基本樣式。

  該例假定GradientButton.xaml和GradientButtonVariant.xaml資源使用元素類型樣式自動改變按鈕。還有一種方法——通過手動設置Button對象的Template或Style屬性來選用新的模板。如果使用這種方法,務必使用DynamicResource應用,而不能使用StaticResource。如果使用Resource,當切換皮膚時不會更新按鈕模板。

  還有一種通過編寫代碼載入資源字典的方法。可使用與為視窗創建帶阿媽隱藏類幾乎相同的方法,為資源字典創建代碼隱藏類。然後就可以直接實例化這個類,而不是使用ResourceDictionary.Source屬性。這種方法有一個優點,他是強類型的(沒有機會為Source屬性輸入無效的URI),並且可為資源類添加屬性、方法以及其他功能。例如,可以使用這種方法,為自定義視窗模板創建具有事件處理代碼的資源。

  儘管為資源字典創建代碼隱藏類很容易,但Visual Studio不能自動完成該工作,需要為繼承自ResourceDictionary的部分類添加代碼文件,併為構造函數中調用InitializeComponent()方法:

public partial class GradientButtonVariant : ResourceDictionary
    {
        public GradientButtonVariant()
        {
            InitializeComponent();
        }
    }

  這裡使用的類名為GradientButtonVariant,而且該類存儲在GradientButtonVariant.xaml.cs文件中。包含資源的XAML文件被命名為GradientButtonVariant.xaml。不是必須使用一致的名稱,但這是一個好主意,並且在創建視窗以及創建頁面時也遵循了Visual Studio使用的這一約定。

  接下來將類鏈接到資源字典。通過為資源字典的根元素添加Class特性完成該工作,就想為視窗應用Class特性一樣,並且可為任何XAML類應用Class特性。然後提供完全限定的類名。在這個示例中,項目名稱是ControlTemplates,因此這是預設名稱空間,最後的標簽可能如下所示:

<ResourceDictionary x:Class="ControlTemplates.GradientButtonVariant">

  現在可使用該代碼創建資源字典並將它應用於視窗:

GradientButtonVariant newDict = new GradientButtonVariant();
this.Resources.MergedDictionaries[0] = newDict;

  在Solution Explorer中,如果希望GradientButtonVariant.xaml.cs文件嵌套在GradientButtonVariant.xaml文件的下麵,需要在文本編輯器中修改.csproj項目文件。在<ItemGroup>部分,找到代碼因此文件,並將下麵的代碼:

<Compile Include="Resources\GradientButtonVariant.xaml.cs"/>

  修改為:

<Compile Include="Resources\GradientButtonVariant.xaml.cs">
    <DependentUpon>Resources\GradientButtonVariant.xaml</DependentUpon>
</Compile>

 


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

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...