自定义WPF控件中的鼠标事件

问题描述 投票:0回答:1

我正在WPF中构建自定义控件,并且在捕获输入鼠标事件时遇到一些困难。我已经阅读了routed eventsclass 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),但它引入了其他问题(命中测试等),并且似乎是错误的答案。

c# wpf mouseevent wpf-controls custom-controls
1个回答
0
投票

您已经接近,但是您像WinForms一样思考太多,而像WPF一样思考不够。在WPF中几乎从未进行过自定义渲染,至少在我的经验中是这样。该框架几乎可以处理您可能需要的所有内容,但是我已经超越了我自己。


面板基础

[第一件事:您不想从Control继承,您想从Panel继承。目的是“定位和排列子对象”。您将在WPF中找到所有常用的“容器”(PanelGrid等),都从此类继承。

[我认为您的大多数问题都源于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。] >


OnRender

请注意,Rect不负责实际渲染其子级。 Panel仅定位它们,渲染由WPF本身处理。

Panel方法可用于“将自定义图形效果添加到布局元素”。 Microsoft提供了在自定义OnRender中使用OnRender的示例:Panel

在我之前显示的代码中,我保留了您的原始问题,并在How to: Override the Panel OnRender Method的背景上画了一个“ X”。然后会自动在Panel的子级上绘制它们。

© www.soinside.com 2019 - 2024. All rights reserved.