У меня есть элемент неправильной формы (форма линии), содержащийся в производном от ContentControl классе («ShapeItem»).Я оформляю его с помощью пользовательского курсора и обрабатываю щелчки мышью в классе ShapeItem.
К сожалению, WPF считает, что мышь находится «над» моим элементом, если она находится где-нибудь в прямоугольной ограничительной рамке ContentControl.Это нормально для замкнутых фигур, таких как прямоугольник или круг, но это проблема для диагональной линии.Рассмотрим это изображение, на котором представлены 3 такие фигуры и их ограничивающие рамки, показанные белым цветом:
Даже если я нахожусь в самом нижнем левом углуограничивающий прямоугольник вокруг линии, он все еще показывает курсор, и щелчки мыши все еще достигают моего пользовательского элемента.
Я хочу изменить это так, чтобы мышь считалась только «над» линией, обнаруженной, еслиЯ на определенном расстоянии от него.Мол, эта область в красном (простите за грубый рисунок).
Мой вопрос: как мне подойти к этому?Можно ли переопределить какую-нибудь виртуальную функцию, связанную с «HitTest», на моем ShapeItem?
Я уже знаю математику, чтобы выяснить, нахожусь ли я в нужном месте.Мне просто интересно, какой подход лучше выбрать.Какие функции мне переопределить?Или какие события я обрабатываю и т. Д. Я потерялся в документации WPF по тестированию Hit.Это вопрос переопределения HitTestCore или что-то в этом роде?
Теперь о коде.Я размещаю элементы в пользовательском ItemsControl, называемом «ShapesControl».который использует пользовательский контейнер "ShapeItem" для размещения объектов моей модели представления:
<Canvas x:Name="Scene" HorizontalAlignment="Left" VerticalAlignment="Top">
<gcs:ShapesControl x:Name="ShapesControl" Canvas.Left="0" Canvas.Top="0"
ItemsSource="{Binding Shapes}">
<gcs:ShapesControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" IsItemsHost="True" />
</ItemsPanelTemplate>
</gcs:ShapesControl.ItemsPanel>
<gcs:ShapesControl.ItemTemplate>
<DataTemplate DataType="{x:Type gcs:ShapeVm}">
<Path ClipToBounds="False"
Data="{Binding RelativeGeometry}"
Fill="Transparent"/>
</DataTemplate>
</gcs:ShapesControl.ItemTemplate>
<!-- Style the "ShapeItem" container that the ShapesControl wraps each ShapeVm ine -->
<gcs:ShapesControl.ShapeItemStyle>
<Style TargetType="{x:Type gcs:ShapeItem}"
d:DataContext="{d:DesignInstance {x:Type gcs:ShapeVm}}"
>
<!-- Use a custom cursor -->
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Cursor" Value="SizeAll"/>
<Setter Property="Canvas.Left" Value="{Binding Path=Left, Mode=OneWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Top, Mode=OneWay}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type gcs:ShapeItem}">
<Grid SnapsToDevicePixels="True" Background="{TemplateBinding Panel.Background}">
<!-- First draw the item (i.e. the ShapeVm) -->
<ContentPresenter x:Name="PART_Shape"
Content="{TemplateBinding ContentControl.Content}"
ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentControl.ContentTemplateSelector}"
ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}"
HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
IsHitTestVisible="False"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"
RenderTransformOrigin="{TemplateBinding ContentControl.RenderTransformOrigin}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</gcs:ShapesControl.ShapeItemStyle>
</gcs:ShapesControl>
</Canvas>
Мой "ShapesControl"
public class ShapesControl : ItemsControl
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is ShapeItem);
}
protected override DependencyObject GetContainerForItemOverride()
{
// Each item we display is wrapped in our own container: ShapeItem
// This override is how we enable that.
// Make sure that the new item gets any ItemTemplate or
// ItemTemplateSelector that might have been set on this ShapesControl.
return new ShapeItem
{
ContentTemplate = this.ItemTemplate,
ContentTemplateSelector = this.ItemTemplateSelector,
};
}
}
И мой "ShapeItem"
/// <summary>
/// A ShapeItem is a ContentControl wrapper used by the ShapesControl to
/// manage the underlying ShapeVm. It is like the the item types used by
/// other ItemControls, including ListBox, ItemsControls, etc.
/// </summary>
[TemplatePart(Name="PART_Shape", Type=typeof(ContentPresenter))]
public class ShapeItem : ContentControl
{
private ShapeVm Shape => DataContext as ShapeVm;
static ShapeItem()
{
DefaultStyleKeyProperty.OverrideMetadata
(typeof(ShapeItem),
new FrameworkPropertyMetadata(typeof(ShapeItem)));
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
// Toggle selection when the left mouse button is hit
base.OnMouseLeftButtonDown(e);
ShapeVm.IsSelected = !ShapeVm.IsSelected;
e.Handled = true;
}
internal ShapesControl ParentSelector =>
ItemsControl.ItemsControlFromItemContainer(this) as ShapesControl;
}
ShapeVm - это просто абстрактный базовый класс для моих моделей представлений.Примерно это:
public abstract class ShapeVm : BaseVm, IShape
{
public virtual Geometry RelativeGeometry { get; }
public bool IsSelected { get; set; }
public double Top { get; set; }
public double Left { get; set; }
public double Width { get; }
public double Height { get; }
}