Browse Source

Refactor pointer events to support touch events

Nikita Tsukanov 6 years ago
parent
commit
f556db57b4
25 changed files with 515 additions and 400 deletions
  1. 0 13
      src/Avalonia.Controls/Calendar/CalendarButton.cs
  2. 0 13
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  3. 1 31
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  4. 6 12
      src/Avalonia.Controls/MenuItem.cs
  5. 2 0
      src/Avalonia.Input/IMouseDevice.cs
  6. 17 0
      src/Avalonia.Input/IPointer.cs
  7. 0 2
      src/Avalonia.Input/IPointerDevice.cs
  8. 74 60
      src/Avalonia.Input/MouseDevice.cs
  9. 75 23
      src/Avalonia.Input/PointerEventArgs.cs
  10. 45 0
      src/Avalonia.Input/PointerPoint.cs
  11. 11 0
      src/Avalonia.Input/PointerWheelEventArgs.cs
  12. 3 0
      src/Avalonia.Input/Raw/RawMouseEventArgs.cs
  13. 15 0
      src/Avalonia.Input/Raw/RawTouchEventArgs.cs
  14. 34 72
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  15. 5 9
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  16. 8 35
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  17. 3 6
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  18. 9 32
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  19. 114 0
      tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs
  20. 38 14
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  21. 4 11
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  22. 35 54
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  23. 2 0
      tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
  24. 7 7
      tests/Avalonia.Interactivity.UnitTests/GestureTests.cs
  25. 7 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

+ 0 - 13
src/Avalonia.Controls/Calendar/CalendarButton.cs

@@ -176,18 +176,5 @@ namespace Avalonia.Controls.Primitives
             if (e.MouseButton == MouseButton.Left)
                 CalendarLeftMouseButtonUp?.Invoke(this, e);
         }
-        
-        /// <summary>
-        /// We need to simulate the MouseLeftButtonUp event for the
-        /// CalendarButton that stays in Pressed state after MouseCapture is
-        /// released since there is no actual MouseLeftButtonUp event for the
-        /// release.
-        /// </summary>
-        /// <param name="e">Event arguments.</param>
-        internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
-        {
-            e.Handled = false;
-            base.OnPointerReleased(e);
-        }
     }
 }

+ 0 - 13
src/Avalonia.Controls/Calendar/CalendarDayButton.cs

@@ -234,18 +234,5 @@ namespace Avalonia.Controls.Primitives
             if (e.MouseButton == MouseButton.Left)
                 CalendarDayButtonMouseUp?.Invoke(this, e);
         }
-
-        /// <summary>
-        /// We need to simulate the MouseLeftButtonUp event for the
-        /// CalendarDayButton that stays in Pressed state after MouseCapture is
-        /// released since there is no actual MouseLeftButtonUp event for the
-        /// release.
-        /// </summary>
-        /// <param name="e">Event arguments.</param>
-        internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
-        {
-            e.Handled = false;
-            base.OnPointerReleased(e);
-        }
     }
 }

+ 1 - 31
src/Avalonia.Controls/Calendar/CalendarItem.cs

@@ -934,22 +934,6 @@ namespace Avalonia.Controls.Primitives
                 // The button is in Pressed state. Change the state to normal.
                 if (e.Device.Captured == b)
                     e.Device.Capture(null);
-                // null check is added for unit tests
-                if (_downEventArg != null)
-                {
-                    var arg =
-                        new PointerReleasedEventArgs()
-                        {
-                            Device = _downEventArg.Device,
-                            MouseButton = _downEventArg.MouseButton,
-                            Handled = _downEventArg.Handled,
-                            InputModifiers = _downEventArg.InputModifiers,
-                            Route = _downEventArg.Route,
-                            Source = _downEventArg.Source
-                        };
-
-                    b.SendMouseLeftButtonUp(arg);
-                }
                 _lastCalendarDayButton = b;
             }
         }
@@ -1221,21 +1205,7 @@ namespace Avalonia.Controls.Primitives
                 if (e.Device.Captured == b)
                     e.Device.Capture(null);
                 //b.ReleaseMouseCapture();
-                if (_downEventArgYearView != null)
-                {
-                    var args =
-                        new PointerReleasedEventArgs()
-                        {
-                            Device = _downEventArgYearView.Device,
-                            MouseButton = _downEventArgYearView.MouseButton,
-                            Handled = _downEventArgYearView.Handled,
-                            InputModifiers = _downEventArgYearView.InputModifiers,
-                            Route = _downEventArgYearView.Route,
-                            Source = _downEventArgYearView.Source
-                        };
-
-                    b.SendMouseLeftButtonUp(args);
-                }
+
                 _lastCalendarButton = b;
             }
         }

+ 6 - 12
src/Avalonia.Controls/MenuItem.cs

@@ -337,12 +337,9 @@ namespace Avalonia.Controls
         {
             base.OnPointerEnter(e);
 
-            RaiseEvent(new PointerEventArgs
-            {
-                Device = e.Device,
-                RoutedEvent = PointerEnterItemEvent,
-                Source = this,
-            });
+            var point = e.GetPointerPoint(null);
+            RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
+                point.Properties, e.InputModifiers));
         }
 
         /// <inheritdoc/>
@@ -350,12 +347,9 @@ namespace Avalonia.Controls
         {
             base.OnPointerLeave(e);
 
-            RaiseEvent(new PointerEventArgs
-            {
-                Device = e.Device,
-                RoutedEvent = PointerLeaveItemEvent,
-                Source = this,
-            });
+            var point = e.GetPointerPoint(null);
+            RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
+                point.Properties, e.InputModifiers));
         }
 
         /// <summary>

+ 2 - 0
src/Avalonia.Input/IMouseDevice.cs

@@ -12,5 +12,7 @@ namespace Avalonia.Input
         /// Gets the mouse position, in screen coordinates.
         /// </summary>
         PixelPoint Position { get; }
+
+        void SceneInvalidated(IInputRoot root, Rect rect);
     }
 }

+ 17 - 0
src/Avalonia.Input/IPointer.cs

@@ -0,0 +1,17 @@
+namespace Avalonia.Input
+{
+    public interface IPointer
+    {
+        void Capture(IInputElement control);
+        IInputElement Captured { get; }
+        PointerType Type { get; }
+        bool IsPrimary { get; }
+        
+    }
+
+    public enum PointerType
+    {
+        Mouse,
+        Touch
+    }
+}

+ 0 - 2
src/Avalonia.Input/IPointerDevice.cs

@@ -12,7 +12,5 @@ namespace Avalonia.Input
         void Capture(IInputElement control);
 
         Point GetPosition(IVisual relativeTo);
-
-        void SceneInvalidated(IInputRoot root, Rect rect);
     }
 }

+ 74 - 60
src/Avalonia.Input/MouseDevice.cs

@@ -14,14 +14,17 @@ namespace Avalonia.Input
     /// <summary>
     /// Represents a mouse device.
     /// </summary>
