将类处理程序限制为内容

时间:2018-03-31 09:29:34

标签: c# wpf events

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

我尝试使用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。在Andy的回答指出我可以通过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;
}

1 个答案:

答案 0 :(得分:1)

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

我的MainWindow有一个带有椭圆的画布和一个用户控件。 usercontrol将包含另一个形状。

<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中传递为originalsource:

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

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

请注意,RoutedEventargs并不像您从点击事件中获得的事件那样丰富。但是一个形状不会有点击事件。

当我旋转它并单击椭圆时,没有任何反应。单击矩形,我得到一个消息框。这表明事件会触发usercontrol画布的子项。仅

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