Tuesday, January 10, 2017

How does the W10 News app stretch the items in the gridview?

Leave a Comment

I'm trying to create a gridview like in the default News app in Windows 10. As far as I know I have to set the ItemHeight an ItemWidth for the VariableSizedWrapGrid. But then it does not stretch the items to fit the full grid width, while the News app does do that as you can see in the pictures below. How do they do that? Is it a special custom control? Widescreen, 4 columns and items are quite wide

Narrowscreen, 3 small columns

4 Answers

Answers 1

I will just give you the concept:

 +---------+ +---------+ +---------+  |  small  | |         | |         | +---------+ |         | |         | --- gap --- | medium  | |         | +---------+ |         | |         | |  small  | |         | |   big   | +---------+ +---------+ |         | --- gap --- --- gap --- |         | +---------+ +---------+ |         | |  small  | |  small  | |         | +---------+ +---------+ +---------+ --- gap --- --- gap --- --- gap --- 

So we only have 3 different boxes for presentation with a known height. The view itself can decide (codebehind) which template is used to present the content.

  • small: headline with a small thumbnail
  • medium: small image with headline
  • large: image with headline and some text from article

Alle items are arranged in groups by 5 to 9 items for each group. The groups are presented inside an ItemsControl and each group is presented by a WrapPanel (vertical oriantation).

Lets see that for some rows:

  • 2 columns, 6 items
 lllll  mmmmm lllll  mmmmm lllll  mmmmm lllll lllll  sssss  lllll  mmmmm lllll  mmmmm lllll  mmmmm lllll lllll  sssss 
  • 3 columns, 6 items
 lllll  lllll  mmmmm lllll  lllll  mmmmm lllll  lllll  mmmmm lllll  lllll   lllll  lllll  lllll               lllll mmmmm  mmmmm  lllll mmmmm  mmmmm  lllll mmmmm  mmmmm  lllll 
  • 4 columns, 6 items
 lllll  lllll  lllll  sssss lllll  lllll  lllll lllll  lllll  lllll  sssss lllll  lllll  lllll lllll  lllll  lllll  sssss 
  • 5 columns, 6 items
 mmmmm  mmmmm  mmmmm  mmmmm  sssss mmmmm  mmmmm  mmmmm  mmmmm mmmmm  mmmmm  mmmmm  mmmmm  sssss 

So we need three DataTemplates for the content items, some templates for the WrapPanel and some logic inside the view to group the items into rows and the template mangement for the WrapPanel and the items inside.

Here a simple XAML PoC just to test the concept:

<Grid>     <ScrollViewer>         <ItemsControl>             <ItemsControl.Resources>                 <Style x:Key="col" TargetType="WrapPanel">                     <Setter Property="Orientation" Value="Vertical"/>                     <Setter Property="ItemWidth" Value="80"/>                 </Style>                  <Style x:Key="content" TargetType="Border">                     <Setter Property="Margin" Value="5,5"/>                 </Style>                 <Style x:Key="small" TargetType="Border" BasedOn="{StaticResource content}">                     <Setter Property="Background" Value="Orange"/>                     <Setter Property="Height" Value="30"/>                 </Style>                 <Style x:Key="medium" TargetType="Border" BasedOn="{StaticResource content}">                     <Setter Property="Background" Value="Green"/>                     <Setter Property="Height" Value="70"/>                 </Style>                 <Style x:Key="large" TargetType="Border" BasedOn="{StaticResource content}">                     <Setter Property="Background" Value="Red"/>                     <Setter Property="Height" Value="110"/>                 </Style>                  <Style x:Key="2col6items" TargetType="WrapPanel" BasedOn="{StaticResource col}">                     <Setter Property="Height" Value="240"/>                 </Style>                  <Style x:Key="3col6items" TargetType="WrapPanel" BasedOn="{StaticResource col}">                     <Setter Property="Height" Value="200"/>                 </Style>                  <Style x:Key="4col6items" TargetType="WrapPanel" BasedOn="{StaticResource col}">                     <Setter Property="Height" Value="120"/>                 </Style>                  <Style x:Key="5col6items" TargetType="WrapPanel" BasedOn="{StaticResource col}">                     <Setter Property="Height" Value="80"/>                 </Style>               </ItemsControl.Resources>             <!-- first row -->             <WrapPanel Style="{StaticResource 5col6items}">                 <Border Style="{StaticResource medium}"/>                 <Border Style="{StaticResource medium}"/>                 <Border Style="{StaticResource medium}"/>                 <Border Style="{StaticResource medium}"/>                 <Border Style="{StaticResource small}"/>                 <Border Style="{StaticResource small}"/>             </WrapPanel>             <!-- second row -->             <WrapPanel Style="{StaticResource 4col6items}">                 <Border Style="{StaticResource large}"/>                 <Border Style="{StaticResource large}"/>                 <Border Style="{StaticResource large}"/>                 <Border Style="{StaticResource small}"/>                 <Border Style="{StaticResource small}"/>                 <Border Style="{StaticResource small}"/>             </WrapPanel>             <!-- third row -->             <WrapPanel Style="{StaticResource 3col6items}">                 <Border Style="{StaticResource large}"/>                 <Border Style="{StaticResource medium}"/>                 <Border Style="{StaticResource large}"/>                 <Border Style="{StaticResource medium}"/>                 <Border Style="{StaticResource medium}"/>                 <Border Style="{StaticResource large}"/>             </WrapPanel>             <!-- fourth row -->             <WrapPanel Style="{StaticResource 2col6items}">                 <Border Style="{StaticResource large}"/>                 <Border Style="{StaticResource large}"/>                 <Border Style="{StaticResource medium}"/>                 <Border Style="{StaticResource small}"/>                 <Border Style="{StaticResource medium}"/>                 <Border Style="{StaticResource small}"/>             </WrapPanel>         </ItemsControl>     </ScrollViewer> </Grid> 

