[WPFカスタムコントロール]?使用WindowChromeカスタムウィンドウスタイル

オリジナル: ?[WPFカスタムコントロール] WindowChromeカスタムウィンドウスタイルを使用します

1.なぜカスタマイズウィンドウ

小さな規模のデスクトップソフトウェアでは、カスタムウィンドウには、標準では、設計者がウィンドウのUIの調和を変更するために、自分自身がソフトウェアは、よりパーソナライズさだと思う抑制することは常にある、ほとんどであることが必要である。第二に、マルチラインロックされている画面は、そう簡単に選択されたボタンの端にマウスを移動するためのスペースは、特にエッジに、多くの機能を追加することができます。1日のカスタムウィンドウのニーズを満たすデスクトップ開発を行いますので、私はコントロールライブラリで単純なカスタムウィンドウを提供します。

2.私は機能が欲しいです

前回の記事で私は、狭額縁を実現したいと、これらの基本的な機能は、基本的には、Windowsのウィンドウ10年代模倣、窓を含む平らになり、標準のウィンドウ機能を導入していますが、簡単にスタイルをカスタマイズすることができ、影を、その上にアニメーションの予約システムのデフォルト、基本的には非常に魅力的。最後に、簡単に、より多くの機能を追加FunctionBarを配置します。

次のように最後に、結果は以下のとおりです。

これはExtendedWindowウィンドウ、最後の目に見える記事の送信元アドレスと呼ばれるカスタムです。

3. WindowChrome

なぜあなたはWindowChrome 3.1カスタムウィンドウを使用したいですか

二つの主なWPFカスタムウィンドウのプログラムがありますが、「WPFプログラミングの本は、」使用説明WindowStyle="None"AllowsTransparency="True"ボーダレスウィンドウの作成をして、ここではそれらを繰り返さないで、以前にこのように詳細に記載されている多くのブログがあるの内側にウィンドウを真似しました。この方法の原理は、ユーザによって定義されたウィンドウの非クライアント領域(すなわちクロム)から除去した後、すべてのウィンドウの外観と動作を説明します。このように自由度の高い、しかし、多くの問題があります。

  • ウィンドウの影が見することが困難になりませんが、DropShadowEffectは、カスタマイズを追加し、非常にパフォーマンスに影響を与えます。
  • いいえポップアップない、近くに、アニメーションのフルタスクバーが簡単に自分のプログラムを見つけた場合、多数のタスクを最小限に開始されません場合は特に、アニメーションを最小限に抑え、最大限に。
  • いいえアニメーションは、カスタムアニメーションがまた、使用に影響を与えるよくやっている、非常に面倒ではありません。
  • あなたは、元のウィンドウのドラッグを達成するために多くのコードを記述し、サイズ変更、最大化し、他の行為する必要があります。
  • 様々な削除のさらなる詳細は、

カスタムウィンドウの大半以下は、上記の問題を抱えているが、幸いWPFは提供WindowChrome Windowクラス自体が問題の一部は、上記の対処カスタムクラスを作成するために使用されます。

3.2 WindowChromeの基本的な考え方

WindowChrome定義されたウィンドウの非クライアント領域(すなわち、クロム)外観及びウィンドウのウィンドウWindowChrome WindowChrome追加属性におけるアプリケーションの動作は、非クライアント領域WindowChrome(回旋)で置き換えることができます。

<WindowChrome.WindowChrome>
    <WindowChrome />
</WindowChrome.WindowChrome>

ブレンドスタイルでこのウィンドウを生成し、ボーダーの背景の最も外側の層を除去し、このようないくつかの単純化何かをやった後にされています。

<Window.Style>
    <Style TargetType="{x:Type Window}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Window}">
                    <Border>
                        <Grid>
                            <AdornerDecorator>
                                <ContentPresenter />
                            </AdornerDecorator>
                            <ResizeGrip x:Name="WindowResizeGrip"
                                        HorizontalAlignment="Right"
                                        IsTabStop="false"
                                        Visibility="Collapsed"
                                        VerticalAlignment="Bottom" />
                        </Grid>
                    </Border>
                    <ControlTemplate.Triggers>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="ResizeMode" Value="CanResizeWithGrip" />
                                <Condition Property="WindowState" Value="Normal" />
                            </MultiTrigger.Conditions>
                            <Setter Property="Visibility" TargetName="WindowResizeGrip" Value="Visible" />
                        </MultiTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Style>

次のようにコンテンツは、そのようなウィンドウの営業成績ではありません。

あなたは、国境のWindowChromeれて定義されたNOE-クライアント領域を参照の影、タイトルバー、右下隅にある右上隅、ControleTemplateにおける3つのボタンがResizeGripを置き、ドラッグ、サイズ変更、最小化、最大化、アニメーションすることができますその他の機能はやってきました。アイコンとタイトルWindowChromeに加えて、標準のウィンドウがほぼ同じ達成入れています。カスタムウィンドウを実装するために、我々はそれを超えるWindowChromeを阻止する様々な要素のカスタムスタイルボタン、ちょうど国境に、アイコン、タイトルが欲しいです。原理は非常に簡単で、その後、様々な性質WindowChromeを見てください。

3.3 UseAeroCaptionButtons

UseAeroCaptionButtons表示标题栏上的那三个默认按钮是否可以命中,因为我们想要自己管理这三个按钮的样式、显示或隐藏,所以设置为False。

3.4 GlassFrameThickness和ResizeBorderThickness

GlassFrameThicknessResizeBorderThickness,这两个属性用于控制边框,及用户可以单击并拖动以调整窗口大小的区域的宽度。如果两个都设置为50效果如下:

可以看到因为边框和ResizeBorder变大了,标题栏也下移了相应的距离(通过可拖动区域和SystemMenu的位置判断)。当然因为外观是我们自己定义的,ResizeBorderThickness也不需要这么宽,所以两个值都保留默认值就可以了。

3.5 CaptionHeight

CaptionHeight指定WindowChrome的标题栏高度。它不影响外观,因为WindowChrome的标题栏范围实际是不可见的,它包括可以拖动窗体、双击最大化窗体、右键打开SystemMenu等行为。

CaptionHeight、GlassFrameThickness和ResizeBorderThickness的默认值都和SystemParameters的对应的值一致。

3.6 IsHitTestVisibleInChrome附加属性

GlassFrameThickness和CaptionHeight定义了Chrome的范围,默认情况下任何在Chrome的范围内的元素都不可以交互,如果需要在标题栏放自己的按钮(或其它交互元素)需要将这个按钮的WindowsChrome.IsHitTestVisibleInChrome附加属性设置为True。

3.7 使用WindowChrome

综上所述,使用WindowChrome只需要设置UseAeroCaptionButtons为False,并且设置CaptionHeight,比较标准的做法是使用SystemParameter的WindowNonClientFrameThickness的Top,在100% DPI下是 27 像素(其它三个边都为4像素,因为我的目标是窄边框的Window,所以不会用这个值)。

<Setter Property="WindowChrome.WindowChrome">
    <Setter.Value>
        <WindowChrome UseAeroCaptionButtons="False"
                      CaptionHeight="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}" />
    </Setter.Value>
</Setter>

WindowChrome的文档有些旧了,文档中介绍的SystemParameters2在.NET 4.5已经找不到,在Github上还能找到不少它的实现,但没必要勉强用一个旧的API。

4. 自定义Window基本布局

