65.9K
CodeProject 正在变化。 阅读更多。
Home

组织您的 XAML 源

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2012年8月2日

CPOL

3分钟阅读

viewsIcon

52484

downloadIcon

787

通过使用字典来构建您的 XAML 源文件。

引言

在 Visual Studio 中创建自定义控件时,所有控件都会添加到/Themes/Generic.xaml中。很快,这个文件会变得相当大,维护起来也很麻烦。XAML 文件应该以类似于任何其他源文件的方式进行构建。例如,在 C# 和 C++ 中,应该尝试将单个类放在一个文件中。类似地,可以构建 XAML 文件,最终结果是您作为开发人员能够更好地找到自己的源代码。

为了测试一下,我将开发一个非常简单的自定义控件,一个 ImageButton。

步骤 1:创建自定义控件

在 Visual Studio 中,我创建了一个包含两个项目的解决方案。一个项目用于 WPF 应用程序,另一个项目用于 WPF 自定义控件库。暂时不要动 WPF 应用程序,它仅用于测试自定义控件,但我们首先必须创建它。

因此,在 Visual Studio 中,向库添加一个 WPF 自定义控件,并将其命名为 ImageButton。/Themes/Generic.xaml应该看起来像这样

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

生成的代码如下所示

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)));
        }
    }
}

我已经删除了注释,如果您创建自定义控件,您可以自己阅读。

这是一个完全可操作的控件,尽管它没有做很多事情。所以让我们把它变成一个真正的控件。由于这是关于如何构建 XAML 的解释,因此我不会解释控件本身。最重要的是,您可以立即应用结构化,或者先在 generic.xaml 中创建控件,然后再应用结构化。我将采用后一种方法,因此我首先创建我的控件。

步骤 2:编写自定义控件

C# 中控件的编写代码如下

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);
            }
        }
    }
}

这基本上是一个普通的按钮控件,带有一些额外的依赖属性,如果在鼠标悬停在按钮上或简单地按下按钮时,可以引用不同的图像。

XAML 源代码,仍然在 generic.xaml 中,如下所示

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

步骤 3:使用自定义控件

现在您可以实际使用此控件。因此,将mainwindow.xaml中的 XAML 更改为以下内容。不要忘记添加对控件库的引用!

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

这只是一个简单的测试应用程序,但在这一点上,它可以达到证明它有效的目的。

现在我们想将generic.xaml中的所有 XAML 代码移动到它自己的文件中。为此,我们必须创建一个单独的字典,将样式移动到该字典,并将该字典作为合并字典包含在generic.xaml中。 

步骤 4:添加资源字典

在自定义控件库中,创建一个资源字典。我将其命名为ImageButtonDictionary.xaml,并将其放置在项目文件夹 ResourceDictionaries 中。XAML 代码如下所示

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

这实际上与我们之前使用的generic.xaml的内容相同。当然,假设您不是从干净的generic.xaml开始,您只需要将 imagebutton 控件的样式复制/粘贴到此资源文件中。

可以像这样删除此控件在generic.xaml中的样式代码

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

再次运行应用程序。糟糕,它不再起作用了。原因是generic.xaml不再包含样式,这正是重点,对吗?是的,但我们不必在此处放置整个样式,我们可以使用合并字典的概念。

generic.xaml文件将如下所示。

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

再次运行该项目,它应该可以工作。

使用这种方法,您实际上可以创建许多资源字典,并使用合并字典机制将您需要的任何内容包含在另一个字典中。

为了拥有一个可操作的控件,您必须确保将其包含在/Themes/generic.xaml的合并字典列表中。

历史

2012 年 7 月 30 日,创建第一个版本。

© . All rights reserved.