События мыши Silverlight: конфликты MouseEnter и MouseLeave - PullRequest
0 голосов
/ 25 ноября 2011

У меня есть коллекция кнопок в сетке. Для каждой из этих кнопок я хочу обработать события MouseEnter и MouseLeave, чтобы анимировать высоту кнопки (и сделать некоторые другие интересные вещи). Все это работает хорошо, пока я не начну слишком быстро двигать мышь над кнопками и выключать их, что в конечном итоге приводит к тому, что события происходят раньше, чем завершается другая. Как лучше всего убедиться, что события ждут друг друга перед тем, как их запустить?

UPDATE:

Следуя совету x0r, я преобразовал свой код во внутренний класс, который наследуется от Button и имеет необходимые методы для выполнения анимации. К сожалению, это не решило проблему, потому что - я думаю - Я обрабатываю событие Completed первой анимации в двух разных местах. (поправьте меня если я ошибаюсь). Вот мой код:

internal class MockButton : Button
{
    #region Fields

    private Storyboard _mouseEnterStoryBoard;
    private Storyboard _mouseLeaveStoryBoard;
    private Double _width;

    #endregion

    #region Properties

    internal Int32 Index { get; private set; }

    #endregion

    #region Ctors

    internal MockButton(Int32 index) : this(index, 200)
    {

    }

    internal MockButton(Int32 index, Double width)
    {
        this.Index = index;
        this._width = width;
    }

    #endregion

    #region Event Handlers

    internal void OnMouseEnter(Action action, Double targetAnimationHeight)
    {
        if (_mouseEnterStoryBoard == null)
        {
            _mouseEnterStoryBoard = new Storyboard();
            DoubleAnimation heightAnimation = new DoubleAnimation();
            heightAnimation.From = 10;
            heightAnimation.To = targetAnimationHeight;
            heightAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(300));
            _mouseEnterStoryBoard.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Height"));
            Storyboard.SetTarget(heightAnimation, this);
            _mouseEnterStoryBoard.Children.Add(heightAnimation);
        }
        _mouseEnterStoryBoard.Completed += (s, e) =>
        {
            action.Invoke();
        };
        _mouseEnterStoryBoard.Begin();
    }

    internal void OnMouseLeave()
    {
        if (_mouseLeaveStoryBoard == null)
        {
            _mouseLeaveStoryBoard = new Storyboard();
            DoubleAnimation heightAnimation = new DoubleAnimation();
            heightAnimation.To = 10;
            heightAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(300));
            _mouseLeaveStoryBoard.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Height"));
            Storyboard.SetTarget(heightAnimation, this);
            _mouseLeaveStoryBoard.Children.Add(heightAnimation);
        }
        if (_mouseEnterStoryBoard.GetCurrentState() != ClockState.Stopped)
        {
            _mouseEnterStoryBoard.Completed += (s, e) =>
            {
                _mouseLeaveStoryBoard.Begin();
            };
        }
        else
        {
            _mouseLeaveStoryBoard.Begin();
        }
    }

    #endregion
}

ОБНОВЛЕНИЕ 2:

Некоторые события запускаются несколько раз. Примером этого является событие Click на кнопке закрытия моего Rule объекта ...

    public Rule(Action<Int32> closeAction)
    {
        this.Style = Application.Current.Resources["RuleDefaultStyle"] as Style;
        this.CloseAction = closeAction;
        this.Loaded += (s, e) =>
        {
            if (_closeButton != null)
            {
                _closeButton.Click += (btn, args) =>
                {
                    if (this.CloseAction != null)
                    {
                        this.CloseAction.Invoke(this.Index);
                    }
                };
                if (_closeButtonShouldBeVisible)
                {
                    _closeButton.Visibility = System.Windows.Visibility.Visible;
                }
            }
        };
    }

