12

假设我需要在 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:作为一种解决方法,我在边界之外添加了一个大得可笑的透明矩形,并相应地调整了我的蒙版,但这是一个非常讨厌的解决方法。

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

4

4 回答 4

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>

请注意,我已经在这里和那里调整了 +/- 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>

您通过覆盖 来强制 DrawingBrush 大小StretchNone同时保持基本图块的位置和尺寸为默认值并相对于其边界框。此外,您(可以理解)正在覆盖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);

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

于 2009-08-31T11:07:42.957 回答
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>
于 2009-08-26T19:23:06.247 回答
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

于 2009-08-25T20:21:42.803 回答
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>
于 2009-08-29T21:05:15.473 回答