WPF三种自定义窗体的实现

目前我所知道的,有三种方式可以实现自定义窗体:WindowStyle="None"、WindowChrome、第三方库ControlzEx;但它们都有各自的优缺点,下面一一展示如何使用。

 

 

 

一、WindowStyle="None"

<Window x:Class="CustomWindows.TransparentWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:pui="clr-namespace:PumbaaUI.Controls;assembly=PumbaaUI"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:ctrlz="clr-namespace:ControlzEx.Behaviors;assembly=ControlzEx"
        mc:Ignorable="d"
        Title="TransparentWindow" Height="450" Width="800"
        WindowStyle="None" AllowsTransparency="True" ResizeMode="CanResizeWithGrip"
        Background="#CCFFFFFF" BorderThickness="1" BorderBrush="{DynamicResource PuiBrushes.Theme}">
    <Window.Template>
        <ControlTemplate TargetType="{x:Type Window}">
            <Grid>
                <Border x:Name="LayoutRoot" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                    <DockPanel>
                        <Border x:Name="border_title" DockPanel.Dock="Top" Height="28" Background="{DynamicResource PuiBrushes.Window.Title.Background}">
                            <DockPanel>
                                <StackPanel DockPanel.Dock="Right"></StackPanel>
                                <TextBlock Margin="12 0 0 0" VerticalAlignment="Center" Foreground="{DynamicResource PuiBrushes.Window.Title.Foregound}" TextTrimming="WordEllipsis" Text="{TemplateBinding Title}" />
                            </DockPanel>
                        </Border>
                        <Border Background="{TemplateBinding Background}">
                            <AdornerDecorator>
                                <ContentPresenter />
                            </AdornerDecorator>
                        </Border>
                    </DockPanel>
                </Border>
                <ResizeGrip x:Name="WindowResizeGrip" HorizontalAlignment="Right" VerticalAlignment="Bottom" IsTabStop="False" UseLayoutRounding="True" Visibility="Collapsed" />
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="WindowState" Value="Maximized">
                    <Setter TargetName="LayoutRoot" Property="Margin" Value="{x:Static pui:WindowHelper.ChromeThickness}" />
                </Trigger>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="ResizeMode" Value="CanResizeWithGrip" />
                        <Condition Property="WindowState" Value="Normal" />
                    </MultiTrigger.Conditions>
                    <Setter TargetName="WindowResizeGrip" Property="Visibility" Value="Visible" />
                </MultiTrigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Template>
    <i:Interaction.Behaviors>
        <ctrlz:GlowWindowBehavior GlowBrush="{DynamicResource PuiBrushes.Theme80}" NonActiveGlowBrush="{DynamicResource PuiBrushes.Theme80}" ResizeBorderThickness="6" />
    </i:Interaction.Behaviors>
</Window>

要注意的几个点:

1、窗口大小无法拖拽;设置ResizeMode="CanResizeWithGrip",并且ControlTemplate的根元素是Panel,ResizeGrip其实可有可无;

2、窗口最大化时,溢出屏幕;对内容元素设置Margin,不要对根元素设置Margin,不会有效果,Margin的大小可能屏幕有关,计算方式如下

public static class WindowHelper
    {
        private static Nullable<Thickness> chromeThickness;
        public static Thickness ChromeThickness
        {
            get
            {
                if (chromeThickness.HasValue)
                    return chromeThickness.Value;

                var w = (SystemParameters.MaximizedPrimaryScreenWidth - SystemParameters.WorkArea.Width) / 2;
                var h = (SystemParameters.MaximizedPrimaryScreenHeight - SystemParameters.WorkArea.Height) / 2;

                chromeThickness = new Thickness(w, h, w, h);
                return chromeThickness.Value;
            }
        }
    }

未必正确,我这里计算的值是8,实际上7才是完全全屏,第三方UI框架ModernUI就是写死的7,不过它使用的是WindowChrome,而我使用WindowChrome的时候8才是正确的,7相当于去掉1单位的边框,而我只需要最大化时将边框设为0即可;

3、允许窗口透明时,窗口没有发光效果;使用ControlzEx的GlowWindowBehavior可以设置窗口在Active和NoActive下发什么光;

还有什么缺点:

1、窗口最大化时覆盖了任务栏,相当于全屏;

 

二、WindowChrome

<Window x:Class="CustomWindows.ChormeWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:pui="clr-namespace:PumbaaUI.Controls;assembly=PumbaaUI"
        mc:Ignorable="d"
        Title="ChormeWindow" Height="450" Width="800" BorderThickness="1" BorderBrush="{DynamicResource PuiBrushes.Theme}">
    <WindowChrome.WindowChrome>
        <WindowChrome CornerRadius="0" GlassFrameThickness="1" />
    </WindowChrome.WindowChrome>
    <Window.Template>
        <ControlTemplate TargetType="{x:Type Window}">
            <Border>
                <Border x:Name="LayoutRoot" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                    <DockPanel>
                        <Border x:Name="border_title" DockPanel.Dock="Top" Height="28" Background="{DynamicResource PuiBrushes.Window.Title.Background}">
                            <DockPanel>
                                <StackPanel DockPanel.Dock="Right"></StackPanel>
                                <TextBlock Margin="12 0 0 0" VerticalAlignment="Center" Foreground="{DynamicResource PuiBrushes.Window.Title.Foregound}" TextTrimming="WordEllipsis" Text="{TemplateBinding Title}" />
                            </DockPanel>
                        </Border>
                        <Border Background="{TemplateBinding Background}">
                            <AdornerDecorator>
                                <ContentPresenter />
                            </AdornerDecorator>
                        </Border>
                    </DockPanel>
                </Border>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="WindowState" Value="Maximized">
                    <Setter TargetName="LayoutRoot" Property="Margin" Value="{x:Static pui:WindowHelper.ChromeThickness}" />
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Template>
</Window>

