精确的OpacityMask

时间:2009-08-19 16:46:24

标签: wpf layout opacity mask viewport

假设我需要在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>

以下是它的外观:

no mask
(来源: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>

一切看起来都像预期的那样:

masked
(来源:ailon.org

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

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

掩码向左移10个像素:

shifted mask
(来源:ailon.org

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

Update3 :注意:带有矩形和直线的画布就像某个对象的示例一样,它有一些超出界限的对象。在此示例的上下文中,它应被视为某种黑盒子。您无法更改其属性来解决一般问题。这与移动线条相同,因此它不会突出。

4 个答案:

答案 0 :(得分:8)

确实有趣的问题 - 这就是我的想法:你所经历的效果似乎取决于ViewportTileBrush概念/行为(完整见Viewbox}图片)。显然,bounding box的隐式FrameworkElement(即你的情况下的画布)受到以微妙方式突出界限的元素的影响/扩展,即框的尺寸扩大但是坐标框的系统不会缩放,而是扩展到越界方向。

以图形方式说明可能更容易,但由于时间限制,我将首先提供一个解决方案,并解释我目前采取的步骤,以便让您入门:


<强>解决方案:

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

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

答案 1 :(得分:1)

在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>

答案 2 :(得分:0)

可能比您当前更理想的一种解决方法是简单地在更高级别应用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范围的几何?

答案 3 :(得分:0)

由于你有一些从控件中伸出的部分,一个想法就是将控制图像与控制面板分开。

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