WPFでマウスドラッグイベント
WPFにはデフォルトでドラッグのイベントは用意されていません。
今回はドラッグ用のイベントを実装してみたいと思います。
記事内で利用しているObservableクラス、Disposeクラス、Subscribe関数は下記記事を参照してください。
C# eventをIObservableに変換 - 何でもプログラミング
MouseDragArgs
ドラッグイベント時に渡されるクラスを作成します。
public enum MouseDragPhase { Begin, Move, End } public class MouseDragArgs { public Point StartPoint { get; } public ModifierKeys StartModifier { get; } public Point Point { get; } public MouseDragPhase Phase { get; } public MouseDragArgs(Point startPoint, ModifierKeys startModifier, Point point, MouseDragPhase phase) { StartPoint = startPoint; StartModifier = startModifier; Point = point; Phase = phase; } }
MouseLeftButtonDragObs
IObservable<MouseDragArgs>を返す拡張メソッドを作成します。
Down、Up等のIObservableとオペレータの組み合わせで記述できなくもないですが、副作用(CaptureMouse等)や状態(isDragging等)を伴うため、手続き的に記述したほうがわかりやすいです。
マウスDown時にMouseCaptureし、要素から外れてもドラッグが継続されるように実装しています。
LostFocusを監視することにより、Up以外(Alt + Tabや右クリックメニュー等)でドラッグが終了しても通知するようにしてあります。
イベントが並走するのを避けるため、他のボタンがDownの状態では開始しないよう実装してあります。
この辺の仕様は適宜カスタマイズしてください。
public static IObservable<MouseDragArgs> MouseLeftButtonDragObs(this FrameworkElement element) => new Observable<MouseDragArgs>(observer => { bool isDragging = false; Point startPoint = new Point(0, 0); ModifierKeys startModifier = ModifierKeys.None; // Down MouseButtonEventHandler down = (s, e) => { if (e.RightButton == MouseButtonState.Pressed || e.MiddleButton == MouseButtonState.Pressed) return; element.CaptureMouse(); isDragging = true; startPoint = e.GetPosition(element); startModifier = Keyboard.Modifiers; observer.OnNext(new MouseDragArgs(startPoint, startModifier, startPoint, MouseDragPhase.Begin)); }; // Move MouseEventHandler move = (s, e) => { if (isDragging) observer.OnNext(new MouseDragArgs(startPoint, startModifier, e.GetPosition(element), MouseDragPhase.Move)); }; // Up MouseButtonEventHandler up = (s, e) => element.ReleaseMouseCapture(); // LostFocus MouseEventHandler lostFocus = (s, e) => { if (isDragging == false) return; isDragging = false; observer.OnNext(new MouseDragArgs(startPoint, startModifier, e.GetPosition(element), MouseDragPhase.End)); }; // イベント登録 element.MouseLeftButtonDown += down; element.MouseMove += move; element.MouseLeftButtonUp += up; element.LostMouseCapture += lostFocus; return new Disposable(() => { // イベント解除 element.MouseLeftButtonDown -= down; element.MouseMove -= move; element.MouseLeftButtonUp -= up; element.LostMouseCapture -= lostFocus; }); });
動作確認
長方形上でのLeftDrag情報をコンソールに表示するアプリケーションになります。
Disposeボタンを押すと、一切出力されなくなります。
Xaml
<Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="175" Width="135"> <Grid> <Rectangle x:Name="rect" Fill="#FFF4F4F5" Stroke="Black" Margin="10,10,10,35"/> <Button x:Name="buttonDispose" Content="Dispose" Margin="10,115,10,10" /> </Grid> </Window>
public MainWindow() { InitializeComponent(); var subscription = rect.MouseLeftButtonDragObs().Subscribe(args => { if (args.Phase == MouseDragPhase.Begin) Console.WriteLine($"begin {args.StartModifier} {args.Point}"); else if (args.Phase == MouseDragPhase.Move) Console.WriteLine($"move {args.StartModifier} {args.StartPoint} {args.Point}"); else Console.WriteLine($"end {args.StartModifier} {args.StartPoint} {args.Point}"); }); buttonDispose.Click += (s, e) => subscription.Dispose(); }