-    public class MouseDevice : IMouseDevice
+    public class MouseDevice : IMouseDevice, IPointer
     {
         private int _clickCount;
         private Rect _lastClickRect;
         private ulong _lastClickTime;
         private IInputElement _captured;
         private IDisposable _capturedSubscription;
-       
+
+        PointerType IPointer.Type => PointerType.Mouse;
+        bool IPointer.IsPrimary => true;
+        
         /// <summary>
         /// Gets the control that is currently capturing by the mouse, if any.
         /// </summary>
@@ -117,6 +120,18 @@ namespace Avalonia.Input
             }
         }
 
+        int ButtonCount(PointerPointProperties props)
+        {
+            var rv = 0;
+            if (props.IsLeftButtonPressed)
+                rv++;
+            if (props.IsMiddleButtonPressed)
+                rv++;
+            if (props.IsRightButtonPressed)
+                rv++;
+            return rv;
+        }
+        
         private void ProcessRawEvent(RawMouseEventArgs e)
         {
             Contract.Requires<ArgumentNullException>(e != null);
@@ -124,7 +139,7 @@ namespace Avalonia.Input
             var mouse = (IMouseDevice)e.Device;
 
             Position = e.Root.PointToScreen(e.Position);
-
+            var props = CreateProperties(e);
             switch (e.Type)
             {
                 case RawMouseEventType.LeaveWindow:
@@ -133,26 +148,25 @@ namespace Avalonia.Input
                 case RawMouseEventType.LeftButtonDown:
                 case RawMouseEventType.RightButtonDown:
                 case RawMouseEventType.MiddleButtonDown:
-                    e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position,
-                         e.Type == RawMouseEventType.LeftButtonDown
-                            ? MouseButton.Left
-                            : e.Type == RawMouseEventType.RightButtonDown ? MouseButton.Right : MouseButton.Middle,
-                        e.InputModifiers);
+                    if (ButtonCount(props) > 1)
+                        e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
+                    else
+                        e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position,
+                            props, e.InputModifiers);
                     break;
                 case RawMouseEventType.LeftButtonUp:
                 case RawMouseEventType.RightButtonUp:
                 case RawMouseEventType.MiddleButtonUp:
-                    e.Handled = MouseUp(mouse, e.Root, e.Position,
-                        e.Type == RawMouseEventType.LeftButtonUp
-                            ? MouseButton.Left
-                            : e.Type == RawMouseEventType.RightButtonUp ? MouseButton.Right : MouseButton.Middle,
-                        e.InputModifiers);
+                    if (ButtonCount(props) != 0)
+                        e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
+                    else
+                        e.Handled = MouseUp(mouse, e.Root, e.Position, props, e.InputModifiers);
                     break;
                 case RawMouseEventType.Move:
-                    e.Handled = MouseMove(mouse, e.Root, e.Position, e.InputModifiers);
+                    e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
                     break;
                 case RawMouseEventType.Wheel:
-                    e.Handled = MouseWheel(mouse, e.Root, e.Position, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers);
+                    e.Handled = MouseWheel(mouse, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers);
                     break;
             }
         }
@@ -165,7 +179,30 @@ namespace Avalonia.Input
             ClearPointerOver(this, root, inputModifiers);
         }
 
-        private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers)
+
+        PointerPointProperties CreateProperties(RawMouseEventArgs args)
+        {
+            var rv = new PointerPointProperties(args.InputModifiers);
+
+            if (args.Type == RawMouseEventType.LeftButtonDown)
+                rv.IsLeftButtonPressed = true;
+            if (args.Type == RawMouseEventType.MiddleButtonDown)
+                rv.IsMiddleButtonPressed = true;
+            if (args.Type == RawMouseEventType.RightButtonDown)
+                rv.IsRightButtonPressed = true;
+            if (args.Type == RawMouseEventType.LeftButtonUp)
+                rv.IsLeftButtonPressed = false;
+            if (args.Type == RawMouseEventType.MiddleButtonUp)
+                rv.IsMiddleButtonPressed = false;
+            if (args.Type == RawMouseEventType.RightButtonDown)
+                rv.IsRightButtonPressed = false;
+            return rv;
+        }
+
+        private MouseButton _lastMouseDownButton;
+        private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p,
+            PointerPointProperties properties,
+            InputModifiers inputModifiers)
         {
             Contract.Requires<ArgumentNullException>(device != null);
             Contract.Requires<ArgumentNullException>(root != null);
@@ -190,16 +227,8 @@ namespace Avalonia.Input
                     _lastClickTime = timestamp;
                     _lastClickRect = new Rect(p, new Size())
                         .Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2));
-
-                    var e = new PointerPressedEventArgs
-                    {
-                        Device = this,
-                        RoutedEvent = InputElement.PointerPressedEvent,
-                        Source = source,
-                        ClickCount = _clickCount,
-                        MouseButton = button,
-                        InputModifiers = inputModifiers
-                    };
+                    _lastMouseDownButton = properties.GetObsoleteMouseButton();
+                    var e = new PointerPressedEventArgs(source, this, root, p, properties, inputModifiers, _clickCount);
 
                     source.RaiseEvent(e);
                     return e.Handled;
@@ -209,7 +238,8 @@ namespace Avalonia.Input
             return false;
         }
 
-        private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, InputModifiers inputModifiers)
+        private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties properties,
+            InputModifiers inputModifiers)
         {
             Contract.Requires<ArgumentNullException>(device != null);
             Contract.Requires<ArgumentNullException>(root != null);
@@ -226,19 +256,15 @@ namespace Avalonia.Input
                 source = Captured;
             }
 
-            var e = new PointerEventArgs
-            {
-                Device = this,
-                RoutedEvent = InputElement.PointerMovedEvent,
-                Source = source,
-                InputModifiers = inputModifiers
-            };
+            var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, this, root,
+                p, properties, inputModifiers);
 
             source?.RaiseEvent(e);
             return e.Handled;
         }
 
