Map and MapItem input handling

This commit is contained in:
ClemensFischer 2025-03-19 11:14:13 +01:00
parent 9da6a13950
commit 74d3a4adfe
8 changed files with 241 additions and 104 deletions

View file

@ -62,11 +62,24 @@ namespace MapControl
base.OnPointerWheelChanged(e); base.OnPointerWheelChanged(e);
} }
protected override void OnPointerPressed(PointerPressedEventArgs e) protected override void OnPointerMoved(PointerEventArgs e)
{ {
var point = e.GetCurrentPoint(this); var point = e.GetCurrentPoint(this);
if (pointer1 == null && point.Pointer.Type == PointerType.Mouse && point.Properties.IsLeftButtonPressed || if (point.Pointer == pointer1 || point.Pointer == pointer2)
{
if (pointer2 != null)
{
HandleManipulation(point.Pointer, point.Position);
}
else if (point.Pointer.Type == PointerType.Mouse || ManipulationModes.HasFlag(ManipulationModes.Translate))
{
TranslateMap(new Point(point.Position.X - position1.X, point.Position.Y - position1.Y));
position1 = point.Position;
}
}
else if (
pointer1 == null && point.Pointer.Type == PointerType.Mouse && point.Properties.IsLeftButtonPressed && e.KeyModifiers == KeyModifiers.None ||
pointer2 == null && point.Pointer.Type == PointerType.Touch && ManipulationModes != ManipulationModes.None) pointer2 == null && point.Pointer.Type == PointerType.Touch && ManipulationModes != ManipulationModes.None)
{ {
point.Pointer.Capture(this); point.Pointer.Capture(this);
@ -83,7 +96,7 @@ namespace MapControl
} }
} }
base.OnPointerPressed(e); base.OnPointerMoved(e);
} }
protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
@ -102,26 +115,6 @@ namespace MapControl
base.OnPointerCaptureLost(e); base.OnPointerCaptureLost(e);
} }
protected override void OnPointerMoved(PointerEventArgs e)
{
if (e.Pointer == pointer1 || e.Pointer == pointer2)
{
var position = e.GetPosition(this);
if (pointer2 != null)
{
HandleManipulation(e.Pointer, position);
}
else if (e.Pointer.Type == PointerType.Mouse || ManipulationModes.HasFlag(ManipulationModes.Translate))
{
TranslateMap(new Point(position.X - position1.X, position.Y - position1.Y));
position1 = position;
}
}
base.OnPointerMoved(e);
}
private void HandleManipulation(IPointer pointer, Point position) private void HandleManipulation(IPointer pointer, Point position)
{ {
var oldDistance = new Vector(position2.X - position1.X, position2.Y - position1.Y); var oldDistance = new Vector(position2.X - position1.X, position2.Y - position1.Y);

View file

@ -11,14 +11,28 @@
protected override void OnPointerPressed(PointerPressedEventArgs e) protected override void OnPointerPressed(PointerPressedEventArgs e)
{ {
if (e.KeyModifiers.HasFlag(KeyModifiers.Shift)) if (e.Pointer.Type == PointerType.Touch)
{ {
e.Handled = true; UpdateSelection(e);
MapItemsControl.SelectItemsInRange(this);
} }
else
e.Handled = true;
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
UpdateSelection(e);
e.Handled = true;
}
private void UpdateSelection(PointerEventArgs e)
{
if (ItemsControl.ItemsControlFromItemContainer(this) is MapItemsControl mapItemsControl)
{ {
base.OnPointerPressed(e); mapItemsControl.UpdateSelection(this,
e.KeyModifiers.HasFlag(KeyModifiers.Control),
e.KeyModifiers.HasFlag(KeyModifiers.Shift));
} }
} }
} }

View file

@ -56,5 +56,50 @@ namespace MapControl
mapItem.ClearValue(MapItem.LocationProperty); mapItem.ClearValue(MapItem.LocationProperty);
} }
} }
internal void UpdateSelection(MapItem mapItem, bool controlKeyPressed, bool shiftKeyPressed)
{
if (SelectionMode != SelectionMode.Single && shiftKeyPressed)
{
SelectItemsInRange(mapItem);
}
else
{
UpdateSelection(mapItem, true, false, controlKeyPressed);
}
//var item = ItemFromContainer(mapItem);
//if (SelectionMode == SelectionMode.Single)
//{
// if (SelectedItem != item)
// {
// SelectedItem = item;
// }
// else if (controlKeyPressed)
// {
// SelectedItem = null;
// }
//}
//else if (controlKeyPressed)
//{
// if (SelectedItems.Contains(item))
// {
// SelectedItems.Remove(item);
// }
// else
// {
// SelectedItems.Add(item);
// }
//}
//else if (shiftKeyPressed)
//{
// SelectItemsInRange(mapItem);
//}
//else if (SelectedItem != item || SelectedItems.Count != 1)
//{
// SelectedItem = item;
//}
}
} }
} }

