今回はノードをドラッグ&ドロップすることで移動できるようにします。
移動前に選択されているノードを記憶しておき、移動後にもとに戻るようにしていますが、
でもあったように、ノードを移動することで選択されているノードが意図しない変化をすることがあります。前回はフィールド変数を利用してこの問題を解決しましたが、この変数は外部からも参照されることになります。
1 2 3 4 |
public partial class Form1 : Form { static public bool ignoreSelectedNodeChange = false; } |
そこで静的変数にしました。
ドロップされたときにどこに移動するかですが、
で紹介したkie(Knowledge Index Editor) テキストベースのアウトラインプロセッサは、ノードのラベル部分にドロップされたときはそのノードの一番下の子、アイコン部分にドロップされたときはドロップされたノードのひとつ前の位置に移動するようにつくられています。
なぜ「前」なのか? 後ろのほうが自然な気が・・・
そこでこのサイトで作成するアウトラインプロセッサでは
ノードのラベル部分にドロップされたときはそのノードの一番下の子
アイコン部分にドロップされたときはドロップされたノードのひとつ後ろの位置
に移動するようにします。それからドラッグされているときにカーソルの形も変わるようにしてみましょう。
通常のTreeViewではドラッグされているときは、OnDragOver、ドロップされたときはOnDragDropが呼ばれますが、
で作成したクラスではTreeNodeがドラッグされているときはOnDragOverEx、TreeNodeがドロップされたときはOnDragDropExが呼ばれます。引数はDragEventExArgs型で、これを使えばどのTreeNodeがどのTreeNodeにドロップされたかがわかります。これは
で解説しています。
このクラスを使えばTreeNodeのドラッグ&ドロップの処理は以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class SyncTreeViewEx : SyncTreeView { protected override void OnBeginItemDrag(BeginItemDragArgs e) { Form1.ignoreSelectedNodeChange = true; base.OnBeginItemDrag(e); } protected override void OnEndItemDrag(EventArgs e) { base.OnEndItemDrag(e); Form1.ignoreSelectedNodeChange = false; } } |
まず、ドラッグされているときにマウスオーバーされているノードが選択されるので、これによってリッチテキストの内容が変化しないようにForm1.ignoreSelectedNodeChangeをtrueにしています。Form1.ignoreSelectedNodeChange== trueのあいだは選択ノードが変化してもなにもおきません。
ドロップが開始されるときはOnBeginItemDrag、完了したときはOnEndItemDragが呼ばれるので、ここでフィールド変数の変更をおこなっています。
つぎはドラッグされているときの処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class SyncTreeViewEx : SyncTreeView { bool inOnImage = false; protected override void OnDragOverEx(DragEventExArgs e) { SelectedNode = e.DropNodeEx; if (e.HitTestLocation.HasFlag(TreeViewHitTestLocations.Image)) inOnImage = true; else inOnImage = false; if (Control.ModifierKeys.HasFlag(Keys.Control)) e.Effect = DragDropEffects.Copy; else { if (e.CanMove) e.Effect = DragDropEffects.Move; else e.Effect = DragDropEffects.None; } } } |
まずドラッグされているとき。DragEventExArgs.HitTestLocationを調べればドラッグ元、ドロップ先だけでなく、マウスポインタがラベル部分にあるか、アイコン部分にあるかもわかります。この情報をフィールド変数inOnImageに保存しています。
またCtrlキーがおされているときは移動ではなくコピーをします。
ドロップされるとOnDragDropExが呼ばれます。
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 |
public class SyncTreeViewEx : SyncTreeView { protected override void OnDragDropEx(DragEventExArgs e) { if (Control.ModifierKeys.HasFlag(Keys.Control)) { if (inOnImage) { CopyToNext(e.DragNodeEx, e.DropNodeEx, types.ToArray()); } else CopyToLastChild(e.DragNodeEx, e.DropNodeEx, types.ToArray()); } else { if (e.CanMove) { if (inOnImage) MoveToNext(e.DragNodeEx, e.DropNodeEx); else MoveToLastChild(e.DragNodeEx, e.DropNodeEx); } else e.Effect = DragDropEffects.None; } } } |
Ctrlキーが押されているのであれば移動ではなくコピー、アイコンの上にドロップされたのであればそのノードのひとつ後ろの位置に移動(またはコピー)しています。
以下はドラッグされているときにアイコンを変える処理をしています。
このようなアイコンをつくっています。それを使えるようにSyncTreeViewクラスのコンストラクタのなかで以下のような処理をしています。
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 32 33 34 |
public partial class SyncTreeView : UserControl { public Cursor cursorMoveLast = null; public Cursor cursorMoveFirst = null; public Cursor cursorMoveNext = null; public Cursor cursorMovePrev = null; public Cursor cursorCopyLast = null; public Cursor cursorCopyFirst = null; public Cursor cursorCopyNext = null; public Cursor cursorCopyPrev = null; public SyncTreeView() { try { System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly(); cursorMoveLast = new Cursor(asm.GetManifestResourceStream("SyncTreeViewLib.move-last.cur")); cursorMoveFirst = new Cursor(asm.GetManifestResourceStream("SyncTreeViewLib.move-first.cur")); cursorMoveNext = new Cursor(asm.GetManifestResourceStream("SyncTreeViewLib.move-next.cur")); cursorMovePrev = new Cursor(asm.GetManifestResourceStream("SyncTreeViewLib.move-prev.cur")); cursorCopyLast = new Cursor(asm.GetManifestResourceStream("SyncTreeViewLib.copy-last.cur")); cursorCopyFirst = new Cursor(asm.GetManifestResourceStream("SyncTreeViewLib.copy-first.cur")); cursorCopyNext = new Cursor(asm.GetManifestResourceStream("SyncTreeViewLib.copy-next.cur")); cursorCopyPrev = new Cursor(asm.GetManifestResourceStream("SyncTreeViewLib.copy-prev.cur")); } catch { MessageBox.Show("カーソルの読み込みに失敗しました"); } } } |
そこでSyncTreeViewを継承しているSyncTreeViewExでも使用できます。
ドラッグされているときのマウスカーソルの形状変更の処理はOnGiveFeedback内でおこないます。
GiveFeedbackEventArgs.Effectの状態とフィールド変数inOnImageの状態からドロップされたらどうするのかに対応したマウスカーソルの形状に変更しています。マウスカーソルの形状を変更するときはgfbevent.UseDefaultCursorsをfalseにする必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class SyncTreeViewEx : SyncTreeView { protected override void OnGiveFeedback(GiveFeedbackEventArgs gfbevent) { if (gfbevent.Effect == DragDropEffects.Move) { gfbevent.UseDefaultCursors = false; if (inOnImage) Cursor.Current = this.cursorMoveNext; else Cursor.Current = this.cursorMoveLast; } if (gfbevent.Effect == DragDropEffects.Copy) { gfbevent.UseDefaultCursors = false; if (inOnImage) Cursor.Current = this.cursorCopyNext; else Cursor.Current = this.cursorCopyLast; } } } |