-        private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, MouseButton button, InputModifiers inputModifiers)
+        private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties props,
+            InputModifiers inputModifiers)
         {
             Contract.Requires<ArgumentNullException>(device != null);
             Contract.Requires<ArgumentNullException>(root != null);
@@ -248,14 +274,7 @@ namespace Avalonia.Input
             if (hit != null)
             {
                 var source = GetSource(hit);
-                var e = new PointerReleasedEventArgs
-                {
-                    Device = this,
-                    RoutedEvent = InputElement.PointerReleasedEvent,
-                    Source = source,
-                    MouseButton = button,
-                    InputModifiers = inputModifiers
-                };
+                var e = new PointerReleasedEventArgs(source, this, root, p, props, inputModifiers, _lastMouseDownButton);
 
                 source?.RaiseEvent(e);
                 return e.Handled;
@@ -264,7 +283,9 @@ namespace Avalonia.Input
             return false;
         }
 
-        private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p, Vector delta, InputModifiers inputModifiers)
+        private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p,
+            PointerPointProperties props,
+            Vector delta, InputModifiers inputModifiers)
         {
             Contract.Requires<ArgumentNullException>(device != null);
             Contract.Requires<ArgumentNullException>(root != null);
@@ -274,14 +295,7 @@ namespace Avalonia.Input
             if (hit != null)
             {
                 var source = GetSource(hit);
-                var e = new PointerWheelEventArgs
-                {
-                    Device = this,
-                    RoutedEvent = InputElement.PointerWheelChangedEvent,
-                    Source = source,
-                    Delta = delta,
-                    InputModifiers = inputModifiers
-                };
+                var e = new PointerWheelEventArgs(source, this, root, p, props, inputModifiers, delta);
 
                 source?.RaiseEvent(e);
                 return e.Handled;
@@ -306,18 +320,19 @@ namespace Avalonia.Input
             return Captured ?? root.InputHitTest(p);
         }
 
+        PointerEventArgs CreateSimpleEvent(RoutedEvent ev, IInteractive source, InputModifiers inputModifiers)
+        {
+            return new PointerEventArgs(ev, source, this, null, default,
+                new PointerPointProperties(inputModifiers), inputModifiers);
+        }
+
         private void ClearPointerOver(IPointerDevice device, IInputRoot root, InputModifiers inputModifiers)
         {
             Contract.Requires<ArgumentNullException>(device != null);
             Contract.Requires<ArgumentNullException>(root != null);
 
             var element = root.PointerOverElement;
-            var e = new PointerEventArgs
-            {
-                RoutedEvent = InputElement.PointerLeaveEvent,
-                Device = device,
-                InputModifiers = inputModifiers
-            };
+            var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, element, inputModifiers);
 
             if (element!=null && !element.IsAttachedToVisualTree)
             {
@@ -384,7 +399,6 @@ namespace Avalonia.Input
 
             IInputElement branch = null;
 
-            var e = new PointerEventArgs { Device = device, InputModifiers = inputModifiers };
             var el = element;
 
             while (el != null)
@@ -398,8 +412,8 @@ namespace Avalonia.Input
             }
 
             el = root.PointerOverElement;
-            e.RoutedEvent = InputElement.PointerLeaveEvent;
 
+            var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, el, inputModifiers);
             if (el!=null && branch!=null && !el.IsAttachedToVisualTree)
             {
                 ClearChildrenPointerOver(e,branch,false);

+ 75 - 23
src/Avalonia.Input/PointerEventArgs.cs

@@ -1,6 +1,8 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
+using Avalonia.Input.Raw;
 using Avalonia.Interactivity;
 using Avalonia.VisualTree;
 
@@ -8,25 +10,68 @@ namespace Avalonia.Input
 {
     public class PointerEventArgs : RoutedEventArgs
     {
-        public PointerEventArgs()
-        {
+        private readonly IVisual _rootVisual;
+        private readonly Point _rootVisualPosition;
+        private readonly PointerPointProperties _properties;
 
+        public PointerEventArgs(RoutedEvent routedEvent,
+            IInteractive source,
+            IPointer pointer,
+            IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties,
+            InputModifiers modifiers)
+           : base(routedEvent)
+        {
+            Source = source;
+            _rootVisual = rootVisual;
+            _rootVisualPosition = rootVisualPosition;
+            _properties = properties;
+            Pointer = pointer;
+            InputModifiers = modifiers;
+#pragma warning disable 612
+#pragma warning disable 618
+            Device = new EmulatedDevice(this);
+#pragma warning restore 618
+#pragma warning restore 612
         }
 
-        public PointerEventArgs(RoutedEvent routedEvent)
-           : base(routedEvent)
+        class EmulatedDevice : IPointerDevice
         {
+            private readonly PointerEventArgs _ev;
 
+            public EmulatedDevice(PointerEventArgs ev)
+            {
+                _ev = ev;
+            }
+            
+            public void ProcessRawEvent(RawInputEventArgs ev) => throw new NotSupportedException();
+
+            public IInputElement Captured => _ev.Pointer.Captured;
+            public void Capture(IInputElement control)
+            {
+                _ev.Pointer.Capture(control);
+            }
+
+            public Point GetPosition(IVisual relativeTo) => _ev.GetPosition(relativeTo);
         }
 
-        public IPointerDevice Device { get; set; }
+        public IPointer Pointer { get; }
+        
+        [Obsolete("Use Pointer to get pointer-specific information")]
+        public IPointerDevice Device { get; }
 
-        public InputModifiers InputModifiers { get; set; }
+        public InputModifiers InputModifiers { get; }
 
         public Point GetPosition(IVisual relativeTo)
         {
-            return Device.GetPosition(relativeTo);
+            if (_rootVisual == null)
+                return default;
+            if (relativeTo == null)
+                return _rootVisualPosition;
+            return _rootVisualPosition * _rootVisual.TransformToVisual(relativeTo) ?? default;
         }
+
+        public PointerPoint GetPointerPoint(IVisual relativeTo)
+            => new PointerPoint(Pointer, GetPosition(relativeTo), _properties);
     }
     
     public enum MouseButton
@@ -39,32 +84,39 @@ namespace Avalonia.Input
 
     public class PointerPressedEventArgs : PointerEventArgs
     {
-        public PointerPressedEventArgs()
-            : base(InputElement.PointerPressedEvent)
-        {
-        }
+        private readonly int _obsoleteClickCount;
 
-        public PointerPressedEventArgs(RoutedEvent routedEvent)
-            : base(routedEvent)
+        public PointerPressedEventArgs(
+            IInteractive source,
+            IPointer pointer,
+            IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties,
+            InputModifiers modifiers,
+            int obsoleteClickCount = 1)
+            : base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition, properties,
+                modifiers)
         {
+            _obsoleteClickCount = obsoleteClickCount;
         }
 
-        public int ClickCount { get; set; }
-        public MouseButton MouseButton { get; set; }
+        [Obsolete("Use DoubleTapped or DoubleRightTapped event instead")]
+        public int ClickCount => _obsoleteClickCount;
+
+        [Obsolete] public MouseButton MouseButton => GetPointerPoint(null).Properties.GetObsoleteMouseButton();
     }
 
     public class PointerReleasedEventArgs : PointerEventArgs
     {
-        public PointerReleasedEventArgs()
-            : base(InputElement.PointerReleasedEvent)
-        {
-        }
-
-        public PointerReleasedEventArgs(RoutedEvent routedEvent)
-            : base(routedEvent)
+        public PointerReleasedEventArgs(
+            IInteractive source, IPointer pointer,
+            IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties, InputModifiers modifiers,
+            MouseButton obsoleteMouseButton)
+            : base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition,
+                properties, modifiers)
         {
+            MouseButton = obsoleteMouseButton;
         }
 
-        public MouseButton MouseButton { get; set; }
+        [Obsolete()]
+        public MouseButton MouseButton { get; private set; }
     }
 }

+ 45 - 0
src/Avalonia.Input/PointerPoint.cs

