This is going to be straight forward no doubt, but for what ever reason, my mind is drawing a blank on it.
I've got a small, non-resizeable window (325x450) which has 3 Expanders in it, stacked vertically. Each Expander contains an ItemsControl that can potentially have a lot of items in and therefore need to scroll.
What I can't seem to get right is how to layout the Expanders so that they expand to fill any space that is available without pushing other elements off the screen. I can sort of achieve what I'm after by using a Grid and putting each expander in a row with a * height, but this means they are always taking up 1/3 of the window each which defeats the point of the Expander :)
Crappy diagram of what I'm trying to achieve:
The WPF Expander represents a control with an expanded view where the contents of the expanded area can be expanded or collapsed. There are two ways to create an expander control in a WPF app. We can create an Expander control at design-time using the <Expander> element of XAML.
This requirement is a little unusal because the you want the state of the Children
in the Grid
to decide the Height
of the RowDefinition
they are in.
I really like the layout idea though and I can't believe I never had a similar requirement myself.. :)
For a reusable solution I would use an Attached Behavior for the Grid
.
The behavior will subscribe to the Attached Events Expander.Expanded
and Expander.Collapsed
and in the event handlers, get the right RowDefinition
from Grid.GetRow
and update the Height
accordingly. It works like this
<Grid ex:GridExpanderSizeBehavior.SizeRowsToExpanderState="True">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Expander Grid.Row="0" ... />
<Expander Grid.Row="1" ... />
<Expander Grid.Row="2" ... />
<!-- ... -->
</Grid>
And here is GridExpanderSizeBehavior
public class GridExpanderSizeBehavior
{
public static DependencyProperty SizeRowsToExpanderStateProperty =
DependencyProperty.RegisterAttached("SizeRowsToExpanderState",
typeof(bool),
typeof(GridExpanderSizeBehavior),
new FrameworkPropertyMetadata(false, SizeRowsToExpanderStateChanged));
public static void SetSizeRowsToExpanderState(Grid grid, bool value)
{
grid.SetValue(SizeRowsToExpanderStateProperty, value);
}
private static void SizeRowsToExpanderStateChanged(object target, DependencyPropertyChangedEventArgs e)
{
Grid grid = target as Grid;
if (grid != null)
{
if ((bool)e.NewValue == true)
{
grid.AddHandler(Expander.ExpandedEvent, new RoutedEventHandler(Expander_Expanded));
grid.AddHandler(Expander.CollapsedEvent, new RoutedEventHandler(Expander_Collapsed));
}
else if ((bool)e.OldValue == true)
{
grid.RemoveHandler(Expander.ExpandedEvent, new RoutedEventHandler(Expander_Expanded));
grid.RemoveHandler(Expander.CollapsedEvent, new RoutedEventHandler(Expander_Collapsed));
}
}
}
private static void Expander_Expanded(object sender, RoutedEventArgs e)
{
Grid grid = sender as Grid;
Expander expander = e.OriginalSource as Expander;
int row = Grid.GetRow(expander);
if (row <= grid.RowDefinitions.Count)
{
grid.RowDefinitions[row].Height = new GridLength(1.0, GridUnitType.Star);
}
}
private static void Expander_Collapsed(object sender, RoutedEventArgs e)
{
Grid grid = sender as Grid;
Expander expander = e.OriginalSource as Expander;
int row = Grid.GetRow(expander);
if (row <= grid.RowDefinitions.Count)
{
grid.RowDefinitions[row].Height = new GridLength(1.0, GridUnitType.Auto);
}
}
}
If you don't mind a little code-behind, you could probably hook into the Expanded
/Collapsed
events, find the parent Grid
, get the RowDefinition
for the expander, and set the value equal to *
if its expanded, or Auto
if not.
For example,
Expander ex = sender as Expander;
Grid parent = FindAncestor<Grid>(ex);
int rowIndex = Grid.GetRow(ex);
if (parent.RowDefinitions.Count > rowIndex && rowIndex >= 0)
parent.RowDefinitions[rowIndex].Height =
(ex.IsExpanded ? new GridLength(1, GridUnitType.Star) : GridLength.Auto);
And the FindAncestor
method is defined as this:
public static T FindAncestor<T>(DependencyObject current)
where T : DependencyObject
{
// Need this call to avoid returning current object if it is the
// same type as parent we are looking for
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
};
return null;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With