在现代的应用程序开发中,为了提升用户体验,开发者经常需要创建具有独特外观和感觉的窗口主题。本文将介绍如何使用XAML声明式语言来创建一个自定义的窗口主题,并将其应用到应用程序中。
可以从下载Synergy SDK,它包含了完整的源代码。此外,也可以查看在Synergy中的窗口停靠解决方案。将在Twitter账户上发布Synergy SDK的更新信息。
众所周知,XAML是一种声明式语言,它的简单性意味着没有必要采用传统的编写代码的方式来处理窗口和控件。开发者应该能够简单地创建一个视觉模板,并将其放入控件模板中,以便让控件工作。这正是在开发Synergy时创建无外观窗口功能的动机。
创建自定义窗口的第一步是创建一个视觉模板。最简单的方法是在Microsoft Blend中创建一个用户控件,然后在XAML中定义扩展点,这些扩展点将在模板应用后激活。一旦视觉元素准备好,接下来要做的就是为CustomWindow创建一个样式,将模板粘贴进去,然后丢弃临时用户控件。
当前实现支持以下扩展点:
在定义窗口模板时,还必须在AdornerDecorator标签内声明ContentPresenter(最终包含窗口内容),因为这是WPF的要求。
以下是在MixModes.Synergy.Themes项目中的Windows.xaml资源字典内创建的模板:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="MainWindow" TargetType="{x:Type Window}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Grid>
<Border x:Name="MainBorder" BorderBrush="{DynamicResource MainWindowBorderBrush}"
BorderThickness="1" CornerRadius="2"
Background="{DynamicResource MainWindowBackgroundBrush}">
<DockPanel LastChildFill="True">
<Rectangle x:Name="PART_LEFT_BORDER" Width="2" Cursor="SizeWE">
<Rectangle.Fill>
<SolidColorBrush Color="Transparent" />
</Rectangle.Fill>
</Rectangle>
<Rectangle x:Name="PART_RIGHT_BORDER" Cursor="SizeWE" Width="2" DockPanel.Dock="Right">
<Rectangle.Fill>
<SolidColorBrush Color="Transparent" />
</Rectangle.Fill>
</Rectangle>
<Rectangle x:Name="PART_TOP_BORDER" Cursor="SizeNS" DockPanel.Dock="Top" Height="2">
<Rectangle.Fill>
<SolidColorBrush Color="Transparent" />
</Rectangle.Fill>
</Rectangle>
<Rectangle x:Name="PART_BOTTOM_BORDER" Cursor="SizeNS" Height="2" DockPanel.Dock="Bottom">
<Rectangle.Fill>
<SolidColorBrush Color="Transparent" />
</Rectangle.Fill>
</Rectangle>
<Border x:Name="PART_TITLEBAR" Margin="2,0,2,2" Height="40" DockPanel.Dock="Top" CornerRadius="2" Background="Transparent">
<DockPanel LastChildFill="False">
<TextBlock Margin="8,0,0,4" VerticalAlignment="Center" FontStretch="UltraExpanded" Foreground="Black" TextTrimming="CharacterEllipsis" TextWrapping="NoWrap" Text="{TemplateBinding Title}" FontSize="16" />
<Button x:Name="PART_CLOSE" DockPanel.Dock="Right" Style="{DynamicResource FlatButton}" VerticalAlignment="Center" Margin="0,0,4,0">
<Image Source="/MixModes.Synergy.Resources;component/Resources/Close.png" Stretch="None" Margin="4" />
</Button>
<Button x:Name="PART_MAXIMIZE_RESTORE" DockPanel.Dock="Right" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{DynamicResource FlatButton}">
<Image x:Name="MaximizeRestoreImage" Source="/MixModes.Synergy.Resources;component/Resources/Restore.png" Stretch="None" Margin="4" />
</Button>
<Button x:Name="PART_MINIMIZE" HorizontalAlignment="Center" Style="{DynamicResource FlatButton}" VerticalAlignment="Center" DockPanel.Dock="Right">
<Image Margin="4" Source="/MixModes.Synergy.Resources;component/Resources/Minimize.png" Stretch="None" />
</Button>
</DockPanel>
</Border>
<!-- Title bar separator -->
<Border Height="1" DockPanel.Dock="Top" Background="{DynamicResource MainWindowTitleBarSeparator}" />
<!-- Actual Window Content -->
<AdornerDecorator DockPanel.Dock="Bottom">
<ContentPresenter />
</AdornerDecorator>
</DockPanel>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
这是一个相当简单的主题,它创建了一个带有2像素宽调整器和自定义窗口最小化、最大化和还原按钮的圆角矩形窗口。它还包含一个模板触发器,如果窗口没有最大化,则会更改最大化还原按钮的图像。
最后一步是从CustomWindow类(它又继承自Window类)继承,而不是直接从Window类继承,并引用前一步创建的样式。这同样非常简单:
只需要做这些,就可以让自定义窗口工作了!
如果打开CustomWindow类,会发现大部分工作发生在AttachToVisualTree方法中,该方法从OnApplyTemplate调用(它又在每次模板应用到自定义窗口时调用)。AttachToVisualTree反过来调用AttachCloseButton、AttachMaximizeButton、AttachMaximizeRestoreButton、AttachTitleBar和AttachBorders方法,每个方法都查询视觉部件(在模板中定义的PART_…命名部件)并通过事件附加功能。