代码之家  ›  专栏  ›  技术社区  ›  Alan Mendelevich

精确的猫眼石

  •  12
  • Alan Mendelevich  · 技术社区  · 15 年前

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

    所有这些都很好地工作,而没有什么东西是“坚持”在所说的控制范围之外。但如果控件在其边界之外绘制某些内容,则外部点将成为应用不透明度遮罩的起点(如果画笔与该边对齐),并且整个遮罩将按该距离移动,从而导致意外行为。

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

    任何想法都非常感谢!

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

    我们在最后一张图中有两个嵌套边框和画布,上面的方块是:

    <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 http://devblog.ailon.org/devblog/_stuff/maskissue/square-no-mask.gif

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

    <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 http://devblog.ailon.org/devblog/_stuff/maskissue/square-mask.gif

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

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

    并且遮罩向左移动10个像素:

    shifted mask http://devblog.ailon.org/devblog/_stuff/maskissue/square-line-mask.gif

    更新2 :作为一个变通方法,我在边界外添加了一个非常大的透明矩形,并相应地调整我的遮罩,但这确实是一个令人讨厌的变通方法。

    更新3 :注意:带有矩形和线条的画布就像是某个对象的示例,该对象的边界之外有一些内容。在这个示例的上下文中,它应该被视为某种黑盒。您不能更改它的属性来解决常规问题。这将和移动线一样,这样它就不会突出。

    4 回复  |  直到 15 年前
        1
  •  8
  •   Steffen Opel Norm Johanson    15 年前

    事实上,这是一个有趣的问题——我想这就是:你所经历的影响似乎是由 Viewport 概念/行为 TileBrush (见 Viewbox 对于完整的图片也是如此)。显然是隐性的 bounding box A的 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>
    

    此外,违约意味着 视区 ViewportUnits 像这样:

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

    通过重写 Stretch 具有 None ,同时保持基本图块的位置和尺寸为默认值,并且 相对于其边界框 . 此外,你(可以理解)是压倒一切的 AlignmentX / AlignmentY 它决定了基本图块内的位置,即其边界框内的位置。将它们重置为默认值 Center 已经在说:面具会相应地移动,意思是 它必须比边界框小 否则他们就不会成为中心。

    这可以通过改变 视图单元 Absolute ,当然,在正确调整单位之前,将不会生成任何图形;同样,通过实验,以下显式值与自动值匹配,而使用其他值将生成图形更改:

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

    现在 不透明度遮罩已与控件正确对齐 .显然,还有一个问题要解决,因为遮罩现在正在剪裁线条,考虑到线条的大小和缺少任何线条,这并不奇怪。 伸展 效果。相应地调整其大小和位置可解决此问题:

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

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

        2
  •  1
  •   Crispy    15 年前

    在画布对象上添加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>
    
        3
  •  0
  •   Charlie    15 年前

    一个比当前解决方案更理想的解决方案是简单地应用 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>
    

    窗口 调整了大小,因此您最好在后面的代码中动态生成遮罩。

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

        4
  •  0
  •   jyoung    15 年前

    由于有一些部分从控件中突出出来,一个想法是将控件图像与控制遮罩分离。

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