@@ -0,0 +1,45 @@
+namespace Avalonia.Input
+{
+    public sealed class PointerPoint
+    {
+        public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties)
+        {
+            Pointer = pointer;
+            Position = position;
+            Properties = properties;
+        }
+        public IPointer Pointer { get; }
+        public PointerPointProperties Properties { get; }
+        public Point Position { get; }
+    }
+
+    public sealed class PointerPointProperties
+    {
+        public bool IsLeftButtonPressed { get; set; }
+        public bool IsMiddleButtonPressed { get; set; }
+        public bool IsRightButtonPressed { get; set; }
+
+        public PointerPointProperties()
+        {
+            
+        }
+        
+        public PointerPointProperties(InputModifiers modifiers)
+        {
+            IsLeftButtonPressed = modifiers.HasFlag(InputModifiers.LeftMouseButton);
+            IsMiddleButtonPressed = modifiers.HasFlag(InputModifiers.MiddleMouseButton);
+            IsRightButtonPressed = modifiers.HasFlag(InputModifiers.RightMouseButton);
+        }
+        
+        public MouseButton GetObsoleteMouseButton()
+        {
+            if (IsLeftButtonPressed)
+                return MouseButton.Left;
+            if (IsMiddleButtonPressed)
+                return MouseButton.Middle;
+            if (IsRightButtonPressed)
+                return MouseButton.Right;
+            return MouseButton.None;
+        }
+    }
+}

+ 11 - 0
src/Avalonia.Input/PointerWheelEventArgs.cs

@@ -1,10 +1,21 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
+
 namespace Avalonia.Input
 {
     public class PointerWheelEventArgs : PointerEventArgs
     {
         public Vector Delta { get; set; }
+
+        public PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual,
+            Point rootVisualPosition,
+            PointerPointProperties properties, InputModifiers modifiers, Vector delta) 
+            : base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, properties, modifiers)
+        {
+            Delta = delta;
+        }
     }
 }

+ 3 - 0
src/Avalonia.Input/Raw/RawMouseEventArgs.cs

@@ -17,6 +17,9 @@ namespace Avalonia.Input.Raw
         Move,
         Wheel,
         NonClientLeftButtonDown,
+        TouchBegin,
+        TouchUpdate,
+        TouchEnd
     }
 
     /// <summary>

+ 15 - 0
src/Avalonia.Input/Raw/RawTouchEventArgs.cs

@@ -0,0 +1,15 @@
+namespace Avalonia.Input.Raw
+{
+    public class RawTouchEventArgs : RawMouseEventArgs
+    {
+        public RawTouchEventArgs(IInputDevice device, ulong timestamp, IInputRoot root,
+            RawMouseEventType type, Point position, InputModifiers inputModifiers,
+            long touchPointId) 
+            : base(device, timestamp, root, type, position, inputModifiers)
+        {
+            TouchPointId = touchPointId;
+        }
+
+        public long TouchPointId { get; set; }
+    }
+}

+ 34 - 72
tests/Avalonia.Controls.UnitTests/ButtonTests.cs

@@ -14,6 +14,8 @@ namespace Avalonia.Controls.UnitTests
 {
     public class ButtonTests
     {
+        private MouseTestHelper _helper = new MouseTestHelper();
+        
         [Fact]
         public void Button_Is_Disabled_When_Command_Is_Disabled()
         {
@@ -102,12 +104,8 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void Button_Raises_Click()
         {
-            var mouse = Mock.Of<IMouseDevice>();
             var renderer = Mock.Of<IRenderer>();
-            IInputElement captured = null;
-            Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(50, 50));
-            Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
-            Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
+            var pt = new Point(50, 50);
             Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
                 .Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
                     r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]);
@@ -122,15 +120,15 @@ namespace Avalonia.Controls.UnitTests
 
             target.Click += (s, e) => clicked = true;
 
-            RaisePointerEnter(target, mouse);
-            RaisePointerMove(target, mouse);
-            RaisePointerPressed(target, mouse, 1, MouseButton.Left);
+            RaisePointerEnter(target);
+            RaisePointerMove(target, pt);
+            RaisePointerPressed(target, 1, MouseButton.Left, pt);
 
-            Assert.Equal(captured, target);
+            Assert.Equal(_helper.Captured, target);
 
-            RaisePointerReleased(target, mouse, MouseButton.Left);
+            RaisePointerReleased(target, MouseButton.Left, pt);
 
-            Assert.Equal(captured, null);
+            Assert.Equal(_helper.Captured, null);
 
             Assert.True(clicked);
         }
@@ -138,12 +136,8 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside()
         {
-            var mouse = Mock.Of<IMouseDevice>();
             var renderer = Mock.Of<IRenderer>();
-            IInputElement captured = null;
-            Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(200, 50));
-            Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
-            Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
+            
             Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
                 .Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
                     r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]);
@@ -158,16 +152,16 @@ namespace Avalonia.Controls.UnitTests
 
             target.Click += (s, e) => clicked = true;
 
-            RaisePointerEnter(target, mouse);
-            RaisePointerMove(target, mouse);
-            RaisePointerPressed(target, mouse, 1, MouseButton.Left);
-            RaisePointerLeave(target, mouse);
+            RaisePointerEnter(target);
+            RaisePointerMove(target, new Point(50,50));
+            RaisePointerPressed(target, 1, MouseButton.Left, new Point(50, 50));
+            RaisePointerLeave(target);
 
-            Assert.Equal(captured, target);
+            Assert.Equal(_helper.Captured, target);
 
-            RaisePointerReleased(target, mouse, MouseButton.Left);
+            RaisePointerReleased(target, MouseButton.Left, new Point(200, 50));
 
-            Assert.Equal(captured, null);
+            Assert.Equal(_helper.Captured, null);
 
             Assert.False(clicked);
         }
@@ -175,12 +169,8 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void Button_With_RenderTransform_Raises_Click()
         {
-            var mouse = Mock.Of<IMouseDevice>();
             var renderer = Mock.Of<IRenderer>();
-            IInputElement captured = null;
-            Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(150, 50));
-            Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
-            Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
+            var pt = new Point(150, 50);
             Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
                 .Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
                     r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ?
@@ -204,15 +194,15 @@ namespace Avalonia.Controls.UnitTests
 
             target.Click += (s, e) => clicked = true;
 
-            RaisePointerEnter(target, mouse);
-            RaisePointerMove(target, mouse);
-            RaisePointerPressed(target, mouse, 1, MouseButton.Left);
+            RaisePointerEnter(target);
+            RaisePointerMove(target, pt);
+            RaisePointerPressed(target, 1, MouseButton.Left, pt);
 
-            Assert.Equal(captured, target);
+            Assert.Equal(_helper.Captured, target);
 
-            RaisePointerReleased(target, mouse, MouseButton.Left);
+            RaisePointerReleased(target, MouseButton.Left, pt);
 
-            Assert.Equal(captured, null);
+            Assert.Equal(_helper.Captured, null);
 
             Assert.True(clicked);
         }
@@ -278,57 +268,29 @@ namespace Avalonia.Controls.UnitTests
             public PixelPoint PointToScreen(Point p) => throw new NotImplementedException();
         }
 
-        private void RaisePointerPressed(Button button, IMouseDevice device, int clickCount, MouseButton mouseButton)
+        private void RaisePointerPressed(Button button, int clickCount, MouseButton mouseButton, Point position)
         {
-            button.RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                Source = button,
-                MouseButton = mouseButton,
-                ClickCount = clickCount,
-                Device = device,
-            });
+            _helper.Down(button, mouseButton, position, clickCount: clickCount);
         }
 