了解WindowChrome,可查看微软官方文档:https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.shell.windowchrome

要注意的几个点:

1、GlassFrameThickness设置成负数才会出现默认窗口按钮(不推荐,建议自己添加按钮),设置成0则没有发光效果(默认是黑色的光,如果想用不同颜色的光,推荐使用ControlzEx的GlowWindowBehavior)

2、需要注意的是CaptionHeight,指的是标题栏的高度,默认不等于0,右键标题栏会有一些窗口操作功能,并且在标题栏区域的按钮将无法响应鼠标事件,除非设置IsHitTestVisibleInChrome="True",不需要标题栏的可以将CaptionHeight设为0;

3、和WindowStyle="None"一样,最大化时会溢出屏幕,解决方式也一样,不过Margin的计算是正确的;

还有什么缺点:

1、如果不设置WindowStyle="None"的话,窗口不能实现透明,设置了WindowStyle="None",如果GlassFrameThickness="0",则修复了最大化全屏的问题,但是窗口内容底部溢出(在本身的溢出上,又溢出了一个任务栏高度),并且在此时如果窗口的任务栏大小发生或设置成自动隐藏,窗口的会认为此时不是最大化状态,且无法恢复到原来的大小;如果GlassFrameThickness!="0",则跟WindowStyle="None"效果一样,就跟没设置WindowChrome一样;

2、相比ControlzEx的WindowChromeBehavior,不能全屏显示,除非隐藏掉任务栏;

 补充:

在设置ResizeMode=CanMinimize或NoResize时,最大化将显示为全屏;

三、ControlzEx

<Window x:Class="CustomWindows.ControlExWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:ctrlz="clr-namespace:ControlzEx.Behaviors;assembly=ControlzEx"
        mc:Ignorable="d"
        Title="ControlExWindow" Height="450" Width="800"
        BorderThickness="1" BorderBrush="{DynamicResource PuiBrushes.Theme}">
    <Window.Template>
        <ControlTemplate TargetType="{x:Type Window}">
            <Border x:Name="LayoutRoot" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                <DockPanel>
                    <Border x:Name="border_title" DockPanel.Dock="Top" Height="28" Background="{DynamicResource PuiBrushes.Window.Title.Background}">
                        <DockPanel>
                            <StackPanel DockPanel.Dock="Right"></StackPanel>
                            <TextBlock Margin="12 0 0 0" VerticalAlignment="Center" Foreground="{DynamicResource PuiBrushes.Window.Title.Foregound}" TextTrimming="WordEllipsis" Text="{TemplateBinding Title}" />
                        </DockPanel>
                    </Border>
                    <Border Background="{TemplateBinding Background}">
                        <AdornerDecorator>
                            <ContentPresenter />
                        </AdornerDecorator>
                    </Border>
                </DockPanel>
            </Border>
        </ControlTemplate>
    </Window.Template>
    <i:Interaction.Behaviors>
        <ctrlz:WindowChromeBehavior IgnoreTaskbarOnMaximize="True" />
        <ctrlz:GlowWindowBehavior GlowBrush="{DynamicResource PuiBrushes.Theme80}" NonActiveGlowBrush="{DynamicResource PuiBrushes.Theme80}" ResizeBorderThickness="6" />
    </i:Interaction.Behaviors>
</Window>

 了解ControlzEx,请查看https://github.com/ControlzEx/ControlzEx,内容很少,但每一点都可能是困扰你很久的问题;

要注意的几个点:

1、关于WindowChromeBehavior,设置IgnoreTaskbarOnMaximize="True",则最大化时覆盖任务栏,可以实现最大化和全屏的切换;

2、关于GlowWindowBehavior,不设置Brush和ResizeBorderThickness是不会发光的,默认值是Null和0,发光效果实际上是由四条带BlurEffect的Border组成,所以性能上不敢苟同;

还有什么缺点:

1、无论是否设置WindowStyle="None",窗口都无法透明;

 

总结:

如果不需要透明窗体,使用ControlzEx是最完美的,最大化的时候也不用处理溢出,可自由地切换最大化和全屏;如果需要使用透明窗体,只能使用WindowStyle="None"的方式了,唯一的缺点就是最大化就是全屏,而一般的应用都不会超过任务栏的。

另外第三方UI框架MahApps.Metro v2.1.1的窗口是比较完美的,它使用的是ControlzEx,但是效果又与我的demo不太一致,在窗口透明的情况下,窗口溢出且显示诡异,效果如下:

 

posted @ 2020-07-15 16:51  孤独成派  阅读(2513)  评论(1编辑  收藏  举报