アプリケーション内のドラッグアンドドロップ


※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

WPFでは要素のドラッグアンドドロップがサポートされていないので、プログラマが自分で実装する必要があります。

ドロップについてはサポートされており、通常はWindowsのファイルをエクスプローラーなどから受け取ることを目的とした機能のようです。

Thumbコントロールによるマウスキャプチャ

ThumbコントロールはWPFで実装されているドラッグアンドドロップっぽい動きをしそうなコントロールです。
WPFのVisualTreeを解析してみるとスクロールバーなどで使用されていることがわかります。

このコントロールは、マウスのキャプチャやドラッグ、リリースのタイミングでイベントを発行するためそれぞれのタイミングで行いたい処理を実装することができます。

ただし、MSDNには

Thumb コントロールは、ドラッグ アンド ドロップ機能を提供しません。

とあります。

Thumbコントロールは、単にマウスキャプチャできるコントロールといえます。
Thumbコントロールを配置したパネル内で、マウスを使ってコントロールを移動することはできますが、配置したパネルを飛び出して他のパネルへデータを運ぶといった動作はできません。

ドラッグアンドドロップの実装例

多くのドラッグアンドドロップを実装するライブラリでは、singletonなドラッグアンドドロップマネージャクラス(DragAndDropManager)を定義します。
DragAndDropManagerクラスはドラッグ中のアイテムを保持したり、ドラッグ元とドラッグ先の設定を管理します。

WPFではドラッグの開始とドラッグ中のアイテムのプレビュー(Adornerとよばれる)の実装はプログラマが行います。
ドラッグが開始されてからドロップまでは.NET FrameworksのDragDropクラスが面倒をみてくれます。(といっても、ドラッグアンドドロップの情報だけ・・・)

事前準備

このページで説明しているDragAndDropManagerの名前空間はすべて「DDDevelop」です。

DragAndDropManagerの実装方針

DragAndDropManagerはsingletonなインスタンスを保持し、ドラッグ中のアイテムなどの情報を持つ。

ドラッグソースとドラッグターゲット

ドラッグソースとはドラッグを開始するPanelの事です。Panelクラスは.NET Franeworksで背景を持つUIElementです。

ドラッグターゲットとはドラッグしたデータをドロップすることのできるPanelです。

今回実装するドラッグアンドドロップは、ドラッグソースがドラッグが可能であることを設定するところ(IsAllowDrag)から開始します。

フックするマウスイベント

DaDMgrがフックするマウスイベントには注意が必要で、WPFのイベントにはバブルイベントとトンネルイベントの2種類あります。

DaDMgrはドラッグソースが受け取ったマウスイベントを処理しなければならないため、 MouseDownやMouseOverをバブルイベントで受け取ろうとすると、ドラッグソースまでイベントが到達しない可能性があります。 (イベントのイベントルーティング項を参照)

DaDMgrは必ずトンネルイベントでマウスイベントを受け取る必要があります。

ドラッグソースの実装

ドラッグソースとなるPanelは次のようにDragAndDropManager.IsAllowDragプロパティにtrueを設定する。

<Grid Name="DragSourceGrid"
	  DaD:DragAndDropManager.IsAllowDrag="true">
</Grid>

DragAndDropManagerは次のような依存関係プロパティを定義しており、true(または、false)の値を設定されるたびにIsAllodDragChangedハンドラが呼び出される。

public static readonly DependencyProperty IsAllowDragProperty =
	DependencyProperty.RegisterAttached("IsAllowDrag",
		 typeof(bool), 
		 typeof(DragAndDropManager), 
		 new UIPropertyMetadata(false, IsAllowDragChanged));
 
public static bool GetIsAllowDrag (DependencyObject obj) {
	return (bool)obj.GetValue(IsAllowDragProperty);
}
 
public static void SetIsAllowDrag(DependencyObject obj, bool value) {
	obj.SetValue(IsAllowDragProperty, value);
}
 
// イベントハンドラ
private static void IsAllowDragChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
	// ... 
}

IsAllowDragChangedメソッドではドラッグの開始を行うためのマウスイベント(マウスの左クリックイベント:PreviewMouseLeftButtonDown)などのリスナを設定します。
実装については次項から行っていきます。

ドラッグの開始時に行う処理

