WPF Expander.Header horizontal stretch
Asked Answered
E

5

16

I have an Expander in Wpf. In the header I have Label on left aligned and want to have a button on the right site. I use the following XAML:

<Expander HorizontalAlignment="Stretch" IsExpanded="True">
    <Expander.Header >
        <Grid HorizontalAlignment="Stretch" Background="Aqua" Margin="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Label Grid.Column="0" Content="Label on the left site"/>
            <Button Grid.Column="1" Content="Button on the right"/>
         </Grid>
    </Expander.Header>
    <Label Content="Some Content"/>
</Expander>

But that does not work. The button in the header is aligned to the left, next to the label. Can anyone explain me how to do it right?

Epiphenomenon answered 1/7, 2015 at 12:39 Comment(1)
Stretching Content in an Expander HeaderStraw
B
23

I was able to get content stretching in the header to work using the xaml provided below. Basically I data bind the grids HorizontailAlignment to the content presenter ancestor. Unfortunatley the solution provided by scher (data binding to ActualWidth) can cause ui elements to be displayed wider then there container resulting in controls being partially cut off.) Bolu's link to the article "Stretching Content in an Expander Header" uses code behind where as this example uses pure xaml.

<ItemsControl x:Name="ItemGroups" Grid.Column="2" Grid.Row="0"   ItemsSource="{Binding Model.ItemGroups}" ScrollViewer.VerticalScrollBarVisibility="Auto" >
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Expander Margin="4,0"   Header="{Binding}">
                        <Expander.HeaderTemplate>
                            <DataTemplate>
                                <Grid  HorizontalAlignment="{Binding Path=HorizontalAlignment, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Mode=OneWayToSource}" >
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition   />
                                        <ColumnDefinition  Width="Auto"/>
                                        <ColumnDefinition  Width="64"/>
                                    </Grid.ColumnDefinitions>

                                    <TextBox Grid.Column="0"  Text="{Binding Name, Mode=TwoWay}" />
                                    <TextBlock Grid.Column="1" Text="{Binding TotalCostString}" Margin="4,0"/>
                                    <Button Grid.Column="2" Command="{Binding DataContext.RemoveItemGroup, ElementName=ItemGroups, Mode=OneWay}" CommandParameter="{Binding}" Content="Remove"/>
                                </Grid>         
                            </DataTemplate>
                        </Expander.HeaderTemplate>
                        <Expander.Content>
                            <TextBlock Text="{Binding Summary}"></TextBlock>
                        </Expander.Content>
                    </Expander>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
Binnacle answered 9/11, 2016 at 13:32 Comment(4)
thanks, i was just looking at this topic and saw your answer. I didn't think about this, but I confirm this works. It's a bit extra xaml code but it is nice to have it xaml only. I also binded to actualwidth in the header but that was a bit too big. I could have used a converter and adjusted the width width e.g. 2 pixels for the border e.g., but that would be more "static" but shorter code :)Bisutun
Does this work because the default value for Grid.HorizontalAlginment is Stretch?Delafuente
This solution also works, when the expander-button is put on the right side like mentioned in the answer of ray.Epiphenomenon
I've had a similar problem regarding a custom DataGrid header, where the header hadn't been resized according to the column width. The approach here also works for the DataGrid headers, as I found out.Vaporimeter
H
51

Expander header content presenter has horizontal alignment set to Left.

You can change it to Stretch using OneWayToSource binding of HorizontalAlignment (which is by default Stretch for Grid) like this:

<Expander>
    <Expander.Header>
        <Grid Background="Yellow">
            <TextBlock Text="Header"
                       HorizontalAlignment="{Binding HorizontalAlignment, RelativeSource={RelativeSource AncestorType=ContentPresenter}, Mode=OneWayToSource}" />
        </Grid>
    </Expander.Header>
</Expander>

P.S.: it took me more than it should to understand the solution of accepted answer, so I decide to save the time for future readers.

Homeostasis answered 19/1, 2018 at 11:23 Comment(7)
This should be accepted answer, because this one does not suggest that you need to alter header template.Religieux
this is the only answer that worked and is clean, should be the acceptedUnwashed
I agree that this should be the accepted answer. But the binding should be defined on the Grid, not the TextBlock.Shied
The alignment on the ContentPresenter is what helped me. Fixed that in my custom expander template et voila!Aminopyrine
that's perfect !Misspeak
This doesn't work if the expander is not visible in the beginning (e.g. placed in a non-selected tab like in my case). Is there a way to fire the binding when the control is actually shown?Selfsuggestion
I'm adding a checkbox on the right hand side and this solution didn't work for me. I tried the HorizontalAlignment on the checkbox and the grid. It looks correct in the design editor but not at runtime.Biel
B
23

