Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

wpf

xaml

winapi

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.

like image 973
James Avatar asked Mar 23 '15 09:03

James


3 Answers

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.

like image 144
JC. Avatar answered Nov 07 '22 07:11

JC.


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.

like image 44
Paviel Kraskoŭski Avatar answered Nov 07 '22 06:11

Paviel Kraskoŭski


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;
}
like image 2
mDC Avatar answered Nov 07 '22 07:11

mDC