65.9K
CodeProject 正在变化。 阅读更多。
Home

使 UIElement 在 MouseDown 时可见的行为

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2016 年 2 月 16 日

CPOL

6分钟阅读

viewsIcon

13775

downloadIcon

239

本文介绍了一种行为,可以在触发其附加的 UIElement 的鼠标按下事件时使另一个 UIElement 可见。该行为已扩展为直接支持 Popup 控件的淡入淡出。

引言

很多时候,我们可能希望在另一个 UIElement 被点击时使某个 UIElement 可见。在我的案例中,我想显示一组叠加在图像上的按钮,并且不希望它们一直可见,因为它们会弄乱窗口并干扰图像的查看。我最初是在代码隐藏中创建的,计划将代码封装成一个行为。由于弹出窗口旨在包含按钮,因此单击面板会将隐藏控件的 Visibility 设置为 Collapsed。我必须承认,这种功能使得这个弹出行为在许多应用程序中的用途非常有限,也许如果我发现有保留控件打开的需求,我会增强设计。

背景

一旦代码被封装成行为,我对其进行了一些改进。我想要的功能之一是在设定的时间(在本例中为 10 秒)后消失按钮。当然,如果鼠标悬停在先前隐藏的 UIElement 上,我不希望它们立即消失,因此我为 UIElement 附加了 MouseEnterEventMouseLeaveEvent 事件。基本上,只要鼠标在 UIElement 内,就停止计时器,然后在鼠标离开 UIElement 时重置计时器。此外,再次单击原始 UIElement 将隐藏先前隐藏的 UIElement。然后我添加了一个 Storyboard,创建了一个 2 秒的淡出效果,这样按钮就不会突然消失。

行为

实际的 MouseDownUiElementVisibilityBehavior 其实相当简单,因为大部分功能都在辅助类 PopupControlTimer 中。有一个公共的 DependencyProperty 用于隐藏的控件,还有一个私有的 DependencyProperty 用于关联的 PopupControlTimer。隐藏控件的 DependencyProperty 有一个事件处理程序,用于将事件处理程序与 MouseLeftButtonDown 事件关联,并初始化 PopupControlTimer 类的实例。

MouseLeftButtonDown 事件处理程序基本上只是更改隐藏控件的 Visibility,如果 VisibilityVisible,则启动 PopupControlTimer,否则停止 PopupControlTimer

PopupControlTimer 是大部分智能所在的地方。它监视 MouseEnterMouseLeave 事件—当 MouseEnter 事件发生时,计时器停止,当 MouseLeave 事件发生时,计时器从零开始重新启动。有两个公共方法可以启动和停止计时器,以及在计时器到期时使用 Storyboard 启动隐藏控件淡出效果的代码,并在淡出完成后进行清理。

由于最初它是用在一个非 ButtonControl 上,我实现了它基于 MouseLeftButtonDown 事件,而不是点击事件。这更灵活,因为它可用于没有 Click 事件的控件。后来,我开始使用 MouseDownUiElementVisibilityBehavior 用于 PopUp ControlPopupControlTimer(为什么创建一个如此相似的类),这需要对 PopupControlTimer 进行一些小的修改,因为需要确保在计时器到期时关闭 Popup,并且淡出效果必须在 PopupChild 上进行,而不是直接在 Popup 上。

  public class MouseDownUiElementVisibilityBehavior
  {
    public static readonly DependencyProperty HiddenUiElementProperty =
      DependencyProperty.RegisterAttached("HiddenUiElement",
        typeof(UIElement), typeof(MouseDownUiElementVisibilityBehavior),
        new PropertyMetadata(null, OnHiddenUiElementChanged));

    public static UIElement GetHiddenUiElement(UIElement uiElement)
    {
      return (UIElement)uiElement.GetValue(HiddenUiElementProperty);
    }

    public static void SetHiddenUiElement(UIElement uiElement, UIElement value)
    {
      uiElement.Visibility = Visibility.Collapsed;
      uiElement.SetValue(HiddenUiElementProperty, value);
    }

    private static void OnHiddenUiElementChanged(object sender, 
        DependencyPropertyChangedEventArgs e)
    {
      var uiElement = (UIElement)sender;
      if (e.OldValue != null) GetVisibilityTimer((UIElement)e.OldValue).Dispose();

      if (e.NewValue == null)
      {
        uiElement.MouseLeftButtonDown -= MouseDownEvent;
        SetVisibilityTimer(uiElement, null);
      }
      else if (e.OldValue == null)
      {
        var hiddenUiElement = (UIElement)e.NewValue;
        hiddenUiElement.Visibility = Visibility.Collapsed;
        uiElement.MouseLeftButtonDown += MouseDownEvent;
        var visibilityTimer = new PopupControlTimer(hiddenUiElement);
        SetVisibilityTimer(uiElement, visibilityTimer);
      }
    }

    private static void MouseDownEvent(object sender, RoutedEventArgs e)
    {
      var hiddenUiElement = GetHiddenUiElement((UIElement)sender);
      hiddenUiElement.Visibility = (hiddenUiElement.Visibility == Visibility.Collapsed)
        ? Visibility.Visible : Visibility.Collapsed;
      if (hiddenUiElement.Visibility == Visibility.Visible)
        GetVisibilityTimer((UIElement)sender).Start();
      else GetVisibilityTimer((UIElement)sender).Stop();
    }

    private static readonly DependencyProperty VisibilityTimerProperty =
      DependencyProperty.RegisterAttached("VisibilityTimer",
        typeof(PopupControlTimer), typeof(MouseDownUiElementVisibilityBehavior), 
        new PropertyMetadata(null));

    private static PopupControlTimer GetVisibilityTimer(UIElement uiElement)
    {
      return (PopupControlTimer)uiElement.GetValue(VisibilityTimerProperty);
    }

    private static void SetVisibilityTimer(UIElement uiElement, PopupControlTimer value)
    {
      uiElement.SetValue(VisibilityTimerProperty, value);
    }
  }