View file

@ -68,9 +68,9 @@ namespace MapControl
{ {
SelectItems(item => SelectItems(item =>
{ {
var pos = MapPanel.GetViewPosition(ContainerFromItem(item)); var position = MapPanel.GetViewPosition(ContainerFromItem(item));
return pos.HasValue && predicate(pos.Value); return position.HasValue && predicate(position.Value);
}); });
} }
@ -82,40 +82,36 @@ namespace MapControl
/// <summary> /// <summary>
/// Selects all items in a rectangular range between SelectedItem and the specified MapItem. /// Selects all items in a rectangular range between SelectedItem and the specified MapItem.
/// </summary> /// </summary>
internal static void SelectItemsInRange(MapItem mapItem) internal void SelectItemsInRange(MapItem mapItem)
{ {
if (ItemsControlFromItemContainer(mapItem) is MapItemsControl mapItemsControl && var position = MapPanel.GetViewPosition(mapItem);
mapItemsControl.SelectionMode != SelectionMode.Single)
if (position.HasValue)
{ {
var pos = MapPanel.GetViewPosition(mapItem); var xMin = position.Value.X;
var xMax = position.Value.X;
var yMin = position.Value.Y;
var yMax = position.Value.Y;
if (pos.HasValue) if (SelectedItem != null)
{ {
var xMin = pos.Value.X; var selectedMapItem = ContainerFromItem(SelectedItem);
var xMax = pos.Value.X;
var yMin = pos.Value.Y;
var yMax = pos.Value.Y;
if (mapItemsControl.SelectedItem != null) if (selectedMapItem != mapItem)
{ {
var selectedMapItem = mapItemsControl.ContainerFromItem(mapItemsControl.SelectedItem); position = MapPanel.GetViewPosition(selectedMapItem);
if (selectedMapItem != mapItem) if (position.HasValue)
{ {
pos = MapPanel.GetViewPosition(selectedMapItem); xMin = Math.Min(xMin, position.Value.X);
xMax = Math.Max(xMax, position.Value.X);
if (pos.HasValue) yMin = Math.Min(yMin, position.Value.Y);
{ yMax = Math.Max(yMax, position.Value.Y);
xMin = Math.Min(xMin, pos.Value.X);
xMax = Math.Max(xMax, pos.Value.X);
yMin = Math.Min(yMin, pos.Value.Y);
yMax = Math.Max(yMax, pos.Value.Y);
}
} }
} }
mapItemsControl.SelectItemsInRect(new Rect(xMin, yMin, xMax - xMin, yMax - yMin));
} }
SelectItemsInRect(new Rect(xMin, yMin, xMax - xMin, yMax - yMin));
} }
} }
} }

View file

