Skip to content

Instantly share code, notes, and snippets.

@jnm2
Created January 26, 2016 19:48
Show Gist options
  • Select an option

  • Save jnm2/c6e668d844f403546c9d to your computer and use it in GitHub Desktop.

Select an option

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.
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();
}
}
@biohazard999
Copy link

Thank you!

@jnm2
Copy link
Author

jnm2 commented May 20, 2019

@biohazard999 Glad it helped!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment