WPF Sample Series – Expander Control With Popup Content

Collapsed

 ExpandersCollapsed

Expanded

 ExpandersExpanded

This is the next sample in the WPF Sample Applications Series. The purpose of the Sample Series is to provide concise code solutions for specific programming tasks. This sample provides a brief description of the problem, the solution and full source code.

Yesterday I was working on a Validation Summary control similar to the ASP.NET Validation Summary for my WPF business applications.  I wanted to use the Expander control to display the validation errors to the user.  So I dove in, and quickly realized that the Expander control doesn’t have built in behavior to overlay other controls when I expands. 

I searched the web, reviewed my WPF books, viewed example code for an hour but couldn’t find what I was looking for.  To make sure I wasn’t missing something I even rang up a great friend and WPF expert to confirm this is the standard Expander control behavior.

There are several solutions for this problem.  You could restyle other WPF controls that have dropdown overlay capabilities built in, could use a PopUp control or you could load up a ToolTip with the information.

I like the Expander control and wanted to use it.  It remains expanded until either closed by the user or programmatically and Vista users have seen and used this control (or similar control) and know what it does and how it works.

Note: The Expander control has some specific guidelines about setting the Height or Width properties that you should read at MSDN Expander.

The solution I came up with was to wrap the Expander control in a Canvas control.  The Canvas control’s Height was defined and limited by the Grid.Row Height.  I could have also set the Canvas Height property and set the Grid.Row Height to Auto.  I prefer the container controls to manage the layout when possible.  I have tried all the WPF container controls, but only the Canvas control allows this to work as described.

When the Expander control expands, the parent Canvas will not resize.  Since the Canvas control’s ClipToBounds property defaults to False, child controls are permitted to extend past the boundary of the Canvas control.

We still have one more detail to cover.  How to get the Expander’s Content to overlay the controls below it and appear on top.  By setting the Canvas control’s attached property, Panel.ZIndex to 99, the WPF layout system to render the Canvas and it’s child controls on top of other controls that have a lower Panel.ZIndex property values.  Presto, we have an Expander control that over lays controls below it.

Getting the Expander control Content to look like a real PopUp window or ToolTip is super easy in WPF.  I added a DropShadowBitmapEffect to Grid control that lays out the Expander control’s expanded content.

Downloads

After downloading, you must rename the following download by changing the file extension from .doc to .zip. This is a requirement of WordPress.com.

Solution

Here are the bullet points of this solution:

  • Wrap the Expander control inside a Canvas control.
  • Constrain the Canvas height to the desired height when the Expander control is collapsed.
  • Set the Canvas Panel.ZIndex higher than controls you want to overlay.
  • Style and design your Expander Content so that it looks like a PopUp.
<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Expender WPF Sample" Height="300" Width="400"
    >
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <Grid Grid.Column="0">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Expander HorizontalAlignment="Left" Header="Standard Expander" 
                  VerticalAlignment="Top" ExpandDirection="Down" Width="150">
            <TextBlock TextWrapping="Wrap" Background="AntiqueWhite">
                This is the standard expander
                behavior.  The expander opens
                and the controls below it 
                move down.
            </TextBlock>
        </Expander>
        <StackPanel Grid.Row="1"  Margin="10,5,0,0">
            <RadioButton Content="Choice One"/>
            <RadioButton Content="Choice Two"/>
            <RadioButton Content="Choice Three"/>
        </StackPanel>
    </Grid>
    
    <Grid Grid.Column="1">
        <Grid.RowDefinitions>
            <RowDefinition Height="33" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Canvas Panel.ZIndex="99">
            <Expander HorizontalAlignment="Left" Header="PopUp Window Expander" 
                      VerticalAlignment="Top" ExpandDirection="Down" Width="175">
                <Grid Background="Cornsilk">
                    <Grid.BitmapEffect>
                        <DropShadowBitmapEffect />
                    </Grid.BitmapEffect>

                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition />
                    </Grid.RowDefinitions>

                    <TextBlock Text="How Cool Is This!" FontWeight="Bold" 
                               Margin="5"/>
                    <TextBlock Grid.Row="1" TextWrapping="Wrap" Margin="5">
                        This is the popup expander
                        behavior.  The expander opens
                        and overlays the controls 
                        below it.
                    </TextBlock>
                </Grid>
            </Expander>
        </Canvas>
        <StackPanel Grid.Row="1" Margin="10,5,0,0">
            <RadioButton Content="Choice One"/>
            <RadioButton Content="Choice Two"/>
            <RadioButton Content="Choice Three"/>
        </StackPanel>
    </Grid>
</Grid>
</Window>

Hope you can learn just a little bit more about WPF from this article and the Sample Series.

Just a grain of sand on the worlds beaches.

