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:
- Make a clean project WPF project in Visual Studio.
- Insert example source code DragScroller.cs/MainWindow.xaml/MainWindow.xaml.cs
- Compile and click on a button - result: console writes "Inside dragScroller button clicked"
- Next click on a button and start swiping (still staying inside the button area - result: mouse click is cancelled
- 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");
}
}
}