雑記 - otherwise

最近はDQ10しかやっていないダメ技術者がちまちまと綴る雑記帳

Adorner を使ってコントロールでコントロールを修飾する

# 「 WPF で色々やってみている」こと第一弾w
WPF では Adorner コントロールを使って、既存のコントロールを修飾する事が出来ます。

public class SampleAdorner : Adorner
{
    public SampleAdorner(UIElement adornedElement) : base(adornedElement) { }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);
        drawingContext.DrawEllipse(Brushes.Aqua, new Pen(), new Point(80, 80), 100, 100);
    }
}

利用する側のコードはこんな感じ。

// 修飾する対象のコントロールのレイヤーを取得
var layer = AdornerLayer.GetAdornerLayer(Button1);

// Adorner のインスタンスを生成
var adorner = new SampleAdorner(Button1);

// レイヤーに Adorner を追加
layer.Add(adorner);

……で、この機能のサンプルを調べても、↑の様に Draw 系のサンプルしか出てこないんですよね。
でも、うまく利用すればコントロールも追加出来るらしいので、頑張って実装してみました。

ControlAdorner.cs

public class ControlAdorner : Adorner
{
    // レイヤー上の実体は Canvas
    private Canvas _adornerCanvas;

    // Adorner の VisualChild は空っぽなので、固定で 1 を返す様にする
    protected override int VisualChildrenCount { get { return 1; } }

    public ControlAdorner(UIElement adornedElement)
        : base(adornedElement)
    {
        _adornerCanvas = new Canvas() { Background = Brushes.Transparent };
    }

    public void AddAdornment(UIElement child)
    {
        // Canvas にコントロールを追加
        _adornerCanvas.Children.Add(child);
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        // Canvas を Adorner のサイズにリサイズ
        _adornerCanvas.Arrange(new Rect(finalSize));
        Canvas.SetTop(_adornerCanvas, 0);
        Canvas.SetLeft(_adornerCanvas, 0);
        return finalSize;
    }

    protected override Visual GetVisualChild(int index)
    {
        if (VisualChildrenCount <= index)
        {
            throw new IndexOutOfRangeException();
        }
        // 常に Canvas を返す
        return _adornerCanvas;
    }
}

使う側 (Window.xaml.cs)

var layer = AdornerLayer.GetAdornerLayer(TextBox1);
var adorner = new ControlAdorner(TextBox1);

// イメージを追加してみる
var image = new Image() {
                  Stretch = Stretch.Fill,
                  Source = new BitmapImage(new Uri(@"C:\sample.jpg")),
                  Opacity = 0.25f
                  };
// イメージのレイヤー上の表示位置を指定
Canvas.SetTop(image, 15);
Canvas.SetLeft(image, 20);

// Adorner にイメージを追加
adorner.AddAdornment(image);

// レイヤーに Adorner を追加
layer.Add(adorner);

……使う側は、どこかコードとそっくりですw
# まぁ、元々 Visual Studio 2010 の Editor Viewport Adorner と同じ様な事をしたいと云うのが発端だったのでww
# ちなみに、 VS2010 のテキストエディタは、更にテキスト表示部分自体をレイヤー化して、テキストの後ろ側にも独自のレイヤーを追加出来る様にしている様ですが。
# ※なお、↑に書いたコードで追加されたレイヤーは、常に元のコントロールの上に表示されます。(なので、このままだと背景イメージの挿入とかには使えない)
※投稿している環境の制限で画像が貼れないため、スクリーンショットは後で追加予定。。。