14

The result I want to achieve is pretty simple, a list with 2 columns, both with equal width. In Windows Phone 7/8 this could easily be achieved using a ListBox with a WrapPanel as ItemsPanel and setting the ItemWidth to 240 (as the screen width was 480).

Now I'm Writing a Universal App, but here the problem is that the screen is not guaranted to have a width of 480 (not even for the Phone it seems) so I can't set the ItemWidth as I want it to fill the width of the screen. I have been able to achieve almost the desired effect using the following XAML:

<GridView ItemsSource="{Binding Results}" Margin="12">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Image Source="{Binding SampleImage}" />
            </Grid>
        </DataTemplate>
    </GridView.ItemTemplate>

    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapGrid MaximumRowsOrColumns="2" Orientation="Horizontal" HorizontalChildrenAlignment="Stretch" VerticalChildrenAlignment="Stretch">
            </WrapGrid>
        </ItemsPanelTemplate>
    </GridView.ItemsPanel>
</GridView>

Which gives the following result: enter image description here

As seen it successfully gives 2 columns with equal width, BUT the Grid in the GridView.ItemTemlate doesn't fill the whole width of each column. I have tried setting HorizontalAlignment="Stretch" on both that Grid and on the GridView itself witout any success. Anyone has any idea of this do this?

Johan Falk
  • 4,341
  • 2
  • 30
  • 42

5 Answers5

16

My solution is :

<GridView ItemsSource="{Binding Results}" Margin="12"
                       SizeChanged="GridView_SizeChanged"
                       x:Name="MyGridView">
<GridView.ItemTemplate>
    <DataTemplate>
        <Grid>
            <Image Source="{Binding SampleImage}" />
        </Grid>
    </DataTemplate>
</GridView.ItemTemplate>

<GridView.ItemContainerStyle>
            <Style TargetType="GridViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                <Setter Property="HorizontalAlignment" Value="Stretch"/>
                <Setter Property="VerticalContentAlignment" Value="Stretch"/>
                <Setter Property="VerticalAlignment" Value="Stretch"/>
            </Style>
</GridView.ItemContainerStyle>
</GridView>

Code Behind :

private void GridView_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        var panel = (ItemsWrapGrid)MyGridView.ItemsPanelRoot;
        panel.ItemWidth =panel.ItemHeight= e.NewSize.Width / 2;
    }
Ganesh Cauda
  • 969
  • 6
  • 21
7

You could try this:

<GridView.ItemContainerStyle>
    <Style
        TargetType="GridViewItem">
        <Setter
            Property="HorizontalAlignment"
            Value="Stretch" />
        <Setter
            Property="VerticalAlignment"
            Value="Stretch" />
    </Style>
 </GridView.ItemContainerStyle>

The other thing you could try is to manually set ItemWidth/ItemHeight whenever you get the SizeChanged event on the GridView.

If for some reason the above doesn't work - you could also do what I do below and update the Value of both DoubleViewModel resources on SizeChanged events:

<UserControl.Resources>
    <viewModels:DoubleViewModel
        x:Key="ItemWidth"
        Value="120" />
    <viewModels:DoubleViewModel
        x:Key="ItemHeight"
        Value="120" />
</UserControl.Resources>

...

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <local:YourItemTemplateControl
            Width="{Binding Value, Source={StaticResource ItemWidth}}"
            Height="{Binding Value, Source={StaticResource ItemHeight}}" />
    </DataTemplate>
</ItemsControl.ItemTemplate>

Where DoubleViewModel is:

public class DoubleViewModel : BindableBase
{
    #region Value
    /// <summary>
    /// Backing field for the Value property.
    /// </summary>
    private double value;

    /// <summary>
    /// Gets or sets a value indicating the value.
    /// </summary>
    public double Value
    {
        get { return this.value; }
        set { this.SetProperty(ref this.value, value); }
    }
    #endregion
}
Filip Skakun
  • 31,624
  • 6
  • 74
  • 100
6

The solution I used was based in Filip Skakuns suggestion, but a slight different implementation by making a reusable User Control with this behaviour. The User Control has (among others) Columns and ItemsSource properties. I change the ItemWidth of the ItemsWrapGrid instead of the Width of the ItemTemplate and do this directly in the SizeChanged event handler.

I also needed to use a ItemsWrapGrid instead of a WrapGrid for this to work. The XAML for the final user control:

<UserControl
    x:Class="MyProject.CustomControls.ColumnGridView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="ControlRoot">

    <Grid DataContext="{Binding ElementName=ControlRoot}">
        <GridView ItemsSource="{Binding ItemsSource}" ItemTemplate="{Binding ItemTemplate}">
            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsWrapGrid Orientation="Horizontal" SizeChanged="ItemsWrapGrid_SizeChanged" />
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>
        </GridView>
    </Grid>
</UserControl>

