Let WPF Tabcontrol height assume height of largest item?
Asked Answered
G

5

15

Is there any way to have to tabcontrol take the size of the largest tab item (well, actually, the tabitem's content)?

Since the tabcontrol has no specific size assigned it should autosize: it does that correctly, but when you switch tabs it automatically resizes itself to the height (and width) of the contents of the currently selected tab.

I don't want the resizing to happen, and let the tabcontrol assume the height of the largest item, but still have it autosize itself.

Any clues? I tried databinding to the Height property of each element used as content to the using a multibinding, with bindings on both the ActualHeight and the Items properties of the Tabcontrol. But alas, the ActualHeight of the content elements is always 0.

        <TabItem Header="Core" > 
            <Grid Margin="5">
                <Grid.Height>
                    <MultiBinding Converter="{Converters1:AllTabContentEqualHeightConverter}">
                        <Binding Path="ActualHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}"/>
                        <Binding Path="Items" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}"/>
                    </MultiBinding>
                </Grid.Height>

            ...

Can this be done?

Ganger answered 2/7, 2009 at 11:11 Comment(0)
G
5

Actually, it was easier to solve that I thought. Since I had a controltemplate for the TabControl anyway, I set the height of the ContentPresenter presenting the selected tab content. I do this using a converter that binds to the items of the TabControl, measures them if necessary (using Measure) and checks DesiredSize for the size I need.

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var items = value as ItemCollection;

        if (items == null)
            return null;

        double max = 0;
        foreach (TabItem item in items)
        {
            var content = item.Content as FrameworkElement;
            if (content == null) continue;

            if (!content.IsMeasureValid)
                content.Measure(new Size(int.MaxValue, int.MaxValue));

            var height = content.DesiredSize.Height;
            if (max < height)
                max = height;
        }

        return max;
    }

That works just fine, with some caveats:

  • every tab content should be a FrameworkElement
  • the contents don't change size once they are loaded (because the converter is only called when the Items property changes, ie just once).
Ganger answered 2/7, 2009 at 13:29 Comment(1)
Hi @Ganger could you tell me how you bound to the collection of TabItem inside the tab control? I can't seem to find an answer for this.Gorges
M
28

Yes it can be done: reuse-grid-rowdefinitions-for-each-tabitem

Example:

    <TabControl Grid.IsSharedSizeScope="True">
        <TabItem Header="Tab 1">
            <Grid >
                <Grid.RowDefinitions>
                    <RowDefinition SharedSizeGroup="xxx"/>
                </Grid.RowDefinitions>
            </Grid>
        </TabItem>
        <TabItem Header="Tab 2">
            <Grid >
                <Grid.RowDefinitions>
                    <RowDefinition SharedSizeGroup="xxx"/>
                </Grid.RowDefinitions>
            </Grid>
        </TabItem>
   </TabControl>
Matinee answered 7/2, 2013 at 5:48 Comment(3)
The key is to include Grid.IsSharedSizeScope="True", I missed that the first time - without it nothing changed for me. Also note that you don't have to have the TabControl within a grid to apply that value.Tom
Is there a way to apply this though a DataTemplate? I've got a TabControl bound to a collection to populate it's tabs. I tried setting Row and Column definitions to share Width/Height across all tabs without success (I do Grid.IsSharedSizeScope="True" set on the TabControl)Gorges
This solution works indeed. I tried it in a data template. But it may cause the UI to be very slow. Depending on where the data template is used.Caput
M
7

The problem is that the TabControl unloads and reloads its content as you switch tabs. Therefore it only knows about the size of the content in the currently active tab. You should be able to change the TabControl such that it never destroys its children, and they are always present (but maybe hidden).

This blog post by Eric Burke should get you started. From what I can tell by skimming his post, you will need to change it such that:

  • All children are loaded when the TabControl is loaded.
  • Children are hidden rather than collapsed when they are inactive
Marandamarasca answered 2/7, 2009 at 11:42 Comment(1)
That seems a good approach, but I just solved it myself. Thanks anyway.Ganger
G
5

Actually, it was easier to solve that I thought. Since I had a controltemplate for the TabControl anyway, I set the height of the ContentPresenter presenting the selected tab content. I do this using a converter that binds to the items of the TabControl, measures them if necessary (using Measure) and checks DesiredSize for the size I need.

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var items = value as ItemCollection;

        if (items == null)
            return null;

        double max = 0;
        foreach (TabItem item in items)
        {
            var content = item.Content as FrameworkElement;
            if (content == null) continue;

            if (!content.IsMeasureValid)
                content.Measure(new Size(int.MaxValue, int.MaxValue));

            var height = content.DesiredSize.Height;
            if (max < height)
                max = height;
        }

        return max;
    }

That works just fine, with some caveats:

  • every tab content should be a FrameworkElement
  • the contents don't change size once they are loaded (because the converter is only called when the Items property changes, ie just once).
Ganger answered 2/7, 2009 at 13:29 Comment(1)
Hi @Ganger could you tell me how you bound to the collection of TabItem inside the tab control? I can't seem to find an answer for this.Gorges
P
3

This worked for me in conjunction with Grid.IsSharedSizeScope approach shown above.

Note that SetCurrentValue is used instead of just setting the SelectedIndex property - this way we keep possible existing bindings:

private void TabControl_OnLoaded(object sender, RoutedEventArgs e)
{
    //NOTE: loop through tab items to force measurement and size the tab control to the largest tab
    TabControl tabControl = (TabControl)sender;

    // backup selection
    int indexItemLast = tabControl.SelectedIndex;

    int itemCount = tabControl.Items.Count;

    for (
        int indexItem = (itemCount - 1);
        indexItem >= 0;
        indexItem--)
    {
        tabControl.SetCurrentValue(Selector.SelectedIndexProperty, indexItem);
        tabControl.UpdateLayout();
    }

    // restore selection
    tabControl.SetCurrentValue(Selector.SelectedIndexProperty, indexItemLast);
}
Psalm answered 22/8, 2017 at 23:5 Comment(0)
S
1

It's probably not in the proper WPF way, but, if you already have all the content elements, you could maybe loop through them on load and set the height of the TabControl programatically.

Selfimmolation answered 2/7, 2009 at 11:54 Comment(1)
This is the only solution that worked for me (in conjunction is Grid.IsSharedSizeScope) although one have to call TabControl.UpdateLayout() on every tab selected programmatically to force layout measurement.Psalm

© 2022 - 2024 — McMap. All rights reserved.