精确的OpacityMask

Ala*_*ich 12 wpf layout mask viewport opacity

假设我需要在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>
Run Code Online (Sandbox Code Playgroud)

以下是它的外观:

没有面具http://devblog.ailon.org/devblog/_stuff/maskissue/square-no-mask.gif

现在我们将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>
Run Code Online (Sandbox Code Playgroud)

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

蒙面的http://devblog.ailon.org/devblog/_stuff/maskissue/square-mask.gif

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

<Line X1="-10" Y1="150" X2="120" Y2="150"
      Stroke="Red" StrokeThickness="2" 
      />
Run Code Online (Sandbox Code Playgroud)

并且掩码向左移动10个像素:

转移掩码http://devblog.ailon.org/devblog/_stuff/maskissue/square-line-mask.gif

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

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

Ste*_*pel 8

确实有趣的问题 - 这就是我的想法:你所经历的效果似乎取决于Viewport概念/行为TileBrush(参见Viewbox完整图片).显然,a 的隐式边界框FrameworkElement(即你的情况下的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>
Run Code Online (Sandbox Code Playgroud)

请注意,我已经在这里和那里调整了单位+/- 2像素的像素精度,而不知道偏移的来源,但我认为这可以在示例中被忽略,并在以后需要时解决.


说明:

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

内边框从外边框接收198的自动尺寸(240 - 20填充 - 由实验推断的2个像素;不知道它们的来源,但现在可忽略),即如果你指定如下,则不应该改变,而使用其他值会产生图形更改:

<Border Background="LightBlue" Width="198" Height="198">...</Border>
Run Code Online (Sandbox Code Playgroud)

进一步默认暗示Viewport,ViewportUnits如下:

<DrawingBrush Stretch="None" AlignmentX="Left" AlignmentY="Top" 
    Viewport="0,0,1,1" ViewportUnits="RelativeToBoundingBox">...</DrawingBrush>
Run Code Online (Sandbox Code Playgroud)

你是通过重写强制执行DrawingBrush大小StretchNone,同时保持基本图块的位置和尺寸,在默认情况下,并相对于它的边框.此外,您(可以理解)覆盖AlignmentX/ AlignmentY,它确定基本图块内的位置,即在其边界框内.将它们重置为默认值Center已经说明了:掩码相应地移动,这意味着它必须小于边界框,否则它们将无法居中.

这可以通过更改ViewportUnits为进一步进行Absolute,在完成当前正确调整的单位之前,根本不会产生任何图形; 再次,通过实验,以下显式值与自动值匹配,而使用其他值则产生图形更改:

<DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center" 
    Viewport="0,0,202,202" ViewportUnits="Absolute">...</DrawingBrush>
Run Code Online (Sandbox Code Playgroud)

现在,不透明蒙板已经与控件正确对齐.显然还有一个问题,因为面具现在正在修剪线条,鉴于它的大小和没有任何Stretch影响,这并不奇怪.相应地调整其大小和位置可解决此问题:

<RectangleGeometry Rect="-10,0,220,200" />
Run Code Online (Sandbox Code Playgroud)

<DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center" 
    Viewport="-10,0,222,202" ViewportUnits="Absolute">...</DrawingBrush>
Run Code Online (Sandbox Code Playgroud)

最后,不透明度蒙版根据需要匹配控件边界!


补充:

通过上述说明中的演绎和实验确定的所需偏移量可以在运行时通过以下方式检索VisualTreeHelper Class:

Rect descendantBounds = VisualTreeHelper.GetDescendantBounds(myGrid);
Run Code Online (Sandbox Code Playgroud)

根据您的视觉元素组成和需求,您可能需要考虑LayoutInformation Class并构建两者的并集以获得包罗万象的边界框:

Rect descendantBounds = VisualTreeHelper.GetDescendantBounds(myGrid);
Rect layoutSlot = LayoutInformation.GetLayoutSlot(myGrid);
Rect boundingBox = descendantBounds;
boundingBox.Union(layoutSlot);
Run Code Online (Sandbox Code Playgroud)

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