使用依赖项 属性 在 n 秒后隐藏元素的可见性

Hide the visibility of an Element after n seconds using dependency property

我的目标: 我有一个 Button 和一个 ImageImage 默认为 Hidden,一旦用户将鼠标悬停在 Button 上,就会显示 Image。它应该是可见的,直到用户将鼠标悬停在 ImageButton 上。它应该在用户离开鼠标点(从按钮或图像)6 秒后隐藏图像。鼠标在 6 秒前再次悬停并离开应该会重新启动计时器。

我试过的方法 我已经有了一个使用 AttachedProperty 的可行解决方案,但效率不高。我感觉这里会因为 static 而发生内存泄漏。

public class MouseHoverBehavior
{
    public static readonly DependencyProperty ElementProperty = DependencyProperty.RegisterAttached(
        "Element", typeof(UIElement), typeof(MouseHoverBehavior), new UIPropertyMetadata(OnElementChanged));

    private static UIElement target;

    static MouseHoverBehavior()
    {
        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromSeconds(6);
        timer.Tick += Timer_Tick;
    }

    private static void OnElementChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {            
        var element = (sender as Button);
        target = (UIElement)e.NewValue;
        target.Visibility = Visibility.Hidden;

        element.MouseEnter += Element_MouseEnter;
        target.MouseEnter += Element_MouseEnter;
        element.MouseLeave += Element_MouseLeave;
        target.MouseLeave += Element_MouseLeave;
    }

    private static DispatcherTimer timer;

    private static void Element_MouseLeave(object sender, MouseEventArgs e)
    { 
        timer.Start();
    }

    private static void Timer_Tick(object sender, EventArgs e)
    {
        target.Visibility = Visibility.Hidden;
    }

    private static void Element_MouseEnter(object sender, MouseEventArgs e)
    {            
        timer.Stop();
        target.Visibility = Visibility.Visible;
    }

    public static void SetElement(DependencyObject element, UIElement value)
    {
        element.SetValue(ElementProperty, value);
    }

    public static string GetElement(DependencyObject element)
    {
        return (string)element.GetValue(ElementProperty);
    }
}

在xaml中:

<StackPanel>
    <Image Source="steve.jpg" Width="200" x:Name="image"/>
    <Button Width="200" Height="100" Margin="20" local:MouseHoverBehavior.Element="{Binding ElementName=image}"/>
</StackPanel>

有没有人知道如何有效地做到这一点。

谢谢。

正如 Chris W. 暗示的那样,最佳做法是使用 Storyboards 和 EventTriggers。这正是他们为之设计的场景。以下是它如何与您的示例一起使用(我将图像更改为矩形以便轻松测试):

   <StackPanel>
        <StackPanel.Triggers>
            <EventTrigger SourceName="_button" RoutedEvent="MouseEnter">
                <BeginStoryboard>
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="image" 
                                                       Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
            <EventTrigger SourceName="_button" RoutedEvent="MouseLeave">
                <BeginStoryboard>
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="image"                                                            
                                                       Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0:00:06" 
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
            <EventTrigger SourceName="image" RoutedEvent="MouseEnter">
                <BeginStoryboard>
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="image" 
                                                       Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
            <EventTrigger SourceName="image" RoutedEvent="MouseLeave">
                <BeginStoryboard>
                    <Storyboard Duration="1">
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="image"                                                            
                                                       Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0:00:06" 
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </StackPanel.Triggers>

        <Button x:Name="_button"
                Width="200" Height="100" Margin="20" />
        <Rectangle Visibility="Collapsed"
                   Width="200" 
                   Height="200"
                   Fill="Yellow"
                   x:Name="image"/>
    </StackPanel>

您还可以让图像在 5 秒后开始淡出,在 6 秒后完全淡出,等等。我知道这看起来像很多标记,但它非常灵活,避免了您可能会遇到的许多编码逻辑难题如果您想做更复杂的事情,请通过。 (事实上​​ ,如果你想要更复杂的东西,你无论如何都必须使用 Storyboard,并且用代码做它们并不比 XAML 更漂亮)。

但是,如果您出于某种原因不想使用故事板动画,那么对于您的特定场景,您所做的似乎也不错。如果您担心内存泄漏(我不知道这些 XAML 元素在应用程序的整个生命周期中将被创建和销毁多少次,但除非它非常多,否则我不会担心这个;最重要的资源在 FrameworkElement 被卸载时被释放,即使 FE 本身没有得到 GC'd),你可以在你的 OnElementChanged 中订阅这些元素' Unloaded 事件,并在处理这些事件时从您的 Element_MouseEnterElement_MouseLeave 处理程序中取消订阅它们。

自动在几秒钟内隐藏控件并在鼠标移动时显示..

public class VisibilityTimer
    {
        UIElement Parent { get; }
        UIElement Target { get; }
        public VisibilityTimer(UIElement parent, UIElement target)
        {
            Parent = parent;
            Target = target;
            parent.PointerMoved += UIRenderControl_PointerMoved;
            parent.PointerEntered += OnArrowPointerEntered;
            parent.PointerExited += OnArrowPointerExited;
            CreateFadeTimer();
        }
        public bool IsOpen
        {
            get { return Parent.Visibility == Visibility.Visible; }
        }
        private async void ShowAction()
        {
            if (Target.Visibility == Visibility.Visible)
                return;
            this.Target.Visibility = Visibility.Visible;
            await this.Target.FadeInAsync();

        }

        private async void HideAction()
        {
            if (Target.Visibility == Visibility.Collapsed)
                return;
            await this.Target.FadeOutAsync();
            this.Target.Visibility = Visibility.Collapsed;

        }
        private bool _isArrowVisible = false;
        private bool _isArrowOver = false;

        private DispatcherTimer _fadeTimer = null;

        #region Create/Dispose FadeTimer
        private void CreateFadeTimer()
        {
            _fadeTimer = new DispatcherTimer();
            _fadeTimer.Interval = TimeSpan.FromMilliseconds(1500);
            _fadeTimer.Tick += OnFadeTimerTick;
        }

        private void DisposeFadeTimer()
        {
            var fadeTimer = _fadeTimer;
            _fadeTimer = null;
            if (fadeTimer != null)
            {
                fadeTimer.Stop();
            }
        }
        #endregion

        #region ArrowPointerEntered/ArrowPointerExited
        private void OnArrowPointerEntered(object sender, PointerRoutedEventArgs e)
        {
            if (this.IsOpen == false)
                return;
            _isArrowOver = true;
        }

        private void OnArrowPointerExited(object sender, PointerRoutedEventArgs e)
        {
            if (this.IsOpen == false)
                return;
            _isArrowOver = false;
        }
        #endregion



        private void UIRenderControl_PointerMoved(object sender, PointerRoutedEventArgs e)
        {
            if (this.IsOpen == false)
                return;
            if (!_isArrowVisible)
            {
                ShowAction();
                _isArrowVisible = true;
            }
            this._fadeTimer.Start();
        }

        private void OnFadeTimerTick(object sender, object e)
        {
            if (_isArrowVisible)
            {
                _isArrowVisible = false;
                HideAction();
            }
        }
    }