Archive for August, 2012

Windows 8 Lists: Sizing the list item

This past week, I was helping out at a “hack day” at the Microsoft offices in Downers Grove, IL. One of the attendees had an interesting problem: he wanted the contents of his list to dynamically expand to take up the full width of the ListView. The type of item doesn’t matter, so I picked something with 4 properties for the display that would allow me to exercise core of the issue and nothing more.

public class Class1
{
  public string One { get; set; }
  public string Two { get; set; }
  public string Three { get; set; }
  public string Four { get; set; }
}

This was then bound to a ListView using the following XAML (with a border on each item to help visualize how big the actual template wound up being):

<ListView Name="ListView1">
  <ListView.ItemTemplate>
    <DataTemplate>
      <Border BorderBrush="Red" BorderThickness="2">
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="6*"/>
          </Grid.ColumnDefinitions>
          <TextBlock Grid.Column="0" Text="{Binding One}"/>
          <TextBlock Grid.Column="1" Text="{Binding Two}"/>
          <TextBlock Grid.Column="2" Text="{Binding Three}"/>
          <TextBlock Grid.Column="3" Text="{Binding Four}"/>
        </Grid>
      </Border>
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

So far, so good, but when the app displayed the collection of Class1 objects, we saw that the ItemTemplate didn’t extend across the screen.

image

This little item did not do what we wanted it to do, so we thought about what the problem was. A Grid wants to expand to the size of it’s surroundings when not given a limit. The container it was given was one where it was told to size to as big as it needed and no more. As a result, the Grid reported back that it needed to be big enough for the 4 TextBlocks and then was only given that space. Was there a way to go bigger? We looked at the properties and saw one on the ListView called HorizontalContentAlignment, which appeared to let the items align Center, Left, Right, or to Stretch. Stretch usually does good things, so we tried that:

<ListView Name="ListView1" HorizontalContentAlignment="Stretch">

The end result: no change. The screen looked the same as before. At that point, we remembered that we could set the width of the grid manually. Setting the width manually, we were able to achieve the effect, but would still lose any adaptive layout as the screen geometry changed. Still, knowing that the Grid Width property would solve the problem, we only had to find a way to bind the Width of the Grid to the Width of the ListView. On the Grid, we tried binding its width to the Width of ListView 1:

<Grid Width="{Binding ElementName=ListView1, Path=Width}">

Again, no real change. So, we went a bit further. We remembered that we could hook to the SizeChanged event on the ListView and then change a property on the page to feed things. For properties that can be linked to, one can use a combination of DependencyProperty and a value to keep things in sync or implement INotifyPropertyChanged and bind to that value. We tried both paths and found that the DependencyProperty was less code and easier to understand for a control. For this, we added the following to the Page code:

static DependencyProperty ListView1WidthProperty =
  DependencyProperty.Register("ListView1Width", typeof(double), 
  typeof(MainPage), new PropertyMetadata(10));

public double ListView1Width
{
  get { return (double)(GetValue(ListView1WidthProperty)); }
  set { SetValue(ListView1WidthProperty, value); }
}

This code just gave us something to hook into. We then added some code to handle the SizeChanged event on the ListView, which would update the value for ListView1Width:

private void ListView1_SizeChanged(object sender, 
  Windows.UI.Xaml.SizeChangedEventArgs e)
{
  ListView1Width = e.NewSize.Width - 15;
}

This method was hooked up in the XAML as:

<ListView Name="ListView1" SizeChanged="ListView1_SizeChanged">

We then gave the Page the name MyPage in order to allow us to bind to the ListView1Width property. The Grid Width property was now bound as:

<Grid Width="{Binding ElementName=MyPage, Path=ListView1Width}">

The final results were exactly what we wanted:

image

And, as the list was resized to take advantage of other items, like the snap view or portrait view, the resize continued to work.

Assets:

The XAML for this page:

<Page
    x:Class="ColumnsInList.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ColumnsInList"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Name="MyPage" >

  <Grid Name="BigGrid" Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <ListView Name="ListView1" SizeChanged="ListView1_SizeChanged">
      <ListView.ItemTemplate>
        <DataTemplate>
          <Border BorderBrush="Red" BorderThickness="2">
            <Grid Width="{Binding ElementName=MyPage, Path=ListView1Width}">
              <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="6*"/>
              </Grid.ColumnDefinitions>
              <TextBlock Grid.Column="0" Text="{Binding One}"/>
              <TextBlock Grid.Column="1" Text="{Binding Two}"/>
              <TextBlock Grid.Column="2" Text="{Binding Three}"/>
              <TextBlock Grid.Column="3" Text="{Binding Four}"/>
            </Grid>
          </Border>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </Grid>
</Page>

C# Code:

// Class1.cs
namespace ColumnsInList
{
  public class Class1
  {
    public string One { get; set; }
    public string Two { get; set; }
    public string Three { get; set; }
    public string Four { get; set; }
  }
}

// MainPage.xaml.cs
using System.Collections.ObjectModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace ColumnsInList
{
  public sealed partial class MainPage : Page
  {
    static DependencyProperty ListView1WidthProperty =
      DependencyProperty.Register("ListView1Width", typeof(double),
      typeof(MainPage), new PropertyMetadata(10));

    public double ListView1Width
    {
      get { return (double)(GetValue(ListView1WidthProperty)); }
      set { SetValue(ListView1WidthProperty, value); }
    }

    public MainPage()
    {
      this.InitializeComponent();

    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
      ListView1.ItemsSource = new ObservableCollection<Class1>(new Class1[] {
              new Class1{One="One1", Two = "Two1", Three ="Three1", Four= "Four1"},
              new Class1{One="One2", Two = "Two2", Three ="Three2", Four= "Four2"},
              new Class1{One="One3", Two = "Two3", Three ="Three3", Four= "Four3"},
              new Class1{One="One4", Two = "Two4", Three ="Three4", Four= "Four4"},
          });
    }

    private void ListView1_SizeChanged(object sender,
      Windows.UI.Xaml.SizeChangedEventArgs e)
    {
      ListView1Width = e.NewSize.Width - 15;
    }

  }
}

Leave a comment

Follow

Get every new post delivered to your Inbox.