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

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

  當使用控件模板時,須要決定如何更普遍地共享模板,以及是否但願自動地或明確地位用模板。app

  第一個問題是關於但願在何處使用模板的問題。例如,是將它們限制在特定窗口中嗎?大多數狀況下,控件模板應用於多個窗口,甚至可能應用於整個應用程序。爲避免屢次定義模板,可在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>

1、分解按鈕控件模板this

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

  爲查看這種技術的工做狀況,分析下嗎的標記。這些標記表明了一個按鈕的完整資源字典,包括控件模板使用的資源、控件模板,以及爲應用程序中每一個按鈕應用控件模板的樣式規則。始終須要遵循這一順序,由於資源須要在使用以前先定義(若是在模板以後定義畫刷,將收到錯誤信息,由於模板找不到所需的畫刷)。spa

<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>

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

2、經過樣式應用模板

  這種設計存在侷限性,控件模板本質上硬編碼了一些細節,如顏色方案。這意味着若是但願在按鈕中使用相同的元素組合(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屬性,由於樣式不包含控件模板(只是引用模板而已)。所以,樣式和觸發器有必定的限制。它們不能深刻到可視化樹中來改變嵌套的元素的這個方面。反而,樣式須要設置控件的屬性,並且控件中的元素須要使用模板來綁定來綁定 。

3、自動應用模板

  在當前示例中,每一個按鈕負責使用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>

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

4、由用戶選擇的皮膚

  在一些應用程序中,可能但願動態改變模板,一般是根據用戶的我的愛好加以改變。這很容易發現,但文檔沒有對比進行詳細說明。基本技術是在運行時加載新的資源字典,並使用新加載的資源字典代替當前的資源字典(不須要替換全部資源,字須要替換那些敢於皮膚的資源)。

  訣竅在於檢索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>

 

原文出處:https://www.cnblogs.com/Peter-Luo/p/12631750.html

相關文章
相關標籤/搜索