D&Dの開始はドラッグソース内の特定のPanel内でマウスの左クリックが押された時点からとします。

<Window
	x:Class="DDDevelop.MainWindow"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:dd="clr-namespace:DDDevelop"
	Title="MainWindow" Height="350" Width="525">
	<Grid>
		<Grid Name="grid_DragSource"
			  HorizontalAlignment="Left" Margin="12,12,0,12"  Width="200" Background="#FFFFEBEB"
			  DD:DragAndDropManager.IsAllowDrag="True"
		>
			<Canvas Height="20" HorizontalAlignment="Left" Margin="108,90,0,0" Name="canvas1" VerticalAlignment="Top" Width="20" Background="#FF6572FF" />
		</Grid>
	</Grid>
</Window>
#ref error : ファイルが見つかりません (DD1.jpg)

青い矩形の中でマウスの左ボタンを押すとドラッグを開始し、ボタンを放すとドラッグが終了する。という動作を実装します。

// DragAndDropManager.cs
 
private static void IsAllowDragChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
	this.dragSourcePanel = sender as Panel;
	Contract.Requires(dragSourcePanel != null, "ドラッグソースを設定できるのはPanel型のみです");
 
	if(Equals(e.NewValue,true)){
		// ドラッグソース内でマウスの左ボタンが押されたとき
		dragSourcePanel.PreviewMouseLeftButtonDown += Instance.OnDragSource_PreviewMouseLeftButtonDown;
 
		// ドラッグソース内でマウスの左ボタンが離されたとき
		dragSourcePanel.PreviewMouseLeftButtonUp += Instance.OnDragSource_PreviewMouseLeftButtonUp;
	}else {
		// ドラッグソースではなくなった場合、リスナ登録を解放する
 
		dragSourcePanel.PreviewMouseLeftButtonDown -= Instance.OnDragSource_PreviewMouseLeftButtonDown;
		dragSourcePanel.PreviewMouseLeftButtonUp -= Instance.OnDragSource_PreviewMouseLeftButtonUp;
	}
}
 
 
private void OnDragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
	Panel dragSourcePanel = sender as Panel;
	Contract.Requires(dragSourcePanel != null, "ドラッグソースが不正です");
 
	if (dragSourcePanel.Children.Contains(e.OriginalSource as FrameworkElement) ){ // イベントのソースがドラッグソース内にあるか?
		rootWindow = Window.GetWindow(dragSourcePanel);
 
		startPoint = e.GetPosition(rootWindow); // ドラッグを開始しした座標
 
		dragData = "何かドラッグするデータ";
 
		Debug.WriteLine("D&Dを開始");
	}
}
 
private void OnDragSource_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
	// D&D処理を中止とする
	dragData = null;
	Debug.WriteLine("D&Dを中止"); // ※ 今のところ何も処理はないので、デバッグメッセージだけを出力
}
 
 
// ※DragAndDropManagerにインスタンス変数を追加
private Point startPoint;
private Window rootWindow;
Panel dragSourcePanel;
private object dragData;

IsAllowDragChangedメソッドに追加した処理は、IsAlloDragプロパティが「true」に設定された場合にドラッグソースにイベントリスナを登録し、「false」に設定された場合はドラッグソースに登録したイベントリスナを解放します。

OnDragSource_PreviewMouseLeftButtonDownメソッドはドラッグソース内でマウスの左ボタンが押された瞬間に呼び出されるイベントハンドラです。
このメソッドでは、MouseButtonEventArgs.OriginalSourceプロパティを判断しドラッグソース内の子要素でイベントが発生しているかチェックします。子要素内でイベントが発生している場合はドラッグを開始します。 そのため、図の青い矩形内で左ボタンを押すと「D&Dを開始」と出力されますが、それ以外の部分で左クリックを押してもなにも起きません。

ドラッグ中の処理

次にドラッグ中の処理を追加します。
ドラッグが開始されたら、DragAndDropManagerはマウスの動きをキャプチャします。

ドラッグソース内でのマウス移動イベントを処理するイベントハンドラを追加します。