@ -23,16 +23,6 @@ namespace MapControl
IsManipulationEnabledProperty.OverrideMetadata(typeof(Map), new FrameworkPropertyMetadata(true)); IsManipulationEnabledProperty.OverrideMetadata(typeof(Map), new FrameworkPropertyMetadata(true));
} }
public Map()
{
ManipulationStarted += OnManipulationStarted;
ManipulationDelta += OnManipulationDelta;
MouseLeftButtonDown += OnMouseLeftButtonDown;
MouseLeftButtonUp += OnMouseLeftButtonUp;
MouseMove += OnMouseMove;
MouseWheel += OnMouseWheel;
}
/// <summary> /// <summary>
/// Gets or sets the amount by which the ZoomLevel property changes by a MouseWheel event. /// Gets or sets the amount by which the ZoomLevel property changes by a MouseWheel event.
/// The default value is 0.25. /// The default value is 0.25.
@ -52,29 +42,38 @@ namespace MapControl
set => SetValue(ManipulationModeProperty, value); set => SetValue(ManipulationModeProperty, value);
} }
private void OnManipulationStarted(object sender, ManipulationStartedEventArgs e) protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{ {
base.OnManipulationStarted(e);
Manipulation.SetManipulationMode(this, ManipulationMode); Manipulation.SetManipulationMode(this, ManipulationMode);
} }
private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e) protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
{ {
base.OnManipulationDelta(e);
TransformMap(e.ManipulationOrigin, TransformMap(e.ManipulationOrigin,
(Point)e.DeltaManipulation.Translation, (Point)e.DeltaManipulation.Translation,
e.DeltaManipulation.Rotation, e.DeltaManipulation.Rotation,
e.DeltaManipulation.Scale.LengthSquared / 2d); e.DeltaManipulation.Scale.LengthSquared / 2d);
} }
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{ {
if (Keyboard.Modifiers == ModifierKeys.None && CaptureMouse()) base.OnMouseLeftButtonDown(e);
if (Keyboard.Modifiers == ModifierKeys.None &&
CaptureMouse())
{ {
mousePosition = e.GetPosition(this); mousePosition = e.GetPosition(this);
} }
} }
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{ {
base.OnMouseLeftButtonUp(e);
if (mousePosition.HasValue) if (mousePosition.HasValue)
{ {
mousePosition = null; mousePosition = null;
@ -82,18 +81,30 @@ namespace MapControl
} }
} }
private void OnMouseMove(object sender, MouseEventArgs e) protected override void OnMouseMove(MouseEventArgs e)
{ {
base.OnMouseMove(e);
if (mousePosition.HasValue) if (mousePosition.HasValue)
{ {
var p = e.GetPosition(this); var p = e.GetPosition(this);
TranslateMap(new Point(p.X - mousePosition.Value.X, p.Y - mousePosition.Value.Y)); TranslateMap(new Point(p.X - mousePosition.Value.X, p.Y - mousePosition.Value.Y));
mousePosition = p; mousePosition = p;
} }
else if (e.LeftButton == MouseButtonState.Pressed &&
Keyboard.Modifiers == ModifierKeys.None &&
CaptureMouse())
{
// Set mousePosition when no MouseLeftButtonDown event was received.
//
mousePosition = e.GetPosition(this);
}
} }
private void OnMouseWheel(object sender, MouseWheelEventArgs e) protected override void OnMouseWheel(MouseWheelEventArgs e)
{ {
base.OnMouseWheel(e);
// Standard mouse wheel delta value is 120. // Standard mouse wheel delta value is 120.
// //
mouseWheelDelta += e.Delta / 120d; mouseWheelDelta += e.Delta / 120d;

View file

@ -18,17 +18,57 @@ namespace MapControl
DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItem), new FrameworkPropertyMetadata(typeof(MapItem))); DefaultStyleKeyProperty.OverrideMetadata(typeof(MapItem), new FrameworkPropertyMetadata(typeof(MapItem)));
} }
protected override void OnTouchDown(TouchEventArgs e)
{
// Prevent default touch handling.
//
e.Handled = true;
}
protected override void OnTouchUp(TouchEventArgs e)
{
// Prevent default touch handling.
//
e.Handled = true;
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{ {
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)) // Prevent default item selection on mouse down.
//
e.Handled = true;
}
protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
{
// Prevent default item selection on mouse down.
//
e.Handled = true;
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
if (ItemsControl.ItemsControlFromItemContainer(this) is MapItemsControl mapItemsControl)
{ {
e.Handled = true; if (mapItemsControl.SelectionMode == SelectionMode.Extended &&
MapItemsControl.SelectItemsInRange(this); Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
} {
else mapItemsControl.SelectItemsInRange(this);
{ }
base.OnMouseLeftButtonDown(e); else
{
// Perform default mouse down item selection on mouse up.
//
base.OnMouseLeftButtonDown(e);
}
} }
e.Handled = true;
}
protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
{
OnMouseLeftButtonUp(e);
} }
} }
} }

View file

