Canvas 上的重叠矩形触发 MouseDown 事件
Firing MouseDown events for overlapping Rectangles on a Canvas
我有一个 WPF window,其中包含一个 Canvas,它在代码中填充了旋转的矩形。每个矩形都有一个 MouseDown 事件,它们的位置将根据用户提供的坐标进行分配。通常两个或更多个会重叠,部分遮挡其下方的矩形。
我需要为鼠标下方的每个矩形触发 MouseDown 事件,即使该矩形被另一个矩形遮挡也是如此,但我只获得最顶部矩形的 MouseDown 事件。
我已经尝试为单击的矩形设置 e.Handled,并通过 Canvas 路由事件,但没有成功,甚至尝试根据以下方法定位鼠标下方的对象它们的坐标,但是矩形的旋转使得难以计算。
public MainWindow()
{
InitializeComponent();
Rectangle r1 = new Rectangle() {Width = 80, Height = 120, Fill = Brushes.Blue };
r1.MouseDown += r_MouseDown;
RotateTransform rt1 = new RotateTransform(60);
r1.RenderTransform = rt1;
Canvas.SetLeft(r1, 150);
Canvas.SetTop(r1, 50);
canvas1.Children.Add(r1);
Rectangle r2 = new Rectangle() { Width = 150, Height = 50, Fill = Brushes.Green };
r2.MouseDown += r_MouseDown;
RotateTransform rt2 = new RotateTransform(15);
r2.RenderTransform = rt2;
Canvas.SetLeft(r2, 100);
Canvas.SetTop(r2, 100);
canvas1.Children.Add(r2);
}
private void r_MouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("Rectangle Clicked");
}
}
还有一个问题与此类似,但没有公认的答案,也不清楚最终的解决方案应该是什么来解决这个问题。让我们看看能不能说得更清楚一点。
首先,下面列出的解决方案将使用 VisualTreeHelper.HitTest
方法来确定鼠标是否单击了您的矩形。 VisualTreeHelper 允许我们找到矩形,即使它们由于 Canvas.SetTop
和各种 .RenderTransform
操作而四处移动。
其次,我们将捕获 canvas 元素上的点击事件,而不是单个矩形上的点击事件。这使我们能够在 canvas 级别处理事情并立即检查所有矩形。
public MainWindow()
{
InitializeComponent();
//Additional rectangle for testing.
Rectangle r3 = new Rectangle() { Width = 175, Height = 80, Fill = Brushes.Goldenrod };
Canvas.SetLeft(r3, 80);
Canvas.SetTop(r3, 80);
canvas1.Children.Add(r3);
Rectangle r1 = new Rectangle() { Width = 80, Height = 120, Fill = Brushes.Blue };
RotateTransform rt1 = new RotateTransform(60);
r1.RenderTransform = rt1;
Canvas.SetLeft(r1, 100);
Canvas.SetTop(r1, 100);
canvas1.Children.Add(r1);
Rectangle r2 = new Rectangle() { Width = 150, Height = 50, Fill = Brushes.Green };
RotateTransform rt2 = new RotateTransform(15);
r2.LayoutTransform = rt2;
Canvas.SetLeft(r2, 100);
Canvas.SetTop(r2, 100);
canvas1.Children.Add(r2);
//Mouse 'click' event.
canvas1.PreviewMouseDown += canvasMouseDown;
}
//list to store the hit test results
private List<HitTestResult> hitResultsList = new List<HitTestResult>();
所使用的HitTest方法是比较复杂的方法,因为该方法的最简单版本只有returns "the topmost"项。最上面的意思是第一个绘制的项目,所以它实际上是视觉上位于矩形堆栈底部的项目。为了获得所有的矩形,我们需要使用如下所示的 HitTest 方法的复杂版本。
private void canvasMouseDown(object sender, MouseButtonEventArgs e)
{
if (canvas1.Children.Count > 0)
{
// Retrieve the coordinates of the mouse position.
Point pt = e.GetPosition((UIElement)sender);
// Clear the contents of the list used for hit test results.
hitResultsList.Clear();
// Set up a callback to receive the hit test result enumeration.
VisualTreeHelper.HitTest(canvas1,
new HitTestFilterCallback(MyHitTestFilter),
new HitTestResultCallback(MyHitTestResult),
new PointHitTestParameters(pt));
// Perform actions on the hit test results list.
if (hitResultsList.Count > 0)
{
string msg = null;
foreach (HitTestResult htr in hitResultsList)
{
Rectangle r = (Rectangle)htr.VisualHit;
msg += r.Fill.ToString() + "\n";
}
//Message displaying the fill colors of all the rectangles
//under the mouse when it was clicked.
MessageBox.Show(msg);
}
}
}
// Filter the hit test values for each object in the enumeration.
private HitTestFilterBehavior MyHitTestFilter(DependencyObject o)
{
// Test for the object value you want to filter.
if (o.GetType() == typeof(Label))
{
// Visual object and descendants are NOT part of hit test results enumeration.
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
}
else
{
// Visual object is part of hit test results enumeration.
return HitTestFilterBehavior.Continue;
}
}
// Add the hit test result to the list of results.
private HitTestResultBehavior MyHitTestResult(HitTestResult result)
{
//Filter out the canvas object.
if (!result.VisualHit.ToString().Contains("Canvas"))
{
hitResultsList.Add(result);
}
// Set the behavior to return visuals at all z-order levels.
return HitTestResultBehavior.Continue;
}
上面的测试示例只是显示了一个消息框,显示了单击鼠标指针时所有矩形的填充颜色;验证 VisualTreeHelper 实际上确实检索了堆栈中的所有矩形。
我有一个 WPF window,其中包含一个 Canvas,它在代码中填充了旋转的矩形。每个矩形都有一个 MouseDown 事件,它们的位置将根据用户提供的坐标进行分配。通常两个或更多个会重叠,部分遮挡其下方的矩形。
我需要为鼠标下方的每个矩形触发 MouseDown 事件,即使该矩形被另一个矩形遮挡也是如此,但我只获得最顶部矩形的 MouseDown 事件。
我已经尝试为单击的矩形设置 e.Handled,并通过 Canvas 路由事件,但没有成功,甚至尝试根据以下方法定位鼠标下方的对象它们的坐标,但是矩形的旋转使得难以计算。
public MainWindow()
{
InitializeComponent();
Rectangle r1 = new Rectangle() {Width = 80, Height = 120, Fill = Brushes.Blue };
r1.MouseDown += r_MouseDown;
RotateTransform rt1 = new RotateTransform(60);
r1.RenderTransform = rt1;
Canvas.SetLeft(r1, 150);
Canvas.SetTop(r1, 50);
canvas1.Children.Add(r1);
Rectangle r2 = new Rectangle() { Width = 150, Height = 50, Fill = Brushes.Green };
r2.MouseDown += r_MouseDown;
RotateTransform rt2 = new RotateTransform(15);
r2.RenderTransform = rt2;
Canvas.SetLeft(r2, 100);
Canvas.SetTop(r2, 100);
canvas1.Children.Add(r2);
}
private void r_MouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("Rectangle Clicked");
}
}
还有一个问题与此类似,但没有公认的答案,也不清楚最终的解决方案应该是什么来解决这个问题。让我们看看能不能说得更清楚一点。
首先,下面列出的解决方案将使用 VisualTreeHelper.HitTest
方法来确定鼠标是否单击了您的矩形。 VisualTreeHelper 允许我们找到矩形,即使它们由于 Canvas.SetTop
和各种 .RenderTransform
操作而四处移动。
其次,我们将捕获 canvas 元素上的点击事件,而不是单个矩形上的点击事件。这使我们能够在 canvas 级别处理事情并立即检查所有矩形。
public MainWindow()
{
InitializeComponent();
//Additional rectangle for testing.
Rectangle r3 = new Rectangle() { Width = 175, Height = 80, Fill = Brushes.Goldenrod };
Canvas.SetLeft(r3, 80);
Canvas.SetTop(r3, 80);
canvas1.Children.Add(r3);
Rectangle r1 = new Rectangle() { Width = 80, Height = 120, Fill = Brushes.Blue };
RotateTransform rt1 = new RotateTransform(60);
r1.RenderTransform = rt1;
Canvas.SetLeft(r1, 100);
Canvas.SetTop(r1, 100);
canvas1.Children.Add(r1);
Rectangle r2 = new Rectangle() { Width = 150, Height = 50, Fill = Brushes.Green };
RotateTransform rt2 = new RotateTransform(15);
r2.LayoutTransform = rt2;
Canvas.SetLeft(r2, 100);
Canvas.SetTop(r2, 100);
canvas1.Children.Add(r2);
//Mouse 'click' event.
canvas1.PreviewMouseDown += canvasMouseDown;
}
//list to store the hit test results
private List<HitTestResult> hitResultsList = new List<HitTestResult>();
所使用的HitTest方法是比较复杂的方法,因为该方法的最简单版本只有returns "the topmost"项。最上面的意思是第一个绘制的项目,所以它实际上是视觉上位于矩形堆栈底部的项目。为了获得所有的矩形,我们需要使用如下所示的 HitTest 方法的复杂版本。
private void canvasMouseDown(object sender, MouseButtonEventArgs e)
{
if (canvas1.Children.Count > 0)
{
// Retrieve the coordinates of the mouse position.
Point pt = e.GetPosition((UIElement)sender);
// Clear the contents of the list used for hit test results.
hitResultsList.Clear();
// Set up a callback to receive the hit test result enumeration.
VisualTreeHelper.HitTest(canvas1,
new HitTestFilterCallback(MyHitTestFilter),
new HitTestResultCallback(MyHitTestResult),
new PointHitTestParameters(pt));
// Perform actions on the hit test results list.
if (hitResultsList.Count > 0)
{
string msg = null;
foreach (HitTestResult htr in hitResultsList)
{
Rectangle r = (Rectangle)htr.VisualHit;
msg += r.Fill.ToString() + "\n";
}
//Message displaying the fill colors of all the rectangles
//under the mouse when it was clicked.
MessageBox.Show(msg);
}
}
}
// Filter the hit test values for each object in the enumeration.
private HitTestFilterBehavior MyHitTestFilter(DependencyObject o)
{
// Test for the object value you want to filter.
if (o.GetType() == typeof(Label))
{
// Visual object and descendants are NOT part of hit test results enumeration.
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
}
else
{
// Visual object is part of hit test results enumeration.
return HitTestFilterBehavior.Continue;
}
}
// Add the hit test result to the list of results.
private HitTestResultBehavior MyHitTestResult(HitTestResult result)
{
//Filter out the canvas object.
if (!result.VisualHit.ToString().Contains("Canvas"))
{
hitResultsList.Add(result);
}
// Set the behavior to return visuals at all z-order levels.
return HitTestResultBehavior.Continue;
}
上面的测试示例只是显示了一个消息框,显示了单击鼠标指针时所有矩形的填充颜色;验证 VisualTreeHelper 实际上确实检索了堆栈中的所有矩形。