Silverlight鼠标事件:MouseEnter和MouseLeave冲突

时间:2011-11-24 20:17:35

标签: silverlight events animation silverlight-4.0 mouseevent

我在网格中有一组按钮。对于这些按钮中的每一个,我想处理MouseEnter和MouseLeave事件以设置按钮高度的动画(并执行其他一些有趣的内容)。这一切都很好,直到我开始移动我的鼠标过快地关闭按钮,最终导致事件发生在另一个完成之前。在触发事件之前确保事件等待的最佳方法是什么?

更新

按照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 个答案:

答案 0 :(得分:1)

检查您的mouseleave事件处理程序,如果第一个故事板仍在运行,如果是这种情况,则将第二个故事板的开头附加到第一个故事板的Completed事件:

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

    if(_firstStoryboard.GetCurrentState() != ClockState.Stopped)
        _firstStoryboard.Completed += (s,e) =>   _secondStoryboard.Begin();
    else
        _secondStoryboard.Begin()

答案 1 :(得分:0)

Silverlight所做的一切都是异步的,因此最有可能发生的事情是因为你快速进出盒子鼠标离开是在mouseenter有机会完成之前被开除的。你可以设置你的两个事件,因此他们可以指示另一个事件是否在进行中。例如,你可以这样做

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;
    }
}

我实际上没有检查过这段代码,但这应该可以帮助你至少接近你想要的东西。这应该足够快,以至于你的用户没有意识到如果他们快速完成它就不会在退出时准确触发。但这应该有助于防止重叠。

你可以做到这一点的另一种方法是将初始事件设置为null,并且将事件中的鼠标设置为事件中的鼠标,但是问题是如果鼠标在事件设置之前触发,那么你没有事件让事件发生。