Duke Cyrillus
Duke Cyrillus

Reputation: 1237

WPF Expand RadPanelBarItem only in available space

I have a RadPanelBar with each RadPanelItem having a list of entities(Different list in each Item). Each item in the List is shown as a GroupBox. With a large number of items the RadPanelBar has to be scrolled in order for the other RadPanelBarItems to be visible. I want it such that the scrollbar appears within each RadPanelBarItem so that all the RadPanelBarItems will be visible on the screen at the same time and if the contents of an item are too long, the user has to scroll only within each RadPanelBarItem.

I'm using the ItemsSource property of each RadPanelBarItem and setting its ItemTemplate to display the GroupBoxes.

Is there a good way to do this, so that everything(Height and such) is kept dynamic?

Thanks!

Upvotes: 1

Views: 2176

Answers (1)

Björn
Björn

Reputation: 3428

There seems to be no easy way to do this. I got the following response from Telerik when I asked a similar question:

If I got your case correctly you have several options:

1) Set the size for PanelBarItem. This way you will limit how big they could be. If you match items summed size to the size of the PanelBar you should eliminate the clippings.

2) Customize the PanelBar and PanelBarItem control templates in order to support automatic proportional sizing. In this case you should remove the ScrollViewer from PanelBar control template and add a ScrollViewer in the top level PanelBarItem control template (around the ItemsPresenter). Also you should change RadPanelBar ItemsPanel to an appropriate panel. Probably. it is going to be a custom panel in order to measure the items with equal sizes vertically.

I have made a try to do a custom Panel and modifying the control template. I have got it working but it's quite a lot of code, but here goes:

DistributedHeightPanel.cs

This is the custom Panel which do the layout and distributes the available height.

/// <summary>
/// Panel that distributes the available height amongst it's children (like a vertical StackPanel but the children are not allowed to be placed "outside" the parent's visual area).
/// </summary>
public class DistributedHeightPanel : Panel
{
    /// <summary>
    /// If set to a positive number, no child will get less height than specified.
    /// </summary>
    public double ItemsMinHeight
    {
        get { return (double)GetValue(ItemsMinHeightProperty); }
        set { SetValue(ItemsMinHeightProperty, value); }
    }

    public static readonly DependencyProperty ItemsMinHeightProperty =
        DependencyProperty.Register("ItemsMinHeight", typeof(double), typeof(DistributedHeightPanel), new UIPropertyMetadata(0.0));


    public DistributedHeightPanel()
        : base()
    {
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        List<double> heights = new List<double>();
        //Find out how much height each child desire if it was the only child
        foreach (UIElement child in InternalChildren)
        {
            child.Measure(availableSize);
            heights.Add(child.DesiredSize.Height);
        }
        //Calculate ratio
        double ratio = GetRatio(availableSize.Height, heights);
        //Do the "real" Measure
        foreach (UIElement child in InternalChildren)
        {
            double actualHeight = child.DesiredSize.Height;
            if (ratio < 1)
            {
                //If ratio < 1 then the child can't have all the space it wants, calculate the new height
                actualHeight = child.DesiredSize.Height * ratio;
            }
            if (ItemsMinHeight > 0 && actualHeight < ItemsMinHeight)
            {
                //If ItemsMinHeight is set and the child is to small, then set the childs height to ItemsMinHeight
                actualHeight = ItemsMinHeight;
            }
            child.Measure(new Size(availableSize.Width, actualHeight));
        }
        return availableSize;
    }