-        private void RaisePointerReleased(Button button, IMouseDevice device, MouseButton mouseButton)
+        private void RaisePointerReleased(Button button, MouseButton mouseButton, Point pt)
         {
-            button.RaiseEvent(new PointerReleasedEventArgs
-            {
-                RoutedEvent = InputElement.PointerReleasedEvent,
-                Source = button,
-                MouseButton = mouseButton,
-                Device = device,
-            });
+            _helper.Up(button, mouseButton, pt);
         }
 
-        private void RaisePointerEnter(Button button, IMouseDevice device)
+        private void RaisePointerEnter(Button button)
         {
-            button.RaiseEvent(new PointerEventArgs
-            {
-                RoutedEvent = InputElement.PointerEnterEvent,
-                Source = button,
-                Device = device,
-            });
+            _helper.Enter(button);
         }
 
-        private void RaisePointerLeave(Button button, IMouseDevice device)
+        private void RaisePointerLeave(Button button)
         {
-            button.RaiseEvent(new PointerEventArgs
-            {
-                RoutedEvent = InputElement.PointerLeaveEvent,
-                Source = button,
-                Device = device,
-            });
+            _helper.Leave(button);
         }
 
-        private void RaisePointerMove(Button button, IMouseDevice device)
+        private void RaisePointerMove(Button button, Point pos)
         {
-            button.RaiseEvent(new PointerEventArgs
-            {
-                RoutedEvent = InputElement.PointerMovedEvent,
-                Source = button,
-                Device = device,
-            });
+            _helper.Move(button, pos);
         }
 
         private class TestCommand : ICommand

+ 5 - 9
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@@ -15,6 +15,8 @@ namespace Avalonia.Controls.UnitTests
 {
     public class ComboBoxTests
     {
+        MouseTestHelper _helper = new MouseTestHelper();
+        
         [Fact]
         public void Clicking_On_Control_Toggles_IsDropDownOpen()
         {
@@ -23,17 +25,11 @@ namespace Avalonia.Controls.UnitTests
                 Items = new[] { "Foo", "Bar" },
             };
 
-            target.RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-            });
-
+            _helper.Down(target);
+            _helper.Up(target);
             Assert.True(target.IsDropDownOpen);
 
-            target.RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-            });
+            _helper.Down(target);
 
             Assert.False(target.IsDropDownOpen);
         }

+ 8 - 35
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@@ -14,6 +14,7 @@ namespace Avalonia.Controls.UnitTests
     public class ContextMenuTests
     {
         private Mock<IPopupImpl> popupImpl;
+        private MouseTestHelper _mouse = new MouseTestHelper();
 
         [Fact]
         public void Clicking_On_Control_Toggles_ContextMenu()
@@ -31,19 +32,11 @@ namespace Avalonia.Controls.UnitTests
 
                 new Window { Content = target };
 
-                target.RaiseEvent(new PointerReleasedEventArgs
-                {
-                    RoutedEvent = InputElement.PointerReleasedEvent,
-                    MouseButton = MouseButton.Right
-                });
+                _mouse.Click(target, MouseButton.Right);
 
                 Assert.True(sut.IsOpen);
 
-                target.RaiseEvent(new PointerReleasedEventArgs
-                {
-                    RoutedEvent = InputElement.PointerReleasedEvent,
-                    MouseButton = MouseButton.None
-                });
+                _mouse.Click(target);
 
                 Assert.False(sut.IsOpen);
                 popupImpl.Verify(x => x.Show(), Times.Once);
@@ -69,19 +62,11 @@ namespace Avalonia.Controls.UnitTests
 
                 Avalonia.Application.Current.MainWindow = window;
 
-                target.RaiseEvent(new PointerReleasedEventArgs
-                {
-                    RoutedEvent = InputElement.PointerReleasedEvent,
-                    MouseButton = MouseButton.Right
-                });
+                _mouse.Click(target, MouseButton.Right);
 
                 Assert.True(sut.IsOpen);
 
-                target.RaiseEvent(new PointerReleasedEventArgs
-                {
-                    RoutedEvent = InputElement.PointerReleasedEvent,
-                    MouseButton = MouseButton.Right
-                });
+                _mouse.Click(target, MouseButton.Right);
 
                 Assert.True(sut.IsOpen);
                 popupImpl.Verify(x => x.Hide(), Times.Once);
@@ -106,11 +91,7 @@ namespace Avalonia.Controls.UnitTests
 
                 sut.ContextMenuOpening += (c, e) => { eventCalled = true; e.Cancel = true; };
 
-                target.RaiseEvent(new PointerReleasedEventArgs
-                {
-                    RoutedEvent = InputElement.PointerReleasedEvent,
-                    MouseButton = MouseButton.Right
-                });
+                _mouse.Click(target, MouseButton.Right);
 
                 Assert.True(eventCalled);
                 Assert.False(sut.IsOpen);
@@ -136,19 +117,11 @@ namespace Avalonia.Controls.UnitTests
 
                 sut.ContextMenuClosing += (c, e) => { eventCalled = true; e.Cancel = true; };
 
-                target.RaiseEvent(new PointerReleasedEventArgs
-                {
-                    RoutedEvent = InputElement.PointerReleasedEvent,
-                    MouseButton = MouseButton.Right
-                });
+                _mouse.Click(target, MouseButton.Right);
 
                 Assert.True(sut.IsOpen);
 
-                target.RaiseEvent(new PointerReleasedEventArgs
-                {
-                    RoutedEvent = InputElement.PointerReleasedEvent,
-                    MouseButton = MouseButton.None
-                });
+                _mouse.Click(target, MouseButton.Right);
 
                 Assert.True(eventCalled);
                 Assert.True(sut.IsOpen);

+ 3 - 6
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@@ -16,6 +16,8 @@ namespace Avalonia.Controls.UnitTests
 {
     public class ListBoxTests
     {
+        private MouseTestHelper _mouse = new MouseTestHelper();
+        
         [Fact]
         public void Should_Use_ItemTemplate_To_Create_Item_Content()
         {
@@ -225,12 +227,7 @@ namespace Avalonia.Controls.UnitTests
 
         private void RaisePressedEvent(ListBox listBox, ListBoxItem item, MouseButton mouseButton)
         {
-            listBox.RaiseEvent(new PointerPressedEventArgs
-            {
-                Source = item,
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = mouseButton
-            });
+            _mouse.Click(listBox, item, mouseButton);
         }
 
         [Fact]

+ 9 - 32
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@@ -18,6 +18,8 @@ namespace Avalonia.Controls.UnitTests
 {
     public class ListBoxTests_Single
     {
+        MouseTestHelper _mouse = new MouseTestHelper();
+        
         [Fact]
         public void Focusing_Item_With_Tab_Should_Not_Select_It()
         {
@@ -68,12 +70,7 @@ namespace Avalonia.Controls.UnitTests
             };
 
             ApplyTemplate(target);
-
-            target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-            });
+            _mouse.Click(target.Presenter.Panel.Children[0]);
 
             Assert.Equal(0, target.SelectedIndex);
         }
@@ -90,11 +87,7 @@ namespace Avalonia.Controls.UnitTests
             ApplyTemplate(target);
             target.SelectedIndex = 0;
 
-            target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-            });
+            _mouse.Click(target.Presenter.Panel.Children[0]);
 
             Assert.Equal(0, target.SelectedIndex);
         }
