Skip to main content

...

....

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

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >();

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV