【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

相关文章
相关标签/搜索