WPF window with custom chrome has unwanted outline on right and bottom

时光怂恿深爱的人放手 提交于 2020-12-29 05:46:48

问题


I have created a WPF window with custom chrome using the Microsoft.Windows.Shell dll. Here is the code for that:

<Style TargetType="Window" x:Key="ChromeLessWindowStyle">
        <Setter Property="shell:WindowChrome.WindowChrome">
            <Setter.Value>
                <shell:WindowChrome
           GlassFrameThickness="0"
          ResizeBorderThickness="5"          
          CornerRadius="5"
          CaptionHeight="30">
                </shell:WindowChrome>
            </Setter.Value>
        </Setter>
        <Setter Property="WindowStyle" Value="None"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Window}">
                    <Grid>
                            <Grid Background="#FF595959" >
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="*"/>
                                </Grid.RowDefinitions>
                                <Border Grid.Row="0" Height="30" Background="#FF393939">
                                    <DockPanel LastChildFill="False" Margin="0,1,5,0">
                                        <TextBlock DockPanel.Dock="Left" Style="{DynamicResource {x:Static coreKeys:TextBlockKeys.Default}}" FontWeight="Bold" Text="{TemplateBinding Title}" Margin="10,0,0,0" VerticalAlignment="Center"/>
                                        <!--Buttons-->
                                        <Button DockPanel.Dock="Right" behaviors:WindowCommandBehaviors.IsCloseButton="True" Style="{DynamicResource {x:Static coreKeys:ButtonKeys.Close}}" shell:WindowChrome.IsHitTestVisibleInChrome="True"/>
                                        <Button DockPanel.Dock="Right" behaviors:WindowCommandBehaviors.IsMaximizeButton="True" Style="{DynamicResource {x:Static coreKeys:ButtonKeys.Maximize}}" Visibility="{TemplateBinding WindowState,Converter={StaticResource WindowStateToVisibilityConverter},ConverterParameter=MaximizeButton }" shell:WindowChrome.IsHitTestVisibleInChrome="True" />
                                        <Button DockPanel.Dock="Right" behaviors:WindowCommandBehaviors.IsMaximizeButton="True" Style="{DynamicResource {x:Static coreKeys:ButtonKeys.Restore}}"  Visibility="{TemplateBinding WindowState,Converter={StaticResource WindowStateToVisibilityConverter}, ConverterParameter=RestoreButton }" shell:WindowChrome.IsHitTestVisibleInChrome="True" />
                                        <Button DockPanel.Dock="Right" behaviors:WindowCommandBehaviors.IsMinimizeButton="True" Style="{DynamicResource {x:Static coreKeys:ButtonKeys.Minimize}}" shell:WindowChrome.IsHitTestVisibleInChrome="True"/>
                                    </DockPanel>
                                </Border>
                                <ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}"/>
                            </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

This works perfect in normal scenarios and I could not discover a problem until I had a requirement of using the window using C# code. I have a messaging service that:

  1. Creates a modal window.
  2. Populates its content with a WPF user control.
  3. Sets the data context of the window to an appropriate ViewModel.
  4. Shows the window

Here is the code for that:

var userControl = viewRegistry.GetViewByKey(viewKey_); // Get the UserControl.
var modalWindow = new ModalCustomMessageDialog
{
    // Set the content of the window as the user control
    DataContext = viewModel_,
    // Set the data context of the window as the ViewModel
    Owner = Util.AppMainWindow,
    // Set the owner of the modal window to the app window.
    WindowStartupLocation = WindowStartupLocation.CenterOwner,
    //Title = viewModel.TitleText ?? "",
    ShowInTaskbar = false,
    Content = userControl,
    SizeToContent = SizeToContent.WidthAndHeight
};
if (showAsToolWindow_)
{
    modalWindow.ResizeMode = ResizeMode.NoResize;
    modalWindow.WindowStyle = WindowStyle.ToolWindow;
}
modalWindow.Loaded += modalWindow_Loaded;
modalWindow.Closed += CleanModalWindow;
modalWindow.Show();

Note the line

SizeToContent = SizeToContent.WidthAndHeight

This takes care of resizing the window to honour the Width and Height of the user control. The modal window thus produced has a thick black outline on the right and bottom of the window. Like so:

