FC2ブログ

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

ドラッグで要素を移動させるビヘイビアを作る

 前回はBlend SDKのMouseDragElementBehaviorを使用して要素を移動させる方法を紹介しました。この方法だと要素の座標が常にルートパネル上の位置になるため扱いにくいという話をしました。
 今回は、要素の位置を指定したCanvas上の位置で指定できる要素をドラッグで移動させるビヘイビアを作ります。
 ただ移動させるだけではもったいないので、X軸方向への移動をロック、Y軸方向への移動をロックさせる機能もついでにつけます。
 このビヘイビアの要点をまとめると、
1)指定したCanvas内で要素を移動させる。
2)要素の座標は指定したCanvas内の座標になる。
3)X軸方向への移動ロック機能(おまけ)
4)Y軸方向への移動ロック機能(おまけ)
5)このビヘイビアはBlend SDKを利用して作成する。
となります。
 まずはビヘイビアのコードを紹介します。
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace WpfExtensions.Behaviors.Actions
{
    /// <summary>
    /// 要素をドラッグで移動可能にするビヘイビア
    /// </summary>
    public class MouseDragElementBehaviorEx : Behavior<FrameworkElement>
    {
        private bool IsDrag = false;
        private Point MouseStartPosition;
        private Point ElementStartPosition;


        #region ParentCanvas
        /// <summary>
        /// 移動させる要素の親Canvasを設定、又は取得する。
        /// </summary>
        public Canvas ParentCanvas
        {
            get { return (Canvas)GetValue(ParentCanvasProperty); }
            set { SetValue(ParentCanvasProperty, value); }
        }

        public static readonly DependencyProperty ParentCanvasProperty =
            DependencyProperty.Register("ParentCanvas", typeof(Canvas), typeof(MouseDragElementBehaviorEx), new PropertyMetadata(null));
        #endregion

        #region LockX
        /// <summary>
        /// X軸方向への移動を固定するかどうかを設定、又は取得する。
        /// </summary>
        public bool LockX
        {
            get { return (bool)GetValue(LockXProperty); }
            set { SetValue(LockXProperty, value); }
        }

        public static readonly DependencyProperty LockXProperty =
            DependencyProperty.Register("LockX", typeof(bool), typeof(MouseDragElementBehaviorEx), new PropertyMetadata(false));
        #endregion

        #region LockY
        /// <summary>
        /// Y軸方向への移動を固定するかどうかを設定、又は取得する。
        /// </summary>
        public bool LockY
        {
            get { return (bool)GetValue(LockYProperty); }
            set { SetValue(LockYProperty, value); }
        }

        public static readonly DependencyProperty LockYProperty =
            DependencyProperty.Register("LockY", typeof(bool), typeof(MouseDragElementBehaviorEx), new PropertyMetadata(false));
        #endregion

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
            AssociatedObject.MouseMove += AssociatedObject_MouseMove;
            AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
            AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
            AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
            base.OnDetaching();
        }

        // ドラッグ開始時の位置を取得し、ドラッグ処理を開始する
        void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (ParentCanvas == null) return;

            // マウス位置取得
            MouseStartPosition = e.GetPosition(ParentCanvas);

            // 要素の位置取得
            ElementStartPosition = new Point(Canvas.GetLeft(AssociatedObject), Canvas.GetTop(AssociatedObject));
            if (double.IsNaN(ElementStartPosition.X)) ElementStartPosition.X = 0.0;
            if (double.IsNaN(ElementStartPosition.Y)) ElementStartPosition.Y = 0.0;

            // ドラッグ開始
            IsDrag = true;
            AssociatedObject.CaptureMouse();
        }

        // ドラッグ中の移動処理
        void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
        {
            if (ParentCanvas == null) return;
            if (!IsDrag) return;

            // 移動後の要素の位置を計算する
            var currentMousePosition = e.GetPosition(ParentCanvas);
            var currentElementPosition = ElementStartPosition + (currentMousePosition - MouseStartPosition);

            // ParentCanvas内に収まる様に修正し移動させる
            if (!LockX)
            {
                var maxX = ParentCanvas.ActualWidth - AssociatedObject.ActualWidth;
                if (currentElementPosition.X < 0)
                {
                    currentElementPosition.X = 0.0;
                }
                else if (maxX < currentElementPosition.X)
                {
                    currentElementPosition.X = maxX;
                }

                // X軸方向の移動
                Canvas.SetLeft(AssociatedObject, currentElementPosition.X);
            }

            if (!LockY)
            {
                var maxY = ParentCanvas.ActualHeight - AssociatedObject.ActualHeight;
                if (currentElementPosition.Y < 0)
                {
                    currentElementPosition.Y = 0.0;
                }
                else if (maxY < currentElementPosition.Y)
                {
                    currentElementPosition.Y = maxY;
                }

                // Y軸方向の移動
                Canvas.SetTop(AssociatedObject, currentElementPosition.Y);
            }
        }

        // ドラッグ解除
        void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (IsDrag)
            {
                IsDrag = false;
                AssociatedObject.ReleaseMouseCapture();
            }
        }
    }
}
 ParentCanvasは要素の移動範囲(Canvas)を指定させるためのプロパティです。
 LockXとLockYは要素の移動方向に制限を付けるかどうかのプロパティです。
 これらのプロパティは依存関係プロパティで定義します。(コードスニペットpropdpを使うと簡単に定義できます)
 通常のCLRプロパティでもエラーにはなりませんが、その場合データバインディングはできなくなります。データバインディング可能なプロパティにしたい場合は依存関係プロパティにする必要があります。
 このビヘイビアにXやYといった要素の位置を示すプロパティは作っていません。
 要素の位置はCanvas.Top、Canvas.Leftで指定させるので、ビヘイビアに位置情報を作成する必要がないためです。

 OnAttachedメソッドをオーバーライドしてビヘイビアの機能を付加します。
 OnDetachingメソッドではOnAttachedでアタッチしたイベントハンドラの解除を行います。

 ビヘイビアの機能ですが、単純なドラッグ操作です。そのため、
