Created
January 26, 2016 19:48
-
-
Save jnm2/c6e668d844f403546c9d to your computer and use it in GitHub Desktop.
Gives the TokenEdit control the ability to be reordered via drag and drop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections; | |
| using System.ComponentModel; | |
| using System.Drawing; | |
| using System.Drawing.Drawing2D; | |
| using System.Linq; | |
| using System.Reflection; | |
| using System.Windows.Forms; | |
| using DevExpress.XtraEditors; | |
| using DevExpress.XtraEditors.ViewInfo; | |
| public sealed class TokenEditDragHelper : IDisposable | |
| { | |
| private readonly TokenEdit edit; | |
| private readonly bool previousAllowDrop; | |
| private TokenEditToken mouseDownToken; | |
| private TokenEditTokenInfo insertBefore; | |
| private bool shouldDrawDropIndicator; | |
| public TokenEditDragHelper(TokenEdit edit) | |
| { | |
| this.edit = edit; | |
| previousAllowDrop = edit.AllowDrop; | |
| edit.AllowDrop = true; | |
| edit.DragEnter += Edit_DragEnter; | |
| edit.DragLeave += Edit_DragLeave; | |
| edit.DragDrop += Edit_DragDrop; | |
| edit.DragOver += Edit_DragOver; | |
| edit.MouseDown += Edit_MouseDown; | |
| edit.MouseMove += Edit_MouseMove; | |
| edit.Paint += Edit_Paint; | |
| } | |
| public void Dispose() | |
| { | |
| edit.DragEnter -= Edit_DragEnter; | |
| edit.DragLeave -= Edit_DragLeave; | |
| edit.DragDrop -= Edit_DragDrop; | |
| edit.DragOver -= Edit_DragOver; | |
| edit.MouseDown -= Edit_MouseDown; | |
| edit.MouseMove -= Edit_MouseMove; | |
| edit.Paint -= Edit_Paint; | |
| edit.AllowDrop = previousAllowDrop; | |
| } | |
| private void Edit_Paint(object sender, PaintEventArgs e) | |
| { | |
| if (!shouldDrawDropIndicator) return; | |
| using (var p = new Pen(Color.Black, 2)) | |
| { | |
| p.CustomEndCap = p.CustomStartCap = GetInsertionLineCap(); | |
| if (insertBefore != null) | |
| e.Graphics.DrawLine(p, insertBefore.Bounds.Left + .5f, insertBefore.Bounds.Top, insertBefore.Bounds.Left + .5f, insertBefore.Bounds.Bottom - .5f); | |
| else | |
| { | |
| var insertAfter = edit.GetViewInfo().GetTokenInfo(edit.SelectedItems.LastToken); | |
| e.Graphics.DrawLine(p, insertAfter.Bounds.Right - .5f, insertAfter.Bounds.Top, insertAfter.Bounds.Right - .5f, insertAfter.Bounds.Bottom - .5f); | |
| } | |
| } | |
| } | |
| private static CustomLineCap insertionLineCap; | |
| private static CustomLineCap GetInsertionLineCap() | |
| { | |
| if (insertionLineCap == null) | |
| using (var customCapPath = new GraphicsPath()) | |
| { | |
| customCapPath.AddPolygon(new[] | |
| { | |
| new PointF(0, 0), | |
| new PointF(-.5f, 0), | |
| new PointF(0, -.5f), | |
| new PointF(.5f, 0) | |
| }); | |
| insertionLineCap = new CustomLineCap(null, customCapPath); | |
| } | |
| return insertionLineCap; | |
| } | |
| private void Edit_MouseDown(object sender, MouseEventArgs e) | |
| { | |
| mouseDownToken = edit.CalcHitInfo(e.Location).Token; | |
| } | |
| private void Edit_MouseMove(object sender, MouseEventArgs e) | |
| { | |
| if (e.Button == MouseButtons.Left && mouseDownToken != null) | |
| edit.DoDragDrop(mouseDownToken, DragDropEffects.Move); | |
| } | |
| private TokenEditToken TryGetValidToken(IDataObject dataObject) | |
| { | |
| var token = (TokenEditToken)dataObject.GetData(typeof(TokenEditToken)); | |
| return token != null && edit.Properties.Tokens.Contains(token) ? token : null; | |
| } | |
| private void Edit_DragEnter(object sender, DragEventArgs e) | |
| { | |
| e.Effect = TryGetValidToken(e.Data) != null ? DragDropEffects.Move : DragDropEffects.None; | |
| shouldDrawDropIndicator = true; | |
| } | |
| private void Edit_DragOver(object sender, DragEventArgs e) | |
| { | |
| insertBefore = GetTokenInsertBefore(edit, edit.PointToClient(new Point(e.X, e.Y))); | |
| edit.Invalidate(); | |
| } | |
| private void Edit_DragLeave(object sender, EventArgs e) | |
| { | |
| shouldDrawDropIndicator = false; | |
| edit.Invalidate(); | |
| } | |
| private void Edit_DragDrop(object sender, DragEventArgs e) | |
| { | |
| shouldDrawDropIndicator = false; | |
| edit.Invalidate(); | |
| var validToken = TryGetValidToken(e.Data); | |
| if (validToken == null) return; | |
| var tokenInsertBefore = GetTokenInsertBefore(edit, edit.PointToClient(new Point(e.X, e.Y))); | |
| var removeIndex = edit.SelectedItems.IndexOf(validToken); | |
| var insertIndex = tokenInsertBefore == null ? edit.SelectedItems.Count : edit.SelectedItems.IndexOf(tokenInsertBefore.Token); | |
| if (insertIndex > removeIndex) insertIndex--; | |
| if (insertIndex == removeIndex) return; | |
| // There is no other way to do a generic insert that works for any binding mode | |
| var innerList = (IList)typeof(ReadOnlyCollectionBase).GetProperty("InnerList", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(edit.SelectedItems); | |
| innerList.RemoveAt(removeIndex); | |
| innerList.Insert(insertIndex, validToken); | |
| typeof(TokenEditTokenReadOnlyCollectionBase).GetMethod("RaiseListChanged", BindingFlags.Instance | BindingFlags.NonPublic) | |
| .Invoke(edit.SelectedItems, new object[] { new ListChangedEventArgs(ListChangedType.Reset, -1) }); | |
| } | |
| private static TokenEditTokenInfo GetTokenInsertBefore(TokenEdit edit, Point location) | |
| { | |
| // We can't rely on CalcHitInfo because it does not give nearest row/nearest token information for points between tokens. | |
| var rows = edit.GetViewInfo().VisibleItems.Cast<TokenEditTokenInfo>().GroupBy(_ => _.RowIndex).OrderBy(_ => _.Key).ToList(); | |
| int rowIndex; | |
| for (rowIndex = 0; rowIndex < rows.Count - 1; rowIndex++) | |
| { | |
| var rowBottom = rows[rowIndex].First().Bounds.Bottom - 1; | |
| if (location.Y <= rowBottom) break; | |
| var nextRowTop = rows[rowIndex + 1].First().Bounds.Top; | |
| if (location.Y < nextRowTop) | |
| { | |
| if (nextRowTop - location.Y < location.Y - rowBottom) rowIndex++; | |
| break; | |
| } | |
| } | |
| var tokensInRow = rows[rowIndex].OrderBy(_ => _.Bounds.Left).ToList(); | |
| int tokenIndex; | |
| for (tokenIndex = 0; tokenIndex < tokensInRow.Count; tokenIndex++) | |
| if (location.X <= tokensInRow[tokenIndex].Bounds.Left + (tokensInRow[tokenIndex].Bounds.Width + 1) / 2) | |
| return tokensInRow[tokenIndex]; | |
| rowIndex++; | |
| return rowIndex == rows.Count ? null : rows[rowIndex].OrderBy(_ => _.Bounds.Left).First(); | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@biohazard999 Glad it helped!