<ControlTemplate TargetType="{x:Type Window}">
    <Border BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            x:Name="WindowBorder">
        <Grid x:Name="LayoutRoot"
              Background="{TemplateBinding Background}">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Grid x:Name="WindowTitlePanel"
                  Height="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
                  Background="{TemplateBinding BorderBrush}"
                  Margin="0,-1,0,0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>

                <StackPanel Orientation="Horizontal">
                    <Image Source="{TemplateBinding Icon}"
                           Height="{x:Static SystemParameters.SmallIconHeight}"
                           Width="{x:Static SystemParameters.SmallIconWidth}"
                           WindowChrome.IsHitTestVisibleInChrome="True" />
                    <ContentControl FontSize="{DynamicResource {x:Static SystemFonts.CaptionFontSize}}"
                                    Content="{TemplateBinding Title}" />
                </StackPanel>

                <StackPanel x:Name="WindowCommandButtonsPanel"
                            Grid.Column="1"
                            HorizontalAlignment="Right"
                            Orientation="Horizontal"
                            WindowChrome.IsHitTestVisibleInChrome="True"
                            Margin="0,0,-1,0">
                    <ContentPresenter Content="{Binding FunctionBar, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
                                      Focusable="False" />
                    <Button x:Name="MinimizeButton" />
                    <Grid Margin="1,0,1,0">
                        <Button x:Name="RestoreButton"
                                Visibility="Collapsed" />
                        <Button x:Name="MaximizeButton" />
                    </Grid>
                    <Button x:Name="CloseButton"
                            Background="Red" />
                </StackPanel>
            </Grid>
            <AdornerDecorator Grid.Row="1"
                              KeyboardNavigation.IsTabStop="False">
                <ContentPresenter Content="{TemplateBinding Content}"
                                  x:Name="MainContentPresenter"
                                  KeyboardNavigation.TabNavigation="Cycle" />
            </AdornerDecorator>
            <ResizeGrip x:Name="ResizeGrip"
                        HorizontalAlignment="Right"
                        VerticalAlignment="Bottom"
                        Grid.Row="1" />
        </Grid>
    </Border>
</ControlTemplate>

上面是简化后的ControlTemplate及运行时的VisualTree结构,它包含以下部分:

  • WindowBorder,外层的边框,它的Border颜色即Window的边框颜色。
  • LayoutRoot,分为两行,第一行为标题栏,第二行为Content。
  • 标题栏,里面包含Icon、Title、FunctionBar及WindowCommandButtonsPanel(包含最小化、最大化、还原和关闭等按钮)。
  • MainContentPresenter,即cient area。
  • ResizeGrip。

5. 绑定到SystemCommand

SystemCommands有5个命令CloseWindowCommand、MaximizeWindowCommand、MinimizeWindowCommand、RestoreWindowCommand、ShowSystemMenuCommand,并且还提供了CloseWindow、MaximizeWindow、MinimizeWindow、RestoreWindow、ShowSystemMenu5个静态方法。Window标题栏上的各个按钮需要绑定到这些命名并执行对应的静态方法。写在自定义的Window类里太复杂了而且不能重用,所以我把这个功能做成附加属性,用法如下:

<Setter Property="local:WindowService.IsBindingToSystemCommands"
        Value="True" />

具体实现代码很普通,就是IsBindingToSystemCommands属性改变时调用WindowCommandHelper绑定到各个命令:

private class WindowCommandHelper
{
    private Window _window;

    public WindowCommandHelper(Window window)
    {
        _window = window;
    }

    public void ActiveCommands()
    {
        _window.CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, CloseWindow));
        _window.CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, MaximizeWindow, CanResizeWindow));
        _window.CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, MinimizeWindow, CanMinimizeWindow));
        _window.CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand, RestoreWindow, CanResizeWindow));
        _window.CommandBindings.Add(new CommandBinding(SystemCommands.ShowSystemMenuCommand, ShowSystemMenu));
    }

    /*SOME CODE*/
}

6. UI元素的实现细节

接下来介绍ControlTemplate中各个UI元素的实现细节。

6.1 标题栏

<Grid x:Name="WindowTitlePanel"
      VerticalAlignment="Top"
      Height="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}"
      Background="{TemplateBinding BorderBrush}">

标题栏的高度和WindowChrome的CaptionHeight一致,而Background则和Window的BorderBrush一致。

Icon

<Image Source="{TemplateBinding Icon}"
       VerticalAlignment="Center"
       Margin="5,0,5,0"
       Height="{x:Static SystemParameters.SmallIconHeight}"
       Width="{x:Static SystemParameters.SmallIconWidth}"
       WindowChrome.IsHitTestVisibleInChrome="True">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseLeftButtonDown">
            <i:InvokeCommandAction Command="{x:Static SystemCommands.ShowSystemMenuCommand}" />
        </i:EventTrigger>
        <i:EventTrigger EventName="MouseRightButtonDown">
            <i:InvokeCommandAction Command="{x:Static SystemCommands.ShowSystemMenuCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Image>

