代码之家  ›  专栏  ›  技术社区  ›  Reb.Cabin

防止事件冒泡:WPF&Rx

  •  0
  • Reb.Cabin  · 技术社区  · 14 年前

    我有一个小的WPF应用程序,它在画布上绘制一些点,然后跟踪鼠标在任何点内移动后的移动,直到鼠标在画布上移动。我的问题是如何防止点里面的鼠标垫冒泡到画布上。我想让画布鼠标下坠的观察者在鼠标下坠时开火 外部 圆点,当且仅当圆点内部发生鼠标下坠时,圆点下坠观察者才会开火。

    下面是我构建可观察链的技巧,显示如果DownInAndTrackWrtObservable的第三个参数为true,则在触发mouseDown事件中设置EventArgs.Handled属性:

    private IObservable<BasedVector> DownInAndTrackWrtObservable(
        UIElement hitTestElement, 
        UIElement trackWrtElement,
        bool handleItP = false)
    {
        var hitObs = hitTestElement.GetLeftMouseDownObservable();
    
        var trackObs = from mDown in hitObs
                       let mDownP = mDown.EventArgs.GetPosition(hitTestElement)
                       from mMove in trackWrtElement.GetMouseMoveObservable().
                           TakeUntil(trackWrtElement.GetLeftMouseUpObservable())
                       select new {D = mDown, P = mDownP, M = mMove};
    
        if (handleItP)
            trackObs.Subscribe(e =>
                e.D.EventArgs.Handled = true);
    
        return from obs in trackObs 
               select new BasedVector
               (
                   tail: obs.P,
                   head: obs.M.EventArgs.GetPosition(trackWrtElement)
               );
    }
    

    然后,我在两个地方设置了处理程序,一个是跟踪画布上的点击、点和拖动

    DownInAndTrackWrtObservable(ellipse, canvas1, handleItP:true).Subscribe(bv =>
    {
        PointMouseDownTextBox.Text =
            String.Format("Point {2} MouseDown({0:G}, {1:G})", bv.Tail.X, bv.Tail.Y, ellipse.Uid);
        CanvasMouseMoveTextBox.Text =
            String.Format("Canvas MouseMove({0:G}, {1:G})", bv.Head.X, bv.Head.Y);
    });
    

    DownInAndTrackWrtObservable(canvas1, canvas1).Subscribe(bv =>
    {
        CanvasMouseDownTextBox.Text =
            String.Format("Canvas MouseDown({0:G}, {1:G})", bv.Tail.X, bv.Tail.Y);
        CanvasMouseMoveTextBox.Text =
            String.Format("Canvas MouseMove({0:G}, {1:G})", bv.Head.X, bv.Head.Y);
    });
    

    问题是,当鼠标下移发生在点上时,画布上的鼠标下移仍然会被检测到,如第二个处理程序所示。但我不希望因为设置了Event.Handled属性而发生这种情况。顺便说一句,如果我设置了事件。在hitObs而不是trackObs上处理,那么在点内根本检测不到命中。 这是完整的应用程序,带有它的XAML(请“添加引用”到System.coreex、System.Reactive和System.Interactive)

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Shapes;
    
    namespace WpfTest
    {
        public static partial class UIElementExtensions
        {
            public static IObservable<IEvent<MouseButtonEventArgs>>
                GetLeftMouseDownObservable(this UIElement uiElement)
            {
                return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
                   h => new MouseButtonEventHandler(h),
                   h => uiElement.MouseLeftButtonDown += h,
                   h => uiElement.MouseLeftButtonDown -= h);
            }
    
            public static IObservable<IEvent<MouseButtonEventArgs>>
                GetLeftMouseUpObservable(this UIElement uiElement)
            {
                return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
                   h => new MouseButtonEventHandler(h),
                   h => uiElement.MouseLeftButtonUp += h,
                   h => uiElement.MouseLeftButtonUp -= h);
            }
    
            public static IObservable<IEvent<MouseEventArgs>>
                GetMouseMoveObservable(this UIElement uiElement)
            {
                return Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
                   h => new MouseEventHandler(h),
                   h => uiElement.MouseMove += h,
                   h => uiElement.MouseMove -= h);
            }
        }
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                Draw();
            }
    
            private static double scaleCanvasPerWorld = 150D;
            private static Point offsetInCanvas = new Point(15D, 15D);
            private static Point worldOrigin = new Point();
            private static Point WorldToCanvas(Point worldPoint)
            {
                // Difference of two points gives a System.Drawing.Vector, which has operator overloads.
                var result = ((worldPoint - worldOrigin) * scaleCanvasPerWorld + offsetInCanvas);
                return result;
            }
            private static Point CanvasToWorld(Point canvasPoint)
            {
                var result = ((canvasPoint - offsetInCanvas) / scaleCanvasPerWorld);
                return (Point)result;
            }
    
            private void DrawPoint(Point worldPoint)
            {
                var ellipse = new Ellipse();
                ellipse.Stroke = System.Windows.Media.Brushes.Black;
                ellipse.Fill = System.Windows.Media.Brushes.Black;
                ellipse.HorizontalAlignment = HorizontalAlignment.Center;
                ellipse.VerticalAlignment = VerticalAlignment.Center;
                ellipse.Width = 12;
                ellipse.Height = 12;
    
                var canvasPoint = WorldToCanvas(worldPoint);
                ellipse.SetValue(Canvas.LeftProperty, canvasPoint.X);
                ellipse.SetValue(Canvas.TopProperty, canvasPoint.Y);
    
                DownInAndTrackWrtObservable(ellipse, canvas1, handleItP: true).Subscribe(bv =>
                {
                    PointMouseDownTextBox.Text =
                        String.Format("Point {2} MouseDown({0:G}, {1:G})", bv.Tail.X, bv.Tail.Y, ellipse.Uid);
                    CanvasMouseMoveTextBox.Text =
                        String.Format("Canvas MouseMove({0:G}, {1:G})", bv.Head.X, bv.Head.Y);
                });
    
                canvas1.Children.Add(ellipse);
            }
    
            private class BasedVector
            {
                public Point Tail { get; set; }
                public Point Head { get; set; }
                public Vector Vector { get; set; }
                public BasedVector(Point tail, Point head)
                {
                    Debug.Assert(tail != null);
                    Debug.Assert(head != null);
    
                    this.Tail = tail;
                    this.Head = head;
    
                    this.Vector = head - tail;
                }
            }
    
            private IObservable<BasedVector> DownInAndTrackWrtObservable(
                UIElement hitTestElement,
                UIElement trackWrtElement,
                bool handleItP = false)
            {
                var hitObs = hitTestElement.GetLeftMouseDownObservable();
    
                var trackObs = from mDown in hitObs
                               let mDownP = mDown.EventArgs.GetPosition(hitTestElement)
                               from mMove in trackWrtElement.GetMouseMoveObservable().
                                   TakeUntil(trackWrtElement.GetLeftMouseUpObservable())
                               select new { D = mDown, P = mDownP, M = mMove };
    
                if (handleItP)
                    trackObs.Subscribe(e =>
                        e.D.EventArgs.Handled = true);
    
                return from obs in trackObs
                       select new BasedVector
                       (
                           tail: obs.P,
                           head: obs.M.EventArgs.GetPosition(trackWrtElement)
                       );
            }
    
            private void Draw()
            {
                var controlPoints = new Point[]
                {
                    new Point(0D, 0D),
                    new Point(0D, 1D),
                    new Point(0.5D, 1D),
                    new Point(1.5D, 0D),
                    new Point(2D, 0D),
                    new Point(2D, 1D),
                };
    
                controlPoints.Run(DrawPoint);
    
                DownInAndTrackWrtObservable(canvas1, canvas1).Subscribe(bv =>
                {
                    CanvasMouseDownTextBox.Text =
                        String.Format("Canvas MouseDown({0:G}, {1:G})", bv.Tail.X, bv.Tail.Y);
                    CanvasMouseMoveTextBox.Text =
                        String.Format("Canvas MouseMove({0:G}, {1:G})", bv.Head.X, bv.Head.Y);
                });
            }
        }
    }
    

    以及XAML

    <Window x:Class="WpfTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="350" Width="522">
        <Grid Height="313" Name="grid1" Width="500" >
            <Canvas Height="237" HorizontalAlignment="Left" Margin="0,75,0,0" Name="canvas1" VerticalAlignment="Top" Width="500" Background="#00000000" />
            <StackPanel Height="52" HorizontalAlignment="Left" Margin="12,12,0,0" Name="stackPanel1" VerticalAlignment="Top" Width="200">
                <TextBox Height="23" Name="CanvasMouseDownTextBox" Width="186" BorderBrush="Blue" />
                <TextBox Height="23" Name="PointMouseDownTextBox" Width="186" BorderBrush="Blue" />
            </StackPanel>
            <StackPanel Height="52" HorizontalAlignment="Left" Margin="211,12,0,0" Name="stackPanel2" VerticalAlignment="Top" Width="200">
                <TextBox Height="23" Name="CanvasMouseMoveTextBox" Width="186" BorderBrush="Blue" />
            </StackPanel>
        </Grid>
    </Window>
    

    会很感激你的建议

    2 回复  |  直到 14 年前
        1
  •  1
  •   John Bowen    14 年前

    试着用这个来处理hitObs上的事件作为同一可观察链中的副作用:

    var hitObs = hitTestElement.GetLeftMouseDownObservable().Do(e => e.EventArgs.Handled = handleItP);
    

    同时删除 if (handleItP) 这将取代。

    您尝试的其他两种方法是使用单独的订阅,该订阅可以抢占您的主订阅,或者在其他处理程序已收到事件之后在链中处理的设置太晚。

        2
  •  1
  •   Richard Szalay    14 年前

    与其说这是一个Rx问题,不如说是一个事件冒泡问题。

    如果我正确理解您的问题,我建议根据 OriginalSource 属性以确保最初引发事件的元素是有问题的元素(而不是子元素)。