@@ -111,11 +104,7 @@ namespace Avalonia.Controls.UnitTests
 
             ApplyTemplate(target);
 
-            target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-            });
+            _mouse.Click(target.Presenter.Panel.Children[0]);
 
             Assert.Equal(0, target.SelectedIndex);
         }
@@ -133,11 +122,7 @@ namespace Avalonia.Controls.UnitTests
             ApplyTemplate(target);
             target.SelectedIndex = 0;
 
-            target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-            });
+            _mouse.Click(target.Presenter.Panel.Children[0]);
 
             Assert.Equal(-1, target.SelectedIndex);
         }
@@ -155,11 +140,7 @@ namespace Avalonia.Controls.UnitTests
             ApplyTemplate(target);
             target.SelectedIndex = 0;
 
-            target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-            });
+            _mouse.Click(target.Presenter.Panel.Children[0]);
 
             Assert.Equal(0, target.SelectedIndex);
         }
@@ -177,11 +158,7 @@ namespace Avalonia.Controls.UnitTests
             ApplyTemplate(target);
             target.SelectedIndex = 1;
 
-            target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-            });
+            _mouse.Click(target.Presenter.Panel.Children[0]);
 
             Assert.Equal(0, target.SelectedIndex);
         }
@@ -306,4 +283,4 @@ namespace Avalonia.Controls.UnitTests
             target.Presenter.ApplyTemplate();
         }
     }
-}
+}

+ 114 - 0
tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs

@@ -0,0 +1,114 @@
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.UnitTests
+{
+    public class MouseTestHelper
+    {
+
+        class TestPointer : IPointer
+        {
+            public void Capture(IInputElement control)
+            {
+                Captured = control;
+            }
+
+            public IInputElement Captured { get; set; }
+            public PointerType Type => PointerType.Mouse;
+            public bool IsPrimary => true;
+        }
+        
+        TestPointer _pointer = new TestPointer();
+
+        private InputModifiers _pressedButtons;
+        public IInputElement Captured => _pointer.Captured;
+
+        InputModifiers Convert(MouseButton mouseButton)
+            => (mouseButton == MouseButton.Left ? InputModifiers.LeftMouseButton
+                : mouseButton == MouseButton.Middle ? InputModifiers.MiddleMouseButton
+                : mouseButton == MouseButton.Right ? InputModifiers.RightMouseButton : InputModifiers.None);
+        
+        int ButtonCount(PointerPointProperties props)
+        {
+            var rv = 0;
+            if (props.IsLeftButtonPressed)
+                rv++;
+            if (props.IsMiddleButtonPressed)
+                rv++;
+            if (props.IsRightButtonPressed)
+                rv++;
+            return rv;
+        }
+
+        private MouseButton _pressedButton;
+
+        InputModifiers GetModifiers(InputModifiers modifiers) => modifiers | _pressedButtons;
+        
+        public void Down(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default,
+            InputModifiers modifiers = default, int clickCount = 1)
+            => Down(target, target, mouseButton, position, modifiers, clickCount);
+        
+        public void Down(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left, 
+            Point position = default, InputModifiers modifiers = default, int clickCount = 1)
+        {
+            _pressedButtons |= Convert(mouseButton);
+            var props = new PointerPointProperties(_pressedButtons);
+            if (ButtonCount(props) > 1)
+                Move(target, source, position);
+            else
+            {
+                _pressedButton = mouseButton;
+                target.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (IVisual)source, position, props,
+                    GetModifiers(modifiers), clickCount));
+            }
+        }
+
+        public void Move(IInteractive target, in Point position, InputModifiers modifiers = default) => Move(target, target, position, modifiers);
+        public void Move(IInteractive target, IInteractive source, in Point position, InputModifiers modifiers = default)
+        {
+            target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (IVisual)target, position,
+                new PointerPointProperties(_pressedButtons), GetModifiers(modifiers)));
+        }
+
+        public void Up(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default,
+            InputModifiers modifiers = default)
+            => Up(target, target, mouseButton, position, modifiers);
+        
+        public void Up(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left,
+            Point position = default, InputModifiers modifiers = default)
+        {
+            var conv = Convert(mouseButton);
+            _pressedButtons = (_pressedButtons | conv) ^ conv;
+            var props = new PointerPointProperties(_pressedButtons);
+            if (ButtonCount(props) == 0)
+                target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position, props,
+                    GetModifiers(modifiers), _pressedButton));
+            else
+                Move(target, source, position);
+        }
+
+        public void Click(IInteractive target, MouseButton button = MouseButton.Left, Point position = default,
+            InputModifiers modifiers = default)
+            => Click(target, target, button, position, modifiers);
+        public void Click(IInteractive target, IInteractive source, MouseButton button = MouseButton.Left, 
+            Point position = default, InputModifiers modifiers = default)
+        {
+            Down(target, source, button, position, modifiers);
+            Up(target, source, button, position, modifiers);
+        }
+        
+        public void Enter(IInteractive target)
+        {
+            target.RaiseEvent(new PointerEventArgs(InputElement.PointerEnterEvent, target, _pointer, (IVisual)target, default,
+                new PointerPointProperties(_pressedButtons), _pressedButtons));
+        }
+
+        public void Leave(IInteractive target)
+        {
+            target.RaiseEvent(new PointerEventArgs(InputElement.PointerLeaveEvent, target, _pointer, (IVisual)target, default,
+                new PointerPointProperties(_pressedButtons), _pressedButtons));
+        }
+
+    }
+}

+ 38 - 14
tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs

@@ -2,6 +2,7 @@
 using Avalonia.Controls.Platform;
 using Avalonia.Input;
 using Avalonia.Interactivity;
+using Avalonia.VisualTree;
 using Moq;
 using Xunit;
 
@@ -9,6 +10,28 @@ namespace Avalonia.Controls.UnitTests.Platform
 {
     public class DefaultMenuInteractionHandlerTests
     {
+        class FakePointer : IPointer
+        {
+            public void Capture(IInputElement control)
+            {
+                Captured = control;
+            }
+
+            public IInputElement Captured { get; set; }
+            public PointerType Type { get; }
+            public bool IsPrimary { get; } = true;
+        }
+        
+        static PointerEventArgs CreateArgs(RoutedEvent ev, IInteractive source) 
+            => new PointerEventArgs(ev, source, new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default);
+
+        static PointerPressedEventArgs CreatePressed(IInteractive source) => new PointerPressedEventArgs(source,
+            new FakePointer(), (IVisual)source, default, new PointerPointProperties {IsLeftButtonPressed = true},
+            default);
+        
+        static PointerReleasedEventArgs CreateReleased(IInteractive source) => new PointerReleasedEventArgs(source,
+            new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default, MouseButton.Left);
+        
         public class TopLevel
         {
             [Fact]
@@ -121,7 +144,8 @@ namespace Avalonia.Controls.UnitTests.Platform
                     x.IsTopLevel == true && 
                     x.HasSubMenu == true &&
                     x.Parent == menu);
-                var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item };
+
+                var e = CreatePressed(item);
 
                 target.PointerPressed(item, e);
                 Mock.Get(menu).Verify(x => x.Close());
@@ -141,7 +165,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                     x.IsTopLevel == true &&
                     x.HasSubMenu == true &&
                     x.Parent == menu.Object);
