Last active
June 17, 2022 14:30
-
-
Save JoshData/9bb131f7b8ebd92d5f8f884354e08304 to your computer and use it in GitHub Desktop.
System.Windows.Forms TreeView Drag-Drop with Dotted Box Around Drop Target
This file contains 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.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