Icon是一张图片,它的大小由SystemParameters.SmallIconHeightSystemParameters.SmallIconWidth决定,通常来说是16 * 16像素。

Icon还绑定到SystemCommands.ShowSystemMenuCommand,点击鼠标左右键都可以打开SystemMenu。

最后记得设置WindowChrome.IsHitTestVisibleInChrome="True"

Title

<ContentControl IsTabStop="False"
                Foreground="White"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontSize="{DynamicResource {x:Static SystemFonts.CaptionFontSize}}"
                Content="{TemplateBinding Title}" />

标题的字号由SystemFonts.CaptionFontSize决定,但颜色、字体都自己定义。

6.2 按钮

<Style x:Key="MinimizeButtonStyle"
       TargetType="Button"
       BasedOn="{StaticResource WindowTitleBarButtonStyle}">
    <Setter  Property="ToolTip"
             Value="Minimize" />
    <Setter Property="ContentTemplate"
            Value="{StaticResource MinimizeWhite}" />
    <Setter Property="Command"
            Value="{Binding Source={x:Static SystemCommands.MinimizeWindowCommand}}" />
</Style>

<!--OTHER BUTTON STYLES-->

<Button x:Name="MinimizeButton"
        Style="{StaticResource MinimizeButtonStyle}" />
<Grid Margin="1,0,1,0">
    <Button x:Name="RestoreButton"
            Style="{StaticResource RestoreButtonStyle}"
            Visibility="Collapsed" />
    <Button x:Name="MaximizeButton"
            Style="{StaticResource MaximizeButtonStyle}" />
</Grid>
<Button x:Name="CloseButton"
        Background="Red"
        Style="{StaticResource CloseButtonStyle}" />

按钮基本上使用相同的样式,不过CloseButton的背景是红色。按钮的图标参考Windows 10(具体来说是Segoe MDL2里的ChromeMinimize、ChromeMaximize、ChromeRestore、ChromeClose,不过没有在项目中引入Segoe MDL2字体,而是把它们转换成Path来使用)。各个按钮绑定了对应的SystemCommand。

6.3 FunctionBar

<ContentPresenter Content="{Binding FunctionBar, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
                  Focusable="False" />

这篇文章中介绍了FunctionBar的实现及应用,这段XAML即在标题栏为FunctionBar留一个占位符。

6.4 ClientArea

<AdornerDecorator Grid.Row="1"
                  KeyboardNavigation.IsTabStop="False">
    <ContentPresenter Content="{TemplateBinding Content}"
                      x:Name="MainContentPresenter"
                      KeyboardNavigation.TabNavigation="Cycle" />
</AdornerDecorator>

这是Client Area部分的内容。一个Window中只有client area中的内容可以获得键盘焦点,而且tab键只会让键盘焦点在Window的内容中循环。当一个Window从非激活状态会到激活状态,之前获得键盘焦点的元素将重新获得键盘焦点。所以AdornerDecorator不要让它获得焦点,而MainContentPresenter则要设置为KeyboardNavigation.TabNavigation="Cycle"

AdornerDecorator 为可视化树中的子元素提供 AdornerLayer,如果没有它的话一些装饰效果不能显示(例如下图Button控件的Focus效果),Window的 ContentPresenter 外面套个 AdornerDecorator 是 必不能忘的。

6.5 ResizeGrip

<ResizeGrip x:Name="ResizeGrip"
            HorizontalAlignment="Right"
            VerticalAlignment="Bottom"
            Grid.Row="1"
            IsTabStop="False"
            Visibility="Hidden"
            WindowChrome.ResizeGripDirection="BottomRight" />

ResizeGrip是当ResizeMode = ResizeMode.CanResizeWithGrip;并且WindowState = Normal时时出现的Window右下角的大小调整手柄,外观为组成三角形的一些点。除了让可以操作的区域变大一些,还可以用来提示Window是可以调整大小的。

7. 处理Triggers

虽然我平时喜欢用VisualState的方式实现模板化控件UI再状态之间的转变,但有时还是Trigger方便快捷,尤其是不需要做动画的时候。自定义Window有以下几组需要处理的Trigger:

