c # wpf перекрывающиеся элементы управления не получают события мыши - PullRequest
5 голосов
/ 12 апреля 2009

Я создаю элемент управления canvas. У этого корневого холста есть несколько перекрывающихся дочерних элементов (также и холста). Это сделано для того, чтобы каждый ребенок мог обрабатывать свой собственный рисунок, а затем я могу составить окончательный результат с любой комбинацией детей, чтобы получить желаемое поведение.

Это работает очень хорошо, что касается рендеринга. Однако это не очень хорошо работает с событиями мыши. Вот как работают события мыши (на примере previewmousemove):

1 - Если корневой холст находится под мышью, событие пожара 2- Проверьте всех детей, если один из них находится под мышью, событие пожара и остановка

Таким образом, только первый добавленный мной ребенок получит событие перемещения мыши. Событие распространяется не на всех детей, потому что они перекрываются.

Чтобы преодолеть это, я попытался сделать следующее: 1- переопределить события мыши в корневом холсте 2- Для каждого события найдите всех потомков, которые хотят обработать событие, используя VisualTreeHelper.HitTest. 3- Для всех детей, которые вернули действительный результат теста на попадание (то есть: под мышкой и готовы обработать событие (IsHitTestVisible == true)), ???

Вот где я застрял, мне как-то нужно отправить событие мыши всем дочерним элементам и остановить нормальный поток события, чтобы убедиться, что первый дочерний элемент не получил его дважды (через handled = true в событии ).

При использовании RaiseEvent с тем же событием, передаваемым дочерним элементам, кажется, что все работает, но каким-то образом оно также вызывает событие на родительском объекте (корневой холст). Чтобы обойти это, мне нужно было создать копию события и установить принудительно установить источник, хотя это скорее хак, чем решение. Есть ли правильный способ сделать то, что я пытаюсь сделать? Пример кода приведен ниже.

    public class CustomCanvas : Canvas
    {
        private List<object> m_HitTestResults = new List<object>();

        public new event MouseEventHandler MouseMove;

        public CustomCanvas()
        {
            base.PreviewMouseMove += new MouseEventHandler(CustomCanvas_MouseMove);
        }

        private void CustomCanvas_MouseMove(object sender, MouseEventArgs e)
        {
// Hack here, why is the event raised on the parent as well???
            if (e.OriginalSource == this)
            {
                return;
            }

                Point pt = e.GetPosition((UIElement)sender);
                m_HitTestResults.Clear();

                VisualTreeHelper.HitTest(this,
                    new HitTestFilterCallback(OnHitTest),
                    new HitTestResultCallback(OnHitTest),
                    new PointHitTestParameters(pt));

                MouseEventArgs tmpe = new MouseEventArgs(e.MouseDevice, e.Timestamp, e.StylusDevice);
                tmpe.RoutedEvent = e.RoutedEvent;
                tmpe.Source = this;

                foreach (object hit in m_HitTestResults)
                {
                    UIElement element = hit as UIElement;
                    if (element != null)
                    {
 // This somehow raises the event on us as well as the element here, why???
                        element.RaiseEvent(tmpe);
                    }
                }


            var handlers = MouseMove;
            if (handlers != null)
            {
                handlers(sender, e);
            }

            e.Handled = true;
        }

        private HitTestFilterBehavior OnHitTest(DependencyObject o)
        {
            UIElement element = o as UIElement;
            if (element == this)
            {
                return HitTestFilterBehavior.ContinueSkipSelf;
            }
            else if (element != null && element.IsHitTestVisible && element != this)
            {
                return HitTestFilterBehavior.Continue;
            }
            return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
        }

        private HitTestResultBehavior OnHitTest(HitTestResult result)
        {
            // Add the hit test result to the list that will be processed after the enumeration.
            m_HitTestResults.Add(result.VisualHit);
            // Set the behavior to return visuals at all z-order levels.
            return HitTestResultBehavior.Continue;
        }

Ответы [ 3 ]

3 голосов
/ 05 января 2010

Я думаю, вы должны использовать события предварительного просмотра, потому что это RoutingStrategy.Tunnel от Window до самого высокого элемента управления в Z-Order, а обычные события - RoutingStrategy.Bubble.

В этих RoutedEvents есть свойство Handle, когда оно истинно, система прекратит проходить визуальное дерево, потому что кто-то использовал это событие.

0 голосов
/ 08 января 2010

Точно так же, как сказал @GuerreroTook, вы должны решить эту проблему, используя WPF RoutedEvents (более подробную информацию здесь .

0 голосов
/ 26 ноября 2009

Мне показался интересным твой пример кода, поэтому я попробовал ... но мне пришлось сделать небольшую модификацию, чтобы он нормально работал в моих вещах.

Мне пришлось изменить второе «если» в методе HitTestFilter следующим образом:

if (element == null || element.IsHitTestVisible)

Как вы можете видеть, я удалил бесполезный "element! = This" в конце (вы уже проверяли это условие в первом "if") и добавил "element == null" в начале.

Почему? Потому что в какой-то момент во время фильтрации тип параметра был System.Windows.Media.ContainerVisual, который не наследуется от UIElement и поэтому элемент будет иметь значение null, и будет возвращено ContinueSkipSelfAndChildren. Но я не хочу пропускать дочерние элементы, потому что мой Canvas содержится в его коллекции "Children", а элементы UIE, с которыми я хочу провести тестирование, содержатся в Canvas.

...