views:

497

answers:

1

I'm implementing drag & drop from a ListBox, but I'm seeing some strange behaviour with a ContextMenu elsewhere in the window. If you open the context menu and then start a drag from the ListBox, the context menu closes but won't open again until after you perform another drag.

Does this make sense? Anybody got any ideas what might be going on?

<ListBox Grid.Row="0" ItemsSource="{Binding SourceItems}" MultiSelectListboxDragDrop:ListBoxExtension.SelectedItemsSource="{Binding SelectedItems}" SelectionMode="Multiple" PreviewMouseLeftButtonDown="HandleLeftButtonDown" PreviewMouseLeftButtonUp="HandleLeftButtonUp" PreviewMouseMove="HandleMouseMove"/>
<ListBox Grid.Row="1" ItemsSource="{Binding DestinationItems}" AllowDrop="True" Drop="DropOnToDestination" />
<Button Grid.Row="2">
    <Button.ContextMenu>
     <ContextMenu x:Name="theContextMenu">
      <MenuItem Header="context 1"/>
      <MenuItem Header="context 2"/>
      <MenuItem Header="context 3"/>
     </ContextMenu>
    </Button.ContextMenu>
    Button with context menu
</Button>

...

public partial class Window1
{
    private bool clickedOnSourceItem;

    public Window1()
    {
     InitializeComponent();

     DataContext = new WindowViewModel();
    }

    private void DropOnToDestination(object sender, DragEventArgs e)
    {
     var viewModel = (WindowViewModel)e.Data.GetData(typeof(WindowViewModel));
     viewModel.CopySelectedItems();
    }

    private void HandleLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
     var sourceElement = (FrameworkElement)sender;
     var hitItem = sourceElement.InputHitTest(e.GetPosition(sourceElement)) as FrameworkElement;

     if(hitItem != null)
     {
      clickedOnSourceItem = true;
     }
    }

    private void HandleLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
     clickedOnSourceItem = false;
    }

    private void HandleMouseMove(object sender, MouseEventArgs e)
    {
     if(clickedOnSourceItem)
     {
      var sourceItems = (FrameworkElement)sender;
      var viewModel = (WindowViewModel)DataContext;

      DragDrop.DoDragDrop(sourceItems, viewModel, DragDropEffects.Move);
      clickedOnSourceItem = false;
     }
    }
}
+1  A: 

It seemed to be something to do with the mouse capture!?

The normal sequence of events during a drag goes something like this...

  1. The PreviewMouseLeftButtonDown handler gets called and ListBox.IsMouseCaptureWithin is false.
  2. The PreviewMouseMove handler gets called. By this time ListBox.IsMouseCaptureWithin is true.
  3. During the PreviewMouseMove handler DragDrop.DoDragDrop gets called and sometime during this the mouse capture is released from the ListBox.

But, what seems to happening for a drag started when the context menu is open is...

  1. The PreviewMouseLeftButtonDown handler gets called and ListBox.IsMouseCaptureWithin is false.
  2. The PreviewMouseMove handler gets called. But this time ListBox.IsMouseCaptureWithin is still false.
  3. Sometime after the end of the PreviewMouseMove handler the ListBox then gets the mouse capture (ListBox.IsMouseCaptureWithin becomes true)

The result of this is that after the drag, the ListBox still has the mouse capture so any clicks on the button to open the context menu are actually going to the listbox not the button.

Adding the following code to the start of the PreviewMouseLeftButtonDown handler seems to help by swallowing up the click that closes that context menu rather than trying to start a drag from it...

if (!contextMenuCloseComplete)
{
    sourceElement.CaptureMouse();
    return;
}

...with the contextMenuCloseComplete bool getting set in handlers for the context menu's Closed and Opened events.

Does that make sense? Does anyone understand where this mouse capture behaviour is coming from?

IanR