Я пытаюсь реализовать выбор с помощью резиновой ленты объектов пути WPF на холсте. К сожалению, мое использование VisualTreeHelper.HitTest с геометрией прямоугольника не работает, как я ожидаю.
Я ожидаю получить удар, только когда какая-то часть моего прямоугольника с резинкой пересекает траекторию линии. Но с прямоугольником, каждый раз, когда мой прямоугольник находится где-нибудь слева или над линией, я получаю удар, даже если я не нахожусь рядом с линией или даже с ее ограничительной рамкой.
IsЕсть ли способ обойти это или что-то очевидное, что я делаю не так?
Я написал простое приложение, чтобы продемонстрировать проблему. Это одна строка и метка. Если мой вызов VisualTreeHelper.HitTest (с использованием прямоугольника с резинкой) обнаруживает, что он находится над формой, я устанавливаю метку внизу для Visible. В противном случае метка свернута.
Здесь я нахожусь прямо над линией и, как я ожидаю, обнаруживает попадание. Это хорошо.
![Successful hit as expected](https://i.stack.imgur.com/xXZ5S.png)
Здесь я ниже линии и никакого удара нет. Это также хорошо
![Below line, no hit](https://i.stack.imgur.com/IGWoK.png)
Но когда я нахожусь где-нибудь слева или над линией, независимо от того, как далеко, я получаю удар
![enter image description here](https://i.stack.imgur.com/ASacz.png)
Вот окно тестового приложения:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
Title="MainWindow" Height="500" Width="525">
<Window.Resources>
<LineGeometry x:Key="LineGeo" StartPoint="50, 100" EndPoint="200, 75"/>
</Window.Resources>
<Canvas
x:Name="MyCanvas"
Background="Yellow"
MouseLeftButtonDown="MyCanvas_OnMouseLeftButtonDown"
MouseMove="MyCanvas_OnMouseMove"
MouseLeftButtonUp="MyCanvas_OnMouseLeftButtonUp"
>
<!-- The line I hit-test -->
<Path x:Name="MyLine" Data="{StaticResource LineGeo}"
Stroke="Black" StrokeThickness="5" Tag="1234" />
<!-- This label's is hidden by default and only shows up when code-behind sets it to Visible -->
<Label x:Name="MyLabel" Canvas.Left="100" Canvas.Top="200"
Content="HIT DETECTED!!!" FontSize="25" FontWeight="Bold"
Visibility="{x:Static Visibility.Collapsed}"/>
</Canvas>
</Window>
А вот обработчики кода мыши с кодом HitTest
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow() => InitializeComponent();
private Point _startPosition;
Path _path;
private RectangleGeometry _rectGeo;
private static readonly SolidColorBrush _brush = new SolidColorBrush(Colors.BlueViolet) { Opacity=0.3 };
private void MyCanvas_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
MyCanvas.CaptureMouse();
_startPosition = e.GetPosition(MyCanvas);
// Create the visible selection rect and add it to the canvas
_rectGeo = new RectangleGeometry();
_rectGeo.Rect = new Rect(_startPosition, _startPosition);
_path = new Path()
{
Data = _rectGeo,
Fill =_brush,
StrokeThickness = 0,
IsHitTestVisible = false
};
MyCanvas.Children.Add(_path);
}
private void MyCanvas_OnMouseMove(object sender, MouseEventArgs e)
{
// Sanity check
if (e.MouseDevice.LeftButton != MouseButtonState.Pressed ||
null == _path ||
!MyCanvas.IsMouseCaptured)
{
return;
}
e.Handled = true;
// Get the second position for the rect geometry
var curPos = e.GetPosition(MyCanvas);
var rect = new Rect(_startPosition, curPos);
_rectGeo.Rect = rect;
_path.Data = _rectGeo;
// This is set up like a loop because my real production code is looking
// for many shapes.
var paths = new List<Path>();
var htp = new GeometryHitTestParameters(_rectGeo);
var resultCallback = new HitTestResultCallback(r => HitTestResultBehavior.Continue);
var filterCallback = new HitTestFilterCallback(
el =>
{
// Filter accepts any object of type Path. There should be just one
if (el is Path s && s.Tag != null)
paths.Add(s);
return HitTestFilterBehavior.Continue;
});
VisualTreeHelper.HitTest(MyCanvas, filterCallback, resultCallback, htp);
// Set the label visibility based on whether or not we hit the line
var line = paths.FirstOrDefault();
MyLabel.Visibility = ReferenceEquals(line, MyLine) ? Visibility.Visible : Visibility.Collapsed;
}
private void MyCanvas_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (null == _path)
return;
e.Handled = true;
MyLabel.Visibility = Visibility.Collapsed;
MyCanvas.Children.Remove(_path);
_path = null;
if (MyCanvas.IsMouseCaptured)
MyCanvas.ReleaseMouseCapture();
}
}
}