And for the code-behind:

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace MyProject.CustomControls
{
    public sealed partial class ColumnGridView : UserControl
    {
        public static readonly DependencyProperty ItemTemplateProperty =
            DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(ColumnGridView), new PropertyMetadata(null));

        public DataTemplate ItemTemplate
        {
            get { return (DataTemplate)GetValue(ItemTemplateProperty); }
            set { SetValue(ItemTemplateProperty, value); }
        }

        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(object), typeof(ColumnGridView), new PropertyMetadata(null));

        public object ItemsSource
        {
            get { return (object)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register("Columns", typeof(int), typeof(ColumnGridView), new PropertyMetadata(1));

        public int Columns
        {
            get { return (int)GetValue(ColumnsProperty); }
            set
            {
                if (value <= 0) throw new ArgumentOutOfRangeException("Columns must be greater than 0");
                SetValue(ColumnsProperty, value);
            }
        }

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

        private void ItemsWrapGrid_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            ItemsWrapGrid itemsWrapGrid = sender as ItemsWrapGrid;
            if (itemsWrapGrid != null)
            {
                itemsWrapGrid.ItemWidth = e.NewSize.Width / Columns;
            }
        }
    }
}
Johan Falk
  • 4,341
  • 2
  • 30
  • 42
  • This is nice solution, but it doesn't play well in design time mode for some reason, unfortunately. – Sevenate Mar 02 '15 at 08:46
4

I was able to solve something very similar just binding the Item Width to the parent ActualWidth, like this:

<ListView Name="allDevicesListView" d:DataContext="{d:DesignData /SampleData/VeraServerSampleData.xaml}" ItemsSource="{Binding Devices}">
<ListView.ItemTemplate>
    <DataTemplate>
        <StackPanel Orientation="Horizontal" Width="{Binding ElementName=allDevicesListView, Path=ActualWidth}">
            <Grid Width="{Binding ElementName=allDevicesListView, Path=ActualWidth}">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="2*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Name}" FontSize="24" Grid.Column="0" Margin="0,0,14.333,0" />
                <local:VeraDeviceControl VeraDeviceCategory="DimmableLight" Width="auto" Grid.Column="1"/>
            </Grid>
        </StackPanel>
    </DataTemplate>
</ListView.ItemTemplate>

Vinicius
  • 1,601
  • 19
  • 19
  • This appears to work when one defines the ListVewItems manually in the XAML. However, when items are created based on binding from ItemSource, this does not work. In fact, I see nothing at all. – Klaus Nji Aug 13 '15 at 01:03
  • That's not true. This is a chunk of working code in which we do have items being created by the binding from ItemsSource (look closely, the ListView is binded to the Devices observable collection). Maybe if you bind it to an observable collection and then add items to this collection instead of defining the ItemsSource binding directly via code? – Vinicius Jan 27 '16 at 18:26
  • For some reason I got the same result, the width&height bindings don't work. And I am using a ObservableCollection for itemsource. – joe Jan 09 '18 at 07:17
0

Alternatively you could use the Loaded-Event from your WrapGrid to set at least the ItemWidth

XAML

<Grid Background="LightGreen">

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="auto" />
        <RowDefinition Height="auto" />
    </Grid.RowDefinitions>

    <Grid Grid.Row="0" Grid.Column="0" Name="MyGrid" Background="Red">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="60" />
            <ColumnDefinition Width="60" />
            <ColumnDefinition Width="60" />
        </Grid.ColumnDefinitions>

        <TextBlock Grid.Column="0" />
        <TextBlock Grid.Column="1" Text="I.O" />
        <TextBlock Grid.Column="2" Text="N.V" />
        <TextBlock Grid.Column="3" Text="n.I.O" />
    </Grid>

    <Grid Grid.Row="0" Grid.Column="1" Background="Aqua">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="60" />
            <ColumnDefinition Width="60" />
            <ColumnDefinition Width="60" />
        </Grid.ColumnDefinitions>

        <TextBlock Grid.Column="0" />
        <TextBlock Grid.Column="1" Text="I.O" />
        <TextBlock Grid.Column="2" Text="N.V" />
        <TextBlock Grid.Column="3" Text="n.I.O" />
    </Grid>

    <GridView Grid.Row="1"  Grid.ColumnSpan="2"
              Background="LightBlue"
              HorizontalAlignment="Stretch"
              ItemsSource="{Binding Details}"
              ItemContainerStyle="{StaticResource GridViewItemStyleIOhneHover}"
              ItemTemplateSelector="{StaticResource MyProtokollElementDataTemplateSelector}">
        <GridView.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapGrid Orientation="Horizontal"
                          HorizontalAlignment="Stretch"
                          MaximumRowsOrColumns="2"
                          HorizontalChildrenAlignment="Stretch"
                          VerticalChildrenAlignment="Stretch" Loaded="MyWrapGrid_Loaded">
                </WrapGrid>
            </ItemsPanelTemplate>
        </GridView.ItemsPanel>
    </GridView>
</Grid>

Codebehind

    private void MyWrapGrid_Loaded(object sender, RoutedEventArgs e)
    {
        var wg = sender as WrapGrid;
        wg.ItemWidth = MyGrid.ActualWidth;
    }

Example

enter image description here

WiiMaxx
  • 5,322
  • 8
  • 51
  • 89