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:
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:
The window should be like (and after resize becomes) like this:
There are a few points worth noting :
This black outline disappears as soon as the window is resized.
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.
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.
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.
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.
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.
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.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With