前回は同一コントロール内部以外からデータがドロップされたときの処理をやりました。
今回は同一コントロール内部のドラッグ&ドロップです。
1 2 3 4 5 6 7 |
public partial class SyncRichTextBox : UserControl { void OnDragDropInternal(DragEventArgs e, DragDropRtfInfo _dragDropRtfInfo) { // どうする? } } |
まずドロップされた場所を知る必要があります。これは単純にSelectionStartプロパティを参照すれば取得できます。
ドラッグされているのがn文字目からm文字目の場合、この場所に移動することはできません。本当に移動させることができるのか調べます。
1 2 3 4 5 6 7 |
bool CanMove(int moveTo, int dragStart, int dragLength) { if (moveTo < dragStart || dragStart + dragLength < moveTo) return true; else return false; } |
移動させることができるのであれば移動させます。ドラッグされている文字を消去し、新しい位置に挿入します。新しい位置は、ドロップされた位置がドラッグが開始された位置よりも前の場合とそれ以外では計算方法が変わってきます。
1 2 3 4 5 |
int move = 0; if (drop < dragStart) move = drop - dragStart; else move = drop - dragStart - dragLen; |
新しい位置というよりは今の位置からどれだけズラすかという考え方です。
Undobufに新しい変数とプロパティを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Undobuf { internal int moveDistance = 0; public int MoveDistance { get { if (IsUndo) return -moveDistance; return moveDistance; } } } |
移動させるためのメソッドです。
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
public partial class SyncRichTextBox : UserControl { void OnDragDropInternal(DragEventArgs e, DragDropRtfInfo dragDropRtfInfo) { int drop = SelectionStart; int dragStart = dragDropRtfInfo.SelectionStart; int dragLen = dragDropRtfInfo.SelectionLength; if (CanMove(drop, dragStart, dragLen)) { int move = 0; if (drop < dragStart) move = drop - dragStart; else move = drop - dragStart - dragLen; Undobuf buf = new Undobuf(); buf.moveDistance = move; buf.removeStart = dragStart; buf.removeLength = dragLen; buf.removeRtf = dragDropRtfInfo.Rtf; buf.removeText = dragDropRtfInfo.Text; buf.insertStart = drop < dragStart ? drop: drop- dragLen; buf.insertLength = dragLen; buf.insertRtf = dragDropRtfInfo.Rtf; buf.insertText = dragDropRtfInfo.Text; buf.oldSelectionStart = dragStart; buf.oldSelectionLength = dragLen; buf.newSelectionStart = drop < dragStart ? drop : drop - dragLen; buf.newSelectionLength = dragLen; buf.action = "MoveText"; if(MoveText(buf)) Data.InsertUndobuf(buf); } } bool MoveText(Undobuf buf) { foreach (var rich in Data.SyncRichTextBoxes) { rich.Data.GetRichTextBoxInfo(rich).SelectionStart = rich.SelectionStart; } OnTextChanging(buf); if (buf.IsCancel) return false; foreach (var rich in Data.SyncRichTextBoxes) { var info = rich.Data.GetRichTextBoxInfo(rich); if (rich.Data == Data && rich == this) { rich.MoveText(buf.RemoveStart, buf.RemoveLength, buf.RemoveRtf, buf.MoveDistance); rich.SelectionStart = buf.NewSelectionStart; rich.SelectionLength = buf.NewSelectionLength; } if (rich.Data == Data && rich != this) { rich.MoveText(buf.RemoveStart, buf.RemoveLength, buf.RemoveRtf, buf.MoveDistance); int start = info.SelectionStart; start = GetPositionAfterRemove(start, buf.RemoveStart, buf.RemoveLength); start = GetPositionAfterInsert(start, buf.InsertStart, buf.InsertLength); rich.SelectionStart = start; rich.SelectionLength = 0; } if (rich.Data != Data) { int start = info.SelectionStart; start = GetPositionAfterRemove(start, buf.RemoveStart, buf.RemoveLength); start = GetPositionAfterInsert(start, buf.InsertStart, buf.InsertLength); rich.Data.GetRichTextBoxInfo(rich).SelectionStart = start; } } return true; } void MoveText(int moveStart, int moveLength, string moveRtf, int moveDistance) { richTextBoxEx1.SelectionStart = moveStart; richTextBoxEx1.SelectionLength = moveLength; richTextBoxEx1.SelectedText = ""; richTextBoxEx1.SelectionStart = moveStart + moveDistance; richTextBoxEx1.SelectedRtf = moveRtf; } bool CanMove(int moveTo, int dragStart, int dragLength) { if (moveTo < dragStart || dragStart + dragLength < moveTo) return true; else return false; } } |
またUndoに対応させる必要があります。
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 35 36 37 38 39 40 41 |
public partial class SyncRichTextBox : UserControl { public void Undo() { var buf = Data.GetUndobuf(); if (buf == null) return; buf.IsUndo = true; if (buf.tabInsertIndexesUndo.Count > 0) UndoInsertTab(buf); else if (buf.tabRemoveIndexesUndo.Count > 0) UndoRemoveTab(buf); else if (buf.MoveDistance != 0) MoveText(buf); else DoUndo(buf); buf.IsUndo = false; Data.MoveToRedobufs(); } public void Redo() { var buf = Data.GetRedobuf(); if (buf == null) return; if (buf.tabInsertIndexes.Count > 0) DoInsertTab(buf); else if (buf.tabRemoveIndexes.Count > 0) DoRemoveTab(buf); else if (buf.MoveDistance != 0) MoveText(buf); else DoUndo(buf); Data.MoveToUndobufs(); } } |
これで完成といいたいのですが、他のアプリのRichTextBoxにドラッグ&ドロップすると文字列が消えてしまうことがあります。ドラッグ&ドロップされた場合、ドラッグ元のデータをどうするかはドロップ先によって決まるので、もし他のアプリのRichTextBoxにドロップされて文字列が消えてしまった場合、元に戻す必要があります。
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
public partial class SyncRichTextBox : UserControl { public SyncRichTextBox() { // その他は省略 richTextBoxEx1.TextChanged += RichTextBoxEx1_TextChanged; } protected override void OnDragDrop(DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) // 省略 else if (_dragDropRtfInfo == null || _dragDropRtfInfo.DragSourceCtrl.Data != this.Data) // 省略 else { // 早めに _dragDropRtfInfo = nullにしてしまう。 var info = _dragDropRtfInfo; _dragDropRtfInfo = null; if (e.Effect == DragDropEffects.Move) OnDragDropInternal(e, info); else if (e.Effect == DragDropEffects.Copy) { string rtf = (string)e.Data.GetData(DataFormats.Rtf); SelectionLength = 0; SetSelectedRtf(rtf, "DropText"); } } e.Effect = DragDropEffects.None; } private void RichTextBoxEx1_TextChanged(object sender, EventArgs e) { if (_dragDropRtfInfo != null) { // 元に戻す var info = _dragDropRtfInfo; _dragDropRtfInfo = null; SyncRichTextBox rich = info.DragSourceCtrl; rich.richTextBoxEx1.Select(info.SelectionStart, 0); rich.richTextBoxEx1.SelectedRtf = info.Rtf; } } } |
あと、_dragDropRtfInfoがnullに戻されていないとSetSelectedTextメソッドとSetSelectedRtfメソッドを実行したときにおかしなことがおこります。そこで
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class SyncRichTextBox : UserControl { public bool SetSelectedText(string str, string action) { _dragDropRtfInfo = null; // ここから先はこれまでどおり } public bool SetSelectedRtf(string str, string action) { _dragDropRtfInfo = null; // ここから先はこれまでどおり } } |
ついでにOnDragOverにも改良を加えます。コピーされる場合と移動の場合がはっきりとわかるようにしました。Ctrlキーが押されながらドラッグされているときは内部で文字列を移動させるのではなくコピーします。
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 partial class SyncRichTextBox : UserControl { protected override void OnDragOver(DragEventArgs e) { if (_dragDropRtfInfo == null || _dragDropRtfInfo.DragSourceCtrl.Data != this.Data) e.Effect = DragDropEffects.Copy; else { Point dropPoint = richTextBoxEx1.PointToClient(new Point(e.X, e.Y)); int drop = richTextBoxEx1.GetCharIndexFromPosition(dropPoint); Point lastPoint = richTextBoxEx1.GetPositionFromCharIndex(richTextBoxEx1.Text.Length); if(lastPoint.X < dropPoint.X && lastPoint.Y < dropPoint.Y) drop = richTextBoxEx1.Text.Length; if (CanMove(drop, _dragDropRtfInfo.SelectionStart, _dragDropRtfInfo.SelectionLength)) { if (Control.ModifierKeys.HasFlag(Keys.Control)) e.Effect = DragDropEffects.Copy; else e.Effect = DragDropEffects.Move; } else e.Effect = DragDropEffects.None; } } } |
これに伴いOnDragDropも一部変更します。
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 partial class SyncRichTextBox : UserControl { protected override void OnDragDrop(DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) // 省略 else if (_dragDropRtfInfo == null || _dragDropRtfInfo.DragSourceCtrl.Data != this.Data) // 省略 else { var info = _dragDropRtfInfo; _dragDropRtfInfo = null; if (e.Effect == DragDropEffects.Move) OnDragDropInternal(e, _dragDropRtfInfo); else if (e.Effect == DragDropEffects.Copy) { string rtf = (string)e.Data.GetData(DataFormats.Rtf); SelectionLength = 0; SelectedRtf = rtf; Data.GetUndobuf().action = "DropText"; } } e.Effect = DragDropEffects.None; } } |