Silverlight WrapPanel not streching vertically inside an Accordion

269 views Asked by At

I have a menu screen with buttons in my Silverlight app. For that I'm using ItemsControl with a WrapPanel as ItemsPanel template, so the buttons wrap to next line (just like a textbox do with text).

The menu is something like this:

enter image description here

Now I need a new feature: to group the buttons into categories. So I've decided to use Accordion with ZeroOrMore items for that. I'm using a ScrollViewer as Accordion ContentTemplate, with MaxHeight of 150 to limit the group height in 150 and use vertical scrollbar after that. Inside the ScrollViewer there is the same ItemsControl with the WrapPanel and the buttons.

I want the WrapPanel to have 3 rows of items without scrolling, so I chose MaxHeight = 150 for a reason: because each button (with margins) has height of 50. That would allow 3 rows without scroll bars. That works perfectly without the Accordion, but doesn't work with it.

The problem is that the Accordion doesn't seem to understand the WrapPanel Height, and assumes it has the Height of 1 row only, that is, the height of 1 button with margins. So, when I expand an Accordion item, it always has height of 50. That is ok if I have only 1 row of items, but if I have 2 rows or more the height is not growing to 100 or 150, it's still 50 and scrollbars appear as the height of accordion is not enough.

Here is the what is happening right now: enter image description here

If I set manually the width or height of the ScrollViewer or of the WrapPanel, the issue doesn't happen. But it's a resizable window and I don't know its size or how many itens will be in each group. The user customize that, so I can't just set a height or width. I need the Accordion to calculate correctly the height.

Here, for testing purpose, I set the ScrollViewer Width=350 (a little smaller than the Accordion/UserControl width, so we can see it easily), and also the MaxHeight=100 (so it would be not enough for 2nd group). enter image description here

The picture above shows what I wanted, but without the Width, so the user can resize the window and have more buttons visible on screen.

When I expand an accordion, I notice that the WrapPanel adjust the buttons dinamically, as I can see it happening, I can see the items wrapping from the end of the first row to the next row. So I assume that, before expanding the Accordion, the WrapPanel doesn't have a width (maybe it's infinity) and that is why it hasn't wrapped the buttons yet. It "thinks" there is space for all items in the first row.

Below is the Silverlight app I wrote to show the issue. The MainPage.xaml:

    <ScrollViewer Padding="0" BorderThickness="0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="5">
    <Grid>
        <toolkit:Accordion HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="10"
                           HorizontalContentAlignment="Left" VerticalContentAlignment="Stretch" 
                           ItemsSource="{Binding Groups}" SelectionMode="ZeroOrMore">

            <toolkit:Accordion.ItemContainerStyle>
                <Style TargetType="toolkit:AccordionItem">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                </Style>
            </toolkit:Accordion.ItemContainerStyle>

            <toolkit:Accordion.AccordionButtonStyle>
                <Style TargetType="toolkit:AccordionButton">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="toolkit:AccordionButton">
                                <Border Background="LightCyan" BorderBrush="Black" BorderThickness="1">
                                    <TextBlock Text="{Binding Description}"/>
                                </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </toolkit:Accordion.AccordionButtonStyle>

            <toolkit:Accordion.ContentTemplate>
                <DataTemplate>
                    <Grid>
                        <ScrollViewer MaxHeight="150" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" >
                            <ItemsControl ItemsSource="{Binding Items}" >
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <toolkit:WrapPanel HorizontalAlignment="Left" Margin="10"/>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>

                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <Button HorizontalContentAlignment="Left" Margin="3" Width="195" HorizontalAlignment="Left" VerticalAlignment="Top" Content="{Binding Description}"/>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </ScrollViewer>
                    </Grid>
                </DataTemplate>
            </toolkit:Accordion.ContentTemplate>
        </toolkit:Accordion>
    </Grid>
</ScrollViewer>

And the code behind in C#: MainPage.xaml.cs, with the properties' bindings and code for adding sample groups/items:

public partial class MainPage : UserControl, INotifyPropertyChanged
{
    public class ItemViewModel
    {
        public string Description { get; set; }
    }

    public class GroupViewModel
    {
        public List<ItemViewModel> Items { get; set; }
        public string Description { get; set; }
    }

    public List<ItemViewModel> Items { get; set; }
    public List<GroupViewModel> Groups { get; set; }