Window with the outline

The window should be like (and after resize becomes) like this:

Good window

There are a few points worth noting :

  1. This black outline disappears as soon as the window is resized.

  2. This outline doesnt appear if the SizeToContent is set to either SizeToContent.Height or SizeToContent.Width. But then it blows off either the Width or Height of the modal window respectively.

  3. I thought there might be some issue with window getting redrawn. So I tried the following code to redraw the window:

    private const int WmPaint = 0x000F;
    
    [DllImport("User32.dll")]
    public static extern Int64 SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
     ......................
    //Inside the Loaded event handler of the modalWindow
    var windowHandle = new WindowInteropHelper(modalWindow).Handle;
    SendMessage(windowHandle, WmPaint, IntPtr.Zero, IntPtr.Zero);
    

    This has no effect.

  4. This problem does not appear if I have fixed Height and Width property given to the User Control that populates the window. However, I can't do that always.

  5. The messaging service has been in place since ages and this ghost outline has made its appearance recently after the custom chrome change.

Has anybody faced a similar situation? Any help will be appreciated.


回答1:


I recently hit on this problem when using a custom window chrome on a window which contains dynamically generated elements.

Why does this happen?

If we are using a window with static contents, the window can be aware upon initialization what will be with maximum width/height required to contain its child elements.

In the case that we want to use dynamic elements with automatic scaling, such as Views if using the MVVM pattern, we need to request the window update its visual state once all its bindngs (Views, for example) have been resolved.

The Solution

To enforce the behaviour we've reasoned about above, we need to use the window's ContentRendered event, and use it to InvalidateVisual().

In the XAML of the window you're experiencing the problem:

ContentRendered="Window_OnContentRendered"

In the the codebehind:

private void Window_OnContentRendered(object sender, EventArgs e)
{
    InvalidateVisual();
}

Best of luck.




回答2:


I struggled with the same problem and created the following extension for Window class:

public static void FixLayout(this Window window)
{
    bool arrangeRequired = false;
    double deltaWidth = 0;
    double deltaHeight = 0;

    void Window_SourceInitialized(object sender, EventArgs e)
    {
        window.InvalidateMeasure();
        arrangeRequired = true;
        window.SourceInitialized -= Window_SourceInitialized;
    }

    void CalculateDeltaSize()
    {
        deltaWidth = window.ActualWidth - deltaWidth;
        deltaHeight = window.ActualHeight - deltaHeight;
    }

    void Window_LayoutUpdated(object sender, EventArgs e)
    {
        if (arrangeRequired)
        {
            if (window.SizeToContent == SizeToContent.WidthAndHeight)
            {
                CalculateDeltaSize();
            }
            window.Left -= deltaWidth * 0.5;
            window.Top -= deltaHeight * 0.5;
            window.LayoutUpdated -= Window_LayoutUpdated;
        }
        else
        {
            CalculateDeltaSize();
        }
    }

    window.SourceInitialized += Window_SourceInitialized;
    window.LayoutUpdated += Window_LayoutUpdated;
}

Let's see what this code does. We create two handlers for SourceIntialized and LayoutUpdated events. The SourceIntialized event handler performs window remeasurment (removes black stripes on right and bottom edges of the window). You can stop here and the code will be like this:

public static void FixLayout(this Window window)
{    
    void Window_SourceInitialized(object sender, EventArgs e)
    {
        window.InvalidateMeasure();
        window.SourceInitialized -= Window_SourceInitialized;
    }

    window.SourceInitialized += Window_SourceInitialized;
}

The remain part of code is responsible for window rearrangement. I noticed that my window has some offset from the ideal center of the screen. This is because of WPF uses wrong window size when it calculates position of the window. LayoutUpdated event fires several times before SourceInitialized event occurs (count depends on SizeToContent property). Firstly we calculate the difference between correct and wrong window sizes. After that SourceInitialized event fires, performs window remeasurment and sets arrangeRequired flag for the upcoming LayoutUpdated event to perform window rearrangement. Then the LayoutUpdated event handler calculates the final offset (if the SizeToContent property is WidthAndHeight) and shifts the window to the right place. After that the window has no more black stripes, and it is positioned in the center of the screen or the owner. This method should be called in the window constructor after the InitializeComponent method.




