【问题标题】:Silverlight Mouse Events: MouseEnter and MouseLeave conflictsSilverlight 鼠标事件:MouseEnter 和 MouseLeave 冲突
【发布时间】:2011-11-24 20:17:35
【问题描述】:

我在网格中有一组按钮。对于这些按钮中的每一个,我想处理 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:

某些事件会被多次触发。一个例子是我的Rule 对象的关闭按钮上的Click 事件...

    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&lt;Int32&gt; 我作为CloseAction 传递给Rule 对象:

    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 属性,那将无法防止这种行为发生,因为即使动画被延迟,它最终还是会开始......那么有什么方法可以防止这种情况发生吗?

有什么建议吗?

【问题讨论】:

    标签: silverlight events animation silverlight-4.0 mouseevent


    【解决方案1】:

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

    private void OnOddRowMouseLeave(object sender, MouseEventArgs e)
    {
        ...
    
        if(_firstStoryboard.GetCurrentState() != ClockState.Stopped)
            _firstStoryboard.Completed += (s,e) =>   _secondStoryboard.Begin();
        else
            _secondStoryboard.Begin()
    

    【讨论】:

    • 我已经更新了问题,你能看一下吗?谢谢。
    • @Kassem:您的代码表现如何(什么不正常工作?)顺便说一句:如果 MouseLeave 故事板仍在运行,您可能还应该检查 MouseEnter,如果是这种情况,请停止MouseLeave 故事板
    • 我仍然无法理解为什么会遇到这些问题。我注意到的一件事是,几个事件被多次触发,而它们应该只被调用一次!一个例子是关闭按钮的点击事件(检查新的更新)。
    • @Kassem:事件只被触发一次,你可能不止一次附加事件处理程序吗?您的代码看起来不错,但您应该通过设置断点来检查
    【解决方案2】:

    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,并在事件完成时让鼠标在事件中设置鼠标在事件中,但问题是如果鼠标在事件设置之前触发,那么你没有得到事件触发。

    【讨论】:

      猜你喜欢
      • 2016-04-03
      • 1970-01-01
      • 2011-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多