[Wpf] . [Theme] 重构/Themes/Generic.xaml & 创建一个Custom Control的典型做法

转载自:https://www.codeproject.com/Articles/433476/Structuring-Your-XAML-Sources

--------------

Structuring your XAML source files by using dictionaries.

Introduction

When creating custom controls in Visual Studio, all controls are added to /Themes/Generic.xaml. Soon this file becomes quite big, and the maintenance of the file becomes tedious. XAML files should be structured in a similar way as any other source file. For instance in C# and in C++ one should attempt to put only a single class in a file. Similarly the XAML files can be structured where the net result should be that you as a developer find your way around your own sources a lot better.

To put things to the test I will develop a very simple custom control, an ImageButton.

Step 1: Creating a Custom Control

In Visual Studio I created myself a solution with two projects. One project for a WPF application, and another one for a WPF Custom Control Library. Leave the WPF application untouched for now, it is only used to test the custom control, but we first have to create it.

So in Visual Studio add a WPF Custom control to the library and name it ImageButton. The /Themes/Generic.xamlshould look similar like this:

<ResourceDictionary

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:CodeProjectLibrary">
   
    <Style TargetType="{x:Type local:ImageButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ImageButton}">
                    <Border Background="{TemplateBinding Background}"

                            BorderBrush="{TemplateBinding BorderBrush}"

                            BorderThickness="{TemplateBinding BorderThickness}">
                    </Border>
                </ControlTemplate>
            </Setter.Value>
       </Setter>
    </Style>
</ResourceDictionary>

And the code that is being generated will look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace CodeProjectLibrary
{
    public class ImageButton : Control
    {
        static ImageButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
        }
    }
}

Where I already removed the comment which you can read yourself if you create a custom control.

This is a fully operational control, though it does not do a whole lot. So let's make a real control out of this. Since this is an explanation about how to structure your XAML, I won’t explain the control itself. Bottom line is that either you immediately apply the structuring, or you first create the control in generic.xaml, and apply the structuring later. I will follow the latter approach so I create my control first.

Step 2: Authoring the Custom Control

The authored code for the control in C# is like this :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;

namespace ICeTechControlLibrary
{
    public class ImageButton : ButtonBase
    {
        private const string NormalImageSourcePropertyName = "NormalImageSource";
        private const string MouseOverImageSourcePropertyName = "MouseOverImageSource";
        private const string MouseOverPressedImageSourcePropertyName = "MouseOverPressedImageSource";
        private const string PressedImageSourcePropertyName = "PressedImageSource";

        public static readonly DependencyProperty NormalImageSourceProperty =
            DependencyProperty.Register(NormalImageSourcePropertyName, typeof(ImageSource), typeof(ImageButton));
        public static readonly DependencyProperty MouseOverImageSourceProperty =
            DependencyProperty.Register(MouseOverImageSourcePropertyName,
            typeof(ImageSource), typeof(ImageButton));
        public static readonly DependencyProperty MouseOverPressedImageSourceProperty =
            DependencyProperty.Register(MouseOverPressedImageSourcePropertyName,
            typeof(ImageSource), typeof(ImageButton));
        public static readonly DependencyProperty PressedImageSourceProperty =
            DependencyProperty.Register(PressedImageSourcePropertyName, typeof(ImageSource), typeof(ImageButton));

        static ImageButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
        }

        public ImageSource NormalImageSource
        {
            get
            {
                return (ImageSource)GetValue(NormalImageSourceProperty);
            }
            set
            {
                SetValue(NormalImageSourceProperty, value);
            }
        }

        public ImageSource MouseOverImageSource
        {
            get
            {
                return (ImageSource)GetValue(MouseOverImageSourceProperty);
            }
            set
            {
                SetValue(MouseOverImageSourceProperty, value);
            }
        }

        public ImageSource MouseOverPressedImageSource
        {
            get
            {
                return (ImageSource)GetValue(MouseOverPressedImageSourceProperty);
            }
            set
            {
                SetValue(MouseOverPressedImageSourceProperty, value);
            }
        }

