Search code examples
c#.netwinformspanelflowlayoutpanel

Drag and drop panels within a FlowLayoutPanel


I am creating a windows form with several panels (10) in a flow layout panel, and I want these panels to be able to drag and drop to preferably another flow layout panel on top of it. I don't want the panels to overlap. I want to be able to track their position in the flow layout panel and use the label created to help sort them out. I can't seem to get the dragging and dropping to work though.

This is my code:

public partial class Form1 : Form
{
    private List<Book> books = new List<Book>();
    private List<Book> sortedBooks;
    private Panel currentPanel;

    public Form1()
    {
        InitializeComponent();
        InitializePanels();
    }

    private void InitializePanels()
    {
        flowLayoutSender.Controls.Clear();
        flowLayoutReceiver.Controls.Clear();

        books = GenerateRandomBooks(10);
        sortedBooks = new List<Book>(books);
        sortedBooks.Sort();

        for (int i = 0; i < books.Count; i++)
        {
            Book book = books[i];

            // Create a panel to hold the book information
            Panel panel = new Panel
            {
                Name = "panel" + i,
                Width = 50,
                Height = 100,
                BorderStyle = BorderStyle.FixedSingle
            };

            Label label = new Label
            {
                Name = "label" + i,
                Text = book.BookNumber,
                AutoSize = false,
                Width = 50,
                Height = 100,
                TextAlign = ContentAlignment.MiddleCenter,
                Padding = new Padding(0, 30, 0, 0)
            };
            panel.Controls.Add(label);
            panel.MouseDown += Panel_MouseDown;
            panel.DragDrop += flowLayoutSender_DragDrop; // Add the DragDrop event handler
            flowLayoutSender.Controls.Add(panel);
        }

        // Add the DragEnter and DragDrop event handlers for flowLayoutSender
        flowLayoutSender.DragEnter += flowLayoutSender_DragEnter;
    }

    private void Panel_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            currentPanel = (Panel)sender;
            currentPanel.DoDragDrop(currentPanel, DragDropEffects.Move);
        }
    }

    private void flowLayoutSender_DragEnter(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(typeof(Panel)))
        {
            e.Effect = DragDropEffects.Move;
        }
    }

    private void flowLayoutSender_DragDrop(object sender, DragEventArgs e)
    {
        if (currentPanel != null)
        {
            flowLayoutSender.Controls.Remove(currentPanel);
            flowLayoutReceiver.Controls.Add(currentPanel);
            currentPanel = null;
        }
    }

    private List<Book> GenerateRandomBooks(int count)
    {
        List<Book> randomBooks = new List<Book>();
        Random random = new Random();

        for (int i = 0; i < count; i++)
        {
            int deweyCategory = random.Next(1, 10) * 100;
            int deweyNumber = random.Next(1, 1000);

            string bookNumber = $"{deweyCategory}-{deweyNumber:D3}";
            Book book = new Book(bookNumber);
            randomBooks.Add(book);
        }

        return randomBooks;
    }

    private void flowLayoutReceiver_DragEnter(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(typeof(Panel)))
        {
            e.Effect = DragDropEffects.Move;
        }
    }

    private void flowLayoutReceiver_DragDrop(object sender, DragEventArgs e)
    {
        if (currentPanel != null)
        {
            flowLayoutSender.Controls.Remove(currentPanel);
            flowLayoutReceiver.Controls.Add(currentPanel);
            currentPanel = null;
        }
    }
}

But I just cant seem to get the panels to drag and drop, I click and drag panel and nothing happens. Can I get any help pls


Solution

  • The main problem here is that the Labels you're adding to the Panels completely cover their host, so you don't have a MouseDown event on the Panels.
    I've resized the Label and docked them to Top, so the Panels can receive mouse events. Modify as required.

    The Panel that's being dragged is the object reference in ObjectData. You don't need to store this reference.
    For sure, you cannot set it to null, because you set to null the Control's reference itself.
    You also don't need to remove the Control from a Controls collection before you add it to another. A Control can only have one Parent. When you add it to another Controls collection, the original one loses the reference.

    You only need to handle the Panel's MouseDown event.
    Note: I'm using MouseDown to start the operation, but we should actually handle MouseMove + SystemInformation.DragSize as shown here.

    The FlowLayoutPanel that hosts the Panels doesn't need to do much here (unless it should also allow to receive back a Panel that was moved).
    The FlowLayoutPanel that receives the Panels needs to have its AllowDrop Property set to true.
    It also needs to handle, at least, the DragEnter and DragDrop events.

    public Form1() {
        InitializeComponent();
    
        flowLayoutSender.SuspendLayout();
        for (int i = 0; i < 5; i++) {
            // Create a panel to hold the book information
            // [...] 
            Label label = new Label {
                Dock = DockStyle.Top,
                Size = new Size(50, 30),
                Text = book.BookNumber,
                TextAlign = ContentAlignment.MiddleCenter,
            };
            panel.Controls.Add(label);
            panel.MouseDown += Panel_MouseDown;
            flowLayoutSender.Controls.Add(panel);
        }
        flowLayoutSender.ResumeLayout(false);
    }
    
    private void Panel_MouseDown(object sender, MouseEventArgs e) {
        if (e.Button == MouseButtons.Left) {
            var panel = sender as Panel;
            if (panel is null) return;
            panel.DoDragDrop(panel, DragDropEffects.Move);
        }
    }
    
    private void flowLayoutReceiver_DragEnter(object sender, DragEventArgs e) {
        if (e.Data != null && e.Data.GetDataPresent(typeof(Panel))) {
            e.Effect = DragDropEffects.Move;
        }
        else {
            e.Effect = DragDropEffects.None;
        }
    }
    
    private void flowLayoutReceiver_DragDrop(object sender, DragEventArgs e) {
        if (e.Data != null && e.Data.GetDataPresent(typeof(Panel))) {
            var panel = e.Data.GetData(typeof(Panel)) as Panel;
            (sender as Control)?.Controls.Add(panel);
        }
    }
    

    This is how it works:

    FlowLayoutPanel DragDrop