-                var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = nextItem };
+                var e = CreateArgs(MenuItem.PointerEnterItemEvent, nextItem);
 
                 menu.SetupGet(x => x.SelectedItem).Returns(item);
 
@@ -161,7 +185,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var target = new DefaultMenuInteractionHandler(false);
                 var menu = new Mock<IMenu>();
                 var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object);
-                var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+                var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
 
                 menu.SetupGet(x => x.SelectedItem).Returns(item);
                 target.PointerLeave(item, e);
@@ -176,7 +200,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var target = new DefaultMenuInteractionHandler(false);
                 var menu = new Mock<IMenu>();
                 var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object);
-                var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+                var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
 
                 menu.SetupGet(x => x.IsOpen).Returns(true);
                 menu.SetupGet(x => x.SelectedItem).Returns(item);
@@ -330,7 +354,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var menu = Mock.Of<IMenu>();
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
-                var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
+                var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
 
                 target.PointerEnter(item, e);
 
@@ -346,7 +370,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var menu = Mock.Of<IMenu>();
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
-                var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
+                var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
 
                 target.PointerEnter(item, e);
                 Mock.Get(item).Verify(x => x.Open(), Times.Never);
@@ -366,7 +390,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
                 var sibling = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsSubMenuOpen == true);
-                var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
+                var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
 
                 Mock.Get(parentItem).SetupGet(x => x.SubItems).Returns(new[] { item, sibling });
 
@@ -386,7 +410,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var menu = Mock.Of<IMenu>();
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
-                var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+                var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
 
                 Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(item);
                 target.PointerLeave(item, e);
@@ -403,7 +427,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
                 var sibling = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
-                var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+                var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
 
                 Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(sibling);
                 target.PointerLeave(item, e);
@@ -419,7 +443,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var menu = Mock.Of<IMenu>();
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsPointerOverSubMenu == true);
-                var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+                var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
 
                 target.PointerLeave(item, e);
 
@@ -434,7 +458,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var menu = Mock.Of<IMenu>();
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
-                var e = new PointerReleasedEventArgs { MouseButton = MouseButton.Left, Source = item };
+                var e = CreateReleased(item);
 
                 target.PointerReleased(item, e);
 
@@ -452,8 +476,8 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
                 var childItem = Mock.Of<IMenuItem>(x => x.Parent == item);
-                var enter = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
-                var leave = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+                var enter = CreateArgs(MenuItem.PointerEnterItemEvent, item);
+                var leave = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
 
                 // Pointer enters item; item is selected.
                 target.PointerEnter(item, enter);
@@ -488,7 +512,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 var menu = Mock.Of<IMenu>();
                 var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
                 var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
-                var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item };
+                var e = CreatePressed(item);
 
                 target.PointerPressed(item, e);
 

+ 4 - 11
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@@ -22,6 +22,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
 {
     public class SelectingItemsControlTests
     {
+        private MouseTestHelper _helper = new MouseTestHelper();
+        
         [Fact]
         public void SelectedIndex_Should_Initially_Be_Minus_1()
         {
@@ -675,12 +677,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
 
             target.ApplyTemplate();
             target.Presenter.ApplyTemplate();
-
-            target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-            });
+            _helper.Down((Interactive)target.Presenter.Panel.Children[1]);
 
             var panel = target.Presenter.Panel;
 
@@ -703,11 +700,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
             target.ApplyTemplate();
             target.Presenter.ApplyTemplate();
 
-            target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-            });
+            _helper.Down(target.Presenter.Panel.Children[1]);
 
             items.RemoveAt(1);
 

+ 35 - 54
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@@ -19,6 +19,8 @@ namespace Avalonia.Controls.UnitTests
 {
     public class TreeViewTests
     {
+        MouseTestHelper _mouse = new MouseTestHelper();
+        
         [Fact]
         public void Items_Should_Be_Created()
         {
@@ -130,11 +132,7 @@ namespace Avalonia.Controls.UnitTests
 
             Assert.NotNull(container);
 
-            container.RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-            });
+            _mouse.Click(container);
 
             Assert.Equal(item, target.SelectedItem);
             Assert.True(container.IsSelected);
@@ -165,12 +163,7 @@ namespace Avalonia.Controls.UnitTests
 
             Assert.True(container.IsSelected);
 
-            container.RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-                InputModifiers = InputModifiers.Control
-            });
+            _mouse.Click(container, modifiers: InputModifiers.Control);
 
             Assert.Null(target.SelectedItem);
             Assert.False(container.IsSelected);
@@ -205,13 +198,8 @@ namespace Avalonia.Controls.UnitTests
 
             Assert.True(container1.IsSelected);
 
-            container2.RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-                InputModifiers = InputModifiers.Control
-            });
-
+            _mouse.Click(container2, modifiers: InputModifiers.Control);
+            
             Assert.Equal(item2, target.SelectedItem);
             Assert.False(container1.IsSelected);
             Assert.True(container2.IsSelected);
@@ -242,15 +230,15 @@ namespace Avalonia.Controls.UnitTests
             var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
             var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
 
-            TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control);
+            ClickContainer(item1Container, InputModifiers.Control);
             Assert.True(item1Container.IsSelected);
 
-            TreeTestHelper.ClickContainer(item2Container, InputModifiers.Control);
+            ClickContainer(item2Container, InputModifiers.Control);
             Assert.True(item2Container.IsSelected);
 
             Assert.Equal(new[] {item1, item2}, target.SelectedItems.OfType<Node>());
 
-            TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control);
+            ClickContainer(item1Container, InputModifiers.Control);
             Assert.False(item1Container.IsSelected);
 
             Assert.DoesNotContain(item1, target.SelectedItems.OfType<Node>());
@@ -281,12 +269,12 @@ namespace Avalonia.Controls.UnitTests
             var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
             var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
 
-            TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
+            ClickContainer(fromContainer, InputModifiers.None);
 
             Assert.True(fromContainer.IsSelected);
 
-            TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
-            TreeTestHelper.AssertChildrenSelected(target, rootNode);
+            ClickContainer(toContainer, InputModifiers.Shift);
+            AssertChildrenSelected(target, rootNode);
         }
 
         [Fact]
@@ -314,12 +302,12 @@ namespace Avalonia.Controls.UnitTests
             var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
             var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
 
-            TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
+            ClickContainer(fromContainer, InputModifiers.None);
 
             Assert.True(fromContainer.IsSelected);
 
-            TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
-            TreeTestHelper.AssertChildrenSelected(target, rootNode);
+            ClickContainer(toContainer, InputModifiers.Shift);
+            AssertChildrenSelected(target, rootNode);
         }
 
         [Fact]
