精确的遮盖力 [英] Precise OpacityMask
问题描述
假设我需要在WPF控件上设置一个不透明蒙版,以在精确位置突出显示它的一部分(假设在(50; 50)位置为50x50正方形).为此,我创建了一个包含2个GeometryDrawing对象的DrawingGroup:1个半透明矩形(用于控件的整个实际大小)和1个不透明矩形(用于突出显示区域).然后,我从此DrawingGroup创建一个DrawingBrush,将其Stretch属性设置为None,并将此画笔设置为需要遮罩的控件的OpacityMask.
所有这些工作正常,而没有任何东西超出"所述控件的范围.但是,如果控件在其边界之外绘制某些内容,则外部点将成为应用不透明蒙版的起点(如果画笔与该侧对齐),并且整个蒙版会移动该距离,从而导致意外行为.
我似乎无法找到一种方法来强制从控件的边界应用蒙版,或者至少无法获得控件的实际边界(包括粘贴部分),因此我可以相应地调整蒙版.
任何想法都受到赞赏!
更新:以下是一个简单的测试用例XAML和屏幕快照,展示了该问题:
在最后一个带有上述正方形的框中,我们有2个嵌套的Borders和Canvas:
<边框填充="20"背景="DarkGray"宽度="240"高度="240">< Border Background ="LightBlue"><画布><矩形Canvas.Left ="50" Canvas.Top ="50"宽度="50"高度="50"Stroke ="Red" StrokeThickness ="2"填充=白色"/></画布></Border></Border>
这是它的外观:
(来源:
(来源:
(来源: ailon.org )
Update2 :作为一种解决方法,我在边界外添加了一个荒谬的透明矩形,并相应地调整了我的遮罩,但这是一个非常讨厌的解决方法.
Update3 :注意:带有矩形和线条的画布仅作为某些对象的示例,这些对象的边界超出范围.在此示例的上下文中,应将其视为某种黑匣子.您不能更改其属性来解决一般问题.这与只移动线以免伸出线一样.
确实有一个有趣的问题-这就是我所想的:您所遇到的效果似乎由 TileBrush
(请参见边界框://msdn.microsoft.com/zh-cn/library/system.windows.frameworkelement.aspx"rel =" noreferrer> FrameworkElement
(例如您的画布)是受元素超出范围影响的微妙影响/扩展,即框的尺寸会扩展,但框的坐标系不会缩放,而是会扩展到超出范围的方向.
以图形方式进行说明可能会更容易,但是由于时间的限制,我将仅提供一个解决方案,并将介绍我目前为使您入门而采取的步骤:
解决方案:
<边框背景=浅蓝色"宽度="198"高度="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 =黑色"> ...</GeometryDrawing></DrawingGroup></DrawingBrush.Drawing></DrawingBrush></Border.OpacityMask>< Canvas x:Name ="myGrid"> ...</Canvas></Border>
请注意,我已经在这里和那里以+/- 2像素为单位调整了单位,以提高像素精度,而又不知道偏移的来源,但是我认为可以忽略此点,以示例为目的,以后可以根据需要解决.
说明:
为简化图示,通常应首先使所有相关的隐式/自动属性显式显示.
内部边界从外部边界接收198的自动尺寸(240-20填充-2像素(通过实验推导;不知道其来源,但现在可忽略)),也就是说,如果您按照以下方式指定此内容,则不应更改,而使用其他值则产生图形更改:
<边框背景="LightBlue"宽度="198"高度="198"> ...</Border>
进一步默认默示的 Viewport
和 ViewportUnits
就像这样:
< DrawingBrush Stretch ="None" AlignmentX ="Left" AlignmentY ="Top"Viewport ="0,0,1,1" ViewportUnits ="RelativeToBoundingBox"> ...</DrawingBrush>
您正在通过覆盖来增强DrawingBrush的大小使用 None
进行 Stretch
的拉伸,同时将基础图块的位置和尺寸保留为默认值,并且相对于其边界框.另外,您(可以理解)正在覆盖 AlignmentX
/ AlignmentY
,它确定基础图块在其边界框内的位置.将其重置为默认值 Center
已经可以说明:遮罩会相应移动,这意味着它必须小于边界框,否则它们将不能居中.
可以通过更改 ViewportUnits
转换为 Absolute
,这将完全不产生图形,除非对单元进行了适当的调整.再次,通过实验,以下显式值与自动值匹配,而使用其他值则产生图形变化:
< DrawingBrush Stretch ="None" AlignmentX ="Center" AlignmentY ="Center"Viewport ="0,0,202,202" ViewportUnits ="Absolute"> ...</DrawingBrush>
现在不透明蒙版已与控件正确对齐.显然,仍然存在一个问题,因为该掩码现在正在剪裁,考虑到它的大小以及没有任何 和 最后,不透明蒙版会根据需要匹配控制范围! 补充: 可以通过 有关以下两个主题的更多详细信息,请参见以下链接: Suppose I need to set an opacity mask on a WPF control that highlights a portion of it in precise position (suppose a 50x50 square at (50;50) position). To do that I create a DrawingGroup containing 2 GeometryDrawing objects: 1 semi-transparent rectangle for the whole actual size of the control and 1 opaque rectangle for highlighted area. Then I create a DrawingBrush from this DrawingGroup, set it's Stretch property to None and set this brush as OpacityMask of the control that needs to be masked. All this works fine while nothing is "sticking" out of bounds of said control. But if control draws something outside of it's bounds the outer point becomes a starting point from where opacity mask is applied (if the brush is aligned to that side) and the whole mask shifts by that distance resulting in unexpected behavior. I can't seem to find a way to force mask to be applied from control's bounds or at least get the actual bounds of the control (including sticking parts) so I can adjust my mask accordingly. Any ideas highly appreciated! Update: Here's a simple test-case XAML and screenshots demonstrating the issue: We have 2 nested Borders and Canvas in the last one with the above mentioned square: Here's how it looks: Now we add an OpacityMask to the second border so that every part of it except our square is semi-transparent: Everything looks as expected: And now we add a line to the canvas that sticks 10 pixels out on the left of our border: And the mask shifts 10 pixels to the left: Update2: As a workaround I add a ridiculously large transparent rectangle outside of bounds and adjust my mask accordingly but that is a really nasty workaround. Update3: Note: The canvas with rectangle and line is there just as an example of some object that has something outside of it bounds. In context of this sample it should be treated as some sort of a black box. You can't change it's properties to solve the general issue. This would be the same as just moving the line so it doesn't stick out. Interesting issue indeed - here's what I've figured: The effect you are experiencing seems to be determined by the It might be easier to illustrate that graphically, but due to time constraints I'll just offer a solution first and will explain the steps I've taken for the moment in order to get you started: Solution: Please note that I've adjusted units by +/- 2 pixels here and there for pixel precision without knowing where the offset originates, but I think this can be ignored for the purpose of the example and resolved later if need be. Explanation: To simplify the illustration one should usually make all related implied/auto properties explicit first. The inner border receives auto dimensions of 198 from the outer border (240 - 20 padding - 2 pixels deduced by experiment; don't know their origin, but ignorable right now), that is if you specify this as follows nothing should change, while using other values yields graphical changes: Further the default implied You are enforcing the DrawingBrush size by overriding This can be taken further by changing Now the opacity mask already aligns properly with the control. Obviously there is one problem left though, as the mask is clipping the line now, which is no surprise given its size and the absence of any and Finally the opacity mask matches the control bounds as desired! Supplement: The required offsets determined by deduction and experiment in the explanation above can be retrieved at runtime by means of the Depending on your visual element composition and needs you may need to factor in the See the following links for more details on both topics: 这篇关于精确的遮盖力的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
< DrawingBrush Stretch ="None" AlignmentX ="Center" AlignmentY ="Center"Viewport =-10,0,222,202" ViewportUnits ="Absolute"> ...</DrawingBrush>
LayoutInformation类
并构建两者的并集,以得到包罗万象的边界框:
RectDescendantBounds = VisualTreeHelper.GetDescendantBounds(myGrid);矩形layoutSlot = LayoutInformation.GetLayoutSlot(myGrid);矩形的boundingBox =后裔边界;boundingBox.Union(layoutSlot);
<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>
(source: ailon.org) <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>
(source: ailon.org) <Line X1="-10" Y1="150" X2="120" Y2="150"
Stroke="Red" StrokeThickness="2"
/>
(source: ailon.org) Viewport
concept/behavior of TileBrush
(see Viewbox
too for the complete picture). Apparently the implicit bounding box of a FrameworkElement
(i.e. the Canvas in your case) is affected/expanded by elements sticking out of bounds in a subtle way, that is, the dimensions of the box expand but the coordinate system of the box does not scale, rather expands too into the out of bounds direction.
<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>
<Border Background="LightBlue" Width="198" Height="198">...</Border>
Viewport
and ViewportUnits
like so:<DrawingBrush Stretch="None" AlignmentX="Left" AlignmentY="Top"
Viewport="0,0,1,1" ViewportUnits="RelativeToBoundingBox">...</DrawingBrush>
Stretch
with None
, while keeping the position and dimension of the base tile at default and relative to its bounding box. In addition you (understandably) are overriding AlignmentX
/AlignmentY
, which determine the placement within the base tile, that is within its bounding box. Resetting those to their defaults of Center
is already telling: The mask shifts accordingly, meaning it has to be smaller than the bounding box, else their would be nothing to center within.ViewportUnits
to Absolute
, which will yield no graphics at all until the units are properly adjusted of course; again, by experiment the following explicit values are matching the auto ones, while using other values yields graphical changes:<DrawingBrush Stretch="None" AlignmentX="Center" AlignmentY="Center"
Viewport="0,0,202,202" ViewportUnits="Absolute">...</DrawingBrush>
Stretch
effect. Adjusting its size and position accordingly resolves this:<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
and build the union of both to get the all-encompassing bounding box:Rect descendantBounds = VisualTreeHelper.GetDescendantBounds(myGrid);
Rect layoutSlot = LayoutInformation.GetLayoutSlot(myGrid);
Rect boundingBox = descendantBounds;
boundingBox.Union(layoutSlot);