@ -20,8 +20,8 @@ namespace MapControl
public static readonly DependencyProperty MouseWheelZoomDeltaProperty = public static readonly DependencyProperty MouseWheelZoomDeltaProperty =
DependencyPropertyHelper.Register<Map, double>(nameof(MouseWheelZoomDelta), 0.25); DependencyPropertyHelper.Register<Map, double>(nameof(MouseWheelZoomDelta), 0.25);
private uint capturedPointerId;
private double mouseWheelDelta; private double mouseWheelDelta;
private bool? manipulationEnabled;
public Map() public Map()
{ {
@ -32,8 +32,10 @@ namespace MapControl
| ManipulationModes.TranslateInertia; | ManipulationModes.TranslateInertia;
ManipulationDelta += OnManipulationDelta; ManipulationDelta += OnManipulationDelta;
ManipulationCompleted += OnManipulationCompleted;
PointerPressed += OnPointerPressed; PointerPressed += OnPointerPressed;
PointerCaptureLost += OnPointerCaptureLost; PointerReleased += OnPointerReleased;
PointerMoved += OnPointerMoved;
PointerWheelChanged += OnPointerWheelChanged; PointerWheelChanged += OnPointerWheelChanged;
} }
@ -49,32 +51,46 @@ namespace MapControl
private void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) private void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{ {
if (capturedPointerId != 0) if (manipulationEnabled.HasValue && manipulationEnabled.Value)
{ {
TranslateMap(e.Delta.Translation); if (e.PointerDeviceType == PointerDeviceType.Mouse)
} {
else if (e.PointerDeviceType != PointerDeviceType.Mouse) TranslateMap(e.Delta.Translation);
{ }
TransformMap(e.Position, e.Delta.Translation, e.Delta.Rotation, e.Delta.Scale); else
{
TransformMap(e.Position, e.Delta.Translation, e.Delta.Rotation, e.Delta.Scale);
}
} }
} }
private void OnManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
manipulationEnabled = null;
}
private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
manipulationEnabled = null;
}
private void OnPointerPressed(object sender, PointerRoutedEventArgs e) private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{ {
if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse && // IsLeftButtonPressed: input was triggered by the primary action mode of an input device.
e.KeyModifiers == VirtualKeyModifiers.None && //
manipulationEnabled =
e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed &&
CapturePointer(e.Pointer)) e.KeyModifiers == VirtualKeyModifiers.None;
{
capturedPointerId = e.Pointer.PointerId;
}
} }
private void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e) private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{ {
if (capturedPointerId == e.Pointer.PointerId) // Set manipulationEnabled when no PointerPressed was received.
//
if (!manipulationEnabled.HasValue &&
e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{ {
capturedPointerId = 0; manipulationEnabled = e.KeyModifiers == VirtualKeyModifiers.None;
} }
} }

View file

@ -1,10 +1,13 @@
using Windows.System; using System;
using Windows.System;
#if UWP #if UWP
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Input;
#else #else
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Input;
#endif #endif
@ -25,25 +28,44 @@ namespace MapControl
item.UpdateMapTransform(newValue); item.UpdateMapTransform(newValue);
}); });
// Used to detect pointer movement between PointerPressed and
// PointerReleased in order to possibly cancel item selection.
//
private const float pointerMovementThreshold = 2f;
private Windows.Foundation.Point pointerPressedPosition;
public MapItem() public MapItem()
{ {
DefaultStyleKey = typeof(MapItem); DefaultStyleKey = typeof(MapItem);
MapPanel.InitMapElement(this); MapPanel.InitMapElement(this);
} }
protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
base.OnPointerPressed(e);
pointerPressedPosition = e.GetCurrentPoint(null).Position;
}
protected override void OnPointerReleased(PointerRoutedEventArgs e) protected override void OnPointerReleased(PointerRoutedEventArgs e)
{ {
// In contrast to WPF and Avalonia, item selection is done on PointerReleased. var p = e.GetCurrentPoint(null).Position;
//
if (e.KeyModifiers.HasFlag(VirtualKeyModifiers.Shift)) if (Math.Abs(p.X - pointerPressedPosition.X) <= pointerMovementThreshold &&
Math.Abs(p.Y - pointerPressedPosition.Y) <= pointerMovementThreshold &&
ItemsControl.ItemsControlFromItemContainer(this) is MapItemsControl mapItemsControl)
{ {
e.Handled = true; if (mapItemsControl.SelectionMode == SelectionMode.Extended &&
MapItemsControl.SelectItemsInRange(this); e.KeyModifiers.HasFlag(VirtualKeyModifiers.Shift))
} {
else mapItemsControl.SelectItemsInRange(this);
{ }
base.OnPointerReleased(e); else
{
base.OnPointerReleased(e);
}
} }
e.Handled = true;
} }
protected override void OnApplyTemplate() protected override void OnApplyTemplate()