        public ImageSource PressedImageSource
        {
            get
            {
                return (ImageSource)GetValue(PressedImageSourceProperty);
            }
            set
            {
                SetValue(PressedImageSourceProperty, value);
            }
        }
    }
}

This is basically an average button control with some extra dependency properties where different images can be referred to in case the button is being hovered by the mouse or if the button is simply pressed.

The XAML source, still in generic.xaml is like this:

<ResourceDictionary

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:CodeProjectLibrary">
   
    <Style TargetType="{x:Type local:ImageButton}">
        <Setter Property="IsTabStop" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ImageButton}">
                    <Image x:Name="ButtonImage" Source="{TemplateBinding NormalImageSource}" SnapsToDevicePixels="True" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
                    <ControlTemplate.Triggers>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="True"/>
                                <Condition Property="IsPressed" Value="False"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=MouseOverImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="True"/>
   
                            <Condition Property="IsPressed" Value="True"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=MouseOverPressedImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="False"/>
                                <Condition Property="IsPressed" Value="True"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=PressedImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

Step 3: Using the Custom Control

Now you can actually use this control. So change the XAML in mainwindow.xaml to the following. Don’t forget to add a reference to the control library!

<Window x:Class="CodeProject.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:CodeProjectLibrary;assembly=CodeProjectLibrary">
    <Grid>
        <my:ImageButton HorizontalAlignment="Left" Margin="64,44,0,0" Name="imageButton1" VerticalAlignment="Top" Width="16" Height="16" 

                        NormalImageSource="/CodeProject;component/checkbox-unchecked.png" 

                        MouseOverImageSource="/CodeProject;component/checkbox-unchecked.png" 

                        MouseOverPressedImageSource="/CodeProject;component/checkbox-checked.png" 

                        PressedImageSource="/CodeProject;component/checkbox-checked.png" />
    </Grid>
</Window>

This is just a simple test application, but at this point, it serves its purpose to show that it works.

Now we want to move all XAML code in generic.xaml to its own file. For this we have to create a separate dictionary, move the style to the dictionary, and include the dictionary in the generic.xaml as a merged dictionary. 

Step 4: Add a Resource Dictionary

In the custom control library create a resource dictionary. I named it ImageButtonDictionary.xaml, and placed it in a project folder ResourceDictionaries. The XAML code looks like this :

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                    xmlns:local="clr-namespace:ICeTechControlLibrary">
 
    <Style TargetType="{x:Type local:ImageButton}">
        <Setter Property="IsTabStop" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ImageButton}">
                    <Image x:Name="ButtonImage" Source="{TemplateBinding NormalImageSource}" SnapsToDevicePixels="True" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
                    <ControlTemplate.Triggers>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="True"/>
                                <Condition Property="IsPressed" Value="False"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=MouseOverImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="True"/>
                                <Condition Property="IsPressed" Value="True"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=MouseOverPressedImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsMouseOver" Value="False"/>
                                <Condition Property="IsPressed" Value="True"/>
                            </MultiTrigger.Conditions>
                            <Setter TargetName="ButtonImage" Property="Source" Value="{Binding Path=PressedImageSource, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </MultiTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

This is actually the same content as the generic.xaml we used before. Of course suppose you did not start from a clean generic.xaml, you would only have to copy/paste the style of the imagebutton control to this resource file.

The style code in generic.xaml for this control can be removed like this :

<ResourceDictionary

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:CodeProjectLibrary">

</ResourceDictionary>

Run the application again. Oops, it does not work anymore. The reason is that the generic.xaml does not contain the style anymore, which was the whole point right ? Yes, but we don’t have to put the whole style here, we can use the merged dictionary concept.

The generic.xaml file will look like this.

<ResourceDictionary

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:CodeProjectLibrary">
 
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/CodeProjectLibrary;component/ResourceDictionaries/ImageButtonDictionary.xaml"/>
    </ResourceDictionary.MergedDictionaries>
 
