Search code examples
c#custom-controlsflowlayoutpanel

Reorder customcontrol inside multiple flowLayoutPanel


I am trying to make an app to generate a tierlist. Each tier consists of a flowlayout and you basically move the controls around by drag-and-drop. enter image description here The problem is that even though I can move the elements with drag-and-drop, the element is always added to the end of the list, and not where you drop the pointer. Is there any way to maintain order during drag-and-drop?

These are the methods that are in the flowlayout

private void flow_DragEnter(object sender, DragEventArgs e)
{
    e.Effect = DragDropEffects.Move;
}
private void flow_DragDrop(object sender, DragEventArgs e)
{
    ((UserControl1)e.Data.GetData(typeof(UserControl1))).Parent = (Panel)sender;
}

While these are from the usercontrol:

private void UserControl1_MouseDown(object sender, MouseEventArgs e)
{
    this.DoDragDrop(this, DragDropEffects.Move);
}

Solution

  • You can opt this solution to reorder the dropped controls into a FlowLayoutPanel control. The part that utilizes the Control.ControlCollection.GetChildIndex and Control.ControlCollection.SetChildIndex methods.

    Let's say you have a custom Control or UserControl named DragDropControl:

    public class DragDropControl : Control
    {
        public DragDropControl()
        {
            AllowDrop = true;
            BackColor = Color.LightSteelBlue;
        }
    
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
    
            TextRenderer.DrawText(e.Graphics, Text, Font, 
                ClientRectangle, Color.Black, 
                TextFormatFlags.HorizontalCenter |
                TextFormatFlags.VerticalCenter);
        }       
    }
    

    Note: From what I see in the images, just use a simple Label control instead.

    Let's create a custom FlowLayoutPanel and encapsulate all the required functionalities to not repeat that in your implementation for each FLP.

    public class DragDropFlowLayoutPanel : FlowLayoutPanel
    {
        public DragDropFlowLayoutPanel()
        {
            AllowDrop = true;            
        }
    
        [DefaultValue(true)]
        public override bool AllowDrop 
        { 
            get => base.AllowDrop; 
            set => base.AllowDrop = value; 
        }
    

    The custom FLP implements the mouse, drag and drop events of its children as well.

        protected override void OnControlAdded(ControlEventArgs e)
        {
            base.OnControlAdded(e);
    
            if (e.Control is DragDropControl)
            {
                e.Control.DragOver += OnControlDragOver;
                e.Control.DragDrop += OnControlDragDrop;
                e.Control.MouseDown += OnControlMouseDown;
            }
        }
    
        protected override void OnControlRemoved(ControlEventArgs e)
        {
            base.OnControlRemoved(e);
    
            e.Control.DragOver -= OnControlDragOver;
            e.Control.DragDrop -= OnControlDragDrop;
            e.Control.MouseDown -= OnControlMouseDown;
        }
    

    Handle the child controls MouseDown event to do the drag:

        private void OnControlMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                var control = sender as DragDropControl;
                DoDragDrop(control, DragDropEffects.Move);
            }
        }
    

    Handle the DragEnter and DragOver methods and events of the FLP and its children to set the drop effect.

        protected override void OnDragEnter(DragEventArgs e)
        {
            base.OnDragEnter(e);
    
            if (e.Data.GetDataPresent(typeof(DragDropControl)))
                e.Effect = DragDropEffects.Move;
        }
    
        protected override void OnDragOver(DragEventArgs e)
        {
            base.OnDragOver(e);
    
            if (e.Data.GetDataPresent(typeof(DragDropControl)))
                e.Effect = DragDropEffects.Move;
        }
    
        private void OnControlDragOver(object sender, DragEventArgs e)
        {
            if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
            {
                var p = PointToClient(new Point(e.X, e.Y));
    
                if (GetChildAtPoint(p) == ddc)
                    e.Effect = DragDropEffects.None;
                else
                    e.Effect = DragDropEffects.Move;
            }
        }
    

    Finally, call from the DragDrop method override and event the DropControl method and pass the DragEventArgs param.

        protected override void OnDragDrop(DragEventArgs e)
        {
            base.OnDragDrop(e);
            DropControl(e);
        }
    
        private void OnControlDragDrop(object sender, DragEventArgs e)
        {
            DropControl(e);
        }
    

    The dropped control takes the index of the control under the mouse position if any otherwise it will be inserted at the end of the Controls collection.

        private void DropControl(DragEventArgs e)
        {
            if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
            {
                var p = PointToClient(new Point(e.X, e.Y));
                var child = GetChildAtPoint(p);
                var index = child == null
                    ? Controls.Count
                    : Controls.GetChildIndex(child);
    
                ddc.Parent = this;
                Controls.SetChildIndex(ddc, index);
            }
        }
    }
    

    Put it all together.

    public class DragDropFlowLayoutPanel : FlowLayoutPanel
    {
        public DragDropFlowLayoutPanel()
        {
            AllowDrop = true;            
        }
    
        [DefaultValue(true)]
        public override bool AllowDrop 
        {
            get => base.AllowDrop; 
            set => base.AllowDrop = value; 
        }
    
        protected override void OnControlAdded(ControlEventArgs e)
        {
            base.OnControlAdded(e);
    
            if (e.Control is DragDropControl)
            {
                e.Control.DragOver += OnControlDragOver;
                e.Control.DragDrop += OnControlDragDrop;
                e.Control.MouseDown += OnControlMouseDown;
            }
        }
    
        protected override void OnControlRemoved(ControlEventArgs e)
        {
            base.OnControlRemoved(e);
    
            e.Control.DragOver -= OnControlDragOver;
            e.Control.DragDrop -= OnControlDragDrop;
            e.Control.MouseDown -= OnControlMouseDown;
        }
    
        protected override void OnDragEnter(DragEventArgs e)
        {
            base.OnDragEnter(e);
    
            if (e.Data.GetDataPresent(typeof(DragDropControl)))
                e.Effect = DragDropEffects.Move;
        }
    
        protected override void OnDragOver(DragEventArgs e)
        {
            base.OnDragOver(e);
    
            if (e.Data.GetDataPresent(typeof(DragDropControl)))
                e.Effect = DragDropEffects.Move;
        }
    
        protected override void OnDragDrop(DragEventArgs e)
        {
            base.OnDragDrop(e);
            DropControl(e);
        }
    
        private void OnControlDragOver(object sender, DragEventArgs e)
        {
            if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
            {
                var p = PointToClient(new Point(e.X, e.Y));
    
                if (GetChildAtPoint(p) == ddc)
                    e.Effect = DragDropEffects.None;
                else
                    e.Effect = DragDropEffects.Move;
            }
        }
    
        private void OnControlDragDrop(object sender, DragEventArgs e)
        {
            DropControl(e);
        }
    
        private void OnControlMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                var control = sender as DragDropControl;
                DoDragDrop(control, DragDropEffects.Move);
            }
        }
    
        private void DropControl(DragEventArgs e)
        {
            if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
            {
                var p = PointToClient(new Point(e.X, e.Y));
                var child = GetChildAtPoint(p);
                var index = child == null
                    ? Controls.Count
                    : Controls.GetChildIndex(child);
    
                ddc.Parent = this;
                Controls.SetChildIndex(ddc, index);
            }
        }
    }
    

    SO75471363