【问题标题】:Precise OpacityMask精确的不透明度蒙版
【发布时间】:2010-11-21 00:48:22
【问题描述】:

假设我需要在 WPF 控件上设置不透明蒙版,以在精确位置突出显示它的一部分(假设在 (50;50) 位置有一个 50x50 的正方形)。为此,我创建了一个包含 2 个 GeometryDrawing 对象的 DrawingGroup:1 个半透明矩形用于控件的整个实际大小,1 个不透明矩形用于突出显示区域。然后我从这个 DrawingGroup 创建一个 DrawingBrush,将它的 Stretch 属性设置为 None 并将这个画笔设置为需要遮罩的控件的 OpacityMask。

所有这些都可以正常工作,而没有任何东西“坚持”超出所述控制范围。但是,如果控件在其边界之外绘制某些内容,则外部点将成为应用不透明蒙版的起点(如果画笔与该侧对齐),并且整个蒙版移动该距离,从而导致意外行为。

我似乎无法找到一种方法来强制从控件的边界应用蒙版,或者至少获得控件的实际边界(包括粘贴部分),以便我可以相应地调整我的蒙版。

任何想法都非常感谢!

更新:这是一个简单的测试用例 XAML 和演示问题的屏幕截图:

我们有 2 个嵌套的 Borders 和 Canvas 在最后一个与上面提到的正方形:

<Border Padding="20" Background="DarkGray" Width="240" Height="240">
    <Border Background="LightBlue">
        <Canvas>
            <Rectangle Canvas.Left="50" Canvas.Top="50" Width="50" Height="50" 
                       Stroke="Red" StrokeThickness="2" 
                       Fill="White"
                       />
        </Canvas>
    </Border>
</Border>

它的外观如下:


