我使用Microsoft.Windows.Shell dll创建了一个带自定义chrome的WPF窗口。 这是代码:
<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>
这在正常情况下非常有效,在我需要使用C#代码使用窗口之前,我无法发现问题。 我有一个消息服务:
以下是代码:
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();
注意这一行
SizeToContent = SizeToContent.WidthAndHeight
这需要调整窗口大小以符合用户控件的宽度和高度。 由此产生的模态窗口在窗口的右侧和底部具有粗黑色轮廓。像这样:
窗口应该像(并在调整大小后),如下所示:
有几点值得注意:
只要调整窗口大小,此黑色轮廓就会消失。
如果SizeToContent设置为SizeToContent.Height或SizeToContent.Width,则不会出现此大纲。但是它会分别吹掉模态窗口的宽度或高度。
我认为窗口重绘可能存在一些问题。所以我尝试了以下代码来重绘窗口:
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);
这没有效果。
如果我已将固定的Height和Width属性赋予填充窗口的用户控件,则不会出现此问题。但是,我无法做到这一点。
消息服务已经存在了很久以来,这个幽灵大纲最近在自定义铬更换后出现了。
有没有人遇到类似的情况?任何帮助将不胜感激。
答案 0 :(得分:5)
在包含动态生成元素的窗口上使用自定义窗口镶边时,我最近遇到了这个问题。
为什么会这样?
如果我们使用带有静态内容的窗口,则窗口可以在初始化时知道包含其子元素所需的最大宽度/高度。
如果我们想要使用具有自动缩放功能的动态元素,例如使用MVVM模式的视图,我们需要在所有bindngs(例如Views)都已解析后请求窗口更新其可视状态。
解决方案
为了强制执行我们上面提到的行为,我们需要使用窗口的ContentRendered事件,并将其用于InvalidateVisual()。
在窗口的XAML中,您遇到了问题:
ContentRendered="Window_OnContentRendered"
在代码隐藏中:
private void Window_OnContentRendered(object sender, EventArgs e)
{
InvalidateVisual();
}
祝你好运。
答案 1 :(得分:2)
有同样的问题。作为一种解决方法,我在window_loaded-Method:
中自己做了“SizeToContent”void Window_Loaded(object sender, RoutedEventArgs e)
{
Height = outerpanel.DesiredSize.Height +
WindowChrome.GetWindowChrome(this).CaptionHeight;
Width = outerpanel.DesiredSize.Width;
}
答案 2 :(得分:1)
我也遇到同样的问题,并为Window
类创建了以下扩展名:
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;
}
让我们看看这段代码的作用。我们为SourceIntialized
和LayoutUpdated
事件创建两个处理程序。 SourceIntialized
事件处理程序执行窗口修复(删除窗口的右边缘和下边缘的黑色条纹)。您可以在此处停止,代码将如下所示:
public static void FixLayout(this Window window)
{
void Window_SourceInitialized(object sender, EventArgs e)
{
window.InvalidateMeasure();
window.SourceInitialized -= Window_SourceInitialized;
}
window.SourceInitialized += Window_SourceInitialized;
}
代码的其余部分负责窗口重排。我注意到我的窗口与屏幕的理想中心有些偏离。这是因为WPF在计算窗口位置时使用了错误的窗口大小。 LayoutUpdated
事件在SourceInitialized
事件发生之前触发了几次(计数取决于SizeToContent
属性)。首先,我们计算正确和错误的窗口大小之间的差异。在该SourceInitialized
事件触发之后,执行窗口修复,并为即将到来的arrangeRequired
事件设置LayoutUpdated
标志以执行窗口重排。然后,LayoutUpdated
事件处理程序将计算最终偏移量(如果SizeToContent
属性为WidthAndHeight
),并将窗口移至正确的位置。之后,该窗口将不再具有黑色条纹,并将其放置在屏幕或所有者的中心。该方法应在InitializeComponent
方法之后在窗口构造函数中调用。
答案 3 :(得分:0)
您可以在窗口标记中设置AllowsTransparency =“ True”,还可以添加OpacityMask。而且,如果您有时间观看此视频https://www.youtube.com/watch?v=TDOxHx-AMqQ&t=1s,那么下面是一个示例。
<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>
答案 4 :(得分:0)
出于完整性考虑,针对该问题的MahApps解决方法似乎也可以工作(但这是一种破解。Paviel's answer似乎更好)。
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;
};
显然,它与调用InvalidateMeasure()
的作用相同,就像Paviel的回答一样。
仅在InvalidateVisual()
上调用ContentRendered
(就像在另一个答案中一样)会在窗口中留下某种错误的填充。
编辑:我发现在我的情况下,Paviel对窗口未居中的修复不起作用。实际上,这使情况变得更糟。
以下改编版完成了该工作:
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;
}
正确的窗口位置在调用InvalidateMeasure()
之后仅计算一次,非常简单:给定修复之前的窗口大小和之后的窗口大小,只需减去差即可。
答案 5 :(得分:0)
有点晚了,但也许有人可以使用它。
我遇到了同样的问题,我通过在 ModalCustomMessageDialog 中添加宽度和高度作为参数来解决。 (我认为它继承了 System.Windows.Window)
像这样:
public partial class ModalCustomMessageDialog: Window
{
public ModalCustomMessageDialog(int width,int height)
{
InitializeComponent();
Width = width;
Height = height;
}
}//Cls
然后在您的对话服务中更改为:
var modalWindow = new ModalCustomMessageDialog(viewModel_.Width, viewModel_.Height)
{
// 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
};
您需要定义一个视图模型可以实现的具有 Width 和 Height 的接口。