И ниже Action<Int32>, который я передаю Rule объекту как CloseAction:

    private void RemoveRule(Int32 ruleIndex)
    {
        Rule ruleToRemove = Rules.FirstOrDefault(x => x.Index.Equals(ruleIndex));
        Storyboard sb = new Storyboard();
        DoubleAnimation animation = new DoubleAnimation();
        sb.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Opacity"));
        animation.Duration = TimeSpan.FromMilliseconds(300);
        animation.From = 1;
        animation.To = 0;
        sb.Children.Add(animation);
        Storyboard.SetTarget(animation, ruleToRemove);
        sb.Completed += (s, e) =>
        {
            if (Rules.FirstOrDefault(x => x.Index.Equals(ruleIndex)) != null)
            {
                this.Rules.RemoveAt(ruleIndex);
            }
        };
        sb.Begin();
    }

ОБНОВЛЕНИЕ 3:

Чтобы избежать запуска анимации слишком рано, я подумал, что могу отложить событие MouseEnter, поэтому, если пользователь просто слишком быстро прокручивает элемент, он не запускается. Но сейчас у меня проблема: скажем, пользователь наведет курсор мыши на элемент, а затем наведет курсор мыши. Если я использую свойство Storyboard.BeginTime, это не защитит от такого поведения, потому что, несмотря на то, что анимация задерживается, она все равно в конечном итоге запустится ... Так есть ли способ, которым я мог бы предотвратить это?

Есть предложения?

Ответы [ 2 ]

1 голос
/ 25 ноября 2011

проверьте ваш обработчик событий mouseleave, если первая раскадровка все еще работает, и если это так, присоедините начало второй раскадровки к событию Completed первого рассказа:

private void OnOddRowMouseLeave(object sender, MouseEventArgs e)
{
    ...

    if(_firstStoryboard.GetCurrentState() != ClockState.Stopped)
        _firstStoryboard.Completed += (s,e) =>   _secondStoryboard.Begin();
    else
        _secondStoryboard.Begin()
0 голосов
/ 27 ноября 2011

Все, что делает Silverlight, является асинхронным, и, скорее всего, происходит то, что из-за того, что вы быстро входите и выходите из коробки, отпускание мыши запускается до того, как ввод мыши может завершиться. Вы можете настроить два события так, чтобы они имели индикатор того, находится ли другое в процессе. Например, вы можете сделать это

bool mouseOut =false;
bool mouseIn =false;

void OnMouseEnter(Action action, Double targetAnimationHeight)
{
    if(!this.mouseOut)
    {
        this.mouseIn = true;

        if (_mouseEnterStoryBoard == null)
        {
            _mouseEnterStoryBoard = new Storyboard();
            DoubleAnimation heightAnimation = new DoubleAnimation();
            heightAnimation.From = 10;
            heightAnimation.To = targetAnimationHeight;
            heightAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(300));
            _mouseEnterStoryBoard.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Height"));
            Storyboard.SetTarget(heightAnimation, this);
            _mouseEnterStoryBoard.Children.Add(heightAnimation);
        }
        _mouseEnterStoryBoard.Completed += (s, e) =>
        {
            action.Invoke();
        };
        _mouseEnterStoryBoard.Begin();

        if(this.mouseOut)
        {
            this.OnMouseLeave();
        }

        this.mouseIn = false;
    }
}

void OnMouseLeave()
{
    if(!this.mouseIn)
    {
        this.mouseOut = false;

        if (_mouseLeaveStoryBoard == null)
        {
            _mouseLeaveStoryBoard = new Storyboard();
            DoubleAnimation heightAnimation = new DoubleAnimation();
            heightAnimation.To = 10;
            heightAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(300));
            _mouseLeaveStoryBoard.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("Height"));
            Storyboard.SetTarget(heightAnimation, this);
            _mouseLeaveStoryBoard.Children.Add(heightAnimation);
        }
        if (_mouseEnterStoryBoard.GetCurrentState() != ClockState.Stopped)
        {
            _mouseEnterStoryBoard.Completed += (s, e) =>
            {
                _mouseLeaveStoryBoard.Begin();
            };
        }
        else
        {
            _mouseLeaveStoryBoard.Begin();
        }
    }
    else
    {
        this.mouseOut = true;
    }
}

На самом деле я не проверял этот код, но это должно помочь вам хотя бы приблизиться к тому, что вы хотите. Это должно быть достаточно быстрым, чтобы ваш пользователь не осознавал, что он не запускается точно при выходе, если он быстро его преодолевает. Но это должно помочь вам избежать дублирования.

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

...