HiddenUiElement 是唯一可见的 DependencyProperty。该行为附加到将接收点击以显示隐藏 UIElement 的控件。当 HiddenUiElement 的值发生变化时,会创建一个与 MouseLeftButtonDown 事件关联的事件处理程序和一个新的 PopupControlTimer 实例,用于隐藏的 UIElementPopupControlTimer 需要对隐藏的 UIElement 的引用,因为它将监视 MouseEnterMouseLeave 事件,以确保只要鼠标在隐藏的 UIElement 上,该 UIElement 就不会消失。实例保存在与最初单击的 UIElement 关联的 DependencyProperty 中,以便在隐藏的 UIElement 更改时可以将其处置。当 UIElement 更改时发生的另一件事是删除 MouseLeftButtonDown 的事件处理程序。

捕获 MouseLeftButtonDown 用于隐藏 Popup 控件。如果需要不同的行为,删除此事件处理程序即可实现。

Popup 控件类

  internal class PopupControlTimer : IDisposable
  {
    private readonly UIElement _hiddenUiElement;
    private readonly DispatcherTimer _timer = new DispatcherTimer();

    public PopupControlTimer(UIElement hiddenUiElement)
    {
      _hiddenUiElement = hiddenUiElement;
      _timer.Interval = new TimeSpan(0, 0, 10);
      _timer.Tick += (s, arg) =>
      {
        FadeOutStoryBoard(_hiddenUiElement);
        _timer.Stop();
      };
      hiddenUiElement.MouseEnter += HiddenUiElementMouseEnter;
      hiddenUiElement.MouseLeave += HiddenUiElementMouseLeave;
    }

    public void Start()
    {
      _timer.Start();
      _hiddenUiElement.Visibility = Visibility.Visible;
    }

    public void Stop()
    {
      _timer.Stop();
    }

    private void HiddenUiElementMouseLeave(object sender, MouseEventArgs e) { Start(); }

    private void HiddenUiElementMouseEnter(object sender, MouseEventArgs e) { Stop(); }

    public void Dispose()
    {
      _hiddenUiElement.MouseEnter += HiddenUiElementMouseEnter;
      _hiddenUiElement.MouseLeave += HiddenUiElementMouseLeave;
    }

    private void FadeOutStoryBoard(UIElement hiddenUiElement)
    {
      if (_fadeOutStoryboard == null)
      {
        // Create the fade out storyboard
        _fadeOutStoryboard = new Storyboard();
        _fadeOutStoryboard.Completed += (s, e) =>
        {
          if (!(hiddenUiElement is Popup))
            hiddenUiElement.Visibility = Visibility.Collapsed;
          else
            ((Popup) hiddenUiElement).IsOpen = false;
          _fadeOutStoryboard.Stop();
        };
        var fadeOutAnimation = new DoubleAnimation(1.0F, 0.0F, 
                new Duration(TimeSpan.FromSeconds(2)));
        Storyboard.SetTarget(fadeOutAnimation, (hiddenUiElement is Popup) 
                ? ((Popup)hiddenUiElement).Child : hiddenUiElement);
        Storyboard.SetTargetProperty(fadeOutAnimation, 
                new PropertyPath(UIElement.OpacityProperty));
        _fadeOutStoryboard.Children.Add(fadeOutAnimation);
      }

      hiddenUiElement.Dispatcher.BeginInvoke(new Action(_fadeOutStoryboard.Begin), 
           DispatcherPriority.Render, null);
    }
    private  Storyboard _fadeOutStoryboard;
  }

PopupControlTimer 主要负责在 10 秒时间过去后将 UIElementVisibility 设置为 Collapsed,但它这样做的前提是先通过动画在 2 秒内将 UIElementOpacity 更改为零。一旦 2 秒过去,Visibility 才会被设置为 Collapsed

还有一个 FadeOutStoryBoard 方法用于创建动画 StoryboardStoryboard 只创建一次然后重复使用,并且包含动画完成时的代码。