private static void IsAllowDragChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
	Panel dragSourcePanel = sender as Panel;
	Contract.Requires(dragSourcePanel != null, "ドラッグソースを設定できるのはPanel型のみです");
 
	if (Equals(e.NewValue, true)) {
		dragSourcePanel.PreviewMouseLeftButtonDown += Instance.OnDragSource_PreviewMouseLeftButtonDown;
		dragSourcePanel.PreviewMouseLeftButtonUp += Instance.OnDragSource_PreviewMouseLeftButtonUp;
 
		// ↓追加
		dragSourcePanel.PreviewMouseMove += Instance.OnDragSource_PreviewMouseMove;
	}
	else {
		dragSourcePanel.PreviewMouseLeftButtonDown -= Instance.OnDragSource_PreviewMouseLeftButtonDown;
		dragSourcePanel.PreviewMouseLeftButtonUp -= Instance.OnDragSource_PreviewMouseLeftButtonUp;
 
		// ↓追加
		dragSourcePanel.PreviewMouseMove -= Instance.OnDragSource_PreviewMouseMove;
	}
}
 
private void OnDragSource_PreviewMouseMove(object sender, MouseEventArgs e) {
	if (dragData != null) { // ドラッグしているデータがあるかどうか。
		Debug.WriteLine("ドラッグ中 " + DateTime.Now);
	}
}

ドラッグを開始するとマウスの左クリックを押しながらマウスを動かすと「ドラッグ中 2010/07/01 11:15:12」のような出力が永遠と出力されます。(マウスの動きを止めると、PreviewMouseMoveイベントが発行されなくなるので表示は止まる)

ただし、PreviewMouseMoveイベントを発行はドラッグソース内でマウスを動かしている間のみ行われるので、ドラッグ中にドラッグソース外へマウスカーソルを移動してしまうと、PreviewMouseLeftButtonUpやPreviewMouseMoveが発行されなくなり、おかしな動作になってしまいます。

DragDropの使用

ドラッグに関する処理は.NET Frameworksでもサポートする処理が実装されています。
DragDropクラスを使用すると、システムレベルのドロップをサポートすることができます。

ドラッグ中の処理はDragDrop.DoDragDrop()を使って行うのですが、DragDrop.DoDragDrop()の使用には注意する必要があります。DragDrop.DoDragDrop()を呼び出すとそのスレッドはDragDropによってドラッグが終了されるまでブロックされます。


DragDropを使用するとUIElementクラスのDragEnter/DragOver/DragLeaveが呼び出されるようになります。(AllowDropがセットされている場合)

private void OnDragSource_PreviewMouseMove(object sender, MouseEventArgs e) {
	if (dragData != null) {
		//
		// rootWindowのD&Dに関する設定
		bool oldAllowDrop = this.rootWindow.AllowDrop;
		rootWindow.AllowDrop = true;
		rootWindow.DragEnter += OnRootWindow_DragEnter; // ※後述
		rootWindow.DragOver += OnRootWindow_DragOver;   // ※後述
		rootWindow.DragLeave += OnRootWindow_DragLeave; // ※後述
 
		// ドラッグアンドドロップのデータ
		// DataObjectクラスを使用する。
		DataObject dddata = new DataObject(DataFormats.StringFormat, this.dragData);
 
 
		//------------------
		// ドラッグアンドドロップ開始
 
		DragDropEffects effects = DragDrop.DoDragDrop(
									(DependencyObject)sender,
									dddata,
									DragDropEffects.Move);
 
		// DragDrop.DoDragDropは、ドラッグが終了するまでスレッドはブロックされる
 
 
		//----------------
		// ドラッグアンドドロップ終了
 
		//
		// rootWindowのD&Dに関する設定を復帰
		this.rootWindow.AllowDrop = oldAllowDrop; // AllowDropの値を元に戻す
		rootWindow.DragEnter -= OnRootWindow_DragEnter;
		rootWindow.DragOver -= OnRootWindow_DragOver;
		rootWindow.DragLeave -= OnRootWindow_DragLeave;
 
		this.dragData = null;
	}
}

ドラッグアンドドロップを行うデータ形式については、任意のオブジェクトか、他のアプリケーションへもデータを渡すことができるDataObjectクラスを使用します。D&Dがアプリケーション内だけで完結するのならば好きな型で問題ありません。
他のアプリケーションにデータを渡すようなD&Dの場合には、共通のデータ形式名をDataObjectに渡すことで、ドロップ先のアプリケーションがそのデータ形式名のデータを受け取るように実装されていれば、アプリケーション間でD&Dを使ってデータの引き渡しができます。

