WPF Tutorial - Resizeable Popup

Skill

WPF Tutorial - Resizeable Popup

Posted in:

We are going to be taking a look at how to use two WPF controls today, the Popup control and the Thumb. Both are located in the system.windows.controls.primitives namespace, because they are used in a number of regular WPF controls. What are we going to do with these two controls? Well, as the title of the tutorial says, we are going to make a resizeable popup.

You can probably guess what the popup control does - it makes a popup window. In the standard WPF controls, the popup control is used mainly for menus and tooltips, but it can hold pretty much any content you want to throw at it. The thumb control does not have an as obvious a name as popup, but it serves an important purpose. It is used to do mouse movement tracking, and the two places where it shows up most often are the Scrollbar and the Slider. The thumb is the component that the user grabs and drags around.

So today we are going to create a popup that uses thumbs to make resizing that popup extremely easy. Below is a screenshot of the silly little application we will be building:

Popup Screenshot

Sadly, popups do not have any user resizing capability built right in. But as you're about to see, using thumbs makes it pretty easy to do. First, the XAML:

<Window x:Class="ResizePopup.Window1"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="Resizeable Popup" Height="100" Width="100">
  <Window.Resources>
   
    <Style TargetType="{x:Type Thumb}"
          x:Key="PopupThumb">
      <Setter Property="HorizontalAlignment"
             Value="Stretch"/>
      <Setter Property="VerticalAlignment"
             Value="Stretch"/>
      <EventSetter Event="DragStarted"
                  Handler="ThumbDragStarted" />
      <EventSetter Event="DragDelta"
                  Handler="ThumbDragDelta" />
      <EventSetter Event="DragCompleted"
                  Handler="ThumbDragCompleted" />
    </Style>
   
    <Popup StaysOpen="False" Width="100" Height="100"
          x:Key="myPopup" Placement="Mouse">
      <Border BorderBrush="Black" BorderThickness="1">
        <Grid Background="White">
          <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="10" />
          </Grid.RowDefinitions>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="10" />
          </Grid.ColumnDefinitions>
          <TextBlock Grid.Row="0" Grid.Column="0"
                    x:Name="PopupTxt"
                    TextAlignment="Center"
                    VerticalAlignment="Center">
            I'm A Popup!!
          </TextBlock>
          <Thumb Grid.Row="0" Grid.Column="1"
                Cursor="SizeWE"
                Style="{StaticResource PopupThumb}"/>
          <Thumb Grid.Row="1" Grid.Column="0"
                Cursor="SizeNS"
              Style="{StaticResource PopupThumb}"/>
          <Thumb Grid.Row="1" Grid.Column="1"
                Cursor="SizeNWSE"
              Style="{StaticResource PopupThumb}"/>
        </Grid>
      </Border>
    </Popup>
   
  </Window.Resources>
 
  <Button Click="ShowPopup" Content="Popup!!" />
 
</Window>

Ok, let's work our way down from the top. First, we have a resources section, and the first thing in there is a style. We have a style because we are actually going to be using multiple thumbs for resizing (one for the bottom edge, one for the corner, and one for the right edge), and this way we don't have to repeat these property and event setters. Setting the alignment properties to stretch just guarantees that the thumbs will fill their respective containers - not very interesting. The event setters, however are interesting. We will be getting to the code behind in a minute, but at this point you are probably realizing what using a Thumb gives us. We get these three events (DragStarted, DragDelta and DragCompleted) that give us all sorts of information when a user tries to drag a Thumb.

Next comes the popup. I've put it here in resources because I like to keep my popups out of the way, but you can actually put it anywhere in your XAML that a regular control would go. The only difference when doing that is that the Popup does not get added as a visible element (they only appear when told to, and even then, as a separate window), even though it is part of the logical tree.

The popup here doesn't have much to it: a border, a grid, and a textblock taking up most of that grid. But we do have our three thumbs - one on the right, one on the bottom, and one in the corner, each with an appropriate cursor. And as you might expect, the style on each of the thumbs is set to the style we defined above.

And that brings us to the end of the resources, which is almost the end of the XAML file. The only thing left is the button which triggers the popup to show, by calling the method ShowPopup when clicked. And since the code behind is next, we will be getting to the content of that method real soon.

using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace ResizePopup
{
  public partial class Window1 : Window
  {
    private const int MaxSize = 500;
    private const int MinSize = 50;

    private Popup _MyPopup;

    public Window1()
    {
      InitializeComponent();
      _MyPopup = Resources["myPopup"] as Popup;
    }

    private void ShowPopup(object sender, RoutedEventArgs e)
    {
      _MyPopup.IsOpen = true;
    }

    private void ThumbDragDelta(object sender,
      DragDeltaEventArgs e)
    {
      Thumb t = sender as Thumb;

      if (t.Cursor == Cursors.SizeWE
        || t.Cursor == Cursors.SizeNWSE)
      {
        _MyPopup.Width = Math.Min(MaxSize,
          Math.Max(_MyPopup.Width + e.HorizontalChange,
          MinSize));
      }

      if (t.Cursor == Cursors.SizeNS
        || t.Cursor == Cursors.SizeNWSE)
      {
        _MyPopup.Height = Math.Min(MaxSize,
          Math.Max(_MyPopup.Height + e.VerticalChange,
          MinSize));
      }
    }

    private void ThumbDragStarted(object sender,
      DragStartedEventArgs e)
    {
      //This is called when the user
      //starts dragging the thumb
    }

    private void ThumbDragCompleted(object sender,
      DragCompletedEventArgs e)
    {
      //This is called when the user
      //finishes dragging the thumb
    }
  }
}

First off, we have ourselves two constants - a minimum size and a maximum size, just because it is always good to put limits on something like this. Next, in the constructor, we pull the popup out of the resources for the window and store it in the private variable _MyPopup. This is mostly because we are going to be poking at it a lot, and so I didn't want to have to keep pulling it out of the resource dictionary.

Next, we have the ShowPopup method that the button on the window calls. It doesn't take much to show a popup, does it? You can control exactly where the popup appears through a couple different properties on the popup - namely PlacementTarget, PlacementRectangle, Placement. And if those don't give you enough control, there is always the CustomPopupPlacementCallback property, which allows you to give the popup a method of yours to call when it wants to figure out its position.

By default, a popup will disappear when it loses focus, but you can override that behavior using the StaysOpen property. If that is set to true, the popup will stay open until IsOpen gets set to false.

Ok, enough about opening the popup. Now we get to the meat - resizing the popup. In this particular case, we don't actually care about the DragStarted and DragCompeted parts - we don't need them - but I left them in there for you to see. All the actual logic is in DragDelta - although there isn't that much.

In DragDelta, we first deal with width. If the thumb being dragged is the right edge or the corner (that is the cursor check), we add the HorizontalChange (which could be positive or negative) on the DragDeltaEventArgs to the width of the popup. We then constrain the new width within the min and max constants using the math min and max functions, and set the new value as the new width of the popup. We do the exact same thing for height, except in that case we only do it if the thumb being dragged is the bottom edge or the corner.

And that is it! So there you go, you have now learned how to use Popups and Thumbs in one fell swoop. If you would like to download the Visual Studio project for this example, you can grab it here, and as always, questions or comments are welcome.

Takhion
12/19/2008 - 15:42

Useful little tutorial, as always!
There’s only one strange behaviour I’ve encountered: while the horizontal resizing works perfectly, when I try to resize vertically it fixes the side I’ve clicked and moves the other side! I’ve checked the code and couldn’t find a solution, the two methods are apparently identical…

reply

Vinoth
12/22/2008 - 06:04

Hi,

please copy paste and run this code below and help me in doing the animation in popup when you click on the listview items, the popup should expand very slowly when the mouse enter and collapse back when the mouse leave, expecting your reply :). i tried with the time duration found in the popup say some 5 secs, but it displaying only after 5 secs suddenly, i dont want the popup to display suddenlt, hope ui understand my problem, please rply soon ;-)

Close Window

Details of the Employee

Age

Job Status

Details

help me in doing this, expecting ur reply soon as i am in need to complete this task as soon as possible,
Any one help me please…

reply

The Reddest
12/22/2008 - 09:27

Vinoth, our comment system eats opening and closing tags. You'll have to surround them in a language block. See this post for the syntax. For XAML, we've been using XML.

reply

Add Comment

Put code snippets inside language tags:
[language] [/language]

Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]

See here for supported languages.

Javascript must be enabled to submit anonymous comments - or you can login.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.