メモ帳ブログ @ wiki

ゴースト付きのドラッグ・アンド・ドロップ

最終更新:

nina_a

- view
管理者のみ編集可

ゴースト付きドラッグ・アンド・ドロップ(D&D)


概要

Windows 7のデスクトップのアイコンをドラッグするとアイコンが半透明になってついてくる(正式名称があるのか知らないが,ゴーストと呼ぶことにする).今回はこれを実現する.


利用例

ドラッグを開始する前に,DragGhostクラスのインスタンスを作成し,Showメソッドを呼び出す.ドラッグ終了時にはRemoveメソッドを呼び出す.また,QueryContinueDragイベントでDragGhostの座標の更新処理を行う.
+ DragGhost利用例(C#)
DragGhost利用例(C#)
protected DragGhost ghost;
 
private void OnMouseDown(object s, MouseButtonEventArgs e)
{
    var sender = s as UIElement;
    if ( ghost == null )
    {
        // DragGhostのインスタンス作成
        // 引数は
        //     1. 作成するゴースト(ドラッグされる物)
        //     2. ゴーストの透明度
        //     3. マウスカーソルがゴーストを掴む位置(オプション)
        ghost = new DragGhost(sender, 0.5, e.GetPosition(sender));
        // ゴーストを表示
        ghost.Show();
    }
    // D&Dを開始
    DragDrop.DoDragDrop( ... );
    if ( ghost != null )
        // ゴーストを削除
        ghost.Remove();
    ghost = null;
}
 
private void ContinueDrag(object s, QueryContinueDragEventArgs e)
{
    if (ghost != null)
        // ゴーストの位置を更新
        ghost.Position = CursorInfo.GetNowPosition(this);
} 
+ DragGhost利用例(XAML)
DragGhost利用例(XAML)
  1. <UIElement MouseDown="OnMouseDown" QueryContinueDrag="ContinueDrag">...</UIElement>

利用するクラス


利用するクラスはCursorInfoとDragGhostの2つ.CursorInfoは情報元サイトと全く同じで,DragGhostは情報元サイトのDragAdornerクラスを自分の使いやすいように改造している.
+ CursorInfo
CursorInfo.cs
using System;
using System.Windows;
using System.Windows.Interop;
using System.Runtime.InteropServices;
 
namespace WpfSampleApplication
{
    class CursorInfo
    {
        [DllImport("user32.dll")]
        private static extern void GetCursorPos(out POINT pt);
 
        [DllImport("user32.dll")]
        private static extern int ScreenToClient(IntPtr hwnd, ref POINT pt);
 
        private struct POINT
        {
            public UInt32 X;
            public UInt32 Y;
        }
 
        public static Point GetNowPosition(System.Windows.Media.Visual v)
        {
            POINT p;
            GetCursorPos(out p);
 
            var source = HwndSource.FromVisual(v) as HwndSource;
            var hwnd = source.Handle;
 
            ScreenToClient(hwnd, ref p);
            return new Point(p.X, p.Y);
        }
    }
} 
+ DragGhost.cs
DragGhost.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Documents;
 
namespace WpfSampleApplication
{
    public class DragGhost
        : Adorner
    {
        protected UIElement ghost;
        protected Vector movement;
        protected bool isRendering;
 
        public DragGhost(UIElement draggedElement, double opacity)
            : this(draggedElement, opacity, new Point(0,0))
        { }
 
        public DragGhost(UIElement draggedElement, double opacity, Point clicked)
            : base(draggedElement)
        {
            isRendering = false;
 
            // 1. ゴーストを作成
 
            // draggedElementを囲むサイズの長方形領域を作成
            var b = VisualTreeHelper.GetDescendantBounds(draggedElement);
            var rect = new System.Windows.Shapes.Rectangle() { Width = b.Width, Height = b.Height };
 
            // rectを指定の透過度のdraggedElementで塗りつぶすブラシ
            var brush = new VisualBrush(draggedElement) { Opacity = opacity };
 
            // rectに ブラシを適用
            rect.Fill = brush;
 
            // rectをghostとして覚えておく
            ghost = rect;
 
            // 2. ゴーストの描画位置を決定するための前準備
 
            // clickedのクライアント座標を求め反転させたものを移動量とする
            movement = (Vector) Window.GetWindow(draggedElement).PointFromScreen(draggedElement.PointToScreen(clicked));
            movement.Negate(); 
        }
 
        private Point _Position;
        public Point Position{
            get { return _Position; }
            set
            {
                // 現在のマウス位置(クライアント座標系)に移動量を加算したものがゴーストの描画位置
                _Position = (Point)( value + movement );
                UpdatePosition();
            }
        }
 
        /// <summary>
        /// ゴーストの描画を開始する。
        /// DragDrop.DoDragDropメソッドを呼び出す直前に実行する。
        /// </summary>
        public bool Show()
        {
            if (!isRendering)
            {
                var adorner = AdornerLayer.GetAdornerLayer(this.AdornedElement);
                if (adorner != null)
                {
                    isRendering = true;
                    adorner.Add(this);
                }
            }
            return isRendering;
        }
 
        /// <summary>
        /// ゴーストの描画を終了する。
        /// DragDrop.DoDragDropメソッドを呼び出した直後に実行する。
        /// </summary>
        public void Remove()
        {
            if (isRendering)
            {
                var adorner = this.Parent as AdornerLayer;
                if (adorner != null)
                    adorner.Remove(this);
                isRendering = false;
            }
        }
 
        protected void UpdatePosition()
        {
            // Adornerの親はAdornerLayer
            var adorner = this.Parent as AdornerLayer;
            if (adorner != null)
            {
                adorner.Update(this.AdornedElement);
            }
        }
 
        protected override Visual GetVisualChild(int index)
        {
            return ghost;
        }
 
        protected override int VisualChildrenCount
        {
            get { return 1; }
        }
 
        protected override Size MeasureOverride(Size finalSize)
        {
            ghost.Measure(finalSize);
            return ghost.DesiredSize;
        }
 
        protected override Size ArrangeOverride(Size finalSize)
        {
 
            ghost.Arrange(new Rect(ghost.DesiredSize));
            return finalSize;
        }
 
        public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
        {
            var result = new GeneralTransformGroup();
            result.Children.Add(base.GetDesiredTransform(transform));
            result.Children.Add(new TranslateTransform(Position.X, Position.Y));
            return result;
        }
    }
} 

拡張版DragGhost

上記を拡張して,以下の機能を追加した.
  1. ドラッグされた要素と異なるエレメントをゴーストにできるように
  2. マウスの位置がゴーストの下段中央(などの指定位置)にくる という指定をできるように

ソースコードは以下の通り.GhostAlignmentで2.のマウスの位置を指定する.基本的な使い方は上と同じ.

+ DragGhost.cs 拡張版
DragGhost.cs 拡張版
public class DragGhost
    : Adorner
{
    public DragGhost(UIElement draggedElement, double opacity)
        : this(draggedElement, null, opacity, null, null)
    { }
 
    public DragGhost(UIElement draggedElement, double opacity, Point clicked)
        : this(draggedElement, null, opacity, null, clicked)
    { }
 
    public DragGhost(UIElement draggedElement, double opacity, GhostAlignment? align)
        : this(draggedElement, null, opacity, align, null)
    { }
 
    public DragGhost(UIElement draggedElement, double opacity, GhostAlignment? align, Point additional)
        : this(draggedElement, null, opacity, align, additional)
    { }
 
    public DragGhost(UIElement draggedElement, UIElement draggedGhost, double opacity)
        : this(draggedElement, draggedGhost, opacity, null, null)
    { }
 
    public DragGhost(UIElement draggedElement, UIElement draggedGhost, double opacity, GhostAlignment align)
        : this(draggedElement, draggedGhost, opacity, align, null)
    { }
 
    public DragGhost(UIElement draggedElement, UIElement draggedGhost, double opacity, GhostAlignment? align, Point? additional)
        : base(draggedElement)
    {
        if (draggedElement == null)
            throw new NullReferenceException("ドラッグされた要素が指定されていません.");
        draggedGhost = draggedGhost ?? draggedElement;
        if (opacity < 0)
            opacity = 0D;
        else if (opacity > 1)
            opacity = 1D;
        align = align ?? GhostAlignment.TopLeft;
        additional = additional ?? new Point(0, 0);
 
        isRendering = false;
 
        // 1. ゴーストを作成
 
        // ghostを囲むサイズの長方形領域を作成
        var b = VisualTreeHelper.GetDescendantBounds(draggedGhost);
        var rect = new System.Windows.Shapes.Rectangle() { Width = b.Width, Height = b.Height };
        // rectを指定の透過度のdraggedElementで塗りつぶすブラシ
        var brush = new VisualBrush(draggedGhost) { Opacity = opacity };
        // rectに ブラシを適用
        rect.Fill = brush;
        // rectをghostとして覚えておく
        ghost = rect;
 
        // 2. ゴーストの描画位置を決定するための前準備
 
        // draggedElementのクライアント座標を求める
        movement = (Vector)Window.GetWindow(draggedElement).PointFromScreen(draggedElement.PointToScreen(new Point(0,0)));
        // alignmentによる処理
        switch (align)
        {
            case GhostAlignment.TopLeft:
                break;
            case GhostAlignment.TopCenter:
                movement.X += rect.Width / 2;
                break;
            case GhostAlignment.TopRight:
                movement.X += rect.Width;
                break;
            case GhostAlignment.MiddleLeft:
                movement.Y += rect.Height / 2;
                break;
            case GhostAlignment.MiddleCenter:
                movement.X += rect.Width / 2;
                movement.Y += rect.Height / 2;
                break;
            case GhostAlignment.MiddleRight:
                movement.X += rect.Width;
                movement.Y += rect.Height / 2;
                break;
            case GhostAlignment.BottomLeft:
                movement.Y += rect.Height;
                break;
            case GhostAlignment.BottomCenter:
                movement.X += rect.Width / 2;
                movement.Y += rect.Height;
                break;
            case GhostAlignment.BottomRight:
                movement.X += rect.Width;
                movement.Y += rect.Height;
                break;
        }
        // additional
        movement += (Vector) additional;
        movement.Negate();
    }
 
    private Point _Position;
    public Point Position
    {
        get { return _Position; }
        set
        {
            // 現在のマウス位置(クライアント座標系)に移動量を加算したものがゴーストの描画位置
            _Position = (Point)(value + movement);
            UpdatePosition();
        }
    }
 
    /// <summary>
    /// ゴーストの描画を開始する。
    /// DragDrop.DoDragDropメソッドを呼び出す直前に実行する。
    /// </summary>
    public bool Show()
    {
        if (!isRendering)
        {
            var adorner = AdornerLayer.GetAdornerLayer(base.AdornedElement);
            if (adorner != null)
            {
                isRendering = true;
                adorner.Add(this);
            }
        }
        return isRendering;
    }
 
    /// <summary>
    /// ゴーストの描画を終了する。
    /// DragDrop.DoDragDropメソッドを呼び出した直後に実行する。
    /// </summary>
    public void Remove()
    {
        if (isRendering)
        {
            var adorner = this.Parent as AdornerLayer;
            if (adorner != null)
                adorner.Remove(this);
            isRendering = false;
        }
    }
 
    protected void UpdatePosition()
    {
        // Adornerの親はAdornerLayer
        var adorner = this.Parent as AdornerLayer;
        if (adorner != null)
        {
            adorner.Update(this.AdornedElement);
        }
    }
 
    protected override Visual GetVisualChild(int index)
    {
        return ghost;
    }
 
    protected override int VisualChildrenCount
    {
        get { return 1; }
    }
 
    protected override Size MeasureOverride(Size finalSize)
    {
        ghost.Measure(finalSize);
        return ghost.DesiredSize;
    }
 
    protected override Size ArrangeOverride(Size finalSize)
    {
 
        ghost.Arrange(new Rect(ghost.DesiredSize));
        return finalSize;
    }
 
    public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
    {
        var result = new GeneralTransformGroup();
        result.Children.Add(base.GetDesiredTransform(transform));
        result.Children.Add(new TranslateTransform(Position.X, Position.Y));
        return result;
    }
 
    protected UIElement ghost;
    protected Vector movement;
    protected bool isRendering;
} 
+ GhostAlignment.cs
GhostAlignment.cs
public enum GhostAlignment
{
    BottomCenter,
    BottomLeft,
    BottomRight, 
    MiddleCenter,
    MiddleLeft, 
    MiddleRight,
    TopCenter,
    TopLeft,
    TopRight
} 



カテゴリ:WPF

  • 古い記事にコメントスミマセン。Formの外にはゴーストは表示出来ないでしょうか - てんてん 2017-07-31 21:35:43






記事メニュー
ウィキ募集バナー