今回はアプリケーション内でのD&Dの説明ということで、D&Dのデータ形式についてはこれ以上は追求しません。

Drag関係のイベントハンドラの実装

rootWindowのDragEnter・DragOver・DragLeaveに設定したイベントハンドラです。
具体的な実装はまだ行っていませんが、デバッグプリントによってある程度の流れはつかめるハズです。

e.Handledはイベントルーティングを停止するかのフラグです。
DragEnter/DragOver/DragLeaveはバブル型イベントで、今回の場合VisualTreeの下位階層にイベントを伝える必要がないため、これ以上のイベントルーティングを停止させています。

private void OnRootWindow_DragEnter(object sender, DragEventArgs e) {
	Debug.WriteLine("OnRootWindow_DragEnter");
 
	e.Effects = DragDropEffects.None; // マウスカーソルの形状をドロップ不可能に。
	e.Handled = true;                 // イベントを処理したことを通知(イベントルーティングの停止)
}
 
private void OnRootWindow_DragOver(object sender, DragEventArgs e) {
	Debug.WriteLine("OnRootWindow_DragOver");
 
 
	e.Effects = DragDropEffects.None; // マウスカーソルの形状をドロップ不可能に。
	e.Handled = true;                 // イベントを処理したことを通知(イベントルーティングの停止)
}
 
private void OnRootWindow_DragLeave(object sender, DragEventArgs e) {
	Debug.WriteLine("OnRootWindow_DragLeave");
 
	e.Handled = true; // イベントを処理したことを通知(イベントルーティングの停止)
}

ドラッグターゲットの実装

ドラッグターゲットとはD&Dのドロップ先のことです。
これまではドラッグを開始するための実装を行ってきましたが、次にドラッグ中のデータを受け取るための受け取り側の実装を行います。

ドラッグ可能なパネルにはIsAllowDropプロパティを「true」とすることで、ドラッグターゲットとしてDragAndDropManagerに登録します。

ドロップターゲットでのマウス処理は、DragDrop.DoDragDrop()を使用しているためマウスイベントを処理するにではなくDragEnterやDragLeaveなどのイベントを処理します。

ドロップターゲットの実装はドロップソースの時と同じなので、詳しい説明は省略します。

public static readonly DependencyProperty IsAllowDropProperty =
			DependencyProperty.RegisterAttached("IsAllowDrop",
			typeof(bool),
			typeof(DragAndDropManager),
			new UIPropertyMetadata(false, IsAllowDropChanged));
 
public static bool GetIsAllowDrop(DependencyObject obj) {
	return (bool)obj.GetValue(IsAllowDropProperty);
}
 
public static void SetIsAllowDrop(DependencyObject obj, bool value) {
	obj.SetValue(IsAllowDropProperty, value);
}
 
 
private static void IsAllowDropChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
	Panel dragTargetPanel = sender as Panel;
	Contract.Requires(dragTargetPanel != null, "ドラッグターゲットを設定できるのはPanel型のみです");
 
	if (Equals(e.NewValue, true)) {
		dragTargetPanel.AllowDrop = true;
 
		dragTargetPanel.PreviewDragEnter += Instance.OnDragTarget_PreviewDragEnter;
		dragTargetPanel.PreviewDragOver += Instance.OnDragTarget_PreviewDragOver;
		dragTargetPanel.PreviewDragLeave += Instance.OnDragTarget_PreviewDragLeave;
		dragTargetPanel.PreviewDrop += Instance.OnDragTarget_PreviewDrop;
	}
	else {
		dragTargetPanel.AllowDrop = false;
 
		dragTargetPanel.PreviewDragEnter -= Instance.OnDragTarget_PreviewDragEnter;
		dragTargetPanel.PreviewDragOver -= Instance.OnDragTarget_PreviewDragOver;
		dragTargetPanel.PreviewDragLeave -= Instance.OnDragTarget_PreviewDragLeave;
		dragTargetPanel.PreviewDrop -= Instance.OnDragTarget_PreviewDrop;
	}
}
 
private void OnDragTarget_PreviewDrop(object sender, DragEventArgs e) {
	object draggedItem = e.Data.GetData(DataFormats.StringFormat);
	if (draggedItem != null) {
		// TODO: ドロップしたときの処理を追加する
 
		e.Handled = true; // イベントを処理したことを通知
	}
}
 
