c# xaml gridview

c# - listview uwp



¿Cómo la aplicación W10 News amplía los elementos en la vista de cuadrícula? (4)

Estoy tratando de crear una vista de cuadrícula como en la aplicación de noticias predeterminada en Windows 10. Por lo que sé, tengo que configurar ItemHeight y ItemWidth para VariableSizedWrapGrid. Pero luego no estira los elementos para que se ajusten al ancho completo de la cuadrícula, mientras que la aplicación Noticias lo hace como se puede ver en las imágenes a continuación. ¿Cómo lo hacen? ¿Es un control personalizado especial?


UWP

Como una adición a mi respuesta anterior, donde muestro el concepto básico aquí, una solución para la plataforma UWP utilizando el VariableSizedWrapPanel como se menciona en la pregunta:

El trabajo principal es hecho por

<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>

junto con

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( ); } } }

y

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( ); } } }

Para completar aquí los otros archivos.

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; } } }

Si alguna vez piensas "Lo sé de algún lado", deberías echarle un vistazo al blog de Jerry Nixon : o)


Resulta que la mejor manera de lograr el resultado deseado es escribir un panel personalizado y usarlo como ItemPanel en GridView (o ListView ).

Así es como se ve:

Este control te permite lograr lo siguiente:

  • Todos los bloques en la fila individual tienen el mismo ancho y alto.
  • El número de columnas y el ancho de columnas se calcula automáticamente según la propiedad ItemMinWidth (el valor predeterminado es 300px)
  • No hay espacio en el lado derecho de GridView
  • Altura de la celda calculada sobre la marcha en función del contenido (por lo tanto, si el contenido cambió, por ejemplo, si el usuario cambió el tamaño de fuente en la aplicación, se recalculará automáticamente).
  • Cada fila tiene una altura diferente ( ¡sic! ) (No una para todo el panel como en VariableSizedWrapGrid )
  • También puede establecer la propiedad MaxColumnCount si desea limitar el número de columnas (por ejemplo, si se establece en 4, el número de columnas puede ser 1, 2, 3 o 4).

Mi panel personalizado (lo llamé SmartPanel , lo siento por esto):

using System; using System.Linq; using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; class SmartPanel : Panel { int _colCount; double _cellWidth; double[] _cellHeights; public static readonly DependencyProperty ItemMinWidthProperty = DependencyProperty.Register("ItemMinWidth", typeof(double), typeof(SmartPanel), new PropertyMetadata(300D)); public double ItemMinWidth { get { return (double)GetValue(ItemMinWidthProperty); } } public static readonly DependencyProperty MaxColumnCountProperty = DependencyProperty.Register("MaxColumnCount", typeof(int), typeof(SmartPanel), new PropertyMetadata(10)); public int MaxColumnCount { get { return (int)GetValue(MaxColumnCountProperty); } set { SetValue(MaxColumnCountProperty, value); } } protected override Size MeasureOverride(Size availableSize) { _colCount = (int)(availableSize.Width / ItemMinWidth); if (_colCount > MaxColumnCount) _colCount = MaxColumnCount; _cellWidth = (int)(availableSize.Width / _colCount); var rowCount = (int)Math.Ceiling((float)Children.Count / _colCount); _cellHeights = new double[rowCount]; var y = 0; var x = 0; foreach (UIElement child in Children) { child.Measure(new Size(_cellWidth, double.PositiveInfinity)); _cellHeights[y] = Math.Max(_cellHeights[y], child.DesiredSize.Height); x++; if (x >= _colCount) { x = 0; y++; } } y = 0; x = 0; foreach (UIElement child in Children) { child.Measure(new Size(_cellWidth, _cellHeights[y])); x++; if (x >= _colCount) { x = 0; y++; } } if (double.IsInfinity(availableSize.Height)) { availableSize.Height = _cellHeights.Sum(); } return availableSize; } protected override Size ArrangeOverride(Size finalSize) { double x = 0; double y = 0; int colNum = 0; int rowNum = 0; foreach (UIElement child in Children) { child.Arrange(new Rect(x, y, _cellWidth, _cellHeights[rowNum])); //child.Arrange(new Rect(new Point(x, y), child.DesiredSize)); x += _cellWidth; colNum++; if (colNum >= _colCount) { x = 0; y += _cellHeights[rowNum]; colNum = 0; rowNum++; } } return finalSize; } }

Uso:

<GridView x:Name="MainGrid" Grid.Row="1" Margin="0,20,0,20" ItemsSource="{Binding Tiles}" ItemTemplateSelector="{StaticResource TemplateSelector}" > <GridView.ItemContainerStyle> <Style TargetType="GridViewItem"> <Setter Property="Margin" Value="10" /> <Setter Property="Background" Value="White" /> <Setter Property="HorizontalAlignment" Value="Stretch" /> <Setter Property="VerticalAlignment" Value="Stretch" /> <Setter Property="VerticalContentAlignment" Value="Top" /> <Setter Property="HorizontalContentAlignment" Value="Left" /> <Setter Property="Padding" Value="20" /> </Style> </GridView.ItemContainerStyle> <GridView.ItemsPanel> <ItemsPanelTemplate> <ctl:SmartPanel Margin="10,0,10,0" MaxColumnCount="4" /> </ItemsPanelTemplate> </GridView.ItemsPanel> </GridView>


Según MSDN ItemWidth se puede establecer en Auto.

El valor predeterminado de ItemHeight y ItemWidth no es 0, es Double.NaN. ItemHeight y ItemWidth admiten la capacidad de ser un valor "Auto" no establecido. Debido a que ItemHeight y ItemWidth son valores Dobles, Double.NaN se usa como un valor especial para representar este comportamiento "Auto". El sistema de diseño interpreta el valor "Automático" para significar generalmente que el objeto debe dimensionarse al tamaño disponible en diseño, en lugar de a un valor de píxel específico.

No sé si esto resultará en el comportamiento que deseas sin embargo. Si no es así, es posible que pueda obtenerlo vinculando ItemWidth a una propiedad en la que calcule el ancho del elemento en función del ancho de la cuadrícula. Se vería algo como esto:

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; } }


Solo te daré el concepto:

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

Así que solo tenemos 3 cajas diferentes para presentación con una altura conocida. La vista en sí misma puede decidir (codebehind) qué plantilla se usa para presentar el contenido.

  • pequeño: titular con una pequeña miniatura
  • Medio: imagen pequeña con titular.
  • grande: imagen con título y texto del artículo

Todos los artículos se organizan en grupos de 5 a 9 artículos para cada grupo. Los grupos se presentan dentro de un ItemsControl y cada grupo se presenta mediante un WrapPanel (oriantación vertical).

Veamos que para algunas filas:

  • 2 columnas, 6 artículos

lllll mmmmm lllll mmmmm lllll mmmmm lllll lllll sssss lllll mmmmm lllll mmmmm lllll mmmmm lllll lllll sssss

  • 3 columnas, 6 artículos

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 columnas, 6 artículos

lllll lllll lllll sssss lllll lllll lllll lllll lllll lllll sssss lllll lllll lllll lllll lllll lllll sssss

  • 5 columnas, 6 artículos

mmmmm mmmmm mmmmm mmmmm sssss mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm sssss

Por lo tanto, necesitamos tres plantillas de datos para los elementos de contenido, algunas plantillas para WrapPanel y algo de lógica dentro de la vista para agrupar los elementos en filas y la gestión de plantillas para WrapPanel y los elementos dentro.

Aquí un simple XAML PoC solo para probar el concepto:

<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>

y el lookalike

Actualizar

Un ProofOfConcept muy simple con estiramiento y plantillas en el cambio de tamaño

<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>

y el código

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( ); } }

y finalmente el resultado