Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Behavior to Make a UIElement Visible on MouseDown

0.00/5 (No votes)
16 Feb 2016 1  
This article presents a behavior that can make another UIElement visible when the mouse down event occurs on the UIElement this behavior is attached to. Have extended this to directly support fading of a Popup control

Introduction

There are many times when it might be desirable to make a UIElement visible when another UIElement is clicked. In my case I wanted to display a set of buttons that overlaid an image, and did not want them always visible because they would clutter up the window and interfere with viewing the image. I initially created this in code behind, with the plan to encapsulate the code in a behavior. Because the pop up is intended to contain buttons, a click on the panel will set the Visibility of the hidden control to Collapsed. I have to admit that this functionality does make this pop behavior of limited usefulness in many applications, and maybe will enhance the design if I find a need for keeping the control open..

Background

Once this code was encapsulated in a behavior, I beefed it up a bit. One of the features I wanted is for the buttons to disappear after a set time, in this case 10 seconds. Of course did not want them just disappearing if the mouse was over the previously hidden UIElement so I attached events to the UIElement for the MouseEnterEvent and MouseLeaveEvent. Basically stopped the timer as long as the mouse was in the UIElement and then reset the timer when the mouse left the UIElement. Also, clicking the original UIElement again will hide the previously hidden UIElement. I then added a Storyboard that created a 2 second fade out so the buttons did not just disappear.

The Behavior

The actual MouseDownUiElementVisibilityBehavior is actually fairly simple because most of the functionality is in the helper class PopupControlTimer. There is a public DependencyProperty for the hidden control, and a private DependencyProperty for the associated PopupControlTimer. The hidden control DependencyProperty has an event handler to associate an event handler with the MouseLeftButtonDown event, and to initialize an instance of the PopupControlTimer class.

The MouseLeftButtonDown event handler basically just changes the Visibility of the hidden control, and starts the PopupControlTimer if the Visibility is Visible, and otherwise it stops the PopupControlTimer.

The PopupControlTimer is where most of the intelligence is. It monitors the MouseEnter and MouseLeave event—when the MouseEnter event occurs, the timer is stopped and when the MouseLeave event occurs, the timer is restarted from zero. There are two public methods to start and stop the timer, and code to start the fade out of the hidden control using a Storyboard when the timer expires, and cleanup when the fade out completes

Since it was initially used on a Control that is not a Button, I implemented it based on the MouseLeftButtonDown event, not the click event. This is more flexible since it can be used on controls that do not have a Click event. Later I started using the PopupControlTimer that is used by the MouseDownUiElementVisibilityBehavior for a PopUp Control (why create a class that would be so similar), which required some small modifications to the PopupControlTimer since needed to make sure close the Popup when the timer expires, and the fade had to be done on the Child of the Popup instead of directly on the 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);
  }
}

The HiddenUiElement is the only visible DependencyProperty. The bahaviour is attached the control that will receive the click to show the hidden UIElement. When the value of the HiddenUiElement changes, an event handler associated to the MouseLeftButtonDown event and a new instance of the PopupControlTimer is created for the hidden UIElement. The PopupControlTimer needs a refernce to the hidden UIElement since it will monitor the MouseEnter and MouseLeave events to ensure that the hidden UIElement does not disappear as long at the mouse is over the hiddne UIElement. An instance is saved in a DependencyProperty associated with the UIElement that is initially clicked so that it can be disposed of when the hidden UIElement is changed. The other thing that happens when the UIElement in changed is that the event handler for the MouseLeftButtonDown is removed.

The capture of the MouseLeftButtonDown is used to hide the Popup control. If a different behavior is desired, removing this event handler would do the trick.

The Popup Control Class

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

The PopupControlTimer is primarily tasked with setting the Visibility of the UIElement to Collapsed when the 10 second time elapses, but it does this only after an animation changes the Opacity of the UIElement to Zero over a period of 2 seconds. Once the 2 seconds elapses, the Visibility is actually set to Collapsed.

There is also a FadeOutStoryBoard menthod create the animation Storyboard. The Storyboard is created once and then reused, and contains the code for when the animation completes..

The PopupControlTimer also monitors the MouseEnter and MouseLeave events, and disables the timer on MouseEnter, and resets and restarts the timer when on the MouseLeave event.

The Popup Control Class Behavior

The code above handles everything required when using the MouseDownUiElementVisibilityBehavior, but has been enhance to support the fading of a Popup. Part of what is required is in the code above, which includes recognizing that the attached Control is a Popup, and setting the Popup's IsOpen property to false when the fading timer has elapsed and also doing the fade on the Child of the Popup since it is not possible to fade a Popup. There is also a DependencyProperty that is added which will be set to true on the Popup to enable the fade behavior once the Popup is opened.

When the UseWithPopup DependencyProperty is set to true, an event handler to the Popup's Opened event is attached and an instance of the PopupControlTimer is save in a DepencencyProperty associated with the Popup. The Popup_Opened event handler will then get this instance of the timer and execute the Start method of the timer.

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

The Sample

Application at startup

Application after top control (Border) clicked showing hidden Border with contained controls

Application as hidden border is fading after 10 second timer elapses

Application after the ToggleButton is pushed to set IsChecked equal to true, which causes the Popup control to appear

The sample has a Border that is clicked to bring up a Border containing a TextBox. Clicking the Border again will close the Border containing a TextBox. It is possible to click in the TextBox and type,

Using the timer

Using the timer with a Popup

To use this PopupControlTimer on a Popup Control, just need to set the UseWithPopup property of the PopupControlTimer to true as shown in sample XAML for a Popup Control below:

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

Using the timer with Generic Controls

To use the PopupControlTimer on a generic UIElement, just set the HiddenUiElementproperty property of the MouseDownUiElementVisibilityBehavior on the UIElement that is to be clicked to show the make the hidden UIElement visible:

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

It should be noted that in the Binding for the MouseDownUiElementVisibilityBehanvior only the ElementName is specified.  This is how Binding to another UIElement is done.

History

  • 02/16/2016: Initial Version.
  • 02/19/2016: Updated code to include using Timer with Popup.
  • 02/23/2016: Additional explanation of Popup

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here