【问题标题】:WPF window with custom chrome has unwanted outline on right and bottom带有自定义镶边的 WPF 窗口在右侧和底部有不需要的轮廓
【发布时间】:2015-05-26 06:29:56
【问题描述】:

我使用 Microsoft.Windows.Shell dll 创建了一个带有自定义镶边的 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. 显示窗口

下面是代码:

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

这负责调整窗口大小以符合用户控件的宽度和高度。 这样生成的模态窗口在窗口的右侧和底部有一个粗黑的轮廓。像这样:

窗口应该是这样的(并且在调整大小之后):

有几点值得注意:

  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. 如果我为填充窗口的用户控件提供了固定的高度和宽度属性,则不会出现此问题。但是,我不能总是这样做。

  5. 消息服务已经存在很长时间了,这个幽灵轮廓最近在自定义 chrome 更改后出现了。

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

【问题讨论】:

  • 不要直接发送WM_PAINT。使用InvalidateRect(hwnd, NULL, TRUE) 使窗口的客户区无效,使用UpdateWindow(hwnd) 强制立即重绘。我不认为这是解决您问题的方法,但这样说可以避免将来出现错误...
  • 您找到解决问题的方法了吗?我自己也遇到了完全相同的问题,并且已经在网上扫描了解决方案,但到目前为止还没有发现任何解决方案。

标签: wpf xaml winapi


【解决方案1】:

我最近在包含动态生成元素的窗口上使用自定义窗口镶边时遇到了这个问题。

为什么会这样?

如果我们使用具有静态内容的窗口,则该窗口可以在初始化时知道​​包含其子元素所需的最大宽度/高度。

如果我们想要使用具有自动缩放功能的动态元素,例如使用 MVVM 模式的视图,我们需要在其所有绑定(例如视图)已解决后请求窗口更新其视觉状态。

解决方案

为了强制执行我们上面推断的行为,我们需要使用窗口的 ContentRendered 事件,并将其用于 InvalidateVisual()。

在您遇到问题的窗口的 XAML 中:

ContentRendered="Window_OnContentRendered"

在代码隐藏中:

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

祝你好运。

【讨论】:

    【解决方案2】:

    我遇到了同样的问题,并为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 在计算窗口位置时使用了错误的窗口大小。在SourceInitialized 事件发生之前LayoutUpdated 事件会触发多次(计数取决于SizeToContent 属性)。首先,我们计算正确和错误窗口大小之间的差异。之后SourceInitialized 事件触发,执行窗口重新测量并为即将到来的LayoutUpdated 事件设置arrangeRequired 标志以执行窗口重新排列。然后LayoutUpdated 事件处理程序计算最终偏移量(如果SizeToContent 属性为WidthAndHeight)并将窗口移动到正确的位置。之后窗口不再有黑色条纹,它位于屏幕或所有者的中心。这个方法应该在InitializeComponent方法之后的窗口构造函数中调用。

    【讨论】:

    • 这无疑是正确的答案。扩展方法,自定义类,无论您选择什么路线,在 SourceInitialized 上调用 InvalidateMeasure() 都会修复黑条。我还没有测试定位修复,但逻辑似乎合理。
    • 另一方面,这个答案比在 ContentRendered 上做的要好,因为使用这种方法,在显示窗口时您仍然会看到条形图,并且在修复时会出现一些闪烁的混乱本身。
    【解决方案3】:

    遇到了同样的问题。作为一种解决方法,我自己在 window_loaded-Method 中做了“SizeToContent”:

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

    【讨论】:

    • 感谢您的帮助。你为什么避开SizeToContent = SizeToContent.WidthAndHeight;?它似乎工作得很好,可能更好。
    • 没有避免。但我认为这也行不通,所以我没有尝试:)
    【解决方案4】:

    您可以在窗口标签中设置 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>
    

    【讨论】:

      【解决方案5】:

      为了完整起见,解决此问题的 MahApps 解决方法似乎也有效(但它是一个 hack。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 的回答中一样。

      仅在ContentRendered 上调用InvalidateVisual()(如在另一个答案中)会在窗口中留下某种错误的填充。


      编辑:我发现 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() 之后,只计算一次正确的窗口位置,这非常简单:给定修复前的窗口大小和之后的窗口大小,只需减去差异即可。

      【讨论】:

        【解决方案6】:

        有点晚,但也许有人可以使用它。

        我遇到了同样的问题,我通过在 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
        };
        

        您需要定义视图模型可以实现的具有宽度和高度的接口。

        【讨论】:

          【解决方案7】:

          如果您希望使用 JC 的 OnContentRendered 解决方案。但是因为您正在使用以资源字典中的样式为主题的窗口并且不想在使用此样式的每个窗口中实现 OnContentRendered 处理程序,所以正在苦苦挣扎,您可以为资源字典并将处理程序放在那里。我发现的一个困难是,在样式中,您无法将处理程序设置为 RoutedEvents 以外的事件,而 OnContentRendered 事件不是。

          解决方案

          在窗口模板上为路由事件“Loaded”设置一个处理程序,如果需要,您可以在其中设置 OnContentRendered 处理程序。

          XAML:

          <Style x:Key="ThemedWindow" TargetType="{x:Type Window}">
              <EventSetter Event="Loaded" Handler="Window_Loaded"/>
          

          ResourceDictionary 后面的代码:

              private void Window_Loaded(Object sender, RoutedEventArgs e)
              {
                  if (sender is Window window && window.SizeToContent != SizeToContent.Manual)
                  {
                      window.ContentRendered += Window_OnContentRendered;
                  }
              }
          
              private void Window_OnContentRendered(Object sender, EventArgs e)
              {
                  if (sender is Window window && window.SizeToContent != SizeToContent.Manual)
                  {
                      window.InvalidateVisual();
                      window.ContentRendered -= Window_OnContentRendered;
                  }
              }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-02-09
            • 2014-11-29
            • 2011-10-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多