回答3:


Had the same problem. As a workaround I did the "SizeToContent" myself in the window_loaded-Method:

void Window_Loaded(object sender, RoutedEventArgs e)
{
    Height = outerpanel.DesiredSize.Height + 
             WindowChrome.GetWindowChrome(this).CaptionHeight;
    Width = outerpanel.DesiredSize.Width;
}



回答4:


You can set AllowsTransparency="True" in the window tag, and you can add an OpacityMask. And if you time watch this video https://www.youtube.com/watch?v=TDOxHx-AMqQ&t=1s , and that's an example below.

<Window ..... 
    xmlns:local="clr-namespace:Your.Project"
    AllowsTransparency="True">
<FrameworkElement.Resources>
    <!-- Set the template style of the HolderForm -->
    <Style TargetType="{x:Type local:YourForm}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Window}">

                    <!-- Outer border with the drop shadow margin -->
                    <Border  Padding="{Binding OuterMarginSize, FallbackValue=0}" BorderBrush="{StaticResource BackgroundDarkBrush}" BorderThickness="{Binding FlatBorderThickness}">

                        <!-- Main window outline -->
                        <Grid>
                            <!-- Corner clipping -->
                            <Grid.OpacityMask>
                                <VisualBrush Visual="{Binding ElementName=OpacityContainer}" />
                            </Grid.OpacityMask>

                            <!-- Opacity mask for corners on grid -->
                            <Border x:Name="OpacityContainer"
                                    Background="Black"
                                    CornerRadius="{Binding WindowCornerRadius, FallbackValue=10}" />

                            <!-- The main window content -->
                            <!-- Page Content -->
                            <Border Padding="{Binding InnerContentPadding}" ClipToBounds="True">
                                <ContentPresenter Content="{TemplateBinding Content}" />
                            </Border>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</FrameworkElement.Resources>

<Grid>
    <!--Your Content-->
</Grid>

</Window>



回答5:


For the sake of completeness, the MahApps workaround for this problem seems to work too (but it's a hack. Paviel's answer seems better).

var window = new Window1();
window.SourceInitialized += (s, e) =>
{
    // Without this workaround,
    // black bars appear at the right and bottom edge of the window.
    var sizeToContent = window.SizeToContent;
    var snapsToDevicePixels = window.SnapsToDevicePixels;
    window.SnapsToDevicePixels = true;
    window.SizeToContent = sizeToContent == SizeToContent.WidthAndHeight ? SizeToContent.Height : SizeToContent.Manual;
    window.SizeToContent = sizeToContent;
    window.SnapsToDevicePixels = snapsToDevicePixels;
};

It apparently has the same effect as calling InvalidateMeasure(), like in Paviel's answer.

Only calling InvalidateVisual() on ContentRendered (like in another answer) leaves some kind of wrong padding in the window.


EDIT: I found that Paviel's fix for the window not being centered didn't work in my case. In fact, it made the situation worse.

The following adapted version did the job:

public static void FixLayout(Window window)
{
    bool arrangeRequired = false;
    double widthBeforeFix = 0;
    double heightBeforeFix = 0;

    void Window_SourceInitialized(object sender, EventArgs e)
    {
        widthBeforeFix = window.ActualWidth;
        heightBeforeFix = window.ActualHeight;
        window.InvalidateMeasure();
        arrangeRequired = true;
        window.SourceInitialized -= Window_SourceInitialized;
    }
    void Window_LayoutUpdated(object sender, EventArgs e)
    {
        if (arrangeRequired)
        {
            window.Left += (widthBeforeFix - window.ActualWidth) * 0.5;
            window.Top += (heightBeforeFix - window.ActualHeight) * 0.5;
            window.LayoutUpdated -= Window_LayoutUpdated;
        }
    }

    window.SourceInitialized += Window_SourceInitialized;
    window.LayoutUpdated += Window_LayoutUpdated;
}

The correct window placement is calculated only once, after InvalidateMeasure() was called, and it's pretty straightforward: given the window size before the fix, and the window size after, simply subtract the difference.



来源:https://stackoverflow.com/questions/29207331/wpf-window-with-custom-chrome-has-unwanted-outline-on-right-and-bottom

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!