</ResourceDictionary>

Run the project again, it should work.

Using this method, you can actually create lots of resource dictionaries and use the merged dictionary mechanism to include whatever you need in another dictionary.

To have an operational control, you have to make sure you include it in the merged dictionary list of /Themes/generic.xaml.


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要添加tlagent.wpf.theme,首先需要确保计算机已经安装了相应的WPF主题。然后,按照以下步骤进行操作: 1. 打开Visual Studio开发环境,并打开你想要添加tlagent.wpf.theme的项目。 2. 在项目资源管理器中,找到项目中的References(引用)文件夹。 3. 右键单击References(引用)文件夹,并选择“添加引用”。 4. 在弹出的“添加引用”对话框中,点击左侧的“浏览”按钮。 5. 在浏览对话框中,找到包含tlagent.wpf.theme的程序集文件(通常是.dll文件),然后点击“确定”按钮。 6. 返回“添加引用”对话框,在右侧的引用列表中,确保已经选中了tlagent.wpf.theme。 7. 点击“确定”按钮,将tlagent.wpf.theme添加到项目的引用中。 8. 现在,你可以在项目中使用tlagent.wpf.theme的功能和样式。 需要注意的是,确保你使用的tlagent.wpf.theme版本与你的项目兼容,并且遵循适当的授权和使用法律法规。此外,根据具体情况,你可能还需要进行其他设置或配置来正确地使用tlagent.wpf.theme。 ### 回答2: 要添加tlagent.wpf.theme,可以按照以下步骤进行操作: 1. 首先,确保你已经安装了相关的开发环境和工具,比如Visual Studio等。 2. 打开你的项目,并定位到“引用”(References)部分,右键点击并选择“管理NuGet程序包”(Manage NuGet Packages)。 3. 在NuGet Packages窗口的搜索框中输入“tlagent.wpf.theme”,点击搜索按钮。 4. 在搜索结果中找到并选择正确的tlagent.wpf.theme包。务必仔细查看包的描述、版本等信息,确保选择与你的项目兼容的正确版本。 5. 点击“安装”(Install)按钮,等待包的下载和安装过程完成。 6. 安装完成后,tlagent.wpf.theme的库文件和相关资源文件将会被添加到你的项目中。 7. 现在,你可以在你的项目中使用tlagent.wpf.theme提供的样式和主题了。可以通过在XAML代码中引用其命名空间,或者直接在代码中使用相关的样式和控件。 总之,通过使用NuGet包管理器来添加tlagent.wpf.theme,你可以方便地将其集成到你的项目中,以实现样式和主题的定制和使用。不过,请注意选择正确的版本和确保其与你的项目兼容。 ### 回答3: 要添加tlagent.wpf.theme,可以按照以下步骤进行操作: 1. 首先,在电脑上找到已安装的tlagent.wpf.theme文件。这个文件通常以.dll后缀结尾。如果你没有这个文件,可以在互联网上搜索并下载。 2. 找到你希望添加tlagent.wpf.theme的项目。这可以是一个已经存在的WPF项目,或者是一个新建的项目。 3. 打开你的项目,在项目中找到对应的引用文件夹(References folder)。 4. 右击引用文件夹,在弹出的菜单中选择“添加引用”(Add Reference)选项。 5. 在添加引用的对话框中,选择“浏览”(Browse)选项卡。 6. 浏览你的电脑,找到tlagent.wpf.theme文件并选择它。 7. 点击“确定”(OK)按钮,将tlagent.wpf.theme添加到你的项目中。 8. 确保你的项目中的相关代码正确地使用tlagent.wpf.theme中的类和方法。你可以通过在代码中调用相关的命名空间和类来使用tlagent.wpf.theme。 总之,添加tlagent.wpf.theme需要找到该文件并将其添加到项目的引用中。在添加引用后,确保正确使用相关代码即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值