@@ -347,12 +335,12 @@ namespace Avalonia.Controls.UnitTests
             var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
             var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
 
-            TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
+            ClickContainer(fromContainer, InputModifiers.None);
 
-            TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
-            TreeTestHelper.AssertChildrenSelected(target, rootNode);
+            ClickContainer(toContainer, InputModifiers.Shift);
+            AssertChildrenSelected(target, rootNode);
 
-            TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
+            ClickContainer(fromContainer, InputModifiers.None);
 
             Assert.True(fromContainer.IsSelected);
 
@@ -656,7 +644,7 @@ namespace Avalonia.Controls.UnitTests
 
                 target.RaiseEvent(keyEvent);
 
-                TreeTestHelper.AssertChildrenSelected(target, rootNode);
+                AssertChildrenSelected(target, rootNode);
             }
         }
 
@@ -687,8 +675,8 @@ namespace Avalonia.Controls.UnitTests
                 var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
                 var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
 
-                TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
-                TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
+                ClickContainer(fromContainer, InputModifiers.None);
+                ClickContainer(toContainer, InputModifiers.Shift);
 
                 var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
                 var selectAllGesture = keymap.SelectAll.First();
@@ -702,7 +690,7 @@ namespace Avalonia.Controls.UnitTests
 
                 target.RaiseEvent(keyEvent);
 
-                TreeTestHelper.AssertChildrenSelected(target, rootNode);
+                AssertChildrenSelected(target, rootNode);
             }
         }
 
@@ -733,8 +721,8 @@ namespace Avalonia.Controls.UnitTests
                 var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
                 var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
 
-                TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
-                TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
+                ClickContainer(fromContainer, InputModifiers.None);
+                ClickContainer(toContainer, InputModifiers.Shift);
 
                 var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
                 var selectAllGesture = keymap.SelectAll.First();
@@ -748,7 +736,7 @@ namespace Avalonia.Controls.UnitTests
 
                 target.RaiseEvent(keyEvent);
 
-                TreeTestHelper.AssertChildrenSelected(target, rootNode);
+                AssertChildrenSelected(target, rootNode);
             }
         }
 
@@ -871,29 +859,22 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
-        private static class TreeTestHelper
+        void ClickContainer(IControl container, InputModifiers modifiers)
         {
-            public static void ClickContainer(IControl container, InputModifiers modifiers)
-            {
-                container.RaiseEvent(new PointerPressedEventArgs
-                {
-                    RoutedEvent = InputElement.PointerPressedEvent,
-                    MouseButton = MouseButton.Left,
-                    InputModifiers = modifiers
-                });
-            }
+            _mouse.Click(container, modifiers: modifiers);
+        }
 
-            public static void AssertChildrenSelected(TreeView treeView, Node rootNode)
+        void AssertChildrenSelected(TreeView treeView, Node rootNode)
+        {
+            foreach (var child in rootNode.Children)
             {
-                foreach (var child in rootNode.Children)
-                {
-                    var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child);
+                var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child);
 
-                    Assert.True(container.IsSelected);
-                }
+                Assert.True(container.IsSelected);
             }
         }
 
+
         private class Node : NotifyingBase
         {
             private IAvaloniaList<Node> _children;

+ 2 - 0
tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj

@@ -4,6 +4,7 @@
     <TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
     <OutputType>Library</OutputType>
     <IsTestProject>true</IsTestProject>
+    <LangVersion>latest</LangVersion>
   </PropertyGroup>
   <Import Project="..\..\build\UnitTests.NetCore.targets" />
   <Import Project="..\..\build\UnitTests.NetFX.props" />
@@ -19,6 +20,7 @@
     <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
+    <Compile Include="..\Avalonia.Controls.UnitTests\MouseTestHelper.cs" />
   </ItemGroup>
   <ItemGroup>
     <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />

+ 7 - 7
tests/Avalonia.Interactivity.UnitTests/GestureTests.cs

@@ -3,6 +3,7 @@
 
 using System.Collections.Generic;
 using Avalonia.Controls;
+using Avalonia.Controls.UnitTests;
 using Avalonia.Input;
 using Xunit;
 
@@ -10,6 +11,8 @@ namespace Avalonia.Interactivity.UnitTests
 {
     public class GestureTests
     {
+        private MouseTestHelper _mouse = new MouseTestHelper();
+        
         [Fact]
         public void Tapped_Should_Follow_Pointer_Pressed_Released()
         {
@@ -27,8 +30,7 @@ namespace Avalonia.Interactivity.UnitTests
             border.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("br"));
             border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
 
-            border.RaiseEvent(new PointerPressedEventArgs());
-            border.RaiseEvent(new PointerReleasedEventArgs());
+            _mouse.Click(border);
 
             Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt" }, result);
         }
@@ -47,8 +49,7 @@ namespace Avalonia.Interactivity.UnitTests
             decorator.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("dt"));
             border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
 
-            border.RaiseEvent(new PointerPressedEventArgs());
-            border.RaiseEvent(new PointerReleasedEventArgs());
+            _mouse.Click(border);
 
             Assert.Equal(new[] { "bt", "dt" }, result);
         }
@@ -72,9 +73,8 @@ namespace Avalonia.Interactivity.UnitTests
             border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
             border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt"));
 
-            border.RaiseEvent(new PointerPressedEventArgs());
-            border.RaiseEvent(new PointerReleasedEventArgs());
-            border.RaiseEvent(new PointerPressedEventArgs { ClickCount = 2 });
+            _mouse.Click(border);
+            _mouse.Down(border, clickCount: 2);
 
             Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt", "bp", "dp", "bdt", "ddt" }, result);
         }

+ 7 - 6
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@@ -137,8 +137,9 @@ namespace Avalonia.Markup.Xaml.UnitTests
                     .GetVisualChildren().First()
                     .GetVisualChildren().First()
                     .GetVisualChildren().First();
-                ((Control)item).RaiseEvent(new PointerPressedEventArgs {ClickCount = 20});
-                Assert.Equal(20, w.Args.ClickCount);
+
+                ((Control)item).DataContext = "test";
+                Assert.Equal("test", w.SavedContext);
             }
         }
         
@@ -161,10 +162,10 @@ namespace Avalonia.Markup.Xaml.UnitTests
     
     public class XamlIlBugTestsEventHandlerCodeBehind : Window
     {
-        public PointerPressedEventArgs Args;
-        public void HandlePointerPressed(object sender, PointerPressedEventArgs args)
+        public object SavedContext;
+        public void HandleDataContextChanged(object sender, EventArgs args)
         {
-            Args = args;
+            SavedContext = ((Control)sender).DataContext;
         }
 
         public XamlIlBugTestsEventHandlerCodeBehind()
@@ -178,7 +179,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
   <ItemsControl>
     <ItemsControl.ItemTemplate>
       <DataTemplate>
-        <Button PointerPressed='HandlePointerPressed' Content='{Binding .}' />
+        <Button DataContextChanged='HandleDataContextChanged' Content='{Binding .}' />
       </DataTemplate>
     </ItemsControl.ItemTemplate>
   </ItemsControl>