1)MouseLeftButtonDownイベントでドラッグ開始処理とドラッグ開始時の位置を取得。
2)MouseMoveイベントで、要素の位置を計算し移動。
3)MouseLeftButtonUpイベントで、ドラッグ処理終了。
といった王道パターンで処理を行います。
 まず、MouseLeftButtonDownイベントでドラッグ開始時のマウスと要素の位置を取得します。
 マウスの位置はMouseButtonEventArgs(e)のGetPositionメソッドから取得できます。
 要素の位置はCanvasクラスのGetLeft、GetTopメソッドで位置を取得できます。
 ここでは、念のため値のチェックを行いdouble.NaNの場合は0.0を入力しています。
 あとはドラッグ処理を開始するためのフラグ(IsDrag)を立て、対象の要素にマウスをキャプチャーさせます。

 次に、MouseMoveイベントでドラッグしている際の要素の移動処理を行います。
 現在のマウスの位置を取得し、ドラッグ開始時の位置から移動量を計算し、ドラッグ開始時の要素の位置にマウスの移動量を加えれば現在の要素の位置が計算できます。
 あとは要素の位置を移動するだけなのですが、要素がキャンバスからはみ出ないように補正を加えています。
 また、LockX、LockYの値をチェックしtrueの場合はその方向の移動を行わないようにしています。
 要素の移動はCanvasクラスのSetLeft、SetTopメソッドを使用して行います。

 最後に、MouseLeftButtonUpイベントでドラッグの終了処理を行います。

 このビヘイビアを使って要素を動かしてみましょう。
<Window x:Class="MouseDragElementBehaviorExDemo.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:MouseDragElementBehaviorExDemo.Views"
        xmlns:vm="clr-namespace:MouseDragElementBehaviorExDemo.ViewModels"
        xmlns:ex="http://wpf-extensions.net/2014/wpf"
        Title="MainWindow" Height="350" Width="525">
    
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
        
    <Grid ShowGridLines="True">
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        
        <StackPanel Grid.Row="0" Grid.Column="1">
            <TextBlock Text="{Binding XPosition, StringFormat=X:{0}}"/>
            <TextBlock Text="{Binding YPosition, StringFormat=Y:{0}}"/>
        </StackPanel>
        
        <Canvas Grid.Row="1" Grid.Column="1" Name="MoveArea">
            <Rectangle Width="20" Height="20" Fill="Red"
                       Canvas.Top="{Binding YPosition, Mode=TwoWay}" 
                       Canvas.Left="{Binding XPosition, Mode=TwoWay}">
                <i:Interaction.Behaviors>
                    <ex:MouseDragElementBehaviorEx ParentCanvas="{Binding ElementName=MoveArea}"/>
                </i:Interaction.Behaviors>
            </Rectangle>
        </Canvas>
    </Grid>
</Window>
 MouseDragElementBehaviorExを使うためにXAML名前空間を追加しています(プレフィックスはexとしました)。
 このビヘイビアでは移動させたい要素は必ずCanvas内に設置されている必要があります。
 また、要素の位置はCanvas.TopとCanvas.Leftで行います。データバインディングしたい場合はModeをTwoWayにする必要があります。
 あとは、移動させたい要素にMouseDragElementBehaviorExをアタッチし、ParentCanvasにCanvasを指定すればOKです。
 このコードを実行すると、以下のようになります。
MouseDragElementBehaviorDemo2.png
 要素は指定したCanvas内でのみ移動できます。また、要素の位置はそのCanvas上の位置になります。
 Canvasの左上角に要素を押し付けた時の画像ですが、XとYの値が0になっているのが確認できます。
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

カレンダー
09 | 2018/10 | 11
- 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31 - - -
全記事表示リンク

全ての記事を表示する

カテゴリ
タグリスト

月別アーカイブ
11  09  08  07  06  05  04  03  02  01  12  11  10  09  08  07  06  04  03  02  01  12  11  10  09  08  07  06  05  04  03  02  01  12  11  10  09 
最新記事
リンク
最新コメント
検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。