我有一个带有单个画布的用户控件。画布以及其他嵌套画布中将有很多形状。我希望所有形状都能对鼠标按下事件做出反应,因此我可以识别事件的发件人,并避免进行显式的低级点击测试。但是我不想为每种形状添加单独的事件订阅。

我尝试使用EventManager.RegisterClassHandler,但是随后我从应用程序中的所有元素获取事件,即使那些未包含在用户控件中的事件也是如此。

是否可以将类处理程序限制为用户控件的内容?有没有其他方法可以实现我想要的目标,那就是“确定的”目标?

public class GraphView: UserControl
{
    UIElement currentElement;

    public GraphView()
    {
        Content = new Canvas();
        HorizontalAlignment = HorizontalAlignment.Stretch;
        VerticalAlignment = VerticalAlignment.Stretch;
        VerticalContentAlignment = VerticalAlignment.Top;
        HorizontalContentAlignment = HorizontalAlignment.Left;
        Background = Brushes.White;
        ClipToBounds = true;

        EventManager.RegisterClassHandler(typeof(UIElement),
            UIElement.MouseDownEvent, new RoutedEventHandler(contentMouseDown));
    }

    void contentMouseDown(object sender, RoutedEventArgs er)
    {
        MouseButtonEventArgs e = er as MouseButtonEventArgs;
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            currentElement = sender as UIElement;
        }
    }
}


编辑(后续):事实证明,我实际上对WPF中事件的工作方式有误解,这导致我尝试使用RegisterClassHandler。在安迪的回答向我指出了鼠标下方的对象可以简单地通过RoutedEventArgs.OriginalSource获取的事实之后,我刚刚将事件订阅替换为标准机制

MouseLeftButtonDown += contentMouseLeftButtonDown;


感觉好多了,因为它是为此目的而设计的。唯一的困难是那时还有触发事件的模板化元素(画布边界)。在那种情况下,我用contentMouseLeftButtonDown内部的画布本身替换当前元素:

void contentMouseLeftButtonDown(object sender, RoutedEventArgs e)
{
    currentElement = e.OriginalSource as FrameworkElement;

    // if there is no parent, it's a templated element
    // (Border), so substitute it by the canvas itself
    if (currentElement == null || currentElement.Parent == null)
        currentElement = (FrameworkElement)Content;
}

最佳答案

好的,所以此解决方案针对的是特别想使用代码执行此操作且不想了解样式,模板和命令的人员。
这不完全是您所拥有的,因为我想证明它不会在用户控件之外的任何东西上触发,而且我也不想尝试猜测确切的意图。
考虑到所有这些,对于经验丰富的wpf开发人员来说,这不太可能是理想的解决方案。

我的MainWindow有一个带有椭圆的画布和一个用户控件。用户控件将具有其他形状。

<Grid>
    <Canvas>
        <Ellipse Width="20"
                 Height="20"
                 Canvas.Left="50"
                 Canvas.Top="50"
                 Fill="Red"
                           />
        <Grid Height="100"
              Width="100"
              Canvas.Left="200">

            <local:UserControl1/>
        </Grid>
    </Canvas>
</Grid>


我的usercontrol开始时根本没有任何内容,代码是:

public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
        Canvas canvas = new Canvas();
        Rectangle rect = new Rectangle { Width = 20, Height = 20, Fill = Brushes.Blue };
        Canvas.SetLeft(rect, 50);
        Canvas.SetTop(rect, 50);
        canvas.Children.Add(rect );
        this.Content = canvas;
        canvas.AddHandler(FrameworkElement.MouseLeftButtonDownEvent, new RoutedEventHandler(Canvas_MouseDown));
    }
    private void Canvas_MouseDown(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("You clicked me");
    }
}


如果要引用单击的特定形状,则将其作为原始源传递到routedeventargs中:

    private void Canvas_MouseDown(object sender, RoutedEventArgs e)
    {
        Shape shape = e.OriginalSource as Shape;
        // Do something with the shape
    }


这正在使用现有事件。
您的代码似乎正在尝试注册自定义路由事件。您只需要按下鼠标就不需要这样做。

请注意,RoutedEventargs不如您从click事件中获得的eventargs丰富。但是形状不会有点击事件。

当我旋转并单击椭圆时,什么也没有发生。单击矩形,我得到一个消息框。这说明事件触发了用户控件的画布子级。只要。

您可能会发现将画布上的位置设置有趣的奇怪方式。 Canvas的left和top附加了属性,可用于定位具有这些属性的任何子级。它们仅对画布有意义,您可以将它们设置在几乎任何类型的ui上,无论它是否在画布中。
https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/attached-properties-overview

10-04 13:05
查看更多