    public MainPage()
    {
        InitializeComponent();
        this.DataContext = this;

        this.Groups = new List<GroupViewModel>();
        GroupViewModel g1, g2, g3;

        this.Groups.Add(g1 = new GroupViewModel() { Description = "Group with 4 itens", Items = new List<ItemViewModel>() });
        g1.Items.Add(new ItemViewModel() { Description = "item1" } );
        g1.Items.Add(new ItemViewModel() { Description = "item2" } );
        g1.Items.Add(new ItemViewModel() { Description = "item3" } );
        g1.Items.Add(new ItemViewModel() { Description = "item4" } );

        this.Groups.Add(g2 = new GroupViewModel() { Description = "Group with 10 itens", Items = new List<ItemViewModel>() });
        g2.Items.Add(new ItemViewModel() { Description = "item1" } );
        g2.Items.Add(new ItemViewModel() { Description = "item2" } );
        g2.Items.Add(new ItemViewModel() { Description = "item3" } );
        g2.Items.Add(new ItemViewModel() { Description = "item4" } );
        g2.Items.Add(new ItemViewModel() { Description = "item5" } );
        g2.Items.Add(new ItemViewModel() { Description = "item6" } );
        g2.Items.Add(new ItemViewModel() { Description = "item7" } );
        g2.Items.Add(new ItemViewModel() { Description = "item8" } );
        g2.Items.Add(new ItemViewModel() { Description = "item9" } );
        g2.Items.Add(new ItemViewModel() { Description = "item10" } );

        this.Groups.Add(g3 = new GroupViewModel() { Description = "Group with 3 itens", Items = new List<ItemViewModel>() });
        g3.Items.Add(new ItemViewModel() { Description = "item1" } );
        g3.Items.Add(new ItemViewModel() { Description = "item2" });
        g3.Items.Add(new ItemViewModel() { Description = "item3" });

        NotifyPropertyChanged("Groups");

    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}
2

There are 2 answers

1
ΩmegaMan On

I sense that this doesn't quite answer the question, but if I remove the ScrollViewers MaxHeight attribute and specify conversely that the Wrappanel has MaxWidth of 620, one can get a vertical view with 3 buttons minimum of what is desired and the accordion grows/shrinks accordingly sans scrollbar.

The trade off is that when the user increases the screen size width, the buttons stay at 3 rows consistently ignoring the extra horizontal space.

<toolkit:Accordion.ContentTemplate>
    <DataTemplate>
        <Grid>
            <ScrollViewer VerticalScrollBarVisibility="Auto"
                            HorizontalScrollBarVisibility="Disabled">
                <ItemsControl ItemsSource="{Binding Items}">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <toolkit:WrapPanel HorizontalAlignment="Left"
                                                MaxWidth="620"
                                                Margin="10" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>

enter image description here

0
Gus On

I found a solution, but I had to remove the Accordion, what isn't a problem for me. I have used ItemsControl as "Accordions' list" with a Button as AccordionButton. The rest remains the same, a ScrollViewer with ItemsControl and WrapPanel inside.

The ScrollViewer has a Binding to its Height. The Button has a Command that changes ScrollViewer's Height to 0 to collapse the items, and to NaN to expand. Works flawlessly.

Here is the XAML:

<ItemsControl HorizontalAlignment="Stretch" VerticalAlignment="Top" ItemsSource="{Binding Groups}" 
            HorizontalContentAlignment="Left" VerticalContentAlignment="Stretch"
            ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Disabled" Margin="0">

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <Grid.RowDefinitions>
                    <RowDefinition Height="24"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>

                <Button Grid.Row="0" Command="{Binding ExpandCommand}" Content="{Binding Description}"/>

                <ScrollViewer Grid.Row="1" Padding="0" VerticalScrollBarVisibility="Auto" MaxHeight="100" BorderThickness="0"
                                HorizontalScrollBarVisibility="Disabled" Height="{Binding ContentHeight}" HorizontalAlignment="Stretch">
                    <ItemsControl ItemsSource="{Binding Items}" >
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <toolkit:WrapPanel HorizontalAlignment="Left" Margin="10"/>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>

                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <Button HorizontalContentAlignment="Left" Margin="3" Width="80" HorizontalAlignment="Left" VerticalAlignment="Top" Content="{Binding Description}"/>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </ScrollViewer>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

And the xaml.cs:

public partial class MainPage : UserControl, INotifyPropertyChanged
{
    public class ItemViewModel
    {
        public string Description { get; set; }
    }

    public class GroupViewModel : INotifyPropertyChanged
    {
        public GroupViewModel()
        {
            ContentHeight = double.NaN;
            NotifyPropertyChanged("ContentHeight");
            ExpandCommand = new DelegateCommand<object>(Expand);
        }
        public List<ItemViewModel> Items { get; set; }
        public string Description { get; set; }
        public double ContentHeight { get; set; }

        public DelegateCommand<object> ExpandCommand { get; set; }
        public void Expand(object obj)
        {
            if (double.IsNaN(this.ContentHeight)) this.ContentHeight = 0;
            else this.ContentHeight = double.NaN;
            NotifyPropertyChanged("ContentHeight");
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
    }

    public List<ItemViewModel> Items { get; set; }
    public List<GroupViewModel> Groups { get; set; }

    public MainPage()
    {
        InitializeComponent();
        this.DataContext = this;

        this.Groups = new List<GroupViewModel>();
        GroupViewModel g1, g2, g3;

        this.Groups.Add(g1 = new GroupViewModel() { Description = "Group with 4 itens", Items = new List<ItemViewModel>() });
        g1.Items.Add(new ItemViewModel() { Description = "item1" } );
        g1.Items.Add(new ItemViewModel() { Description = "item2" } );
        g1.Items.Add(new ItemViewModel() { Description = "item3" } );
        g1.Items.Add(new ItemViewModel() { Description = "item4" } );

        this.Groups.Add(g2 = new GroupViewModel() { Description = "Group with 10 itens", Items = new List<ItemViewModel>() });
        g2.Items.Add(new ItemViewModel() { Description = "item1" } );
        g2.Items.Add(new ItemViewModel() { Description = "item2" } );
        g2.Items.Add(new ItemViewModel() { Description = "item3" } );
        g2.Items.Add(new ItemViewModel() { Description = "item4" } );
        g2.Items.Add(new ItemViewModel() { Description = "item5" } );
        g2.Items.Add(new ItemViewModel() { Description = "item6" } );
        g2.Items.Add(new ItemViewModel() { Description = "item7" } );
        g2.Items.Add(new ItemViewModel() { Description = "item8" } );
        g2.Items.Add(new ItemViewModel() { Description = "item9" } );
        g2.Items.Add(new ItemViewModel() { Description = "item10" } );

        this.Groups.Add(g3 = new GroupViewModel() { Description = "Group with 3 itens", Items = new List<ItemViewModel>() });
        g3.Items.Add(new ItemViewModel() { Description = "item1" } );
        g3.Items.Add(new ItemViewModel() { Description = "item2" });
        g3.Items.Add(new ItemViewModel() { Description = "item3" });

        NotifyPropertyChanged("Groups");

    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }


}