Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save JoshData/9bb131f7b8ebd92d5f8f884354e08304 to your computer and use it in GitHub Desktop.
Save JoshData/9bb131f7b8ebd92d5f8f884354e08304 to your computer and use it in GitHub Desktop.
System.Windows.Forms TreeView Drag-Drop with Dotted Box Around Drop Target
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WinFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// These should be set in the designer, but I've put them here for reference.
this.treeView1.AllowDrop = true;
this.treeView1.ItemDrag += new System.Windows.Forms.ItemDragEventHandler(this.treeView1_ItemDrag);
this.treeView1.DragDrop += new System.Windows.Forms.DragEventHandler(this.treeView1_DragDrop);
this.treeView1.DragOver += new System.Windows.Forms.DragEventHandler(this.treeView1_DragOver);
// These relate to drawing a dotted rectangle around the drop target.
this.treeView1.DrawMode = System.Windows.Forms.TreeViewDrawMode.OwnerDrawText;
this.treeView1.DrawNode += new System.Windows.Forms.DrawTreeNodeEventHandler(this.treeView1_DrawNode);
this.treeView1.DragLeave += new System.EventHandler(this.treeView1_DragLeave);
}
private void Form1_Load(object sender, EventArgs e)
{
// Generate some nodes.
Random rnd = new Random();
void GenerateNodes(TreeNodeCollection parent, int count, float backoff)
{
var count_ = rnd.Next(count * 2);
for (var i = 0; i < count_; i++)
{
var node = parent.Add("Node " + i.ToString());
GenerateNodes(node.Nodes, (int)(count * backoff), backoff);
}
};
GenerateNodes(treeView1.Nodes, 30, .5f);
}
private void treeView1_ItemDrag(object sender, ItemDragEventArgs e)
{
treeView1.DoDragDrop(
e.Item,
DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link);
}
private Tuple<TreeNode,TreeNode> GetDropSourceAndTarget(TreeView treeview, DragEventArgs e)
{
var source = e.Data.GetData(typeof(TreeNode)) as TreeNode;
Point targetPoint = treeView1.PointToClient(new Point(e.X, e.Y));
var target = treeView1.GetNodeAt(targetPoint);
return Tuple.Create(source, target);
}
private void treeView1_DragOver(object sender, DragEventArgs e)
{
var source_target = GetDropSourceAndTarget(treeView1, e);
var source = source_target.Item1;
var target = source_target.Item2;
bool IsSameOrDescendant(TreeNode parent, TreeNode descendant)
{
if (descendant == parent) return true;
if (descendant.Parent == null) return false;
return IsSameOrDescendant(parent, descendant.Parent);
}
if (target == null)
e.Effect = DragDropEffects.None;
else if (IsSameOrDescendant(target, source) || IsSameOrDescendant(source, target))
e.Effect = DragDropEffects.None;
else if ((e.KeyState & 8) != 0) // CTRL
e.Effect = DragDropEffects.Copy;
else if ((e.KeyState & 4) != 0) // SHIFT
e.Effect = DragDropEffects.Link;
else if ((e.KeyState & 32) != 0) // ALT
e.Effect = DragDropEffects.None;
else
e.Effect = DragDropEffects.Move;
// Update the dotted rectangle around the drop target.
if (e.Effect == DragDropEffects.None)
target = null;
UpdateDragDropHoverTargetIndicator(target);
}
private void treeView1_DragDrop(object sender, DragEventArgs e)
{
// Clear the dotted rectangle around the drop target.
UpdateDragDropHoverTargetIndicator(null);
var source_target = GetDropSourceAndTarget(treeView1, e);
var source = source_target.Item1;
var target = source_target.Item2;
MessageBox.Show(e.Effect.ToString() + " " + source.ToString() + " to " + target.ToString());
}
TreeNode dragDropHoverTarget;
private void UpdateDragDropHoverTargetIndicator(TreeNode node)
{
// If the drop hover target has not changed, there is nothing to do.
if (dragDropHoverTarget == node)
return;
// "Invalidate" the part of the screen where the previous drop target
// was so that the rectangle is cleared and where the new drop target
// is so that the new rectangle is drawn (but skip if either is null).
if (dragDropHoverTarget != null)
treeView1.Invalidate(dragDropHoverTarget.Bounds);
if (node != null)
treeView1.Invalidate(node.Bounds);
// Update the target.
dragDropHoverTarget = node;
// Redraw the invalidated regions.
treeView1.Update();
}
private void treeView1_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
// DrawMode must be set to OwnerDrawText.
if (e.Node == dragDropHoverTarget)
{
// Draw a dotted rectangle around the drop target.
// Get the bounds of the node's drawing area.
var bounds = e.Node.Bounds;
// Clear the area for the node.
e.Graphics.FillRectangle(new SolidBrush(treeView1.BackColor), bounds);
// Draw the Node's text.
// (On my computer, using 'bounds.Y - 1' was needed to prevent the text
// from jumping, but on another computer it caused jumping. It may be
// difficult to match text rendering exactly without rendering all
// nodes ourselves.)
// See https://stackoverflow.com/questions/63121949/positioning-and-highlighting-of-treeview-node-text-with-ownerdrawtext-mode.
TextFormatFlags textFlags = TextFormatFlags.Left | TextFormatFlags.NoClipping
| TextFormatFlags.NoPrefix | TextFormatFlags.SingleLine;
TextRenderer.DrawText(e.Graphics, e.Node.Text,
e.Node.NodeFont ?? treeView1.Font,
bounds,
treeView1.ForeColor, textFlags);
// Draw the dotted rectangle.
using (Pen pen = new Pen(new SolidBrush(treeView1.ForeColor)))
{
// The right and bottom edges of the rectangle are drawn one pixel
// outside of the actual bounds of the node unless we bring it in.
bounds.Inflate(-1, -1);
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
e.Graphics.DrawRectangle(pen, bounds);
}
return;
}
// Let the platform draw other nodes.
e.DrawDefault = true;
}
private void treeView1_DragLeave(object sender, EventArgs e)
{
// Clear the dotted rectangle around the drop target.
// This event is fired if the drag is cancelled via pressing ESC,
// in addition to drag leaving this control.
UpdateDragDropHoverTargetIndicator(null);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment