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 )

次に、2 番目の境界線に 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 : 回避策として、境界の外側に途方もなく大きな透明な四角形を追加し、それに応じてマスクを調整しますが、これは本当に厄介な回避策です。

Update3 : 注: 長方形と線を含むキャンバスは、境界の外側に何かがあるオブジェクトの例としてあります。このサンプルのコンテキストでは、ある種のブラック ボックスとして扱う必要があります。一般的な問題を解決するためにプロパティを変更することはできません。これは、はみ出さないように線を移動するのと同じです。

4

4 に答える 4

8

確かに興味深い問題-これが私が考え出したことです:あなたが経験している効果は、のViewport概念/動作によって決定されるようですTileBrush(Viewbox全体像についても参照してください)。どうやら 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>

さらに、デフォルトは暗示されViewportViewportUnits次のようになります。

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

ベース タイルの位置と寸法をデフォルトで保持し、そのバウンディング ボックスStretchを基準にしながら、 でオーバーライドすることによって DrawingBrush のサイズを適用しています。さらに、(当然のことながら) /をオーバーライドしています。これは、境界ボックス内にあるベース タイル内の配置を決定します。それらをデフォルトの にリセットすると、すでに次のことがわかります。マスクはそれに応じてシフトします。つまり、バウンディング ボックスよりも小さくする必要があります。NoneAlignmentXAlignmentYCenter

に変更ViewportUnitsすることAbsoluteで、これをさらに進めることができます。もちろん、単位が適切に調整されるまで、グラフィックはまったく生成されません。繰り返しますが、実験により、次の明示的な値は自動のものと一致していますが、他の値を使用するとグラフィックが変化します。

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

これで、不透明マスクはすでにコントロールと適切に位置合わせされています。ただし、マスクが現在ラインをクリッピングしているため、明らかに 1 つの問題が残っています。これは、そのサイズと効果がないことを考えると驚くことではありませ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

現在の回避策よりも理想的な回避策の 1 つは、単純に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

コントロールからはみ出している部分があるため、コントロール イメージをコントロール マスクから分離することも 1 つのアイデアです。

<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 に答える