(来源:ailon.org

现在我们在第二个边框上添加一个 OpacityMask,这样除了我们的正方形之外,它的每个部分都是半透明的:

<Border.OpacityMask>
    <DrawingBrush Stretch="None" AlignmentX="Left" AlignmentY="Top">
        <DrawingBrush.Drawing>
            <DrawingGroup>
                <GeometryDrawing Brush="#30000000">
                    <GeometryDrawing.Geometry>
                        <RectangleGeometry Rect="0,0,200,200" />
                    </GeometryDrawing.Geometry>
                </GeometryDrawing>
                <GeometryDrawing Brush="Black">
                    <GeometryDrawing.Geometry>
                        <RectangleGeometry Rect="50,50,50,50" />
                    </GeometryDrawing.Geometry>
                </GeometryDrawing>
            </DrawingGroup>
        </DrawingBrush.Drawing>
    </DrawingBrush>
</Border.OpacityMask>

一切看起来都如预期:


(来源:ailon.org

现在我们在画布上添加一条线,在边框左侧伸出 10 个像素:

<Line X1="-10" Y1="150" X2="120" Y2="150"
      Stroke="Red" StrokeThickness="2" 
      />

蒙版向左移动 10 个像素:


(来源:ailon.org

Update2:作为一种解决方法,我在边界之外添加了一个大得可笑的透明矩形并相应地调整我的蒙版,但这是一个非常讨厌的解决方法。

Update3:注意:带有矩形和线条的画布只是作为某些对象超出其边界的示例。在此示例的上下文中,它应该被视为某种黑匣子。您无法更改它的属性来解决一般问题。这与只是移动线条使其不会突出一样。

【问题讨论】:

  • 查看您的代码会很有帮助。
  • 添加示例代码和截图
  • +1 用于添加详细的复制案例。
  • 你的问题发生是因为边框由于绘制的线而向左移动(你已经知道:)).. 那么你尝试将矩形绑定到边框或它的不透明蒙版怎么样? (真的不知道是否可能)..如果是的话..我相信它会解决你的问题。
  • 我的例子只是为了表明 OpacityMask 应用于控件的整个边界(包括任何超出边界的部分)。而且我找不到改变这种行为的方法,或者至少找不到获得那些“实际”界限的方法。这不是一个需要解决的现实生活中的例子。我可以解决这个特定的任务。但我需要一个通用的解决方案,因为每次找到解决方法时,我都会在一段时间后再次碰壁。

标签: wpf layout opacity mask viewport


【解决方案1】:

一种可能比您当前更理想的解决方法是简单地在更高级别应用OpacityMask。例如,使用此演示代码,您可以从Border 中删除掩码并将其应用于Window。稍微调整一下就可以了:

<Window.OpacityMask>
  <DrawingBrush AlignmentX="Left" AlignmentY="Top" Stretch="None">
    <DrawingBrush.Drawing>
      <DrawingGroup>
        <GeometryDrawing Brush="#30000000">
          <GeometryDrawing.Geometry>
            <RectangleGeometry Rect="0,0,300,300"/>
          </GeometryDrawing.Geometry>
        </GeometryDrawing>
        <GeometryDrawing Brush="Black">
          <GeometryDrawing.Geometry>
            <RectangleGeometry Rect="92,82,50,50"/>
          </GeometryDrawing.Geometry>
        </GeometryDrawing>
      </DrawingGroup>
    </DrawingBrush.Drawing>
  </DrawingBrush>
</Window.OpacityMask>

Window 调整大小时,您必须编写一些代码来移动掩码,因此您最好在代码隐藏中动态生成掩码。

我的问题是,为什么需要处理超出 Canvas 范围的几何图形?

【讨论】:

  • 一般情况下,我不能在我的控制范围内处理更高级别的容器。我不“拥有”更高级别的对象,也不能像我认为合适的那样弄乱它们的属性。无论如何,在这个例子中,这条线甚至可以粘在窗口边界之外,我不确定同样的问题不会发生。至于你的问题,有很多可能的情况,但为了简单起见,我可以给你一个超级简单的:如果你从 (0;0) 画一条粗线,由于线条的绘制方式,它无论如何都会向左伸出WPF 中的默认设置。
【解决方案2】:

在您的 Canvas 对象上添加 ClipToBounds="True"。

<Canvas ClipToBounds="True">

    <Rectangle Canvas.Left="50" Canvas.Top="50" Width="50" Height="50" 
               Stroke="Red" StrokeThickness="2" 
               Fill="White" />
    <Line X1="-10" Y1="150" X2="120" Y2="150"
          Stroke="Red" StrokeThickness="2"/>

</Canvas>

【讨论】:

  • 是的,这解决了这个特定测试用例中的问题,但这不是重点。带有矩形和线条的画布只是作为某些对象超出其边界的示例。在此示例的上下文中,它应该被视为某种黑匣子。您无法更改它的属性来解决一般问题。这与只是移动线条一样,因此它不会伸出来。
  • 是的,我不确定这是否会有所帮助,我找不到除此之外的解决方案。祝你好运。
  • 哇。虽然这并没有解决 OP 的问题,但它确实解决了我的问题……而且非常简单。 +1
【解决方案3】:

由于您有从控件中伸出的部分,因此一种想法是将控件图像与控件掩码分开。

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">

    <Border Padding="20" Background="DarkGray" Width="240" Height="240"> <!-- user container -->

        <Grid> <!-- the control -->
            <Border Background="LightBlue" HorizontalAlignment="Stretch"> <!-- control mask-->
                <Canvas>
                    <Rectangle Canvas.Left="50" Canvas.Top="50" Width="50" Height="50"
                               Stroke="Red" StrokeThickness="2"
                               Fill="White"
                               />

                    <Canvas.OpacityMask>
                        <DrawingBrush Stretch="None" AlignmentX="Left" AlignmentY="Top" TileMode="None">
                            <DrawingBrush.Drawing>
                                <DrawingGroup>
                                    <GeometryDrawing Brush="#30000000">
                                        <GeometryDrawing.Geometry>
                                            <RectangleGeometry Rect="0,0,200,200" />
                                        </GeometryDrawing.Geometry>
                                    </GeometryDrawing>
                                    <GeometryDrawing Brush="Black">
                                        <GeometryDrawing.Geometry>
                                            <RectangleGeometry Rect="50,50,50,50" />
                                        </GeometryDrawing.Geometry>
                                    </GeometryDrawing>
                                </DrawingGroup>
                            </DrawingBrush.Drawing>
                        </DrawingBrush>
                    </Canvas.OpacityMask>
                </Canvas>
            </Border>

            <Canvas> <!-- control image-->
                <Line X1="-10" Y1="150" X2="120" Y2="150" Stroke="Red" StrokeThickness="2"/>
            </Canvas>
        </Grid>
    </Border>
</Window>

【讨论】:

  • 您已尝试再次更改任务。正如我在问题中所说的,带有矩形和线条的内部画布只是一些内容的一个例子,这些内容的部分超出了它的界限。它应该被视为我们无法控制的黑匣子。假设我们不知道它是什么并且我们无法改变它。否则任务就变得微不足道了。
【解决方案4】:

确实很有趣 - 这就是我的想法:您所体验的效果似乎是由TileBrushViewport 概念/行为决定的(完整图片请参见Viewbox)。显然 FrameworkElement 的隐含 bounding box (即您的情况下的 Canvas )受到元素以一种微妙的方式伸出边界的影响/扩展,即盒子的尺寸扩大但盒子的坐标系不会缩放,而是扩展到越界方向。

用图形来说明这一点可能更容易,但由于时间限制,我将首先提供一个解决方案,并解释我目前为帮助您开始所采取的步骤:


解决方案:

<Border Background="LightBlue" Width="198" Height="198">
    <Border.OpacityMask>
        <DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center" 
                      Viewport="-10,0,222,202" ViewportUnits="Absolute">
            <DrawingBrush.Drawing>
                <DrawingGroup>
                    <GeometryDrawing Brush="#30000000">
                        <GeometryDrawing.Geometry>
                            <RectangleGeometry Rect="-10,0,220,200" />
                        </GeometryDrawing.Geometry>
                    </GeometryDrawing>
                    <GeometryDrawing Brush="Black">...</GeometryDrawing>
                </DrawingGroup>
            </DrawingBrush.Drawing>
        </DrawingBrush>
    </Border.OpacityMask>
    <Canvas x:Name="myGrid">...</Canvas>
</Border>

请注意,我已经在这里和那里调整了 +/- 2 像素的像素精度,但我不知道偏移的来源,但我认为出于示例的目的可以忽略这一点,并在需要时稍后解决。


解释

为了简化说明,通常应该首先明确所有相关的隐含/自动属性。

内边框从外边框接收 198 的自动尺寸(240 - 20 填充 - 实验推导出的 2 个像素;不知道它们的来源,但现在可以忽略),也就是说,如果您按以下方式指定它,则不应该变化,而使用其他值会产生图形变化:

<Border Background="LightBlue" Width="198" Height="198">...</Border>

进一步默认隐含ViewportViewportUnits 像这样:

<DrawingBrush Stretch="None" AlignmentX="Left" AlignmentY="Top" 
    Viewport="0,0,1,1" ViewportUnits="RelativeToBoundingBox">...</DrawingBrush>

您正在通过用None 覆盖Stretch 来强制DrawingBrush 大小,同时保持基本图块的位置和尺寸为默认值并且相对于其边界框。此外,您(可以理解)正在覆盖AlignmentX/AlignmentY,它确定了基本图块内的位置,即在其边界框内。将它们重置为默认值 Center 已经说明:蒙版会相应地移动,这意味着 它必须小于边界框,否则它们将无法居中。

这可以通过将ViewportUnits 更改为Absolute 来实现,这将不会产生任何图形,直到单位被正确调整;同样,通过实验,以下显式值与自动值匹配,而使用其他值会产生图形变化:

<DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center" 
    Viewport="0,0,202,202" ViewportUnits="Absolute">...</DrawingBrush>

现在不透明蒙版已经与控件正确对齐了。显然还有一个问题,因为面具现在正在剪裁线,考虑到它的大小和没有任何Stretch 效果,这不足为奇。相应地调整它的大小和位置可以解决这个问题:

<RectangleGeometry Rect="-10,0,220,200" />

<DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center" 
    Viewport="-10,0,222,202" ViewportUnits="Absolute">...</DrawingBrush>

最后,不透明蒙版与控件边界匹配


补充:

上面解释中通过推导和实验确定的所需偏移量可以在运行时通过VisualTreeHelper Class

Rect descendantBounds = VisualTreeHelper.GetDescendantBounds(myGrid);

根据您的视觉元素组成和需求,您可能需要考虑 LayoutInformation Class 并构建两者的联合以获得无所不包的边界框:

Rect descendantBounds = VisualTreeHelper.GetDescendantBounds(myGrid);
Rect layoutSlot = LayoutInformation.GetLayoutSlot(myGrid);
Rect boundingBox = descendantBounds;
boundingBox.Union(layoutSlot);

有关这两个主题的更多详细信息,请参阅以下链接:

【讨论】:

  • 感谢您提供详细且真正有效的解决方案!我还没有弄清楚为什么这会起作用(阅读视口概念),但它似乎工作得很好(并且也没有指定边框尺寸)。我现在要接受你的回答。但是还有一个未完成的问题:目前我不介意剪断线,您的解决方案现在对我来说非常有效,但在一般情况下,我仍然不知道一种方法来自动确定必要的“-10”像素偏移。
  • 不知何故我预料到了这个剩余的问题;)我现在找到了几分钟,并用一种​​方法补充了我的答案,至少在运行时确定这些偏移量。您最终可能会通过绑定更进一步,但鉴于上述文章中概述的渲染过程的复杂性,这也可能引发其他问题,例如性能方面。
猜你喜欢
  • 2011-06-02
  • 2011-12-17
  • 2018-11-26
  • 1970-01-01
  • 2013-02-19
  • 2022-11-15
  • 1970-01-01
  • 2018-12-21
  • 1970-01-01
相关资源
最近更新 更多