Question

I am developing a dragScrollViewer and are experiencing some weird stuff when using "e.Handled=true" in the OnPreviewMouseLeftButtonUp function.

On the left mousedown, it is not known if the user wants to "click" on the below or if he wants to "swipe/drag" with the mouse. If he has started to "swipe/drag", I would like to "eat/cancel" the mouse click.

This should be very simple... a "e.Handled = true" in the OnPreviewMouseLeftButtonUp function should stop the mouseclick from hitting the higher level buttons (below the mouse). However this gives a very strange behavior... the click (and it's coordinates) is stored and is thrown later (next time the user clicks).

I don't know if there is something wrong my code or if there is a bug in the WPF Routed Events framework... is anyone able to reproduce the problem? (in order to make the code simpler, I have removed all dragging code)

How to reproduce the problem:

  1. Make a clean project WPF project in Visual Studio.
  2. Insert example source code DragScroller.cs/MainWindow.xaml/MainWindow.xaml.cs
  3. Compile and click on a button - result: console writes "Inside dragScroller button clicked"
  4. Next click on a button and start swiping (still staying inside the button area - result: mouse click is cancelled
  5. Now click the "Outside dragScroller" - result: console writes "Inside dragScroller button clicked" (this is the stored "mouse" click)

Is there a better way to cancel the mouse click, if the decision to cancel the click is first known when the user releases the mouse button?


DragScroller.cs:

    using System.Windows.Controls;
    using System.Windows.Input;

    namespace dragScroller
    {
      public class DragScrollViewer : ScrollViewer
        {    
            private bool mouseDown;
            private bool isDragging;
            private int dragMoveCount;

          protected override void OnMouseLeave(MouseEventArgs e)
          {
              base.OnMouseLeave(e);
            CancelMouseDrag();
          }

            protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
            {
                base.OnPreviewMouseLeftButtonDown(e);
                dragMoveCount = 0;      
                mouseDown = true;
            }

            protected override void OnPreviewMouseMove(MouseEventArgs e)
            {
                base.OnPreviewMouseMove(e);      
                dragMoveCount++;
                if (!mouseDown || isDragging || !(dragMoveCount > MoveTicksBeforeDrag)) return;
                Cursor = Cursors.ScrollAll;
                isDragging = true;
            }

            protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
            {
                base.OnPreviewMouseLeftButtonUp(e);

                if (isDragging && dragMoveCount > MoveTicksBeforeDrag)
                {
                    e.Handled = true;// Calling e.Handled here, has an unwanted effect on the next "up" event               
                    CancelMouseDrag();        
                }
                dragMoveCount = 0;
                Cursor = Cursors.Arrow;
            }    

            private void CancelMouseDrag()
            {
                isDragging = false;
                mouseDown = false;
                Cursor = Cursors.Arrow;
                dragMoveCount = 0;
            }    

            private const double MoveTicksBeforeDrag = 5; //times to call previewMouseMove before starting to drag (else click)   
        }
    }

MainWindows.xaml:

<Window x:Class="dragScroller.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:dragScroller="clr-namespace:dragScroller"
        Title="MainWindow" Height="350" Width="525">

  <StackPanel>
    <Button Content="Outside dragScroller" Click="Button_Click" />
    <dragScroller:DragScrollViewer x:Name="dragScroller"  Friction="0.2" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Visible">
     <Grid>
      <Grid.RowDefinitions>
         <RowDefinition Height="100" />
        <RowDefinition Height="100" />
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
           <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="100"/>
      </Grid.ColumnDefinitions>
        <Button Content="Button #1" Grid.Column="0" Grid.Row="0" Click="Button_Click_Inside_DragScroller"></Button>
        <Button Content="Button #2" Grid.Column="1" Grid.Row="0" Click="Button_Click_Inside_DragScroller"></Button>
        <Button Content="Button #3" Grid.Column="2" Grid.Row="1" Click="Button_Click_Inside_DragScroller"></Button>
        <Button Content="Button #4" Grid.Column="3" Grid.Row="1" Click="Button_Click_Inside_DragScroller"></Button>
        <Button Content="Button #5" Grid.Column="4" Grid.Row="0" Click="Button_Click_Inside_DragScroller"></Button>
        <Button Content="Button #6" Grid.Column="5" Grid.Row="0" Click="Button_Click_Inside_DragScroller"></Button>
        <Button Content="Button #7" Grid.Column="6" Grid.Row="1" Click="Button_Click_Inside_DragScroller"></Button>
        <Button Content="Button #8" Grid.Column="7" Grid.Row="1" Click="Button_Click_Inside_DragScroller"></Button>
    </Grid>
    </dragScroller:DragScrollViewer>
  </StackPanel>
</Window>

MainWindows.xaml.cs:

namespace dragScroller
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      Debug.WriteLine("Outside dragScroller button clicked");
    }

    private void Button_Click_Inside_DragScroller(object sender, RoutedEventArgs e)
    {
      Debug.WriteLine("Inside dragScroller button clicked");
    }
  }
}
Was it helpful?

Solution

I modified the OnPreviewMouseLeftButtonUp Method and manage to fixed this behavior. The wrong event handler is called because the button is still in focus. if you move the focus back to the main window it should work as expected:

 protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
    {
        base.OnPreviewMouseLeftButtonUp(e);

        if (isDragging && dragMoveCount > MoveTicksBeforeDrag)
        {
            e.Handled = true;// Calling e.Handled here, has an unwanted effect on the next "up" event       
            var x = e.Source as Button;
            if (x != null)
            {
                FocusManager.SetFocusedElement(FocusManager.GetFocusScope(x), Application.Current.MainWindow);
            }
            CancelMouseDrag();
        }
        dragMoveCount = 0;
        Cursor = Cursors.Arrow;
    }

hope it helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top