Search code examples
wpflayoutitemscontrol

Irregular layout ItemsControl


I have a strange layout for an ItemsControl.

I have a 4x6 grid with the following pattern:

1  2  3  4 
13 14 15 16
5  6  7  8
17 18 19 20
9  10 11 12
21 22 23 24

Is there an easy way to do this? should I be using 6 Items Controls and take "sections" of my list? is there a good way to do this? What about notification?

It's important to note that I may, or may not, have all 24 entries present, but the layout needs to be maintained (think of it like filled slots on a bingo card or something)

Edit:

Ideally, I'd like to be able to take a list, and do some fun sorting/padding type stuff off properties on the items in the list.

for instance, if I have an ObservableCollection with a few units, and Unit has a property "Index", I'd like to have a view consumable Collection generated that automatically uses Index to make a padded list. I guess an observable dictionary could work, but that seems gross. Maybe a new custom layout panel is in order?


Solution

  • There is a clever way of doing this in pure XAML using a custom template for your ItemsControl. It's easiest if all your "cards" have a fixed size, say 100x100:

    <!-- Wrap each card in a decorator twice as high as the card cell -->
    <DataTemplate x:Key="ItemInDoubleHighBox">
      <Decorator Width="100" Height="200">
        <Decorator Width="100" Height="100" ClipToBounds="True">
          <ContentPresenter />
        </Decorator>
      </Decorator>
    </DataTemplate>
    
    <!-- Define a template for use with WrapPanel -->
    <ItemsPanelTemplate x:Key="WrapPanelTemplate">
      <WrapPanel />
    </ItemsPanelTemplate>
    
    <!-- Now the actual ItemsControl template -->
    <ControlTemplate TargetType="ItemsControl">
      <Grid Width="600" Height="600" ClipToBounds="True">
    
        <!-- Items 1 to 12 -->
        <ItemsControl ItemsSource="{TemplateBinding ItemsSource}"
                      ItemsPanel="{StaticResource WrapPanelTemplate}"
                      ItemTemplate="{StaticResource ItemInDoubleHighBox}" />
    
        <!-- Items 13 to 24 -->
        <ItemsControl ItemsSource="{TemplateBinding ItemsSource}"
                      ItemsPanel="{StaticResource WrapPanelTemplate}"
                      ItemTemplate="{StaticResource ItemInDoubleHighBox}"
                      RenderTransform="1 0 0 1 0 -500" />
    
      </Grid>
    </ControlTemplate>
    

    How it works: The DataTemplate causes the items to be "double-spaced" with only 1-12 visible, and the RenderTransform on the second ItemsControl makes items 13-24, which are also "double-spaced" appear in the spaces between the first rows of items.

    Note: You can make the height and width data-bindable, but it takes more XAML. Just add ScaleTransforms everywhere "200", "500" or "600" appears in the XAML. For example, to deal with the "200" you can set a scale transform on the inner decorator with ScaleY="0.5" and on each ItemsControl with ScaleY="2". Now the outer decorator's height will be 100, which can be data-bound. The other constants can be dealt with via similar pre- and post- scaling of the content. And because WPF combines all the transforms before rendering anyway, the extra transforms will cost basically nothing.