How to make an expander control hover over the next element?
Asked Answered
G

2

1

In my setup exemplified below, I didn't notice until recently that when the list of elements listed in the expander grows (so that the length of it exceeds the length of the buttons in the panel next to it), it doesn't hover over the data grid, as intuitively expected. Instead, it pushes it down which makes the whole GUI to appear jumpy vertically.

<StackPanel>
  <StackPanel Orientation="Horizontal">
    <StackPanel>
      <Expander>...</Expander>
    </StackPanel>
    <StackPanel>
      <Button ... /><Button ... /><Button ... />
    </StackPanel>
  </StackPanel>
  <StackPanel Orientation="Horizontal">
    <DataGrid ... />
  </StackPanel>
</StackPanel>

One way to fix this is to put everything in the same cell in a grid and add the expander last. However, that seems to me inappropriate on several levels. Instead, I'd prefer to force expander to expand touchlessly above the other controls. It should affect the layout but only its size from the folded state. The expansion should not affect the layout at all.

How can I tell the stupid expander not to be so pushy?

Gaona answered 4/2, 2015 at 20:40 Comment(2)
Have you considered using a Popup or ComboBox instead of an Expander?Kamp
@Kamp Yes. It needs to be the expander control for several reasons. In fact, at the moment, it's be easier to do it the ugly way (put everything in the same cell in a grid) than fooling around changing the expander. I've got some functionality baked in there and starting to re-develop makes my skin crawl... Too afraid of oopsies, and gotchas.Gaona
S
8

For reference, let's number the StackPanel elements from your original XAML:

<StackPanel #1>
    <StackPanel #2 Orientation="Horizontal">
        <StackPanel #3>
            <Expander>...</Expander>
        </StackPanel>
        <StackPanel #4>
            <Button ... /><Button ... /><Button ... />
        </StackPanel>
    </StackPanel>
    <StackPanel #5 Orientation="Horizontal">
        <DataGrid ... />
    </StackPanel>
</StackPanel>

A StackPanel when oriented horizontally will set its height to that of its tallest child, and when oriented vertically will set its height to the sum of all of its children's heights.

When you expand the Expander control, you increase its height, and therefore you increase the height of its container (#3). This in turn may or may not increase the height of #3's parent container (#2) depending on whether the expander becomes larger in height than the stack panel containing the buttons (#4).

To achieve the effect you seem to be after, you can either use a Grid as already discussed in the question and other answer, or you can use a Canvas element like so:

<Window x:Class="..."
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <StackPanel Orientation="Horizontal" Panel.ZIndex="1">
            <Canvas Width="{Binding ActualWidth, ElementName=Expander}">
                <Expander x:Name="Expander" Header="Header" Background="Yellow">
                    <StackPanel Background="Aqua">
                        <TextBlock Text="Some text" />
                        <TextBlock Text="Some text" />
                        <TextBlock Text="Some text" />
                        <TextBlock Text="Some text" />
                        <TextBlock Text="Some text" />
                        <TextBlock Text="Some text" />
                    </StackPanel>
                </Expander>
            </Canvas>
            <StackPanel>
                <Button Content="Button1" />
                <Button Content="Button2" />
                <Button Content="Button3" />
            </StackPanel>
        </StackPanel>
        <TextBlock Text="Some more text" Background="LimeGreen" />
    </StackPanel>
</Window>

The canvas as used here allows the expander control to "escape" the boundaries of the container. To get the expander control to "float" above the element directly underneath it (the TextBlock in this example), the Panel.ZIndex attached property is used. We also need to bind the width of the canvas to the width of the expander as the canvas will not size itself based on its children.

Here's how it looks when the expander is collapsed:

And how it looks when it is expanded:

(Forgive the horrible colours, they are just so you can see where the control boundaries are).

Seethe answered 5/2, 2015 at 14:24 Comment(5)
The colors are great! One can clearly see what is what. I wonder if this is the intended purpose of the canvas control, though. I was under the impression that its purpose was to draw on...Gaona
@KonradViltersten The purpose of the WPF Canvas is to layout its children at specific coordinates, it doesn't have anything to do with drawing at all really. It also has the useful property of allowing its children to "escape" its bounds by default (ie. it doesn't clip its children to its own bounds). Therefore it is often used when you need an "overlapping" effect. The only other built-in control that has similar layering possibilities is the Grid, but that is a more "heavyweight" panel.Seethe
@KonradViltersten Think of the Canvas in this case as being a bit like position: relative; in CSS, in that the canvas is the layout "slot" and its child elements are positioned relative to the canvas but can end-up overlaying other elements outside of the bounds of the canvas.Seethe
Cool stuff :) Is there a way to let the Expander "flow" over the containing Window boundaries too? I packed my Expander in a Usercontrol hoping the Panel.ZIndex set in the UC would be enough, as it seems, I had to set the ZIndexes in the containing Grid/Panel too, to achieve "on Top".Euphrasy
@Euphrasy I think all WPF controls are clipped to their parent window's bounds. You could try using a Popup control, which is basically its own window, but then you might run into focus issues.Seethe
P
1

This is such a prominent post when you look for answers for this kind of problem, I feel it should be mentioned that a <popup> can work very well in this situation.

Place a <popup> inside the content of the expander and you will achieve the same sort of functionality with a much simpler Xaml layout.

Edit: I've explored this option and there's a caveat... Popups are built int to go above OTHER WINDOWS! You'll need to either program the popup to close when the window loses focus or override the template a bit. This can also be easily googled.

Peptidase answered 15/3, 2022 at 19:4 Comment(1)
Woah... 1 day, 1 week, 1 month and 7 years later - answers still coming in. :)Gaona

© 2022 - 2024 — McMap. All rights reserved.