I was able to get content stretching in the header to work using the xaml provided below. Basically I data bind the grids HorizontailAlignment to the content presenter ancestor. Unfortunatley the solution provided by scher (data binding to ActualWidth) can cause ui elements to be displayed wider then there container resulting in controls being partially cut off.) Bolu's link to the article "Stretching Content in an Expander Header" uses code behind where as this example uses pure xaml.

<ItemsControl x:Name="ItemGroups" Grid.Column="2" Grid.Row="0"   ItemsSource="{Binding Model.ItemGroups}" ScrollViewer.VerticalScrollBarVisibility="Auto" >
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Expander Margin="4,0"   Header="{Binding}">
                        <Expander.HeaderTemplate>
                            <DataTemplate>
                                <Grid  HorizontalAlignment="{Binding Path=HorizontalAlignment, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Mode=OneWayToSource}" >
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition   />
                                        <ColumnDefinition  Width="Auto"/>
                                        <ColumnDefinition  Width="64"/>
                                    </Grid.ColumnDefinitions>

                                    <TextBox Grid.Column="0"  Text="{Binding Name, Mode=TwoWay}" />
                                    <TextBlock Grid.Column="1" Text="{Binding TotalCostString}" Margin="4,0"/>
                                    <Button Grid.Column="2" Command="{Binding DataContext.RemoveItemGroup, ElementName=ItemGroups, Mode=OneWay}" CommandParameter="{Binding}" Content="Remove"/>
                                </Grid>         
                            </DataTemplate>
                        </Expander.HeaderTemplate>
                        <Expander.Content>
                            <TextBlock Text="{Binding Summary}"></TextBlock>
                        </Expander.Content>
                    </Expander>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
Binnacle answered 9/11, 2016 at 13:32 Comment(4)
thanks, i was just looking at this topic and saw your answer. I didn't think about this, but I confirm this works. It's a bit extra xaml code but it is nice to have it xaml only. I also binded to actualwidth in the header but that was a bit too big. I could have used a converter and adjusted the width width e.g. 2 pixels for the border e.g., but that would be more "static" but shorter code :)Bisutun
Does this work because the default value for Grid.HorizontalAlginment is Stretch?Delafuente
This solution also works, when the expander-button is put on the right side like mentioned in the answer of ray.Epiphenomenon
I've had a similar problem regarding a custom DataGrid header, where the header hadn't been resized according to the column width. The approach here also works for the DataGrid headers, as I found out.Vaporimeter
E
10

I took one of the solutions linked by Bolu. This is the result:

    <Expander HorizontalAlignment="Stretch" IsExpanded="True">
        <Expander.Header >
            <!-- Width-Binding is needed, to fill the whole header horizontally-->
            <Grid HorizontalAlignment="Stretch" Background="Aqua" Margin="0" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Expander}}, Path=ActualWidth}">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <Label Grid.Column="0" Content="Label on the left site"/>
                <!-- Margin is needed, to bring the Button into the view -->
                <Button Grid.Column="1" Content="Button on the right" Margin="0,0,40,0"/>
            </Grid>
        </Expander.Header>
        <Label Content="Some Content"/>
    </Expander>
Epiphenomenon answered 2/7, 2015 at 6:53 Comment(0)
M
1

*Heck: right padding to e.g. 100000: Padding="2 2 100000 2"

<Expander Name="Name" ExpandDirection="Down">
    <Expander.Header>
        <TextBlock Text="Expander..." Margin="0 5" Padding="2 2 100000 2" />
    </Expander.Header>
</Expander>
Meaningful answered 23/4, 2018 at 12:48 Comment(0)
M
0

If you don't want to change the Expander Header look at all you could also use Behaviors (System.Windows.Interactivity) that modifies the HorizontalAlignment when the Header is being loaded

internal class StretchingExpanderHeaderBehavior : Behavior<Expander>
{
    protected override void OnAttached()
    {
        AssociatedObject.Loaded += StretchHeader;
    }

    private void StretchHeader(object sender, RoutedEventArgs e)
    {
        DependencyObject header = AssociatedObject.Header as DependencyObject;
        if (header != null)
        {
            ContentPresenter contentPresenter = VisualTreeHelper.GetParent(header) as ContentPresenter;
            if (contentPresenter != null)
            {
                contentPresenter.HorizontalAlignment = HorizontalAlignment.Stretch;
            }
        }
    }
}

This can be attached to the Expander

<Expander>
    <Expander.Header>
        <DockPanel HorizontalAlignment="Stretch">
            <Button DockPanel.Dock="Right" Content="Button on the right" />
            <Label Content="Label on the left site"/>
        </DockPanel>
    </Expander.Header>

    <Border BorderBrush="LightGray" BorderThickness="1">
        <ItemsPresenter />
    </Border>

    <iy:Interaction.Behaviors>
        <bhv:StretchingExpanderHeaderBehavior />
    </iy:Interaction.Behaviors>

</Expander>
Model answered 19/9, 2019 at 6:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.