【发布时间】:2018-09-12 01:31:52
【问题描述】:
我有一个包含在 ContentControl 派生类(“ShapeItem”)中的不规则形状的项目(线条形状)。我使用自定义光标设置样式,并在 ShapeItem 类中处理鼠标点击。
不幸的是,如果鼠标位于 ContentControl 矩形边界框内的任何位置,WPF 就会认为鼠标“悬停”在我的项目上。这对于像矩形或圆形这样的封闭形状是可以的,但对于对角线来说这是一个问题。考虑这个图像,其中显示了 3 个这样的形状,并且它们的边界框以白色显示:
即使我在该线周围边界框的最左下角,它仍然显示光标并且鼠标点击仍然到达我的自定义项。
我想更改此设置,以便仅当我在一定距离内时才认为鼠标“越过”检测到的线。就像,这个区域是红色的(请原谅粗略的绘图)。
我的问题是,我该如何处理?我是否在我的 ShapeItem 上覆盖了一些虚拟的“HitTest”相关函数?
我已经知道计算我是否在正确的地方。我只是想知道什么方法是最好的选择。我要覆盖哪些功能?或者我要处理什么事件,等等。我在关于命中测试的 WPF 文档中迷失了。是覆盖 HitTestCore 还是类似的问题?
现在是代码。我将项目托管在一个名为“ShapesControl”的自定义 ItemsControl 中。 它使用自定义的“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; }
}
【问题讨论】:
-
你看过覆盖 HitTestCore 方法吗?
-
真的那么简单吗?我只是覆盖 HitTestCore?
-
你可能应该实现你的命中测试逻辑
-
我刚试过,但不幸的是它仍然不能解决改变鼠标光标的问题。我还希望光标在用户移动它时反映它在不规则形状上。不幸的是,即使自定义覆盖了 HitTestCore,只要鼠标在内容控件的整个边界框上的任何位置,光标仍然会发生变化。
-
正如 Mehrzad 指出的那样,覆盖 HitTestCore 是解决方案的一部分(感谢 Mehrzad)。但是关于显示正确鼠标光标的问题:经过相当多的搜索和摆弄,它开始看起来像如果我 a) 删除 ShapeItem 样式中的 Cursor 设置 b) 连接到 Mouse.QueryCursorEvent ,我可以在我的事件处理程序中进行手动命中测试并有效地覆盖那里的光标。无论如何,它似乎工作。如果有更清洁的方法,我会全力以赴......