and the lookalike

enter image description here

Update

A very simple ProofOfConcept with stretching and templating on resize

<Window x:Class="WpfApp1.MainWindow"         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"         xmlns:local="clr-namespace:WpfApp1"         mc:Ignorable="d"         Title="MainWindow" Height="350" Width="525">     <Grid>         <ScrollViewer>             <ItemsControl x:Name="ItemsPresenter" SizeChanged="ItemsPresenter_SizeChanged">                 <ItemsControl.Resources>                     <Style x:Key="col" TargetType="WrapPanel">                         <Setter Property="Orientation" Value="Vertical"/>                         <Setter Property="ItemWidth" Value="{Binding ColumnWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}"/>                     </Style>                      <Style x:Key="content" TargetType="TextBlock">                         <Setter Property="Margin" Value="5"/>                         <Setter Property="HorizontalAlignment" Value="Stretch"/>                         <Setter Property="TextAlignment" Value="Center"/>                         <Setter Property="VerticalAlignment" Value="Stretch"/>                     </Style>                     <Style x:Key="small" TargetType="TextBlock" BasedOn="{StaticResource content}">                         <Setter Property="Background" Value="LightBlue"/>                         <Setter Property="Height" Value="30"/>                     </Style>                     <Style x:Key="medium" TargetType="TextBlock" BasedOn="{StaticResource content}">                         <Setter Property="Background" Value="LightGreen"/>                         <Setter Property="Height" Value="70"/>                     </Style>                     <Style x:Key="large" TargetType="TextBlock" BasedOn="{StaticResource content}">                         <Setter Property="Background" Value="LightSalmon"/>                         <Setter Property="Height" Value="110"/>                     </Style>                      <Style x:Key="1col6items" TargetType="WrapPanel" BasedOn="{StaticResource col}">                         <Setter Property="Height" Value="480"/>                     </Style>                      <Style x:Key="2col6items" TargetType="WrapPanel" BasedOn="{StaticResource col}">                         <Setter Property="Height" Value="240"/>                     </Style>                      <Style x:Key="3col6items" TargetType="WrapPanel" BasedOn="{StaticResource col}">                         <Setter Property="Height" Value="200"/>                     </Style>                      <Style x:Key="4col6items" TargetType="WrapPanel" BasedOn="{StaticResource col}">                         <Setter Property="Height" Value="120"/>                     </Style>                      <Style x:Key="5col6items" TargetType="WrapPanel" BasedOn="{StaticResource col}">                         <Setter Property="Height" Value="80"/>                     </Style>                   </ItemsControl.Resources>                 <!-- first row -->                 <WrapPanel >                     <TextBlock>1</TextBlock>                     <TextBlock>2</TextBlock>                     <TextBlock>3</TextBlock>                     <TextBlock>4</TextBlock>                     <TextBlock>5</TextBlock>                     <TextBlock>6</TextBlock>                 </WrapPanel>                 <!-- second row -->                 <WrapPanel >                     <TextBlock>7</TextBlock>                     <TextBlock>8</TextBlock>                     <TextBlock>9</TextBlock>                     <TextBlock>10</TextBlock>                     <TextBlock>11</TextBlock>                     <TextBlock>12</TextBlock>                 </WrapPanel>                 <!-- third row -->                 <WrapPanel >                     <TextBlock>13</TextBlock>                     <TextBlock>14</TextBlock>                     <TextBlock>15</TextBlock>                     <TextBlock>16</TextBlock>                     <TextBlock>17</TextBlock>                     <TextBlock>18</TextBlock>                 </WrapPanel>             </ItemsControl>         </ScrollViewer>     </Grid> </Window> 

and the codebehind

