Ответ 1
Я просто дам вам концепцию:
+---------+ +---------+ +---------+ | small | | | | | +---------+ | | | | --- gap --- | medium | | | +---------+ | | | | | small | | | | big | +---------+ +---------+ | | --- gap --- --- gap --- | | +---------+ +---------+ | | | small | | small | | | +---------+ +---------+ +---------+ --- gap --- --- gap --- --- gap ---
Итак, у нас есть только 3 разных поля для презентации с известной высотой. Само представление может решить (codebehind), какой шаблон используется для представления содержимого.
- small: заголовок с маленьким эскизом
- medium: небольшое изображение с заголовком
- large: изображение с заголовком и некоторым текстом из статьи
Все элементы упорядочены группами по 5-9 элементам для каждой группы. Группы представлены внутри элемента ItemsControl, и каждая группа представлена WrapPanel (вертикальная ориентация).
Посмотрим, что для некоторых строк:
- 2 столбца, 6 элементов
lllll mmmmm lllll mmmmm lllll mmmmm lllll lllll sssss lllll mmmmm lllll mmmmm lllll mmmmm lllll lllll sssss
- 3 столбца, 6 элементов
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 столбца, 6 элементов
lllll lllll lllll sssss lllll lllll lllll lllll lllll lllll sssss lllll lllll lllll lllll lllll lllll sssss
- 5 столбцов, 6 элементов
mmmmm mmmmm mmmmm mmmmm sssss mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm mmmmm sssss
Итак, нам нужны три DataTemplates для элементов контента, некоторые шаблоны для WrapPanel и некоторая логика внутри представления для группировки элементов в строки и шаблона для WrapPanel и элементов внутри.
Здесь простой XAML PoC просто для проверки концепции:
<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>
и посмотрите
Update
Очень простой ProofOfConcept с растяжением и шаблоном при изменении размера
<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>
и 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( );
}
}
и, наконец, результат