PopupControlTimer 还监视 MouseEnter 和 MouseLeave 事件,在 MouseEnter 时禁用计时器,在 MouseLeave 事件时重置并重新启动计时器。

Popup 控件类行为

上面的代码处理了使用 MouseDownUiElementVisibilityBehavior 所需的一切,但为了支持 Popup 的淡入淡出而进行了增强。所需的一部分包含在上面的代码中,包括识别附加的 Control 是一个 Popup,并在淡出计时器结束后将 PopupIsOpen 属性设置为 false,并且对 PopupChild 进行淡出,因为无法对 Popup 进行淡出。还有一个添加的 DependencyProperty,当 Popup 打开后,该属性将被设置为 true 以启用淡出行为。

UseWithPopup DependencyProperty 设置为 true 时,会附加一个事件处理程序到 PopupOpened 事件,并将 PopupControlTimer 的一个实例保存在与 Popup 关联的 DependencyProperty 中。然后 Popup_Opened 事件处理程序将获取此计时器实例并执行计时器的 Start 方法。

public static readonly DependencyProperty UseWithPopupProperty =
    DependencyProperty.RegisterAttached("UseWithPopup",
    typeof(bool), typeof(PopupControlTimer),
    new PropertyMetadata(false, OnUseWithPopupChanged));

public static bool GetUseWithPopup(DependencyObject uiElement)
{
  return (bool)uiElement.GetValue(UseWithPopupProperty);
}

public static void SetUseWithPopup(DependencyObject uiElement, bool value)
{
  uiElement.SetValue(UseWithPopupProperty, value);
}

private static void OnUseWithPopupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  var popup = (Popup)d;
  if ((bool)e.NewValue)
  {
    popup.Opened += Popup_Opened;
    SetPopupControlTimer(popup, new PopupControlTimer(popup));
  }
  else
  {
    popup.Opened += Popup_Opened;
    SetPopupControlTimer(popup, null);
  }
}

private static void Popup_Opened(object sender, EventArgs e)
{
  var timer = GetPopupControlTimer((UIElement)sender);
  timer.Start();
}

private static readonly DependencyProperty PopupControlTimerProperty =
    DependencyProperty.RegisterAttached("PopupControlTimer",
    typeof(PopupControlTimer), typeof(DependencyObject),
    new PropertyMetadata(null));

private static PopupControlTimer GetPopupControlTimer(DependencyObject uiElement)
{
  return (PopupControlTimer)uiElement.GetValue(PopupControlTimerProperty);
}

private static void SetPopupControlTimer(DependencyObject uiElement, PopupControlTimer value)
{
  uiElement.SetValue(PopupControlTimerProperty, value);
}

示例

应用程序启动时

应用程序在点击顶部控件(Border)后,显示隐藏的 Border 及其包含的控件

应用程序在 10 秒计时器到期后,隐藏的 Border 正在淡出

应用程序在按下 ToggleButtonIsChecked 设置为 true 后,导致 Popup 控件出现

示例包含一个被点击以弹出包含 TextBoxBorderBorder。再次点击 Border 将关闭包含 TextBoxBorder。可以点击 TextBox 并输入内容,

使用计时器

使用带 Popup 的计时器

要在 Popup 控件上使用此 PopupControlTimer,只需将 PopupControlTimerUseWithPopup 属性设置为 true,如下面的 Popup Control 示例 XAML 所示。

  <Popup Name="Popup"
         HorizontalAlignment="Center"
         VerticalAlignment="Center"
         fadingPopupMouseDown:PopupControlTimer.UseWithPopup="True"
         IsOpen="{Binding IsChecked,
                          ElementName=ToggleButton}">
   <StackPanel Margin="2"
               Background="Aqua"
               Orientation="Vertical">
    <TextBlock Width="100"
             Margin="2"
             Text="Pop-up" />
    <TextBox Width="200"
             Margin="2"
             Text="You can change this text" />
   </StackPanel>
  </Popup>

使用带通用控件的计时器

要在通用 UIElement 上使用 PopupControlTimer,只需在 UIElement 上将 MouseDownUiElementVisibilityBehaviorHiddenUiElementproperty 属性设置为要点击的那个,以显示隐藏的 UIElement

  <Border Width="100"
          Height="100"
          VerticalAlignment="Top"
          Background="AliceBlue"
          fadingPopupMouseDown:MouseDownUiElementVisibilityBehavior.HiddenUiElement
                  ="{Binding ElementName=Panel}">
   <TextBlock HorizontalAlignment="Center"
              VerticalAlignment="Center"
              Text="Click Here to show stack panel"
              TextAlignment="Center"
              TextWrapping="Wrap" />
  </Border>

需要注意的是,在 MouseDownUiElementVisibilityBehanviorBinding 中只指定了 ElementName。这就是 Binding 到另一个 UIElement 的方式。

历史

  • 2016/02/16: 初始版本。
  • 2016/02/19: 更新代码,包含使用带 Popup 的计时器。
  • 2016/02/23: 对 Popup 的补充说明。
© . All rights reserved.