private void OnDragTarget_PreviewDragOver(object sender, DragEventArgs e) {
	object draggedItem = e.Data.GetData(DataFormats.StringFormat);
	if (draggedItem != null) {
	}
	e.Handled = true;
}
 
private void OnDragTarget_PreviewDragEnter(object sender, DragEventArgs e) {
	object draggedItem = e.Data.GetData(DataFormats.StringFormat);
	if (draggedItem != null) {
	}
	e.Handled = true;
}
 
private void OnDragTarget_PreviewDragLeave(object sender, DragEventArgs e) {
	object draggedItem = e.Data.GetData(DataFormats.StringFormat);
	if (draggedItem != null) {
 
	}
	e.Handled = true;
}

ドラッグターゲットがD&Dされてきたデータに対して何らかの処理を追加する箇所が、OnDragTarget_PreviewDropとなります。

OnDragTarget_PreviewDragOver/OnDragTarget_PreviewDragEnter/OnDragTarget_PreviewDragLeaveは今のところ追加する処理はありません。
ここには、まだ実装していないD&Dの重要な部分であるAdornerの制御を記述します。

Adornerの実装

これまで実装してきたD&Dは、確かにD&Dとしてデータのやりとりができていましたが、ビジュアルの実装を行っていません。
Adornerを実装してよりD&Dらしい実装となります。

Adornetとは、Windowsエクスプローラーでファイルをドラッグアンドドロップ使用とした場合にマウスに追従するものを指します。

Adornerの実装にはSystem.Windows.Documents.Adornerを継承して作成します。
Adornerレイヤーというものを、AdornerLayerから取得しそこにAdornerを表示します。

描画する画像に関してはAdornerクラスを継承したクラスを作成しその中で行います。

DragAdornerの実装

public class DragAdorner : Adorner {
	//=====================================================================
	#region Constructors
	//=====================================================================
 
	/// <summary>
	/// 
	/// </summary>
	/// <param name="owner"></param>
	/// <param name="adornElement"></param>
	public DragAdorner(UIElement owner, UIElement adornElement)
		: base(owner) {
		_Owner = owner;
 
		Brush brush = new VisualBrush(adornElement);
 
		var rectangle = new Rectangle {
			Width = adornElement.RenderSize.Width,
			Height = adornElement.RenderSize.Height,
			Fill = brush,
			Opacity = 0.7
		};
 
		Point mousePoint = Mouse.PrimaryDevice.GetPosition(adornElement);
		XCenter = mousePoint.X;
		YCenter = mousePoint.Y;
 
		_Child = rectangle;
	}
 
	#endregion Constructors
 
 
 
	/// <summary>
	/// Adornerの表示座標
	/// </summary>
	/// <param name="left"></param>
	/// <param name="top"></param>
	public void SetPosition(double left, double top) {
		_TopOffset = top;
		_LeftOffset = left;
 
		UpdatePosition();
	}
 
 
	//=====================================================================
	#region Properties
	//=====================================================================
 
	public double TopOffset {
		get {
			return _TopOffset;
		}
		set {
			_TopOffset = value - YCenter;
			UpdatePosition();
		}
	}
 
	public double LeftOffset {
		get {
			return _LeftOffset;
		}
		set {
			_LeftOffset = value - XCenter;
			UpdatePosition();
		}
	}
 
	#endregion Properties
 
 
	//=====================================================================
	#region Adorner Methods
	//=====================================================================
	protected override Visual GetVisualChild(int index) {
		return this._Child;
	}
 
	protected override int VisualChildrenCount {
		get {
			return 1;
		}
	}
 
	protected override Size MeasureOverride(Size finalSize) {
		_Child.Measure(finalSize);
		return _Child.DesiredSize;
	}
 
 
	protected override Size ArrangeOverride(Size finalSize) {
		_Child.Arrange(new Rect(finalSize));
		return finalSize;
	}
 
	public override GeneralTransform GetDesiredTransform(GeneralTransform transform) {
		var result = new GeneralTransformGroup();
		result.Children.Add(base.GetDesiredTransform(transform));
		result.Children.Add(new TranslateTransform(_LeftOffset, _TopOffset));
		return result;
	}
	#endregion Adorner Methods
 
 
	private void UpdatePosition() {
		var adorner = (AdornerLayer)Parent;
		if (adorner != null)
			adorner.Update(AdornedElement);
	}
 
 
 
 
	private double _LeftOffset;
	private double _TopOffset;
	protected UIElement _Child;
	protected UIElement _Owner;
	protected double XCenter;
	protected double YCenter;
}