public partial class MainWindow : Window {     public MainWindow()     {         InitializeComponent( );     }      public int ColumnCount     {         get { return (int) GetValue( ColumnCountProperty ); }         private set { SetValue( ColumnCountProperty, value ); }     }      // Using a DependencyProperty as the backing store for ColumnCount.  This enables animation, styling, binding, etc...     public static readonly DependencyProperty ColumnCountProperty =         DependencyProperty.Register( "ColumnCount", typeof( int ), typeof( MainWindow ), new PropertyMetadata( 1 ) );      public double ColumnWidth     {         get { return (double) GetValue( ColumnWidthProperty ); }         private set { SetValue( ColumnWidthProperty, value ); }     }      // Using a DependencyProperty as the backing store for ColumnWidth.  This enables animation, styling, binding, etc...     public static readonly DependencyProperty ColumnWidthProperty =         DependencyProperty.Register( "ColumnWidth", typeof( double ), typeof( MainWindow ), new PropertyMetadata( (double) 100 ) );      public double ColumnMinWidth     {         get { return (double) GetValue( ColumnMinWidthProperty ); }         set         {             SetValue( ColumnMinWidthProperty, value );             CalculateColumnLayout( );         }     }      // Using a DependencyProperty as the backing store for ColumnMinWidth.  This enables animation, styling, binding, etc...     public static readonly DependencyProperty ColumnMinWidthProperty =         DependencyProperty.Register( "ColumnMinWidth", typeof( double ), typeof( MainWindow ), new PropertyMetadata( (double) 200 ) );      public double ColumnMaxWidth     {         get { return (double) GetValue( ColumnMaxWidthProperty ); }         set         {             SetValue( ColumnMaxWidthProperty, value );             CalculateColumnLayout( );         }     }      // Using a DependencyProperty as the backing store for ColumnMaxWidth.  This enables animation, styling, binding, etc...     public static readonly DependencyProperty ColumnMaxWidthProperty =         DependencyProperty.Register( "ColumnMaxWidth", typeof( double ), typeof( MainWindow ), new PropertyMetadata( (double) 250 ) );      private void CalculateColumnLayout()     {         int colCount = ColumnCount;         double totalWidth = ItemsPresenter.ActualWidth;         double colWidth = totalWidth / colCount;          while ( colCount > 1 && colWidth < Math.Min( ColumnMinWidth, ColumnMaxWidth ) )         {             colCount--;             colWidth = totalWidth / colCount;         }          while ( colCount < 5 && colWidth > Math.Max( ColumnMinWidth, ColumnMaxWidth ) )         {             colCount++;             colWidth = totalWidth / colCount;         }          if ( ColumnCount != colCount )         {             ColumnCount = colCount;             StyleItemsPresenterItems( );         }         ColumnWidth = colWidth;     }      private Dictionary<int, string[]> _styles = new Dictionary<int, string[]>     {         [ 1 ] = new string[] { "medium", "medium", "medium", "medium", "medium", "medium" },         [ 2 ] = new string[] { "large", "medium", "small", "small", "medium", "large" },         [ 3 ] = new string[] { "large", "medium", "medium", "large", "large", "medium" },         [ 4 ] = new string[] { "large", "large", "large", "small", "small", "small" },         [ 5 ] = new string[] { "medium", "medium", "medium", "medium", "small", "small" },     };       private void StyleItemsPresenterItems()     {         foreach ( var pnl in ItemsPresenter.Items.OfType<WrapPanel>( ) )         {             if ( pnl != null )             {                 pnl.Style = ItemsPresenter.Resources[ $"{ColumnCount}col6items" ] as Style;                  foreach ( var item in pnl.Children.OfType<TextBlock>( ).Zip( _styles[ ColumnCount ], ( border, stylename ) => new { border, stylename } ) )                 {                     item.border.Style = ItemsPresenter.Resources[ item.stylename ] as Style;                 }              }         }     }      private void ItemsPresenter_SizeChanged( object sender, SizeChangedEventArgs e )     {         CalculateColumnLayout( );     } } 

and finally the result

enter image description here

Answers 2

According to MSDN the ItemWidth can be set to Auto.

The default value of ItemHeight and ItemWidth is not 0, it is Double.NaN. ItemHeight and ItemWidth support the ability to be an unset "Auto" value. Because ItemHeight and ItemWidth are Double values, Double.NaN is used as a special value to represent this "Auto" behavior. The layout system interprets the "Auto" value to generally mean that the object should be sized to the available size in layout, instead of to a specific pixel value.

I don't know if this will result in the behavior you want though. If it doesn't then you might be able to get it by binding the ItemWidth to a property where you calculate the item width based on the width of the grid. It would look something like this:

float DynamicItemWidth {   get {     int ItemMinimumWidth = 300, margin = 16; //just some guesses     var gridWidth = ...;      var numberOfColumns = gridWidth % ItemMinimumWidth;     var itemWidth = (gridWidth - margin * (numberOfColumns - 1)) / numberOfColumns;      return itemWidth;   } } 

Answers 3

UWP

As an addition to my previous answer where I show the basic concept here a solution for UWP platform using the VariableSizedWrapPanel as mentioned in the question:

The main job is done by

<local:MyGridView      ItemsSource="{Binding}"      ItemTemplateSelector="{StaticResource MyGridTemplateSelector}"      MinItemWidth="300" MaxItemWidth="600"      ScrollViewer.VerticalScrollBarVisibility="Hidden">     <GridView.ItemsPanel>         <ItemsPanelTemplate>             <VariableSizedWrapGrid ItemHeight="180" Orientation="Horizontal"/>         </ItemsPanelTemplate>     </GridView.ItemsPanel> </local:MyGridView> 

along with

MyGridView.cs

using System; using System.Collections.Generic; using System.Linq; using Windows.Foundation; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls;  namespace App1 {     public class MyGridView : GridView     {         private int _columnCount = 1;         private double _itemWidth = 100;          public double MinItemWidth         {             get { return (double) GetValue( MinItemWidthProperty ); }             set { SetValue( MinItemWidthProperty, value ); }         }          // Using a DependencyProperty as the backing store for MinItemWidth.  This enables animation, styling, binding, etc...         public static readonly DependencyProperty MinItemWidthProperty =             DependencyProperty.Register( "MinItemWidth", typeof( double ), typeof( MyGridView ), new PropertyMetadata( 100.0 ) );           public double MaxItemWidth         {             get { return (double) GetValue( MaxItemWidthProperty ); }             set { SetValue( MaxItemWidthProperty, value ); }         }          // Using a DependencyProperty as the backing store for MaxItemWidth.  This enables animation, styling, binding, etc...         public static readonly DependencyProperty MaxItemWidthProperty =             DependencyProperty.Register( "MaxItemWidth", typeof( double ), typeof( MyGridView ), new PropertyMetadata( 200.0 ) );           private long _itemsPanelPropertyChangedToken;          public MyGridView()         {             _itemsPanelPropertyChangedToken = RegisterPropertyChangedCallback( ItemsPanelProperty, ItemsPanelChangedAsync );         }          private async void ItemsPanelChangedAsync( DependencyObject sender, DependencyProperty dp )         {             UnregisterPropertyChangedCallback( ItemsPanelProperty, _itemsPanelPropertyChangedToken );             await this.Dispatcher.RunIdleAsync( ItemsPanelChangedCallback );         }          private void ItemsPanelChangedCallback( IdleDispatchedHandlerArgs e )         {             var wg = ItemsPanelRoot as VariableSizedWrapGrid;             if (wg != null)             {                 wg.ItemWidth = _itemWidth;             }         }          protected override void PrepareContainerForItemOverride( DependencyObject element, object item )         {             var itemIndex = this.Items.IndexOf( item );              element.SetValue( VariableSizedWrapGrid.RowSpanProperty, GetRowSpanByColumnCountAndIndex( _columnCount, itemIndex ) );             element.SetValue( VerticalContentAlignmentProperty, VerticalAlignment.Stretch );             element.SetValue( HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch );             base.PrepareContainerForItemOverride( element, item );         }          private static readonly Dictionary<int, int[]> _rowSpanLayout = new Dictionary<int, int[]>         {             [ 1 ] = new int[] { /* 5 */ 2, 2, 2, 2, 2, /* 6 */ 2, 2, 2, 2, 2, 2, /* 7 */ 2, 2, 2, 2, 2, 2, 2, /* 8 */ 2, 2, 2, 2, 2, 2, 2, 2, /* 9 */ 2, 2, 2, 2, 2, 2, 2, 2, 2 },             [ 2 ] = new int[] { /* 5 */ 2, 1, 2, 2, 1, /* 6 */ 3, 3, 3, 2, 2, 2, /* 7 */ 3, 3, 1, 2, 3, 1, 1, /* 8 */ 2, 3, 2, 3, 3, 3, 3, 1, /* 9 */ 3, 2, 1, 3, 2, 2, 3, 1, 1 },             [ 3 ] = new int[] { /* 5 */ 3, 2, 2, 1, 1, /* 6 */ 2, 3, 2, 3, 3, 2, /* 7 */ 3, 3, 3, 2, 1, 2, 1, /* 8 */ 2, 3, 3, 1, 2, 1, 2, 1, /* 9 */ 3, 3, 3, 1, 2, 1, 3, 3, 2 },             [ 4 ] = new int[] { /* 5 */ 2, 2, 1, 2, 1, /* 6 */ 3, 3, 2, 2, 1, 1, /* 7 */ 3, 2, 2, 2, 1, 1, 1, /* 8 */ 3, 3, 3, 3, 2, 2, 2, 2, /* 9 */ 3, 3, 3, 2, 2, 2, 2, 2, 1 },             [ 5 ] = new int[] { /* 5 */ 2, 2, 2, 2, 2, /* 6 */ 2, 2, 2, 1, 2, 1, /* 7 */ 3, 3, 3, 2, 2, 1, 1, /* 8 */ 3, 3, 2, 2, 2, 1, 1, 1, /* 9 */ 3, 2, 2, 2, 2, 1, 1, 1, 1 },         };          private int GetRowSpanByColumnCountAndIndex( int columnCount, int itemIndex )         {             return _rowSpanLayout[ columnCount ][ itemIndex % 35 ];         }          protected override Size MeasureOverride( Size availableSize )         {             System.Diagnostics.Debug.WriteLine( availableSize );              int columnCount = _columnCount;             double availableWidth = availableSize.Width;              double itemWidth = availableWidth / columnCount;              while ( columnCount > 1 && itemWidth < Math.Min( MinItemWidth, MaxItemWidth ) )             {                 columnCount--;                 itemWidth = availableWidth / columnCount;             }              while ( columnCount < 5 && itemWidth > Math.Max( MinItemWidth, MaxItemWidth ) )             {                 columnCount++;                 itemWidth = availableWidth / columnCount;             }              var wg = this.ItemsPanelRoot as VariableSizedWrapGrid;              _itemWidth = itemWidth;             if ( _columnCount != columnCount )             {                 _columnCount = columnCount;                 if ( wg != null )                 {                     Update( );                 }             }              if ( wg != null )             {                 wg.ItemWidth = itemWidth;             }              return base.MeasureOverride( availableSize );         }          // refresh the variablesizedwrapgrid layout         private void Update()         {             if ( !( this.ItemsPanelRoot is VariableSizedWrapGrid ) )                 throw new ArgumentException( "ItemsPanel is not VariableSizedWrapGrid" );              int itemIndex = 0;             foreach ( var container in this.ItemsPanelRoot.Children.Cast<GridViewItem>( ) )             {                 int rowSpan = GetRowSpanByColumnCountAndIndex( _columnCount, itemIndex );                 VariableSizedWrapGrid.SetRowSpan( container, rowSpan );                 itemIndex++;             }              this.ItemsPanelRoot.InvalidateMeasure( );         }     } } 

and

MyGridViewTemplateSelector.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls;  namespace App1 {     public class MyGridViewTemplateSelector : DataTemplateSelector     {         public DataTemplate Small { get; set; }         public DataTemplate Medium { get; set; }         public DataTemplate Large { get; set; }          protected override DataTemplate SelectTemplateCore( object item, DependencyObject container )         {             var rowSpan = container.GetValue( VariableSizedWrapGrid.RowSpanProperty );              int index;             try             {                 dynamic model = item;                 index = model.Index;             }             catch ( Exception )             {                 index = -1;             }             long token = 0;              DependencyPropertyChangedCallback lambda = ( sender, dp ) =>             {                 container.UnregisterPropertyChangedCallback( VariableSizedWrapGrid.RowSpanProperty, token );                  var cp = (ContentControl) container;                 cp.ContentTemplateSelector = null;                 cp.ContentTemplateSelector = this;             };              token = container.RegisterPropertyChangedCallback( VariableSizedWrapGrid.RowSpanProperty, lambda );              switch ( rowSpan )             {                 case 1:                     return Small;                 case 2:                     return Medium;                 case 3:                     return Large;                 default:                     throw new InvalidOperationException( );             }         }          private void Foo( DependencyObject sender, DependencyProperty dp )         {             throw new NotImplementedException( );         }     } } 

To complete here the other files

MainPage.xaml

<Page     x:Class="App1.MainPage"     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:local="using:App1"     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     mc:Ignorable="d">     <Page.Resources>          <DataTemplate x:Key="Small">             <Grid Margin="5">                 <Grid.Background>                     <SolidColorBrush Color="{Binding Path=Color}"/>                 </Grid.Background>                 <StackPanel VerticalAlignment="Top">                     <StackPanel.Background>                         <SolidColorBrush Color="White" Opacity="0.75"/>                     </StackPanel.Background>                      <TextBlock FontSize="15" Margin="10">                         <Run Text="{Binding Path=Index}"/>. <Run Text="{Binding Path=Name}"/>                     </TextBlock>                     <TextBlock Text="Small" TextAlignment="Center"/>                 </StackPanel>             </Grid>         </DataTemplate>          <DataTemplate x:Key="Medium">             <Grid Margin="5">                 <Grid.Background>                     <SolidColorBrush Color="{Binding Path=Color}"/>                 </Grid.Background>                 <StackPanel VerticalAlignment="Top">                     <StackPanel.Background>                         <SolidColorBrush Color="White" Opacity="0.75"/>                     </StackPanel.Background>                      <TextBlock FontSize="15" Margin="10">                         <Run Text="{Binding Path=Index}"/>. <Run Text="{Binding Path=Name}"/>                     </TextBlock>                     <TextBlock Text="Medium" TextAlignment="Center"/>                 </StackPanel>             </Grid>         </DataTemplate>          <DataTemplate x:Key="Large">             <Grid Margin="5">                 <Grid.Background>                     <SolidColorBrush Color="{Binding Path=Color}"/>                 </Grid.Background>                 <StackPanel VerticalAlignment="Top">                     <StackPanel.Background>                         <SolidColorBrush Color="White" Opacity="0.75"/>                     </StackPanel.Background>                      <TextBlock FontSize="15" Margin="10">                         <Run Text="{Binding Path=Index}"/>. <Run Text="{Binding Path=Name}"/>                     </TextBlock>                     <TextBlock Text="Large" TextAlignment="Center"/>                 </StackPanel>             </Grid>          </DataTemplate>         <local:MyGridViewTemplateSelector x:Key="MyGridTemplateSelector"                                           Small="{StaticResource Small}"                                           Medium="{StaticResource Medium}"                                           Large="{StaticResource Large}"/>     </Page.Resources>      <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">         <Grid.RowDefinitions>             <RowDefinition Height="48"/>             <RowDefinition Height="*"/>         </Grid.RowDefinitions>         <Grid.ColumnDefinitions>             <ColumnDefinition Width="48"/>             <ColumnDefinition Width="*"/>         </Grid.ColumnDefinitions>          <!-- top left section -->          <Border Background="#D13438">          </Border>          <!-- top bar -->          <Border Grid.Column="1" Grid.Row="0" Padding="5" Background="#F2F2F2">             <TextBlock Text="MenuBar" VerticalAlignment="Center"/>         </Border>          <!-- left bar -->          <Border Grid.Column="0" Grid.Row="1" Width="48" Background="#2B2B2B">          </Border>          <!-- content -->          <Border Grid.Column="1" Grid.Row="1" Background="#E6E6E6">             <Grid>                 <Grid.RowDefinitions>                     <RowDefinition Height="48"/>                     <RowDefinition Height="*"/>                 </Grid.RowDefinitions>                 <Border Grid.Row="0" Padding="5" Background="#F2F2F2">                     <TextBlock Text="SectionBar" VerticalAlignment="Center"/>                 </Border>                 <ScrollViewer Grid.Row="1">                     <Border Margin="7,7,10,7">                          <!-- the wrapped news items -->                          <local:MyGridView ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource MyGridTemplateSelector}" MinItemWidth="300" MaxItemWidth="600" ScrollViewer.VerticalScrollBarVisibility="Hidden">                             <GridView.ItemsPanel>                                 <ItemsPanelTemplate>                                     <VariableSizedWrapGrid ItemHeight="180" Orientation="Horizontal"/>                                 </ItemsPanelTemplate>                             </GridView.ItemsPanel>                         </local:MyGridView>                      </Border>                 </ScrollViewer>             </Grid>         </Border>     </Grid> </Page> 

MainPage.xaml.cs

using System.Linq; using Windows.UI; using Windows.UI.Xaml.Controls; using System.Reflection;  namespace App1 {     public sealed partial class MainPage : Page     {         public MainPage()         {             this.InitializeComponent( );              // just some sample data              var colors = typeof( Colors )                 .GetRuntimeProperties( )                 .Take( 140 )                 .Select( ( x, index ) => new                 {                     Color = (Color) x.GetValue( null ),                     Name = x.Name,                     Index = index,                 } );             this.DataContext = colors;         }      } } 

If you will ever think "I know that from somewhere" you should have a look at Jerry Nixon's blog :o)

Answers 4

VERSION 3: adding some logic just so not only ItemWidth and ItemHeight are a function of the parent's size, but also MaximumRowsOrColumns:

enter image description here

XAML:

<Page     x:Class="App1.MainPage"     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:local="using:App1"     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     mc:Ignorable="d">      <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"            SizeChanged="Grid1_SizeChanged">          <ScrollViewer>             <local:MyItemsControl ItemsSource="{Binding Data}">                 <ItemsControl.ItemsPanel>                     <ItemsPanelTemplate>                         <VariableSizedWrapGrid ItemWidth="{Binding MyWidth}"                                            ItemHeight="{Binding MyHeight}"                                            MaximumRowsOrColumns="{Binding MaxRowCol}"                                            Orientation="Horizontal"/>                     </ItemsPanelTemplate>                 </ItemsControl.ItemsPanel>                 <ItemsControl.ItemTemplate>                     <DataTemplate>                         <StackPanel VariableSizedWrapGrid.RowSpan="{Binding RowSpan}"                                  VariableSizedWrapGrid.ColumnSpan="{Binding ColSpan}"                                 BorderBrush="Gray"                                 BorderThickness="1"                                 Background="{Binding Color}"                                 Margin="5,5,5,5">                             <Image Source="{Binding Image}"                                Width="96"                                Height="96"                                HorizontalAlignment="Left"                                VerticalAlignment="Top"                                />                             <TextBlock FontWeight="Bold"                                         FontSize="16">                                 <Underline>                                     <Run Text="{Binding Headline}" />                                 </Underline>                                                         </TextBlock>                             <TextBlock FontSize="12"                                     TextWrapping="Wrap"                                    Text="{Binding Body}"/>                         </StackPanel>                     </DataTemplate>                 </ItemsControl.ItemTemplate>             </local:MyItemsControl>         </ScrollViewer>     </Grid> </Page> 

MainPage:

namespace App1 {     /// <summary>     /// An empty page that can be used on its own or navigated to within a Frame.     /// </summary>     public sealed partial class MainPage : Page     {         MyViewModel vm;          public MainPage()         {             this.InitializeComponent();              vm = new MyViewModel();             DataContext = vm;         }          private void Grid1_SizeChanged(object sender, SizeChangedEventArgs e)         {             // customize this logic to your needs              double curWidth = e.NewSize.Width;             double curHeight = e.NewSize.Height;              if (curWidth < 200)             {                 vm.MaxRowCol = 2;             }             else if (curWidth < 400)             {                 vm.MaxRowCol = 4;             }             else if (curWidth < 800)             {                 vm.MaxRowCol = 8;             }             else             {                 vm.MaxRowCol = 10;             }             vm.MyWidth = curWidth / vm.MaxRowCol;             vm.MyHeight = curHeight / vm.MaxRowCol;         }     } } 

ViewModel:

namespace App1 {     public class MyViewModel : INotifyPropertyChanged     {         public event PropertyChangedEventHandler PropertyChanged = delegate { };          public ObservableCollection<MyDataModel> Data { get; set; }          public MyViewModel()         {             Data = new ObservableCollection<MyDataModel>             {                 new MyDataModel { Color = "DarkRed", RowSpan = 3, ColSpan = 3, Headline = "Headline 1", Image = "ms-appx:///Assets/64-001.jpg", Body = "Lorem ipsum dolor sit amet, amet class elementum nunc ante." },                 new MyDataModel { Color = "DarkGreen", RowSpan = 4, ColSpan = 3, Headline = "Headline 2", Image = "ms-appx:///Assets/64-002.jpg", Body = "Tristique ullamcorper, nulla lacinia consectetuer."  },                 new MyDataModel { Color = "DarkRed", RowSpan = 3, ColSpan = 3, Headline = "Headline 3", Image = "ms-appx:///Assets/64-003.jpg", Body = "Rerum dui, hendrerit a id suscipit turpis lectus."  },                 new MyDataModel { Color = "DarkBlue", RowSpan = 4, ColSpan = 3, Headline = "Headline 4", Image = "ms-appx:///Assets/64-004.jpg", Body = "Lorem ipsum dolor sit amet, amet class elementum nunc ante."  },                 new MyDataModel { Color = "DarkOrange", RowSpan = 3, ColSpan = 3, Headline = "Headline 5", Image = "ms-appx:///Assets/64-005.jpg", Body = "Rerum dui, hendrerit a id suscipit turpis lectus."  },                 new MyDataModel { Color = "DarkGreen", RowSpan = 4, ColSpan = 3, Headline = "Headline 6", Image = "ms-appx:///Assets/64-006.jpg", Body = "Erat velit eligendi erat ante sapien arcu, wisi mi dictumst curabitur"  },                 new MyDataModel { Color = "DarkOrange", RowSpan = 3, ColSpan = 3, Headline = "Headline 7", Image = "ms-appx:///Assets/64-007.jpg", Body = "Tristique ullamcorper, nulla lacinia consectetuer."  },                 new MyDataModel { Color = "DarkRed", RowSpan = 4, ColSpan = 3, Headline = "Headline 8", Image = "ms-appx:///Assets/64-008.jpg", Body = "Lorem ipsum dolor sit amet, amet class elementum nunc ante."  },                 new MyDataModel { Color = "DarkRed", RowSpan = 3, ColSpan = 3, Headline = "Headline 9", Image = "ms-appx:///Assets/64-009.jpg", Body = "Mus quisque velit libero habitasse libero, nam etiam a ornare pellentesque."  },                 new MyDataModel { Color = "DarkBlue", RowSpan = 4, ColSpan = 3, Headline = "Headline 10", Image = "ms-appx:///Assets/64-010.jpg", Body = "Rerum dui, hendrerit a id suscipit turpis lectus."  },                 new MyDataModel { Color = "DarkGreen", RowSpan = 3, ColSpan = 3, Headline = "Headline 11", Image = "ms-appx:///Assets/64-011.jpg", Body = "Erat velit eligendi erat ante sapien arcu, wisi mi dictumst curabitur"  },                 new MyDataModel { Color = "DarkRed", RowSpan = 4, ColSpan = 3, Headline = "Headline 12", Image = "ms-appx:///Assets/64-012.jpg", Body = "Tristique ullamcorper, nulla lacinia consectetuer."  },             };         }          private double mywidth;         public double MyWidth         {             get { return mywidth; }             set             {                 if (value != mywidth)                 {                     mywidth = value;                     PropertyChanged(this, new PropertyChangedEventArgs("MyWidth"));                 }             }         }         private double myheight;         public double MyHeight         {             get { return myheight; }             set             {                 if (value != myheight)                 {                     myheight = value;                     PropertyChanged(this, new PropertyChangedEventArgs("MyHeight"));                 }             }         }         private double maxrowcol;         public double MaxRowCol         {             get { return maxrowcol; }             set             {                 if (value != maxrowcol)                 {                     maxrowcol = value;                     PropertyChanged(this, new PropertyChangedEventArgs("MaxRowCol"));                 }             }         }     } } 

Simply bind ItemWidth and ItemHeight to a fraction of its parent's size:

enter image description here


VERSION 2: using dynamic grid items with ItemsControl (please see Note below for UWP Apps):

XAML:

<Page     x:Class="App1.MainPage"     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:local="using:App1"     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     mc:Ignorable="d">      <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"            SizeChanged="Grid1_SizeChanged">          <local:MyItemsControl ItemsSource="{Binding Data}">             <ItemsControl.ItemsPanel>                 <ItemsPanelTemplate>                     <VariableSizedWrapGrid ItemWidth="{Binding MyWidth}"                                            ItemHeight="{Binding MyHeight}"                                            MaximumRowsOrColumns="10"/>                 </ItemsPanelTemplate>             </ItemsControl.ItemsPanel>             <ItemsControl.ItemTemplate>                 <DataTemplate>                     <Rectangle Fill="{Binding Color}"                                     VariableSizedWrapGrid.RowSpan="{Binding RowSpan}"                                    VariableSizedWrapGrid.ColumnSpan="{Binding ColSpan}"                                    Margin="5,5,5,5"/>                 </DataTemplate>             </ItemsControl.ItemTemplate>         </local:MyItemsControl>     </Grid> </Page> 

MyItemsControl:

namespace App1 {     public sealed class MyItemsControl : ItemsControl     {         public MyItemsControl()         {             this.DefaultStyleKey = typeof(MyItemsControl);         }         protected override void PrepareContainerForItemOverride(DependencyObject element, object item)         {             var viewModel = item as ISpan;              element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, viewModel.ColSpan);             element.SetValue(VariableSizedWrapGrid.RowSpanProperty, viewModel.RowSpan);              base.PrepareContainerForItemOverride(element, item);         }     } } 

Generic.xaml:

<ResourceDictionary     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:local="using:App1">      <Style x:Key="BaseItemsControl" TargetType="ItemsControl">     </Style>      <Style TargetType="local:MyItemsControl" BasedOn="{StaticResource BaseItemsControl}">     </Style>  </ResourceDictionary> 

MyViewModel:

namespace App1 {     public class MyViewModel : INotifyPropertyChanged     {         public event PropertyChangedEventHandler PropertyChanged = delegate { };          public ObservableCollection<MyDataModel> Data { get; set; }          public MyViewModel()         {             Data = new ObservableCollection<MyDataModel>             {                 new MyDataModel { Color = "LightCoral", RowSpan = 2, ColSpan = 3 },                 new MyDataModel { Color = "LightGreen", RowSpan = 3, ColSpan = 3 },                 new MyDataModel { Color = "LightCoral", RowSpan = 1, ColSpan = 3 },                 new MyDataModel { Color = "LightBlue", RowSpan = 2, ColSpan = 3 },                 new MyDataModel { Color = "LightYellow", RowSpan = 2, ColSpan = 3 },                 new MyDataModel { Color = "LightGreen", RowSpan = 3, ColSpan = 4 },                 new MyDataModel { Color = "LightYellow", RowSpan = 4, ColSpan = 4 },                 new MyDataModel { Color = "LightGreen", RowSpan = 3, ColSpan = 4 },                 new MyDataModel { Color = "LightCoral", RowSpan = 2, ColSpan = 3 },                 new MyDataModel { Color = "LightBlue", RowSpan = 3, ColSpan = 3 },                 new MyDataModel { Color = "LightGreen", RowSpan = 3, ColSpan = 3 },                 new MyDataModel { Color = "LightCoral", RowSpan = 2, ColSpan = 3 },             };         }          private double mywidth;         public double MyWidth         {             get { return mywidth; }             set             {                 if (value != mywidth)                 {                     mywidth = value;                     PropertyChanged(this, new PropertyChangedEventArgs("MyWidth"));                 }             }         }         private double myheight;         public double MyHeight         {             get { return myheight; }             set             {                 if (value != myheight)                 {                     myheight = value;                     PropertyChanged(this, new PropertyChangedEventArgs("MyHeight"));                 }             }         }     } } 

ISpan:

namespace App1 {     public interface ISpan     {         double RowSpan { get; set; }         double ColSpan { get; set; }     } } 

NOTE: It was necessary to extend ItemsControl and override PrepareContainerForItemOverride() in order to get RowSpan and ColumnSpan bindings working. Attached properties VariableSizedWrapGrid.RowSpan and VariableSizedWrapGrid.ColumnSpan are swallowed (talking UWP app) when wrapped by an item container, as discussed in this article.


VERSION 1: using static grid items:

XAML:

<Page     x:Class="App1.MainPage"     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:local="using:App1"     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     mc:Ignorable="d">      <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"            SizeChanged="Grid1_SizeChanged">         <VariableSizedWrapGrid ItemWidth="{Binding MyWidth}"                                ItemHeight="{Binding MyHeight}"                                MaximumRowsOrColumns="10">          <Rectangle Fill="LightCoral" VariableSizedWrapGrid.RowSpan="2" VariableSizedWrapGrid.ColumnSpan="3" Margin="5,5,5,5"/>         <Rectangle Fill="LightGreen" VariableSizedWrapGrid.RowSpan="3" VariableSizedWrapGrid.ColumnSpan="3" Margin="5,5,5,5"/>         <Rectangle Fill="LightCoral" VariableSizedWrapGrid.RowSpan="1" VariableSizedWrapGrid.ColumnSpan="3" Margin="5,5,5,5"/>         <Rectangle Fill="LightBlue" VariableSizedWrapGrid.RowSpan="2" VariableSizedWrapGrid.ColumnSpan="3" Margin="5,5,5,5"/>         <Rectangle Fill="LightYellow" VariableSizedWrapGrid.RowSpan="2" VariableSizedWrapGrid.ColumnSpan="3" Margin="5,5,5,5"/>          <Rectangle Fill="LightGreen" VariableSizedWrapGrid.RowSpan="3" VariableSizedWrapGrid.ColumnSpan="4" Margin="5,5,5,5"/>         <Rectangle Fill="LightYellow" VariableSizedWrapGrid.RowSpan="4" VariableSizedWrapGrid.ColumnSpan="4" Margin="5,5,5,5"/>         <Rectangle Fill="LightGreen" VariableSizedWrapGrid.RowSpan="3" VariableSizedWrapGrid.ColumnSpan="4" Margin="5,5,5,5"/>          <Rectangle Fill="LightCoral" VariableSizedWrapGrid.RowSpan="2" VariableSizedWrapGrid.ColumnSpan="3" Margin="5,5,5,5"/>         <Rectangle Fill="LightBlue" VariableSizedWrapGrid.RowSpan="3" VariableSizedWrapGrid.ColumnSpan="3" Margin="5,5,5,5"/>         <Rectangle Fill="LightGreen" VariableSizedWrapGrid.RowSpan="3" VariableSizedWrapGrid.ColumnSpan="3" Margin="5,5,5,5"/>         <Rectangle Fill="LightCoral" VariableSizedWrapGrid.RowSpan="2" VariableSizedWrapGrid.ColumnSpan="3" Margin="5,5,5,5"/>          </VariableSizedWrapGrid>     </Grid> </Page> 

MainPage:

namespace App1 {     /// <summary>     /// An empty page that can be used on its own or navigated to within a Frame.     /// </summary>     public sealed partial class MainPage : Page     {         MyViewModel vm;          public MainPage()         {             this.InitializeComponent();              vm = new MyViewModel();             DataContext = vm;         }          private void Grid1_SizeChanged(object sender, SizeChangedEventArgs e)         {             vm.MyWidth = e.NewSize.Width / 10;             vm.MyHeight = e.NewSize.Height / 10;         }     } } 

ViewModel:

namespace App1 {     public class MyViewModel : INotifyPropertyChanged     {         public event PropertyChangedEventHandler PropertyChanged = delegate { };          private double mywidth;         public double MyWidth         {             get { return mywidth; }             set             {                 if (value != mywidth)                 {                     mywidth = value;                     PropertyChanged(this, new PropertyChangedEventArgs("MyWidth"));                 }             }         }         private double myheight;         public double MyHeight         {             get { return myheight; }             set             {                 if (value != myheight)                 {                     myheight = value;                     PropertyChanged(this, new PropertyChangedEventArgs("MyHeight"));                 }             }         }     } } 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment