リサイズハンドルをAdornerで実装する

今回はパワーポイントのようなオブジェクトのサイズ変更可能なリサイズハンドルを作ってみます。

Adonerの利用方法と実装のサンプル

まず、サイズ変更対象のオブジェクトの四隅にドラッグ可能なコントロール(リサイズ・ハンドル)を作ります。WPFではこのような用途にAdornerという仕組みが用意されています。ただしAdornerの実装サンプルを調べてみてもあまりヒットしません。またXAMLではなくコードベースのものしか見当たりません。

ResizingAdorner のサンプル | Microsoft Docs
http://denisvuyka.wordpress.com/2007/10/15/wpf-simple-adorner-usage-with-drag-and-resize-operations/

そもそもAdorner自体がXAMLで使うようになっていないので仕方がないと言えるのですが、装飾部分までもコードで書くのではカスタマイズが大変です。まず装飾部分をXAML上でテンプレートで定義できるようにAdornerを拡張するところから始めます。

AdornedByクラスの準備

まずTemplate添付プロパティを作ります。添付プロパティの説明は割愛しますが既に馴染みがあると思います。

AdornedByの使い方

AdornedByを使って今回のリサイズハンドルの表示をXAMLで実装してみます。

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        
        <!--リサイズハンドル用のテンプレート定義-->
        <ControlTemplate TargetType="Thumb" x:Key="ResizeHandleTemplate">
            <Ellipse Width="10" Height="10" Margin="-3"
                     Stroke="DimGray" Fill="LightSteelBlue"/>
        </ControlTemplate>
        
        <!--装飾用のテンプレート定義-->
        <ControlTemplate x:Key="AdornerTemplate">
            <Grid>
                <Thumb Name="ResizeThumb_LT" 
                       HorizontalAlignment="Left" VerticalAlignment="Top"
                       Template="{StaticResource ResizeHandleTemplate}"
                       DragDelta="ResizeThumb_DragDelta"/>
                <Thumb Name="ResizeThumb_RT" 
                       HorizontalAlignment="Right" VerticalAlignment="Top"
                       Template="{StaticResource ResizeHandleTemplate}"
                       DragDelta="ResizeThumb_DragDelta"/>
                <Thumb Name="ResizeThumb_LB" 
                       HorizontalAlignment="Left" VerticalAlignment="Bottom"
                       Template="{StaticResource ResizeHandleTemplate}"
                       DragDelta="ResizeThumb_DragDelta"/>
                <Thumb Name="ResizeThumb_RB" 
                       HorizontalAlignment="Right" VerticalAlignment="Bottom"
                       Template="{StaticResource ResizeHandleTemplate}"
                       DragDelta="ResizeThumb_DragDelta"/>
            </Grid>
        </ControlTemplate>

    </Window.Resources>
    
    <Canvas>
        <!--単純な利用:装飾対象には添付プロパティを設定する-->
        <Button Name="Target"
                Canvas.Left="100" Canvas.Top="20"
                Width="100" Height="40"
                Content="サイズ変更可" 
                my:AdornedBy.Template="{StaticResource AdornerTemplate}"/>

        <!--フォーカスで装飾を有効にする場合-->
        <Button Name="Target2"
                Canvas.Left="100" Canvas.Top="80"
                Width="100" Height="40"
                Content="フォーカスで">
            <Button.Style>
                <Style TargetType="Button">
                    <Style.Triggers>
                        <Trigger Property="IsFocused" Value="True">
                            <Setter Property="my:AdornedBy.Template"
                                    Value="{StaticResource AdornerTemplate}"/>
                        </Trigger>
                    </Style.Triggers> 
                </Style>
            </Button.Style>
        </Button>
                
    </Canvas >
    
</Window>

ThumbコントロールはMarginを負値にすることで少し外側に配置しています。また二つ目のボタンではフォーカス時のみリサイズハンドルを表示しています。

リサイズハンドルのイベント処理です。

        //リサイズハンドルのイベント処理
        private void ResizeThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e) {

            var thumb = sender as Thumb;
            if (thumb == null) return;

            //サイズ変更の対象要素を取得する
            var adored = AdornedBy.GetAdornedElementFromTemplateChild(thumb) as FrameworkElement;
            if (adored == null) return;

            //サイズ変更処理(横)
            if (thumb.Name == "ResizeThumb_LT" || thumb.Name == "ResizeThumb_LB") {
                Canvas.SetLeft(adored, Canvas.GetLeft(adored) + e.HorizontalChange);
                adored.Width = Math.Max(20, adored.Width - e.HorizontalChange);
            } else {
                adored.Width = Math.Max(20, adored.Width + e.HorizontalChange);
            }

            //サイズ変更処理(たて)
            if (thumb.Name == "ResizeThumb_LT" || thumb.Name == "ResizeThumb_RT") {
                Canvas.SetTop(adored, Canvas.GetTop(adored) + e.VerticalChange);
                adored.Height = Math.Max(20, adored.Height - e.VerticalChange);
            } else {
                adored.Height = Math.Max(20, adored.Height + e.VerticalChange);
            }
            e.Handled = true;
        }