组织您的 XAML 源





5.00/5 (12投票s)
通过使用字典来构建您的 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 日,创建第一个版本。