我正在WPF中构建自定义控件,并且在捕获输入鼠标事件时遇到一些困难。我已经阅读了routed events和class event handlers上的各种文档,但是对我来说并不是很有效。我对WPF并不陌生,因为他过去主要使用Forms。
给出以下可以包含多个子项的自定义控件:
// Parent.cs
[ContentProperty(nameof(Children))]
public class Parent : Control
{
private DrawingGroup _backingStore = new DrawingGroup();
public List<UIElement> Children { get; } = new List<UIElement>();
static Parent()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Parent), new FrameworkPropertyMetadata(typeof(Parent)));
}
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
// default event handler
}
protected override void OnRender(DrawingContext drawingContext)
{
/*do some custom drawing*/
var backingContext = _backingStore.Open();
// draw an X indicating the background
backingContext.DrawRectangle(Background, new Pen(Brushes.White, 1), new Rect(0, 0, Width, Height));
backingContext.DrawLine(pen, new Point(0, 0), new Point(Width - 1, Height - 1));
backingContext.DrawLine(pen, new Point(0, Height - 1), new Point(Width - 1, 0));
backingContext.Close();
drawingContext.DrawDrawing(_backingStore);
}
protected override int VisualChildrenCount => Children.Count;
protected override Visual GetVisualChild(int index) => Children[index];
protected override Size ArrangeOverride(Size arrangeBounds)
{
foreach (FrameworkElement child in Children)
child.Arrange(new Rect(0, 0, arrangeBounds.Width, arrangeBounds.Height));
return new Size(arrangeBounds.Width, arrangeBounds.Height);
}
}
// Child.cs
[ContentProperty(nameof(Children))]
public class Child : Control
{
public List<UIElement> Children { get; } = new List<UIElement>();
static Child()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Parent), new FrameworkPropertyMetadata(typeof(Parent)));
}
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
// NEVER FIRED
}
protected override void OnRender(DrawingContext drawingContext)
{
/*do some custom drawing*/
}
// same as Parent
}
// TestWindow.xaml
<Window x:Class="TestApp.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestApp"
Title="TestWindow" Height="450" Width="800">
<Grid>
<local:Parent Background="White">
<local:Child Background="Red" />
<local:Child Background="Green" />
</local:Parent>
</Grid>
</Window>
// ParentStyle.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestApp">
<Style TargetType="local:Parent">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Parent">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:Child">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Child">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
我发现Parent
收到引发的鼠标移动事件。但是,其子级不会收到任何鼠标事件。它们并没有向下传播,虽然我可以遍历Children并调用RaiseEvent(e),但它引入了其他问题(命中测试等),并且似乎是错误的答案。
您已经接近,但是您像WinForms一样思考太多,而像WPF一样思考不够。在WPF中几乎从未进行过自定义渲染,至少在我的经验中是这样。该框架几乎可以处理您可能需要的所有内容,但是我已经超越了我自己。
[第一件事:您不想从Control
继承,您想从Panel
继承。目的是“定位和排列子对象”。您将在WPF中找到所有常用的“容器”(Panel
,Grid
等),都从此类继承。
[我认为您的大多数问题都源于StackPanel
本身不支持子元素的事实。 Control
被构建为仅提供该功能,因此您会发现它已经实现了您必须声明的大多数属性,例如Panel
。
Microsoft有一个制作自定义面板的简单示例:Children
您的How to: Create a Custom Panel Element类应该最终看起来像这样:
Parent
这几乎完成了当前public class Parent : Panel
{
//We'll talk more about OnRender later
protected override void OnRender(DrawingContext drawingContext)
{
var pen = new Pen(Brushes.Gray, 3);
drawingContext.DrawLine(pen, new Point(0, 0), new Point(Width - 1, Height - 1));
drawingContext.DrawLine(pen, new Point(0, Height - 1), new Point(Width - 1, 0));
}
protected override Size MeasureOverride(Size availableSize)
{
foreach (UIElement child in InternalChildren)
{
child.Measure(availableSize);
}
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement child in InternalChildren)
{
child.Arrange(new Rect(finalSize));
}
return finalSize;
}
}
类所做的所有事情。
当然,上面的Parent
只是将子级堆叠在一起,因此它并不是真正有用的。为了解决这个问题,您需要了解WPF布局系统。关于这个话题,有很多话要说,微软已经说了大部分Panel
。总结一下,有两种主要方法:
here,询问一个元素要多大。
Measure
,它告诉控件will实际的大小以及相对于其父项放置的位置。
Arrange
的工作是从其所有子代中获取Panel
结果,确定这些子代的大小和位置,然后在这些子代上调用Measure
以分配最终的Arrange
。] >
请注意,Rect
不负责实际渲染其子级。 Panel
仅定位它们,渲染由WPF本身处理。
Panel
方法可用于“将自定义图形效果添加到布局元素”。 Microsoft提供了在自定义OnRender
中使用OnRender
的示例:Panel
在我之前显示的代码中,我保留了您的原始问题,并在How to: Override the Panel OnRender Method的背景上画了一个“ X”。然后会自动在Panel
的子级上绘制它们。