为什么在WPF中最小化和恢复窗口会影响已经绘制的图形?

7

我有一个非常基础的测试应用程序,只包含一个Window和一个 CustomControl。窗口包含自定义控件,并具有以下额外属性:

TextOptions.TextFormattingMode="Display"
SnapsToDevicePixels="True" 
UseLayoutRounding="True"
RenderOptions.EdgeMode="Aliased"

自定义控件在代码后端文件中添加了以下5行代码:
protected override void OnRender(DrawingContext drawingContext)
{
    var pen = new Pen(Brushes.Black, 1);
    drawingContext.DrawLine(pen, new Point(0, 0), new Point(ActualWidth, ActualHeight)); 
}

一切都运行得很好。从左上角到右下角画了一条黑色的1像素线。完美。虽然有些像素化,但正是我需要的。然而,当我最小化窗口并再次还原它时,这条线就变得"模糊"了。在窗口恢复后,OnRender方法没有被调用。那么为什么绘制的图形在窗口最小化后会发生根本性的变化(失去焦点似乎不是问题)?为什么在窗口简单地改变大小后一切又恢复正常?如何解决这种不希望的行为?每次WindowState更改时强制进行完整的调整/无效似乎有些过度了,不是吗?

更新1:

正如@Backlash建议的那样,我把自定义控件放在一个画布中。

<Canvas Name="CanvasControl" SizeChanged="FrameworkElement_OnSizeChanged">
  <wpfTest:CustomControl x:Name="ChildControl" />
</Canvas>

and handled the SizeChanged event like this

private void FrameworkElement_OnSizeChanged(object sender, SizeChangedEventArgs e)
{
    ChildControl.Width = CanvasControl.ActualWidth;
    ChildControl.Height = CanvasControl.ActualHeight;
}

自动布局画布内部的元素需要手动设置。然而,结果并没有改变。
更新2:
我想到一个方法是监听窗口状态的变化,并通过轻微更改窗口的宽度或高度属性来强制重新调整窗口及其所有控件的大小以进行完全重绘。请注意,此操作会触发完全重绘。
protected override void OnStateChanged(EventArgs e)
{
        if (WindowState != WindowState.Minimized)
        {               
            Width += 1;
        }
}

在某些情况下,这种方法是有效的(但是当窗口最大化时却不行),但总体来说,这是一种明显不好的处理方式,原因有很多。我试图理解在更改大小时什么会导致(正确的)重绘制,可能与CoreFlags.AreTransformsClean有关,但由于所有内容都被声明为内部或私有,所以我放弃使用那些标志和任何相关的方法。
更新3:
鉴于WPF似乎无法直接在屏幕上绘制直线,我尝试先将其绘制到内存位图中,然后再将该位图绘制到屏幕上。这样可以实现。这是方法:
protected override void OnRender(DrawingContext drawingContext)
{
    var rtb = new RenderTargetBitmap((int)ActualWidth, (int)ActualHeight, 96, 96, PixelFormats.Pbgra32);
    var line = new Line();
    RenderOptions.SetEdgeMode(line, EdgeMode.Aliased);

    line.Stroke = Brushes.Black;
    line.StrokeThickness = 1;
    line.X1 = 0;
    line.X2 = ActualWidth;
    line.Y1 = 0;
    line.Y2 = ActualHeight;
    line.Arrange(new Rect(new Size((int)ActualWidth, (int)ActualHeight)));
    rtb.Render(line);

    drawingContext.DrawImage(rtb, new Rect(0, 0, (int)ActualWidth, (int)ActualHeight));
}

“解决方案”可以说是相当的hacky。虽然比我在WPF中迄今为止看到的其他方案要好,但实际上它真的很糟糕。然而,我会继续留下这个线程以等待更多的答案。

无意中问一下,如果将自定义控件放在 Canvas 容器中而不是直接放在窗口上会发生什么? - Backlash
我尝试了,但不幸的是结果没有改变。在从最小化状态恢复窗口后,该行仍然模糊。 - UnclePaul
也许考虑使用 Path 类? - Aaj
你尝试过在整个窗口上设置IsLayoutRounding吗?顺便说一下,你从来没有直接向屏幕绘制任何东西,即使在OnRender中也是如此。WPF将您的更改跟踪为宏或绘图作业列表。这就是为什么WPF不需要调用您的绘图代码,例如在另一个窗口重叠您的窗口之后重新绘制它。它只会在可视化对象无效(由于调整大小)时进行重新绘制。 - dowhilefor
我很想尝试一下这个,但是在最小化和恢复后,我无法再现模糊的线条(只有一个难看的像素化线条)。你还有原始的XAML吗? - A Aiston
1个回答

1
我想这是因为你使用了


SnapsToDevicePixels="True" 

像素对齐并不总是完全准确的,根据Microsoft msdn所说。

不幸的是,仅仅调整容器对象的大小并不能保证设备像素对齐。应用程序的整个布局会影响图像的对齐方式。显示器每英寸的点数(dpi)也会影响图像的对齐方式。在上面的示例中,只有当显示器设置为96 dpi时,图像对齐才能正常工作。在任何其他设置下,布局需要进行调整以适应显示器设置。在 Windows Presentation Foundation (WPF) 应用程序中,尽可能避免使用高频图像。

因此,尝试将该标志更改为false,然后查看其效果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接