[WPF Custom Controls] use WindowChrome Custom Window Style

1. Why Customize Window

On a little scale desktop software, custom Window is almost a standard, a designer is always to restrain myself think software is more personalized, in order to modify the Window UI harmony is also necessary; secondly multi-line the space can add a lot of functionality, especially on the edges, because the screen is locked move the mouse on the edge of the button so easily selected. Do desktop development will one day meet the needs of custom Window, so I offer a simple custom Window in the control library.

2. I want function

In the last article I introduced the standard Window functions, I would like to achieve, narrow frame, flatten these basic features include a Window, basically imitating Windows Window 10's, but you can easily customize the style; shadow, animation reservation system default on it, basically very engaging. Finally, place a FunctionBar easily add more functionality.

Finally, results are as follows:

This is a custom called ExtendedWindow Window, the source address of the last visible article.

3. WindowChrome

Why do you want to use WindowChrome 3.1 Custom Window

There are two main WPF Custom Window programs, "WPF programming book" describes the use WindowStyle="None"and AllowsTransparency="True"creation of borderless Window and then imitated a Window on the inside, there are many blog previously described in detail in this way, not repeat them here. The principle of this method is removed from the Window non-client area (i.e. chrome), defined by the user and then all Window appearance and behavior described. High degree of freedom in this way, but there are many problems:

  • Window no shadows makes it difficult to see, but DropShadowEffect add customized and very affecting performance;
  • No pop-up, close, maximize, minimize animation, especially when a large number of tasks will not start minimized if the task bar full of animation easily find their program;
  • No animation is very troublesome, custom animation is also done well affect the use;
  • You need to write a lot of code to achieve the original Window drag, resize, maximize and other acts;
  • Further details of the various deletion;

Most of Custom Window or less have the above mentioned problems, but fortunately WPF provides WindowChrome Window class is used to create custom class itself part of the problem addressed above.

The basic concept of 3.2 WindowChrome

WindowChrome defined Window non-client area (i.e., chrome) appearance and behavior of the application in the Window WindowChrome WindowChrome additional attributes of the Window can be replaced with a non-client area WindowChrome (convoluted):

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

Then generate this Window with Blend Style, the outermost layer of the Border background is removed and after doing some simplifying something like this:

<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>

Content is not such a Window operating results as follows:

You can see WindowChrome been defined noe-client area of ​​borders, shadows, title bar, three buttons in the upper right corner, ControleTemplate in the bottom right corner placed a ResizeGrip, and drag, resize, maximize minimize, animation and other functions have been doing. In addition to the Icon and title WindowChrome have put a standard Window achieve almost the same. To implement a custom Window, we want just to the border, Icon, title, custom style buttons on the various elements blocking WindowChrome above it. Principle is very simple, then look at the various properties WindowChrome.

3.3 UseAeroCaptionButtons

UseAeroCaptionButtons does it mean that the title bar of the three default buttons can hit, because we want to own management style of these three buttons, show or hide, so to False.

3.4 GlassFrameThickness和ResizeBorderThickness

GlassFrameThickness and ResizeBorderThickness , these two attributes used to control the border, and the user can click and drag to resize the window width of the region. It is set to 50 if both results are as follows:

Because the frame and can be seen ResizeBorder bigger, the title bar also down the corresponding distance (area via drag and SystemMenu position determination). Of course, because of the appearance of our own definition, ResizeBorderThickness do not need to be so wide, so the two values ​​are reserved defaults on it.

3.5 CaptionHeight

CaptionHeight specify the height of the title bar of WindowChrome. It does not affect the appearance, because the title bar of the actual range WindowChrome is not visible, you can drag it includes the form, double-click the maximize window, right-open SystemMenu other acts.

Default CaptionHeight, GlassFrameThickness and are ResizeBorderThickness and SystemParameters values corresponding coincide.

3.6 IsHitTestVisibleInChrome additional properties

GlassFrameThickness Chrome and CaptionHeight define the scope of any element within the scope of Chrome's default can not interact, if need to put your own buttons (or other interactive elements) in the title bar needs to be WindowsChrome.IsHitTestVisibleInChrome additional properties of this button set to True.

3.7 WindowChrome

In summary, using WindowChrome only need to set UseAeroCaptionButtonsto False, and set up CaptionHeight, relatively standard practice is to use SystemParameter of WindowNonClientFrameThickness the Top, is 27 pixels (the other three sides are 4 pixels at 100% DPI, because my goal Window frame is narrow, so do not use this value).

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

WindowChrome some old documents, the document has been introduced SystemParameters2 found in .NET 4.5, but also find a lot of its implementation on Github, but not necessarily reluctant to use an old API.

4. Custom basic layout 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>

The above is a simplified structure VisualTree ControlTemplate and runtime, which comprises the following sections:

  • WindowBorder, outer frame, i.e., its color Border Window border color.
  • LayoutRoot, is divided into two lines, the first acts of the title bar, the second behavior Content.
  • The title bar, which contains Icon, Title, FunctionBar and WindowCommandButtonsPanel (contain minimize, maximize, restore, and close buttons, etc.).
  • MainContentPresenter,即cient area。
  • ResizeGrip。