13 Responses to WPF Sample Series – Expander Control With Popup Content

  1. wazikhan says:

    Hi Karl it really help me but now i have a problem when i use Expander inside in Canvas in Scrollviewer. Scrollviewer clip the expander.
    what should i do to fix it.

    • Not really sure. But since you are in a ScrollViewer, I would not use the above implementation. I would use the default implementation that simply moves controls below the Expander down.

      Cheers,

      Karl

  2. t0ol says:

    Hey Karl cool sample.
    I am looking to do something similar, only I have to expand to the left and outside the grid boundaries.

    In your example you expand down, but I would like to expand to the left and overlay the content to the left with no regard to grid boundaries.

    Sort of like this menu, only expanding to the left intead of right, http://htmldog.com/articles/suckerfish/dropdowns/example/vertical.html

    Thanks

  3. mreines says:

    So – I had this issue as well and found a slick way to handle this without worrying about zindex or any of that… just do this (not real XAML but you will get the idea)

    then in Expander.Expanded just set Popup.IsOpen = true! And Vice versa in the Collapsed event.

    Yay!

    This handles all the positioning of the popup and whatnot.

  4. mreines says:

    It didn’t copy the XAML???
    Try again…

  5. mreines says:

    Alright – again that didn’t work so basically i’ll explain – for the content of the expander use a ‘Popup’ and set IsOpen true and false on expand and collapse.
    Simple.

  6. jordanday says:

    Hi Karl, nice post.

    I am wondering if you have come across a way to perform this same trick, but without absolutely sizing your Canvas(height) or Expander (width)? Despite a few hours of toying around, I haven’t been able to deduce a method for doing this. Not sure if I can post XAML here (mreines seemed to have problems posting XAML).

    Basically, in your example code, you’re explicitly setting the rowheight of the row with the canvas + expander. If the rowheight is set to auto, the row will disappear. I can of course bind to Height or ActualHeight on the content in the expanders header, but if that’s not explicitly set (say a databound itemscontrol using a vertical stack panel to display), I’m back to square one.

    I’m sure this has everything to do with Canvas not sizing to content (which I guess is what lets this overlay effect work?), but it ends up (seemingly) not working as you’d want if you’re trying to do relative sizing. Anyway, just wondering if in the two years or so since you wrote this post you’ve come across another way to do this overlay affect.

    Thanks,
    Jordan

    • Jordan,

      Give this a try. Set the row size to Auto. Set the Height of the Expander control (not the grid inside).

      Cheers,

      Karl

      • jordanday says:

        Hey Karl,
        thanks for the quick reply.

        I think you’ll find if you make that change to your code above, the canvas/expander will end up not taking up any layout space and the three radio buttons begin displaying from the top of the window, instead of being offset by the height set on the expander. They’ll effectively cover up your expander.

        Additionally, as you point out in your post, setting the height on an expander (that expands down, at least) is not a good idea, since it limits the size of the expanded content (I guess if you set the height to some really large value this probably won’t be a concern). Anyway, I don’t expect this stuff to be fresh in your mind, you did write this post more than two years ago, after all :).

        Just wanted to let you know I did try out your suggestions, just reporting my findings.

        Basically it seems that ultimately with this setup, if you can’t explicitly set the height and width, you need the canvas’ height to size to the expanders header content height, and you would generally want the expanders width to size to the containers (the level above the canvas) width. When trying to use relative sizing, I just can’t get this to work (with a header that has some databound itemscontrol, for example).

        Anyway, I’m not too familiar with custom control creation, but at this point I’m thinking I need to write one that acts like an expander but has the ability to let the child content extend beyond its own bounds.

        Anyway, thanks again for your quick response, and for all your work blogging, I think your blog was probably one of the first I came across when I first started out in WPF and it’s been invaluable.

        • jordanday says:

          Wanted to give an update on this — I ultimately resolved the height and width issues with two steps:
          1) Override the default Expander control template. Instead of making the Expander the child of a Canvas, I wrapped the ContentControl in the Expander’s template with the canvas, instead. This allowed the height to be determined by the Expander’s header automatically (so the Grid’s row height could be set to auto).
          2) Create custom panel (I chose to make it basically identical to Canvas). In MeasureOverride when telling the children to measure themselves, it passes in the availableSize.Width value, instead of Double.PositiveInfinity. This causes the ContentControl to receive the correct width.

          This is a little more extreme than I had hoped would be required, and certainly out of the scope that I’m sure you originally wrote this article for, but I figured I’d make this info available if anyone else runs into this need.

        • Glad you figured out how to work this out.

          Each scenario is always just a little different.

          Cheers,

          Karl

Follow

Get every new post delivered to your Inbox.

Join 247 other followers