C# WPF Tutorial - Resizeable Popup [Beginner]


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.

Source Files:

Comments