コンストラクタで表示する内容を作成します。
とりあえず、ドラッグするパネルと同じ色と同じサイズの矩形を作成し、それを表示します。

Adornerの表示

DragAndDropManagerにAdornerを表示するための処理を追加します。

// DragAndDropManager.cs
 
/// <summary>
/// ドラッグ中のAdornerを指定の場所に表示
/// </summary>
/// <param name="currentPosition"></param>
private void ShowDragAdorner(Point currentPosition) {
	if (dragAdorner == null) {
		AdornerLayer layer = AdornerLayer.GetAdornerLayer(dragSourcePanel);
		this.dragAdorner = new DragAdorner(dragSourcePanel, dragSourceItem);
 
		layer.Add(this.dragAdorner);
	}
 
	dragAdorner.SetPosition(currentPosition.X, currentPosition.Y);
}
 
/// <summary>
/// Adornerを非表示
/// </summary>
private void RemoveDragAdorner() {
	if (dragAdorner != null) {
		AdornerLayer layer = AdornerLayer.GetAdornerLayer(dragSourcePanel);
		layer.Remove(dragAdorner);
 
		this.dragAdorner = null;
	}
}
 
DragAdorner dragAdorner;

Adornerはドラッグを行っている最中に表示するものなので、DragOverイベントでShowDragAdorner()を呼び出します。
ShowDragAdorner()は表示する座標を引数として受け取ります。

private void OnRootWindow_DragOver(object sender, DragEventArgs e) {
	Debug.WriteLine("OnRootWindow_DragOver");
 
	// ※追加
	// Adornerを表示
	ShowDragAdorner(new Point {
		X = e.GetPosition(rootWindow).X,
		Y = e.GetPosition(rootWindow).Y
	});
 
	e.Effects = DragDropEffects.None; // マウスカーソルの形状をドロップ不可能に。
	e.Handled = true;                 // イベントを処理したことを通知
}
 
private void OnDragTarget_PreviewDragOver(object sender, DragEventArgs e) {
	object draggedItem = e.Data.GetData(DataFormats.StringFormat);
 
	if (draggedItem != null) {
		// ※追加
		// Adornerを表示
		ShowDragAdorner(new Point {
			X = e.GetPosition(rootWindow).X,
			Y = e.GetPosition(rootWindow).Y
		});
 
	}
 
	e.Handled = true;
}

マウスカーソルがDragTargetに設定したパネルの上にある場合は、OnDragTarget_PreviewDragOverが呼び出されます。
それ以外の箇所ではOnRootWindow_DragOverが呼び出されます。

2つのDragOverが呼び出されないのは、DropTargetがトンネル型イベントであるPreviewDragOverをリスナに登録し、そのイベントハンドラ(OnDragTarget_PreviewDragOver)で「e.Handled=true」としているためです。
Handledがtrueとなった場合は、それ以上のイベントルーティングが行われなくなります。

ソースコード

DragAndDropManager.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Windows.Documents;
 
namespace DDDevelop {
	public class DragAndDropManager {
		private static DragAndDropManager instance;
		public static DragAndDropManager Instance {
			get {
				if (instance == null) {
					instance = new DragAndDropManager();
				}
				return instance;
			}
		}
 
 
		//=====================================================================
		#region Constructors
		//=====================================================================
		/// <summary>
		/// デフォルトコンストラクタ
		/// </summary>
		private DragAndDropManager() {
		}
		#endregion Constructors
 
 
		//=====================================================================
		#region Dependency Properties
		//=====================================================================
		public static readonly DependencyProperty IsAllowDragProperty = 
			DependencyProperty.RegisterAttached("IsAllowDrag",
			typeof(bool),
			typeof(DragAndDropManager),
			new UIPropertyMetadata(false, IsAllowDragChanged));
 
		public static bool GetIsAllowDrag(DependencyObject obj) {
			return (bool)obj.GetValue(IsAllowDragProperty);
		}
 
		public static void SetIsAllowDrag(DependencyObject obj, bool value) {
			obj.SetValue(IsAllowDragProperty, value);
		}
 