    /// <summary>
    /// Calculates the ratio for fitting all heights in <paramref name="heightsToDistribute"/> in the total available height (as supplied in <paramref name="availableHeight"/>)
    /// </summary>
    private double GetRatio(double availableHeight, List<double> heightsToDistribute)
    {
        //Copy the heights list
        List<double> heights = new List<double>(heightsToDistribute);
        double desiredTotalHeight = heights.Sum();
        //If no height is desired then return 1
        if (desiredTotalHeight <= 0)
            return 1;
        //Calculate ratio
        double ratio = availableHeight / desiredTotalHeight;
        //We only want to compress so if ratio is higher than 1 return 1
        if (ratio > 1)
        {
            return 1;
        }
        //Check if heights become too small when the ratio is used
        int tooSmallCount = heights.Count(d => d * ratio < ItemsMinHeight);
        //If no or all all heights are too small: return the calculated ratio
        if (tooSmallCount == 0 || tooSmallCount == heights.Count)
        {
            return ratio;
        }
        else
        {
            //Remove the items which becomes too small and get a ratio without them (they will get ItemsMinHeight)
            heights.RemoveAll(d => d * ratio < ItemsMinHeight);
            return GetRatio(availableHeight - ItemsMinHeight * tooSmallCount, heights);
        }
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        //Arrange all children like a vertical StackPanel
        double y = 0;
        foreach (UIElement child in InternalChildren)
        {
            //child.DesiredSize.Height contains the correct value since it was calculated in MeasureOverride
            child.Arrange(new Rect(0, y, finalSize.Width, child.DesiredSize.Height));
            y += child.DesiredSize.Height;
        }
        return finalSize;
    }
}

MainWindow.xaml

Contains the control template as a style named DistributedHeightRadPanelBarStyle and a RadPanelBar for testing.

<Window x:Class="WpfApplication9.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication9"
    Title="MainWindow" Height="350" Width="525" xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation">
<Window.Resources>
    <Style x:Key="DistributedHeightRadPanelBarStyle" TargetType="{x:Type telerik:RadPanelBar}">
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <local:DistributedHeightPanel ItemsMinHeight="22" /> <!-- 22 is fine for collapsed headers -->
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type telerik:RadPanelBar}">
                    <Grid>
                        <telerik:LayoutTransformControl x:Name="transformationRoot" IsTabStop="False">
                            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                                <!-- <ScrollViewer x:Name="ScrollViewer" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalScrollBarVisibility="Auto" IsTabStop="False" Padding="{TemplateBinding Padding}" VerticalScrollBarVisibility="Auto" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">-->
                                <telerik:StyleManager.Theme>
                                    <telerik:Office_BlackTheme/>
                                </telerik:StyleManager.Theme>
                                <ItemsPresenter/>
                                <!--</ScrollViewer>-->
                            </Border>
                        </telerik:LayoutTransformControl>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="Orientation" Value="Horizontal">
                            <Setter Property="LayoutTransform" TargetName="transformationRoot">
                                <Setter.Value>
                                    <RotateTransform Angle="-90"/>
                                </Setter.Value>
                            </Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Orientation" Value="Vertical"/>
    </Style>
</Window.Resources>
<Grid>
    <telerik:RadPanelBar Style="{StaticResource ResourceKey=DistributedHeightRadPanelBarStyle}" VerticalAlignment="Top" ExpandMode="Multiple" HorizontalAlignment="Stretch">
        <telerik:RadPanelBarItem DropPosition="Inside" Header="A - Colors" IsExpanded="True">
            <ScrollViewer VerticalScrollBarVisibility="Auto">
                <StackPanel>
                    <TextBlock Height="100" Background="AliceBlue" Text="I'm AliceBlue" />
                    <TextBlock Height="100" Background="AntiqueWhite" Text="I'm AntiqueWhite" />
                </StackPanel>
            </ScrollViewer>
        </telerik:RadPanelBarItem>
        <telerik:RadPanelBarItem DropPosition="Inside" Header="B - Colors" IsExpanded="True">
            <ScrollViewer VerticalScrollBarVisibility="Auto">
                <StackPanel>
                    <TextBlock Height="100" Background="Beige" Text="I'm Beige" />
                    <TextBlock Height="100" Background="Bisque" Text="I'm Bisque" />
                </StackPanel>
            </ScrollViewer>
        </telerik:RadPanelBarItem>
        <telerik:RadPanelBarItem DropPosition="Inside" Header="C - Colors">
            <ScrollViewer VerticalScrollBarVisibility="Auto">
                <StackPanel>
                    <TextBlock Height="100" Background="CadetBlue" Text="I'm CadetBlue" />
                    <TextBlock Height="100" Background="Chartreuse" Text="I'm Chartreuse" />
                </StackPanel>
            </ScrollViewer>
        </telerik:RadPanelBarItem>
    </telerik:RadPanelBar>
</Grid>

Maybe this solution is too late for you to use but hopefully someone will find it useful.

Upvotes: 2

Related Questions