带有自定义镶边的WPF窗口在右侧和底部有不需要的轮廓

时间:2015-03-23 09:58:44

标签: wpf xaml winapi

我使用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#代码使用窗口之前,我无法发现问题。 我有一个消息服务:

  1. 创建模态窗口。
  2. 使用WPF用户控件填充其内容。
  3. 将窗口的数据上下文设置为适当的ViewModel。
  4. 显示窗口
  5. 以下是代码:

    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
    

    这需要调整窗口大小以符合用户控件的宽度和高度。 由此产生的模态窗口在窗口的右侧和底部具有粗黑色轮廓。像这样:

    Window with the outline

    窗口应该像(并在调整大小后),如下所示:

    Good window

    有几点值得注意:

    1. 只要调整窗口大小,此黑色轮廓就会消失。

    2. 如果SizeToContent设置为SizeToContent.Height或SizeToContent.Width,则不会出现此大纲。但是它会分别吹掉模态窗口的宽度或高度。

    3. 我认为窗口重绘可能存在一些问题。所以我尝试了以下代码来重绘窗口:

      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);
      

      这没有效果。

    4. 如果我已将固定的Height和Width属性赋予填充窗口的用户控件,则不会出现此问题。但是,我无法做到这一点。

    5. 消息服务已经存在了很久以来,这个幽灵大纲最近在自定义铬更换后出现了。

    6. 有没有人遇到类似的情况?任何帮助将不胜感激。

6 个答案:

答案 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;
}

让我们看看这段代码的作用。我们为SourceIntializedLayoutUpdated事件创建两个处理程序。 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 的接口。