		public static readonly DependencyProperty IsAllowDropProperty =
			DependencyProperty.RegisterAttached("IsAllowDrop",
			typeof(bool),
			typeof(DragAndDropManager),
			new UIPropertyMetadata(false, IsAllowDropChanged));
 
		public static bool GetIsAllowDrop(DependencyObject obj) {
			return (bool)obj.GetValue(IsAllowDropProperty);
		}
 
		public static void SetIsAllowDrop(DependencyObject obj, bool value) {
			obj.SetValue(IsAllowDropProperty, value);
		}
 
		#endregion Dependency Properties
 
 
		//=====================================================================
		#region Handlers
		//=====================================================================
 
		private static void IsAllowDragChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
			Panel dragSourcePanel = sender as Panel;
			Contract.Requires(dragSourcePanel != null, "ドラッグソースを設定できるのはPanel型のみです");
 
			if (Equals(e.NewValue, true)) {
				dragSourcePanel.PreviewMouseLeftButtonDown += Instance.OnDragSource_PreviewMouseLeftButtonDown;
				dragSourcePanel.PreviewMouseLeftButtonUp += Instance.OnDragSource_PreviewMouseLeftButtonUp;
				dragSourcePanel.PreviewMouseMove += Instance.OnDragSource_PreviewMouseMove;
			}
			else {
				dragSourcePanel.PreviewMouseLeftButtonDown -= Instance.OnDragSource_PreviewMouseLeftButtonDown;
				dragSourcePanel.PreviewMouseLeftButtonUp -= Instance.OnDragSource_PreviewMouseLeftButtonUp;
				dragSourcePanel.PreviewMouseMove -= Instance.OnDragSource_PreviewMouseMove;
			}
		}
 
 
		private static void IsAllowDropChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
			Panel dragTargetPanel = sender as Panel;
			Contract.Requires(dragTargetPanel != null, "ドラッグターゲットを設定できるのはPanel型のみです");
 
