キャンバスにいくつかのドットを描画し、キャンバス上のmouseUpまで、任意のドット内でmouseDownが発生した後、マウスの動きを追跡する小さなWPFアプリがあります。私の問題は、ドット内のmouseDownsがキャンバスにバブリングするのを防ぐ方法です。キャンバスのmouseDownオブザーバーは、mouseDownsがドットの外側で発生したときにのみ起動し、dot mouseDownオブザーバーは、mouseDownsがドットの内側で発生したときにのみ起動するようにします。
監視可能なチェーンを構築するための私のテクニックは次のとおりです。DownInAndTrackWrtObservableの3番目の引数がtrueの場合、トリガーとなるmouseDownイベントにEventArgs.Handledプロパティを設定することを示しています。
private IObservable<BasedVector> DownInAndTrackWrtObservable(
UIElement hitTestElement,
UIElement trackWrtElement,
bool handleItP = false)
{
var hitObs = hitTestElement.GetLeftMouseDownObservable();
var trackObs = from mDown in hitObs
let mDownP = mDown.EventArgs.GetPosition(hitTestElement)
from mMove in trackWrtElement.GetMouseMoveObservable().
TakeUntil(trackWrtElement.GetLeftMouseUpObservable())
select new {D = mDown, P = mDownP, M = mMove};
if (handleItP)
trackObs.Subscribe(e =>
e.D.EventArgs.Handled = true);
return from obs in trackObs
select new BasedVector
(
tail: obs.P,
head: obs.M.EventArgs.GetPosition(trackWrtElement)
);
}
次に、2つの場所にハンドラーを設定します。1つは、キャンバス内のヒットとドットおよびドラッグを追跡します。
DownInAndTrackWrtObservable(ellipse, canvas1, handleItP:true).Subscribe(bv =>
{
PointMouseDownTextBox.Text =
String.Format("Point {2} MouseDown({0:G}, {1:G})", bv.Tail.X, bv.Tail.Y, ellipse.Uid);
CanvasMouseMoveTextBox.Text =
String.Format("Canvas MouseMove({0:G}, {1:G})", bv.Head.X, bv.Head.Y);
});
と
DownInAndTrackWrtObservable(canvas1, canvas1).Subscribe(bv =>
{
CanvasMouseDownTextBox.Text =
String.Format("Canvas MouseDown({0:G}, {1:G})", bv.Tail.X, bv.Tail.Y);
CanvasMouseMoveTextBox.Text =
String.Format("Canvas MouseMove({0:G}, {1:G})", bv.Head.X, bv.Head.Y);
});
問題は、ドットでmouseDownが発生した場合でも、2番目のハンドラーに示されているようにCanvasのmouseDownが検出されることです。しかし、Event.Handledプロパティを設定しているため、これが発生することはないと思います。ところで、私がEvent.HandledをtrackObsではなくhitObsに設定した場合、ヒットはドット内でまったく検出されません。これがXAMLを含む完全なアプリです(System.coreex、System.Reactive、およびSystem.Interactiveに「参照を追加」してください)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Shapes;
namespace WpfTest
{
public static partial class UIElementExtensions
{
public static IObservable<IEvent<MouseButtonEventArgs>>
GetLeftMouseDownObservable(this UIElement uiElement)
{
return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
h => new MouseButtonEventHandler(h),
h => uiElement.MouseLeftButtonDown += h,
h => uiElement.MouseLeftButtonDown -= h);
}
public static IObservable<IEvent<MouseButtonEventArgs>>
GetLeftMouseUpObservable(this UIElement uiElement)
{
return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
h => new MouseButtonEventHandler(h),
h => uiElement.MouseLeftButtonUp += h,
h => uiElement.MouseLeftButtonUp -= h);
}
public static IObservable<IEvent<MouseEventArgs>>
GetMouseMoveObservable(this UIElement uiElement)
{
return Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
h => new MouseEventHandler(h),
h => uiElement.MouseMove += h,
h => uiElement.MouseMove -= h);
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Draw();
}
private static double scaleCanvasPerWorld = 150D;
private static Point offsetInCanvas = new Point(15D, 15D);
private static Point worldOrigin = new Point();
private static Point WorldToCanvas(Point worldPoint)
{
// Difference of two points gives a System.Drawing.Vector, which has operator overloads.
var result = ((worldPoint - worldOrigin) * scaleCanvasPerWorld + offsetInCanvas);
return result;
}
private static Point CanvasToWorld(Point canvasPoint)
{
var result = ((canvasPoint - offsetInCanvas) / scaleCanvasPerWorld);
return (Point)result;
}
private void DrawPoint(Point worldPoint)
{
var ellipse = new Ellipse();
ellipse.Stroke = System.Windows.Media.Brushes.Black;
ellipse.Fill = System.Windows.Media.Brushes.Black;
ellipse.HorizontalAlignment = HorizontalAlignment.Center;
ellipse.VerticalAlignment = VerticalAlignment.Center;
ellipse.Width = 12;
ellipse.Height = 12;
var canvasPoint = WorldToCanvas(worldPoint);
ellipse.SetValue(Canvas.LeftProperty, canvasPoint.X);
ellipse.SetValue(Canvas.TopProperty, canvasPoint.Y);
DownInAndTrackWrtObservable(ellipse, canvas1, handleItP: true).Subscribe(bv =>
{
PointMouseDownTextBox.Text =
String.Format("Point {2} MouseDown({0:G}, {1:G})", bv.Tail.X, bv.Tail.Y, ellipse.Uid);
CanvasMouseMoveTextBox.Text =
String.Format("Canvas MouseMove({0:G}, {1:G})", bv.Head.X, bv.Head.Y);
});
canvas1.Children.Add(ellipse);
}
private class BasedVector
{
public Point Tail { get; set; }
public Point Head { get; set; }
public Vector Vector { get; set; }
public BasedVector(Point tail, Point head)
{
Debug.Assert(tail != null);
Debug.Assert(head != null);
this.Tail = tail;
this.Head = head;
this.Vector = head - tail;
}
}
private IObservable<BasedVector> DownInAndTrackWrtObservable(
UIElement hitTestElement,
UIElement trackWrtElement,
bool handleItP = false)
{
var hitObs = hitTestElement.GetLeftMouseDownObservable();
var trackObs = from mDown in hitObs
let mDownP = mDown.EventArgs.GetPosition(hitTestElement)
from mMove in trackWrtElement.GetMouseMoveObservable().
TakeUntil(trackWrtElement.GetLeftMouseUpObservable())
select new { D = mDown, P = mDownP, M = mMove };
if (handleItP)
trackObs.Subscribe(e =>
e.D.EventArgs.Handled = true);
return from obs in trackObs
select new BasedVector
(
tail: obs.P,
head: obs.M.EventArgs.GetPosition(trackWrtElement)
);
}
private void Draw()
{
var controlPoints = new Point[]
{
new Point(0D, 0D),
new Point(0D, 1D),
new Point(0.5D, 1D),
new Point(1.5D, 0D),
new Point(2D, 0D),
new Point(2D, 1D),
};
controlPoints.Run(DrawPoint);
DownInAndTrackWrtObservable(canvas1, canvas1).Subscribe(bv =>
{
CanvasMouseDownTextBox.Text =
String.Format("Canvas MouseDown({0:G}, {1:G})", bv.Tail.X, bv.Tail.Y);
CanvasMouseMoveTextBox.Text =
String.Format("Canvas MouseMove({0:G}, {1:G})", bv.Head.X, bv.Head.Y);
});
}
}
}
およびXAML
<Window x:Class="WpfTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="350" Width="522">
<Grid Height="313" Name="grid1" Width="500" >
<Canvas Height="237" HorizontalAlignment="Left" Margin="0,75,0,0" Name="canvas1" VerticalAlignment="Top" Width="500" Background="#00000000" />
<StackPanel Height="52" HorizontalAlignment="Left" Margin="12,12,0,0" Name="stackPanel1" VerticalAlignment="Top" Width="200">
<TextBox Height="23" Name="CanvasMouseDownTextBox" Width="186" BorderBrush="Blue" />
<TextBox Height="23" Name="PointMouseDownTextBox" Width="186" BorderBrush="Blue" />
</StackPanel>
<StackPanel Height="52" HorizontalAlignment="Left" Margin="211,12,0,0" Name="stackPanel2" VerticalAlignment="Top" Width="200">
<TextBox Height="23" Name="CanvasMouseMoveTextBox" Width="186" BorderBrush="Blue" />
</StackPanel>
</Grid>
</Window>
アドバイスをいただければ幸いです