5. bound to SystemCommand

SystemCommands have five command CloseWindowCommand, MaximizeWindowCommand, MinimizeWindowCommand, RestoreWindowCommand, ShowSystemMenuCommand, and also provides CloseWindow, MaximizeWindow, MinimizeWindow, RestoreWindow, ShowSystemMenu5 static method. Window title bar of each button needs to be bound to these names and the corresponding static methods of execution. Custom written in the Window class's too complicated and can not be reused, so I put this feature to make additional attribute, usage is as follows:

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

Specific implementation code is very common, it is to call WindowCommandHelper bound to each command IsBindingToSystemCommands property changes:

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 elements of the implementation details

Next comes ControlTemplate implementation details of each UI element.

6.1 Title Bar

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

The title bar of the same height and WindowChrome of CaptionHeight, while the Background of the Window and BorderBrush consistent.

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 is an image, its size is determined by SystemParameters.SmallIconHeight and SystemParameters.SmallIconWidth decision, usually is 16 * 16 pixels.

Icon also be bound to SystemCommands.ShowSystemMenuCommand, click the right mouse button can open SystemMenu.

Finally, remember to set WindowChrome.IsHitTestVisibleInChrome="True".

Title

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

The title font size by the SystemFonts.CaptionFontSize decision, but the colors, fonts own definition.

6.2 Button

<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}" />

Button basically use the same style, but CloseButton background is red. Button icons reference Windows 10 (specifically, Segoe MDL2 in the ChromeMinimize, ChromeMaximize, ChromeRestore, ChromeClose, but did not introduce Segoe MDL2 font in the project, but to convert them into Path to use). Each button corresponding bound SystemCommand.

6.3 FunctionBar

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

In this article introduces the implementation and application of FunctionBar, this XAML that is a placeholder for the FunctionBar stay in the title bar.

6.4 ClientArea

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

This is what Client Area section. Only a Window client content area can get the keyboard focus, and the tabkey will cycle the keyboard focus on the content of the Window. When a Window from the inactive state to an active state will, before the keyboard focus element will regain keyboard focus. So do not let it get AdornerDecorator focus, while MainContentPresenter will have to set KeyboardNavigation.TabNavigation="Cycle".

AdornerDecorator provide AdornerLayer sub tree visualization element, without it if a number of decorative effects can not be displayed (e.g. Focus Effect Button control below), a jacket outside ContentPresenter Window AdornerDecorator is necessary not to forget.

6.5 ResizeGrip

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

When ResizeGrip ResizeMode = ResizeMode.CanResizeWithGrip;and WindowState = Normalthe lower right corner of the Window sizing grip always appear, the appearance of the composition at some point of the triangle. In addition to the operation of the region can become larger, can also be used to prompt Window can be resized.

7. Handling Triggers

Although I usually like to achieve templated control with VisualState way UI transitions between states again, but sometimes Trigger convenient, especially when the need to do the animation. Custom Window Trigger have the following groups need to be addressed:

7.1 IsNonClientActive

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

This property is my custom, used in place of IsActive, when it is False border and title bar turns gray.

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>

Above this XAML controls ResizeGrip is displayed.

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 control both minimized, maximize and restore buttons state. Maximize, restore the two buttons IsEnabled state controlled by SystemCommand binding.

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 maximize button hidden under the state, the Restore button appears. And Window's Margin need to be adjusted, specifically to stay next article we'll talk.

8. DragMove

Some people like more than the title bar, hold down any blank part of the Window can drag the Window, just add DragMove to your code:

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

But this is not like DragMove function, those who have opinions, and then add a property to switch this function very troublesome, simply put it into WindowService.IsDragMoveEnabled additional property, set in the DefaultStyle in.

9. Conclusion

Use WindowChrome self defines the basic functionality of Window described here, but in fact there are many defects WindowChrome, the next article will describe and explain how to avoid these traps (or why not / can not be avoided).

ExtendedWindow approach is to try to become a more generic base class, style, and other additional properties and behavior of the class itself ExtendedWindow not necessarily associated with (added only FunctionBar current location dependency property). The benefit of this is to decouple the code and style, and once for the control to add a property, do not support it is difficult to think about the future, anyway, a high degree of freedom XAML, XAML are handed to expand enough.

I've written an article using WindowChrome Custom Window Style simply introduced Custom Window style program, when the program has many problems, fill this be the previous pit.

10. References

WindowChrome Class (System.Windows.Shell) Microsoft Docs

WPF Windows Overview _ Microsoft Docs

_ Microsoft Docs dialog box Overview

SystemParameters Class (System.Windows) Microsoft Docs

11. Source

Kino.Toolkit.Wpf_Window at master

Guess you like

Origin www.cnblogs.com/dino623/p/custom_window_style_using_WindowChrome.html