使用依赖项 属性 在 n 秒后隐藏元素的可见性
Hide the visibility of an Element after n seconds using dependency property
我的目标: 我有一个 Button
和一个 Image
。 Image
默认为 Hidden
,一旦用户将鼠标悬停在 Button
上,就会显示 Image
。它应该是可见的,直到用户将鼠标悬停在 Image
或 Button
上。它应该在用户离开鼠标点(从按钮或图像)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. 暗示的那样,最佳做法是使用 Storyboard
s 和 EventTrigger
s。这正是他们为之设计的场景。以下是它如何与您的示例一起使用(我将图像更改为矩形以便轻松测试):
<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_MouseEnter
和 Element_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();
}
}
}
我的目标: 我有一个 Button
和一个 Image
。 Image
默认为 Hidden
,一旦用户将鼠标悬停在 Button
上,就会显示 Image
。它应该是可见的,直到用户将鼠标悬停在 Image
或 Button
上。它应该在用户离开鼠标点(从按钮或图像)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. 暗示的那样,最佳做法是使用 Storyboard
s 和 EventTrigger
s。这正是他们为之设计的场景。以下是它如何与您的示例一起使用(我将图像更改为矩形以便轻松测试):
<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_MouseEnter
和 Element_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();
}
}
}