7.1 IsNonClientActive

<Trigger Property="IsNonClientActive"
         Value="False">
    <Setter Property="BorderBrush"
            Value="#FF6F7785" />
</Trigger>

这个属性是我自定义的,用于代替IsActive,在它为False的时候边框和标题栏变成灰色。

7.2 ResizeGrip

<MultiTrigger>
    <MultiTrigger.Conditions>
        <Condition Property="ResizeMode"
                   Value="CanResizeWithGrip" />
        <Condition Property="WindowState"
                   Value="Normal" />
    </MultiTrigger.Conditions>
    <Setter TargetName="ResizeGrip"
            Property="Visibility"
            Value="Visible" />
</MultiTrigger>

上面这段XAML控制ResizeGrip是否显示。

7.3 Buttons

<Trigger Property="WindowState"
         Value="Normal">
    <Setter TargetName="MaximizeButton"
            Property="Visibility"
            Value="Visible" />
    <Setter TargetName="RestoreButton"
            Property="Visibility"
            Value="Collapsed" />
</Trigger>
<Trigger Property="ResizeMode"
         Value="NoResize">
    <Setter TargetName="MinimizeButton"
            Property="Visibility"
            Value="Collapsed" />
    <Setter TargetName="MaximizeButton"
            Property="Visibility"
            Value="Collapsed" />
    <Setter TargetName="RestoreButton"
            Property="Visibility"
            Value="Collapsed" />
</Trigger>

这两个Trigger控制最小化、最大化和还原按钮的状态。最大化、还原两个按钮的IsEnabled状态由绑定的SystemCommand控制。

7.4 Maximized

<Trigger Property="WindowState"
         Value="Maximized">
    <Setter TargetName="MaximizeButton"
            Property="Visibility"
            Value="Collapsed" />
    <Setter TargetName="RestoreButton"
            Property="Visibility"
            Value="Visible" />
    <Setter TargetName="WindowBorder"
            Property="BorderThickness"
            Value="0" />
    <Setter TargetName="WindowBorder"
            Property="Padding"
            Value="{x:Static SystemParameters.WindowResizeBorderThickness}" />
    <Setter Property="Margin"
            TargetName="LayoutRoot"
            Value="{x:Static local:WindowParameters.PaddedBorderThickness}" />
</Trigger>

Maximized状态下最大化按钮隐藏,还原按钮出现。并且Window的Margin需要调整,具体留到下一篇文章再说吧。

8. DragMove

有些人喜欢不止标题栏,按住Window的任何空白部分都可以拖动Window,只需要在代码中添加DragMove即可:

protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
    base.OnMouseLeftButtonDown(e);
    if (e.ButtonState == MouseButtonState.Pressed)
        DragMove();
}

但这样做不喜欢DragMove功能的人又会有意见,再添加一个属性来开关这个功能又很麻烦,索性就把它做成WindowService.IsDragMoveEnabled附加属性,在DefaultStyle中设置了。

9. 结语

使用WindowChrome自定义Window的基本功能就介绍到这里了,但其实WindowChrome有很多缺陷,下一篇文章将介绍这些陷阱及讲解如何回避(或者为什么不/不能回避)。

ExtendedWindow的做法是尽量成为一个更通用的基类,样式和其它附加属性中的行为和ExtendedWindow的类本身没有必然关联(目前位置只添加了FunctionBar依赖属性)。这样做的好处是为代码和样式解耦,而且一旦为控件添加了属性,以后再想不支持就很难了,反正XAML的自由度很高,都交给XAML去扩展就好了。

我以前也写过一篇文章使用WindowChrome自定义Window Style简单介绍过自定义Window样式的方案,当时的方案有不少问题,这次算是填上以前的坑。

10. 参考

WindowChrome Class (System.Windows.Shell) Microsoft Docs

WPF Windows 概述 _ Microsoft Docs

对话框概述 _ Microsoft Docs

SystemParameters Class (System.Windows) Microsoft Docs

11. 源码

Kino.Toolkit.Wpf_Window at master

おすすめ

転載: www.cnblogs.com/lonelyxmas/p/11001971.html