WPF drag and drop on a button

10,505

I've found a workaround for this - since the first DoDragDrop calls the second PreviewMouseMove internally, I can track whether I'm already inside a PreviewMouseMove call and ignore it if I am. This seems a bit nasty though so I'm hoping there's a better solution.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void PreviewMouseMove(object sender, MouseEventArgs e)
    {
        if(!m_inMouseMove)
        {
            m_inMouseMove = true;
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                ++m_dragIndex;
                System.Console.WriteLine("Dragged: " + m_dragIndex.ToString());
                DragDrop.DoDragDrop(m_button, m_dragIndex, DragDropEffects.All);
                e.Handled = true;
            }
            m_inMouseMove = false;
        }
    }

    private void Drop(object sender, DragEventArgs e)
    {
        System.Console.WriteLine("Dropped: " + e.Data.GetData(typeof(Int32)).ToString());
    }

    private int m_dragIndex;
    bool m_inMouseMove;
}
Share:
10,505
Aozine
Author by

Aozine

Updated on June 04, 2022

Comments

  • Aozine
    Aozine almost 2 years

    I'm writing a WPF application that has grids of buttons and I want to allow the user to drag and drop buttons between grids, possibly between different instances of the application. I've tried doing this by adding a handler to the PreviewMouseMove event on the button and then calling DoDragDrop if the left mouse button is down, but when I drag and drop the button it always ends up calling DoDragDrop twice and the drop event handler twice. Does anyone know why this happens and how to prevent it?

    Here's some example XAML which demonstrates the problem:

    <Window x:Class="WpfTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <DockPanel>
            <Button PreviewMouseMove="PreviewMouseMove" x:Name="m_button" Width="250">
                Hello, world!
            </Button>
            <Label Drop="Drop" AllowDrop="True">
                Drop here!
            </Label>
        </DockPanel>
    </Window>
    

    and the corresponding code:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private void PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                ++m_dragIndex;
                System.Console.WriteLine("Dragged: " + m_dragIndex.ToString());
                DragDrop.DoDragDrop(m_button, m_dragIndex, DragDropEffects.All);
                e.Handled = true;
            }
        }
    
        private void Drop(object sender, DragEventArgs e)
        {
            System.Console.WriteLine("Dropped: " + e.Data.GetData(typeof(Int32)).ToString());
        }
    
        private int m_dragIndex;
    }
    

    For a single drag, this gets written to the output:

    Dragged: 1
    Dragged: 2
    Dropped: 2
    Dropped: 1
    

    UPDATE: I've changed the example code above to show which drop events get called when the button is dropped onto something.

    UPDATE: Updated the question to include dragging between containers and application instances, since this is the motivating factor for using the DragDrop system.

    • makim
      makim about 11 years
      use the PreviewMouseDown-Event to start dragging, not the PreviewMouseMove-Event!
    • makim
      makim about 11 years
      but I don´t think thats the right way to this, check out this answer stackoverflow.com/questions/12591637/…
    • SvenG
      SvenG about 11 years
      I wouldn't worry about it, the second DoDragDrop call stops the first one. And when you implement the Drop method it is triggered one time only. You won't notice the second DoDragDrop call on the UI.
    • Aozine
      Aozine about 11 years
      SvenG: I was hoping that, but it seems to generate two dropped events when the button is dropped onto something. I've updated the example code to show this.
    • Aozine
      Aozine about 11 years
      @sine: Changing it to PreviewMouseDown does indeed stop the double-call, but it means a drag is initiated when the user clicks the button. Ideally I'd only want it to be initiated when the user holds the button and moves the mouse a bit. Thanks for the link - I'll check it out.
    • makim
      makim about 11 years
      have you tried using a canvas, like described in the link i posted above?
    • Aozine
      Aozine about 11 years
      @sine: I've tried the DragCanvas and it's definitely a better way of moving items within a container - I could probably make a few modifications to get it to keep things grid-aligned. The only issue is allowing dragging between containers or between different instances of the application, which I think I'd still need to use the DragDrop system for? (admittedly this wasn't mentioned in the original question, so I'll update it to clarify)