Creatiung Grids With Evenly-Sized Columns


Click here to change the theme.

This article shows one way to get evenly-spaced columns instead of uneven columns in a Wiindows Universal Platform program as in the following:

Uneven Even

I followed the instructions in Using SQLite databases in UWP apps. Okay, but that application shows only one field. I tried doing the same thing except with multiple fields. Everything got centered, which is not commonly done for business systems, but worse than that, the size of cells varied within the columns, so that the columns were uneven. The Universal Windows Platform (UWP) has progressed from the old style of tables where columns line up; progressed backward that is. I don't see a way to easily do things the old way.

Let's begin with a sample that uses data created internally instead of from a database. I decided to use a short list of attractions around Hollywood. This sample will not support changing the data; it will just show it in a table format like businesses often do. So I created a ClassAttractions (plural) that is a list of ClassAttraction (singular) objects as in the following:

class ClassAttractions : List<ClassAttraction>
{
	public void Load()
	{
		Add(new ClassAttraction("Griffith Observatory",
			"2800 E Observatory Road", "90027"));
		Add(new ClassAttraction("La Brea Tar Pits & Museum",
			"5801 Wilshire Blvd", "90036"));
		Add(new ClassAttraction("Chinese Theatre",
			"6925 Hollywood Blvd", "90028"));
		Add(new ClassAttraction("The Magic Castle",
			"7001 Franklin Ave", "90028"));
		Add(new ClassAttraction("The Playboy Mansion",
			"10236 Charing Cross Road", "90024"));
	}
}

class ClassAttraction
{
	public string Name { get; set; }
	public string Address { get; set; }
	public string Zip { get; set; }
	public ClassAttraction(string Name, string Address, string Zip)
	{
		this.Name = Name;
		this.Address = Address;
		this.Zip = Zip;
	}
}

For the XAML we will need a Grid for the columns so we will use the following in the main Grid:

<ListView Name="AttractionsView">
	<ListView.ItemTemplate>
		<DataTemplate>
			<Grid>
				<Grid.ColumnDefinitions>
					<ColumnDefinition/>
					<ColumnDefinition Width="Auto"/>
					<ColumnDefinition Width="Auto"/>
				</Grid.ColumnDefinitions>
				<TextBlock Grid.Column="0"  Text="{Binding Name}" />
				<TextBlock Grid.Column="1"  Text="{Binding Address}" />
				<TextBlock Grid.Column="2"  Text="{Binding Zip}" />
			</Grid>
		</DataTemplate>
	</ListView.ItemTemplate>
</ListView>

Then create a "Loaded" event handler for the Page and use the following for it:

ClassAttractions Attractions = new ClassAttractions();
Attractions.Load();
AttractionsView.ItemsSource = Attractions;

Build and run that. The result I get is:

That's not a nice looking table. The columns are uneven; not lined up as we expect tables to be. I tried to solve the problem in many ways and the following is what worked for me. I hope the experts have better ways to do this but until I find something better, this will work. The overview is:

  • Create a temporary control to determine the size to use for each column
  • When each Grid (row) is created, set the size of the columns (cells in the row)

Use the followng to determine the size for each column; you can put it in the Page's constructor (after InitializeComponent):

Size InfiniteSize = new Size(Double.PositiveInfinity, Double.PositiveInfinity);
TextBlock tb = new TextBlock();
tb.FontSize = 15;
tb.Text = new string('W', 20);  // allow for 20 characters
tb.Measure(InfiniteSize);
WidthForName = tb.DesiredSize.Width;
WidtheForAddress = tb.DesiredSize.Width;    // use the same for both
tb.Text = "99999";
tb.Measure(InfiniteSize);
WidthForZip = tb.DesiredSize.Width;

Then give names to the Grid that is our rows and to the TextBlocks that are the cells. For the Grid I used the name "Row" and for the cells I used "BlockName", "BlockAddress" and "BlockZip".

Then create a "Loaded" event handler for "Row" and use the following:

Grid g = sender as Grid;
if (g == null)
{
	System.Diagnostics.Debug.WriteLine("No Grid");
	return;
}
if (VisualTreeHelper.GetChildrenCount(g) != 3)
{
	System.Diagnostics.Debug.WriteLine("Grid does not have 3 children");
	return;
}
TextBlock tb;
tb = VisualTreeHelper.GetChild(g, 0) as TextBlock;
if (tb == null)
	System.Diagnostics.Debug.WriteLine("No Grid child 0");
else
	tb.MinWidth = WidthForName;
tb = VisualTreeHelper.GetChild(g, 1) as TextBlock;
if (tb == null)
	System.Diagnostics.Debug.WriteLine("No Grid child 1");
else
	tb.MinWidth = WidtheForAddress;
tb = VisualTreeHelper.GetChild(g, 2) as TextBlock;
if (tb == null)
	System.Diagnostics.Debug.WriteLine("No Grid child 2");
else
	tb.MinWidth = WidthForZip;
// set the size of the row Grid; add 10 for a little extra
g.MinWidth = WidthForName + WidtheForAddress + WidthForZip + 10;

And now we get:

Isn't that better? In my opinion, there is too much space between the rows but I will leave that and other details for you to do.

Note that we can't use the names "BlockName", "BlockAddress" and "BlockZip" in our code-behind, I assume because there are multiple instances of the relevant objects. So I use VisualTreeHelper to get the children of the Grid. Note also the final line that sets the width for the Grid. I don't know what will happen if the Grid is wider than the available space.