			if (Equals(e.NewValue, true)) {
				dragTargetPanel.AllowDrop = true;
 
				dragTargetPanel.PreviewDragEnter += Instance.OnDragTarget_PreviewDragEnter;
				dragTargetPanel.PreviewDragOver += Instance.OnDragTarget_PreviewDragOver;
				dragTargetPanel.PreviewDragLeave += Instance.OnDragTarget_PreviewDragLeave;
				dragTargetPanel.PreviewDrop += Instance.OnDragTarget_PreviewDrop;
			}
			else {
				dragTargetPanel.AllowDrop = false;
 
				dragTargetPanel.PreviewDragEnter -= Instance.OnDragTarget_PreviewDragEnter;
				dragTargetPanel.PreviewDragOver -= Instance.OnDragTarget_PreviewDragOver;
				dragTargetPanel.PreviewDragLeave -= Instance.OnDragTarget_PreviewDragLeave;
				dragTargetPanel.PreviewDrop -= Instance.OnDragTarget_PreviewDrop;
			}
		}
 
 
		/// <summary>
		/// ドラッグソース内でマウスの左クリックを受けた場合の実行関数
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnDragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
			this.dragSourcePanel = sender as Panel;
 
			if (dragSourcePanel.Children.Contains(e.OriginalSource as FrameworkElement) ){
				dragSourceItem = e.OriginalSource as UIElement;
				rootWindow = Window.GetWindow(dragSourcePanel);
 
				startPoint = e.GetPosition(rootWindow);
 
				dragData = "何かドラッグするデータ";
 
				Debug.WriteLine("D&Dを開始");
			}
		}
 
		/// <summary>
		/// ドラッグソース内でマウスの左クリックが解除された場合の実行関数
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnDragSource_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
			// D&D処理を中止とする
			dragData = null;
			Debug.WriteLine("D&Dを中止");
		}
 
 
		/// <summary>
		/// ドラッグソース内でのマウス移動の実行関数
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnDragSource_PreviewMouseMove(object sender, MouseEventArgs e) {
			if (dragData != null) {
				Panel dragTargetPanel = sender as Panel;
 
				Debug.WriteLine("ドラッグ中 " + DateTime.Now);
 
				// D&Dのデータ
				DataObject dddata = new DataObject(DataFormats.StringFormat, this.dragData);
 
 
				bool oldAllowDrop = this.rootWindow.AllowDrop;
				rootWindow.AllowDrop = true;
				rootWindow.DragEnter += OnRootWindow_DragEnter;
				rootWindow.DragOver += OnRootWindow_DragOver;
				rootWindow.DragLeave += OnRootWindow_DragLeave;
 
				DragDropEffects effects = DragDrop.DoDragDrop((DependencyObject)sender, dddata, DragDropEffects.Move);
 
 
				//----------------
				// D&D終了の処理
 
				RemoveDragAdorner();
 
				rootWindow.AllowDrop = oldAllowDrop;
				rootWindow.DragEnter -= OnRootWindow_DragEnter;
				rootWindow.DragOver -= OnRootWindow_DragOver;
				rootWindow.DragLeave -= OnRootWindow_DragLeave;
 
				this.dragData = null;
			}
		}
 
		private void OnRootWindow_DragEnter(object sender, DragEventArgs e) {
			Debug.WriteLine("OnRootWindow_DragEnter");
 
			e.Effects = DragDropEffects.None; // マウスカーソルの形状をドロップ不可能に。
			e.Handled = true;
		}
 
		private void OnRootWindow_DragOver(object sender, DragEventArgs e) {
			Debug.WriteLine("OnRootWindow_DragOver");
 
			var rootWindow = sender as FrameworkElement;
 
			ShowDragAdorner(new Point {
				X = e.GetPosition(rootWindow).X,
				Y = e.GetPosition(rootWindow).Y
			});
 
 
			e.Effects = DragDropEffects.None; // マウスカーソルの形状をドロップ不可能に。
			e.Handled = true;                 // イベントを処理したことを通知
		}
 
		private void OnRootWindow_DragLeave(object sender, DragEventArgs e) {
			Debug.WriteLine("OnRootWindow_DragLeave");
 
			e.Handled = true;
		}
 
		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnDragTarget_PreviewDrop(object sender, DragEventArgs e) {
			object draggedItem = e.Data.GetData(DataFormats.StringFormat);
			if (draggedItem != null) {
				// TODO: ドロップしたときの処理を追加する
 
				e.Handled = true; // イベントを処理したことを通知
			}
		}
 
		private void OnDragTarget_PreviewDragOver(object sender, DragEventArgs e) {
			object draggedItem = e.Data.GetData(DataFormats.StringFormat);
			if (draggedItem != null) {
				var panel = sender as UIElement;
				ShowDragAdorner(new Point {
					X = e.GetPosition(rootWindow).X,
					Y = e.GetPosition(rootWindow).Y
				});
 
			}
			e.Handled = true;
		}
 
		private void OnDragTarget_PreviewDragEnter(object sender, DragEventArgs e) {
			object draggedItem = e.Data.GetData(DataFormats.StringFormat);
			if (draggedItem != null) {
			}
			e.Handled = true;
		}
 
		private void OnDragTarget_PreviewDragLeave(object sender, DragEventArgs e) {
			object draggedItem = e.Data.GetData(DataFormats.StringFormat);
			if (draggedItem != null) {
 
			}
			e.Handled = true;
		}
 
		#endregion Handlers
 
		/// <summary>
		/// ドラッグ中のAdornerを表示
		/// </summary>
		/// <param name="currentPosition"></param>
		private void ShowDragAdorner(Point currentPosition) {
			if (dragAdorner == null) {
				AdornerLayer layer = AdornerLayer.GetAdornerLayer(dragSourcePanel);
				this.dragAdorner = new DragAdorner(dragSourcePanel, dragSourceItem);
 
				layer.Add(this.dragAdorner);
			}
 
			dragAdorner.SetPosition(currentPosition.X, currentPosition.Y);
		}
 
		private void RemoveDragAdorner() {
			if (dragAdorner != null) {
				AdornerLayer layer = AdornerLayer.GetAdornerLayer(dragSourcePanel);
				layer.Remove(dragAdorner);
 
				this.dragAdorner = null;
			}
		}
 
 
		DragAdorner dragAdorner;
		Point startPoint;
		UIElement dragSourceItem;
		Panel dragSourcePanel;
		Window rootWindow;
		object dragData;
	}
}

読んでいる本


Effective C#

QLOOKアクセス解析

ここを編集