Border Margin in Window template doesn't have any effect when used with WindowChrome
Asked Answered
P

3

12

I was checking out the WindowChrome class in System.Windows.Shell library (v 3.5.41019.1). When I try to create a Window template, the margin of the Border element in the template seems to have no effect:

<Window x:Class="WpfApplication7.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:shell="clr-namespace:Microsoft.Windows.Shell;assembly=Microsoft.Windows.Shell"
        Title="MainWindow" Height="350" Width="525" Style="{DynamicResource WindowStyle1}">
    <Window.Resources>
        <Style x:Key="WindowStyle1" TargetType="{x:Type Window}">
<!-- Here is the WindowChrome.-->
            <Setter Property="shell:WindowChrome.WindowChrome">
                <Setter.Value>
                    <shell:WindowChrome />
                </Setter.Value>
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Window}">
<!-- And here is the Border. Its margin has no effect as far as I can tell.-->
                        <Border Margin="25" Background="Red">
                            <AdornerDecorator>
                                <ContentPresenter/>
                            </AdornerDecorator>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>

    </Grid>
</Window>

What do you think is the reason for that? I am wondering that, because I saw that some people use something like*:

<Border x:Name="WindowBorder" Margin="{Binding Source={x:Static shell:SystemParameters2.Current}, Path=WindowNonClientFrameThickness}" Background="Red">

But as it doesn't have any effect in my tests, what could be the point of doing this?

(*) One of the places it is used is the ModernUI project on CodePlex.

Edit: I have tested this on Windows 7 with Aero on.

Edit 2: It's still the same with Aero off.

Peregrine answered 17/5, 2013 at 12:59 Comment(0)
R
9

According to MSDN, WindowChrome is

Represents an object that describes the customizations to the non-client area of a window.

After reading MSDN sample and playing your code a while, I noticed your code should be like following from MSDN sample code:

<Style x:Key="StandardStyle" TargetType="{x:Type local:MainWindow}">
<Setter Property="shell:WindowChrome.WindowChrome">
    <Setter.Value>
        <shell:WindowChrome />
    </Setter.Value>
</Setter>
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:MainWindow}">
            <!--Note there is a Grid as the root-->
            <Grid>
                <Border Background="White"
                        Margin="{Binding Source={x:Static shell:SystemParameters2.Current}, Path=WindowNonClientFrameThickness}">
                    <ContentPresenter Content="{TemplateBinding Content}" />
                </Border>
                <TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Title}" 
                           VerticalAlignment="Top" HorizontalAlignment="Left" 
                           Margin="36,8,0,0"/>
                <Image Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Icon}"
                       VerticalAlignment="Top" HorizontalAlignment="Left"
                       Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(shell:WindowChrome.WindowChrome).ResizeBorderThickness}" 
                       Width="{Binding Source={x:Static shell:SystemParameters2.Current}, Path=SmallIconSize.Width}"
                       shell:WindowChrome.IsHitTestVisibleInChrome="True"/>
            </Grid>
        </ControlTemplate>
    </Setter.Value>
</Setter>

Note, there's a Grid as the root element which contains a few elements for customizing the NC of the window.

UPDATE:

You may notice in the remark of the MSDN page, it contains sections:

WindowStyle.None

WindowChrome

These are the two ways of customizing the appearance of a WPF application window.

However, setting the Window.WindowStyle property to WindowStyle.None:

This removes the non-client frame from the window and leaves only the client area, to which you can apply a custom style. However, when the non-client frame is removed, you also lose the system features and behaviors that it provides, such as caption buttons and window resizing. Another side effect is that the window will cover the Windows taskbar when it is maximized.

Then WindowChrome is introduced to enable NC customization using WPF:

To customize a window while retaining its standard functionality, you can use the WindowChrome class. The WindowChrome class separates the functionality of the window frame from the visuals, and lets you control the boundary between the client and non-client areas of your application window. The WindowChrome class lets you put WPF content in the window frame by extending the client area to cover the non-client area. At the same time, it retains system behaviors through two invisible areas; the resize border and caption areas.

So back to your question, the template you found, should be copied from the MSDN sample code, but missed the true root Grid. The Margin on the Border is for giving some space to the NC. In the MSDN sample code, the ContenPreseter only contains the Client area, while the NC contains the Border, a TextBlock for window title, and an Image for window icon.

To recap, setting WindowChrome enables you to customize the NC area of the window in the Window.Template.

