轻松创建自定义 Windows (WPF)






4.86/5 (13投票s)
声明式和可视化地创建 WPF 中的自定义窗口。
引言
我希望为 Synergy 工具包添加的一项功能是能够快速创建具有标准窗口所有功能的自定义主题窗口。在本文中,我将演示如何使用声明式 XAML 可视化地创建自定义窗口主题,并将其应用于应用程序中的窗口。
您可以在 此处 下载包含完整源代码的 Synergy SDK。您可能还想在 此处 查看我在 Synergy 中的窗口停靠解决方案。我将在我的 Twitter 帐户 MixModes 上发布 Synergy SDK 的更新。
Synergy 示例应用程序使用主窗口作为自定义窗口,该窗口的主题定义在 MixModes.Synergy.Themes 项目中的 Windows.xaml 资源字典内。
声明式是关键
众所周知,XAML 是声明式的且简单,因此没有理由不应用编写窗口和控件代码的传统方法。只需创建一个视觉元素并将其放入控件模板中即可使其正常工作。这正是我在 Synergy 中开始开发无外观窗口功能时的动机。
声明一个模板
创建自定义窗口时,您首先要做的是实际创建一个视觉模板。创建此模板的最简单方法是在 Microsoft Blend 中创建一个用户控件,然后在 XAML 中定义无外观控件应用模板后将激活的扩展点。视觉元素准备就绪后,只需为 CustomWindow 创建一个样式,将模板粘贴到其中,然后就可以丢弃临时用户控件了。
当前实现支持以下扩展点
- PART_TITLEBAR (UIElement)- 用于显示窗口标题、拖动和最大化/还原操作
- PART_MINIMIZE (Button)– 窗口最小化按钮
- PART_MAXIMIZE_RESTORE (Button)– 最大化还原按钮
- PART_CLOSE (Button)– 关闭按钮
- PART_LEFT_BORDER (UIElement)– 左侧可调整大小边框
- PART_RIGHT_BORDER (UIElement)– 右侧可调整大小边框
- PART_TOP_BORDER (UIElement)– 顶部可调整大小边框
- PART_BOTTOM_BORDER (UIElement)– 底部可调整大小边框
还有一点需要注意,在定义窗口模板时,您必须在 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.Triggers>                        
                        <DataTrigger Binding="{Binding RelativeSource=
				{RelativeSource Self}, Path=Maximized}" 
                                     Value="False"> 
                            <Setter TargetName="MaximizeRestoreImage" 
                                    Property="Source" 
                                    Value="/MixModes.Synergy.Resources;
				component/Resources/Maximize.png" /> 
                        </DataTrigger> 
                    </ControlTemplate.Triggers> 
                </ControlTemplate> 
            </Setter.Value> 
        </Setter> 
    </Style> 
</ResourceDictionary>
这是一个非常简单的 theme,它创建了一个圆角矩形窗口,具有 2 像素宽的调整器以及自定义的窗口最小化、最大化和还原按钮。它还包含一个模板触发器,如果窗口未最大化,则会更改最大化还原按钮的图像。
使用此 theme 创建的窗口外观如下:
 
 
继承自 CustomWindow 类
最后一步是继承自 CustomWindow 类(该类又继承自 Window 类),而不是直接继承自 Window 类,并引用上一步创建的样式。这同样非常简单
- 导入视觉框架命名空间
 xmlns:visualFx="http://mixmodes.com/visualFx"
- 继承自 CustomWindow类
 在 XAML 中将“visualFx:CustomWindow”用作您的窗口标签
- 在上一步创建的样式中引用您的 visualFx:CustomWindow 标签Style="{DynamicResource MainWindow}"
这就是让您的自定义窗口正常工作的全部工作!
它是如何工作的?
如果您深入研究 CustomWindow 类,您会发现大部分工作是在 AttachToVisualTree 方法中完成的,该方法从 OnApplyTemplate 调用(当模板应用于我们的自定义窗口时,它会被调用)。
AttachToVisualTree 又调用 AttachCloseButton、AttachMaximizeButton、AttachMaximizeRestoreButton、AttachTitleBar 和 AttachBorders 方法,每个方法都会查询视觉部分(我们在模板中定义的 PART_… 命名部分),并通过事件附加功能。
这就是全部!使用 Synergy 创建自定义窗口就是这么简单!
历史
- 2010 年 12 月 27 日:首次发布


