Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grid with SharedSizeGroup columns is acting very weird (*NOT* infinite loop)

If you run the sample window below the upper ItemsControl will update layout for several seconds until finally all columns will have the correct width (correct = identical to the columns inside the lower ItemsControl).

You can change the width of the window and scroll the lower ItemsControls surrounding ScrollViewer both horizontally and vertically -- but as soon as you change the height of the Window the layout will flip for several seconds.

Note: There is no sizing ambiguity like in other questions where the grid infinitely updates sizes.

Is it me doing something wrong -- and if so, how could I fix this? -- or should I post this problem to Microsoft-Connect?

Code behind:

namespace DynamicGridColumnBinding
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;

    public partial class MainWindow
    {
        private static readonly CultureInfo[] cultureInfos =
            CultureInfo.GetCultures(CultureTypes.NeutralCultures).Take(15).ToArray();

        public MainWindow()
        {
            this.InitializeComponent();
        }

        public static IEnumerable<CultureInfo> AllCultures
        {
            get { return cultureInfos; }
        }

        private void GridInitialized(object sender, EventArgs e)
        {
            var grid = (Grid)sender;
            for ( int i = 0; i < cultureInfos.Length; i++ )
                grid.ColumnDefinitions.Add(new ColumnDefinition
                    {
                        Width = GridLength.Auto,
                        SharedSizeGroup = "g" + i,
                    });
        }

        private void ScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            if ( e.HorizontalChange != 0 )
                this.legendScroller.ScrollToHorizontalOffset(e.HorizontalOffset);
        }
    }
}

Xaml:

<FrameworkElement.Resources>
    <ItemsPanelTemplate x:Key="panelTemplate">
        <Grid Initialized="GridInitialized" />
    </ItemsPanelTemplate>

    <Style TargetType="ContentPresenter" x:Key="containerStyle">
        <Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource Self}, Path=(ItemsControl.AlternationIndex)}" />
        <Setter Property="Grid.Column" Value="{Binding RelativeSource={RelativeSource Self}, Path=(ItemsControl.AlternationIndex)}" />
    </Style>

    <Style TargetType="TextBlock" x:Key="textStyle">
        <Setter Property="Padding" Value="5" />
        <Setter Property="Background" Value="Lime" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="VerticalAlignment" Value="Center" />
    </Style>
</FrameworkElement.Resources>

<DockPanel Grid.IsSharedSizeScope="True" DataContext="{Binding Source={x:Static local:MainWindow.AllCultures}}">

    <ScrollViewer DockPanel.Dock="Top" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Disabled"
            x:Name="legendScroller">
        <ItemsControl ItemsSource="{Binding}" AlternationCount="{x:Static System:Int32.MaxValue}" Margin="0 0 500 0"
                ItemsPanel="{StaticResource panelTemplate}" ItemContainerStyle="{StaticResource containerStyle}">

            <ItemsControl.ItemTemplate>
                <DataTemplate DataType="{x:Type glob:CultureInfo}">
                    <GroupBox Header="{Binding Name}" HeaderStringFormat="[ {0} ]">
                        <TextBlock Style="{StaticResource textStyle}"
                                Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=ContentPresenter, AncestorLevel=2}}" />
                    </GroupBox>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </ScrollViewer>

    <TextBlock Foreground="Red" DockPanel.Dock="Top" Margin="0 10" FontSize="20" Text="some random arbitrary content in between" />

    <ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Auto" ScrollChanged="ScrollViewerScrollChanged">
        <ItemsControl ItemsSource="{Binding}" AlternationCount="{x:Static System:Int32.MaxValue}"
                ItemsPanel="{StaticResource panelTemplate}" ItemContainerStyle="{StaticResource containerStyle}">

            <ItemsControl.ItemTemplate>
                <DataTemplate DataType="{x:Type glob:CultureInfo}">
                    <Border Background="DodgerBlue" Padding="5" Margin="1">
                        <GroupBox Header="{Binding DisplayName}">
                            <TextBlock Style="{StaticResource textStyle}" Padding="5 100"
                                    Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=ContentPresenter, AncestorLevel=2}}" />
                        </GroupBox>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </ScrollViewer>

</DockPanel>

BTW: If you force the items of the upper ItemsControl to be size giving (by adding MinWidth="200" to GroupBox) then the lower ItemsControl will instead act silly.

BTW2: Starting with approx. 8 shared size columns (in the sample there are 15, controlled by .Take(15) ) you see the rearrangement to appear, and it doubles in time by each column you add -- so 20 columns nearly doesn't come to an end for minutes.

BTW3: Receiving not a single comment during 3 months is very frustrating.

like image 235
springy76 Avatar asked Jan 14 '12 01:01

springy76


2 Answers

Well funnily enough I +1'd this question three months ago but didn't answer. Probably the reason you've received no response is because its difficult!

I still don't have much to say now, except that WPF layout is performed in two passes - measure and arrange - and subsequent sizing of elements might trigger a layout on other elements etc...

In order to size the columns correctly WPF is doing something like this:

  • Measure column 1 contents
  • Arrange column 1 contents
  • Measure column 2 contents
    • Wait a sec, column 2 is larger than expected
    • Trigger a measure on all column 1 contents
      • Arrange column 1 contents
      • Measure column 2 contents

... etc

Apologies for my simplistic view. How many columns do you have (hence number of shared size groups)? Another question. What OS and .NET Framework are you running? I heard there was a limitation to the number of SharedSizeGroups you could have in WPF on WindowsXP for instance. Not sure if its fixed in later OS's.

As a workaround could you implement this behaviour yourself? Meaning can you make your own attached property that measures every element in the grid (all columns, all rows) then sets sizes of each column once? As opposed to a column by column basis.

Regards,

like image 162
Dr. Andrew Burnett-Thompson Avatar answered Oct 16 '22 10:10

Dr. Andrew Burnett-Thompson


I had a similar problem. My exact situation followed by how I solved it.

I have a "grid" built where the top row and left column stay in place as the rest of the content scrolls (like frozen Excel cells). The size of the content varied and is determined at run time. It was also hard to predict what the size would be.

To build this out, I have a total of 4 grids - 1 outer grid for layout and 3 grids inside it- 1 for the top row elements, 1 for the left column elements and 1 for the actual content. The rows of the content grid are synced to the rows of the left column via a shared size group and likewise with the content's columns and the top row columns.

To solve it, I populated the data for the actual grid first in the code behind. Then I called Measure and Arrange on the outer grid object to force a render on it. See here for how to do that. https://stackoverflow.com/a/4890217/2352625

By forcing a render, I have the actual size of each cell which I then use to create the row and column definitions for my headers (instead of setting them to Auto size). It's still not perfect - when I display a large grid - but the shifting is minor (a few pixels) instead of jumping all over like it was.

like image 1
MadCityDev Avatar answered Oct 16 '22 12:10

MadCityDev