NOTE: The sample MSDN sample code seems a little out of date in .Net 4.5, the System.Windows.Shell.WindowChrome is now in the PresentationFramework.dll, so the code may look like:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" Style="{DynamicResource WindowStyle1}" Icon="Icon1.ico">
<Window.Resources>
    <Style x:Key="WindowStyle1" TargetType="{x:Type Window}">
        <Setter Property="WindowChrome.WindowChrome">
            <Setter.Value>
                <WindowChrome />
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Window}">
                    <Grid>
                        <Border Background="Red"
                        Margin="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}}">
                            <ContentPresenter Content="{TemplateBinding Content}" />
                        </Border>
                        <TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Title}" 
                           VerticalAlignment="Top" HorizontalAlignment="Left" 
                           Margin="36,8,0,0"/>
                        <Image Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Icon}"
                       VerticalAlignment="Top" HorizontalAlignment="Left"
                       Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=WindowChrome.WindowChrome.ResizeBorderThickness}" 
                       Width="{Binding Source={x:Static SystemParameters.SmallIconWidth}}"
                       WindowChrome.IsHitTestVisibleInChrome="True"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>
<Grid>
    <Button />
</Grid>

Removal answered 26/5, 2013 at 5:24 Comment(1)
You are right I believe. They copied the code from MSDN, and it just stayed that way somehow.Peregrine
F
1

I think there is some misunderstanding in the way you are trying to set the border. Here is the explanation of WindowChrome Class as provided in msdn

The WindowChrome class separates the functionality of the window frame from the visuals, and lets you control the boundary between the client and non-client areas of your application window. The WindowChrome class lets you put WPF content in the window frame by extending the client area to cover the non-client area. At the same time, it retains system behaviors through two invisible areas; the resize border and caption areas.

So if you are trying to customize the NonClient Area of the Window, its not the Content Presenter that you should set Border onto. That is the client area. Instead in the Template you can add your XAML other than Content Presenter to define your NonClient Area. I just tried a simple code based on your code and it shifts the Title Property of the Window to the right by a value of 100. Here is the code.

<Window x:Class="WPF_ToggleButton.ShellWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:shell="clr-namespace:Microsoft.Windows.Shell;assembly=Microsoft.Windows.Shell"
    Title="MainWindow" Height="350" Width="525" Style="{DynamicResource WindowStyle1}" 
    >

<Window.Resources>
    <Style x:Key="WindowStyle1" TargetType="{x:Type Window}">
        <Setter Property="shell:WindowChrome.WindowChrome">
            <Setter.Value>
                <shell:WindowChrome />
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Window}">
                    <Grid>
                        <Border Background="Yellow">
                            <AdornerDecorator>
                                <ContentPresenter Content="{TemplateBinding Content}"/>
                            </AdornerDecorator>
                        </Border>
                        <TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Title}" 
                           VerticalAlignment="Top" HorizontalAlignment="Left" 
                           Margin="100,0,0,0"/>
                    </Grid>                        
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>
<Grid>
    <Border Margin="50" Background="AliceBlue"/>
</Grid>

Thus you can have any elements in the NonClient Area like image representing your Window Close Button, etc using XAML code. The last element in the Window defines the Client Area which is passed to the Content Presenter in the Template

In short if you wan't to customize Client Area use the Content Presenter, whereas if you are interested in changing the NonClient Area like Title Bar display, close image icon then you define it in the Template.

One short observation. I think Margin doesn't make any sense for a Window. Try setting it for a normal window and I think it won't respect it.

Fireboard answered 19/5, 2013 at 15:20 Comment(4)
First, apologies for commenting late. I have been really busy the last few days. I am aware of how to use WindowChrome. The problem is, when you use WindowChrome and define a template, the Margin property of the first Layout element in the ControlTemplate doesn't have any effect. Your code has the exact same thing. Try putting Margin to your Grid (the first one in the ControlTemplate) and it doesn't have any effect. If you remove WindowChrome, you can see that the Margin is filled with black on a normal Window.Peregrine
The thing is, I was taking a look at ModernUI and saw that they put a Margin around the first Layout element in the ControlTemplate of their Window (and then I saw the same thing in some other places too, I am guessing that they all copy-pasted from the same place.) But it didn't change anything for me. I was wondering because maybe there's something I don't know, maybe it works on Windows XP, or Windows 8. I just wanted some clarification on that.Peregrine
I don't really fully understand your comment and I am sorry for that. You wan't to set margin to the Grid in the Control Template. That margin will be relative to which element ? I mean for example, a margin of 100 from which element ?Fireboard
Margin to Window itself. Just try putting a margin in the Grid without WindowChrome and you will see that it behaves different. But the problem is not that I want to put a margin to it. I just saw that in some projects they have put a margin to the first layout element, and I thought maybe there's a catch to it. That's what I wanted to find out.Peregrine
S
0

Run into the same problem. No matter how I tried to set the Margin of the root element (its type does not matter) in MainWindow's ControlTemplate, it was always 0 when starting the app. The answer (thx to ILSpy) is in WindowChrome implementation.

When WindowChrome is attached to a Window, it is applying the Chrome feature, and among others, it executes a method WindowChromWorker._FixupTemplateIssues, which simple resets the window first visual element's Margin to a new Thickness instance, thus, erasing your custom settings by styles, templates, etc.

Workarounds can be:

  • Embedd your template into a new, look-less root element e.g. a Grid, so your original root's Margin won't be overridden
  • Adjust Padding of your root
Seko answered 15/7, 2021 at 10:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.