如何使用网格在 ItemsControl 中动态布置列表项?
How can I dynamically lay out list items within an ItemsControl using a Grid?
我有一个字符串列表,足以代表我需要布置的内容。
我也到处读到,为了完成我需要做的事情,我最好的选择是 ItemsControl
。
问题是 ItemsControl
与开箱即用的 Grid
不兼容。
幸运的是,我发现 a few articles 的代码帮助我指明了正确的方向。
不幸的是,这不是我需要的一切。
这是我正在使用的 DataContext(我很确定我的问题在于我实现它的方式):
public class ListViewModel : INotifyPropertyChanged {
private IEnumerable<IEnumerable<string>> _Items;
public ReadOnlyCollection<IEnumerable<string>> Items {
get { return this._Items.ToList( ).AsReadOnly( ); }
}
private IEnumerable<string> _Current;
public IEnumerable<string> Current {
get { return this._Current; }
set {
this._Current = value;
this.OnPropertyChanged( "Current" );//.DontBlock( ).Wait( );
}
}
public ListViewModel( ) {
this._Items = new List<IEnumerable<string>>( );
List<string> stringsList;
for ( int x = 0; x < 10; x++ ) {
stringsList = new List<string>( );
for ( int y = x * 4; y < 4 + ( x * 4 ); y++ )
stringsList.Add( y.ToString( ) );
( this._Items as List<IEnumerable<string>> ).Add( stringsList );
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged( string p ) {
if ( this.PropertyChanged != null )
this.PropertyChanged( this, new PropertyChangedEventArgs( p ) );
}
public void First( ) { this.Current = this._Items.First( ); }
public void Previous( ) {
int i = this.Items.IndexOf( this.Current );
if ( i <= 0 )
this.Current = this.Items.Last( );
else
this.Current = this.Items[ i - 1 ];
}
public void Next( ) {
int i = this.Items.IndexOf( this.Current );
if ( i + 1 >= this.Items.Count || i < 0 )
this.Current = this.Items.First( );
else
this.Current = this.Items[ i + 1 ];
}
public void Last( ) {
this.Current = this.Items.Last( );
}
}
这是 GridItemsControl 的代码,我从链接文章中找到的相关代码,呃,Frakensteined 一起:
public class GridItemsControl : ItemsControl {
#region RowCount Property
/// <summary>
/// Adds the specified number of Rows to RowDefinitions.
/// Default Height is Auto
/// </summary>
public static readonly DependencyProperty RowCountProperty =
DependencyProperty.RegisterAttached(
"RowCount", typeof(int), typeof(GridItemsControl),
new PropertyMetadata(-1, RowCountChanged));
// Get
public static int GetRowCount( DependencyObject obj ) {
return ( int )obj.GetValue( RowCountProperty );
}
// Set
public static void SetRowCount( DependencyObject obj, int value ) {
obj.SetValue( RowCountProperty, value );
}
// Change Event - Adds the Rows
public static void RowCountChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e ) {
if ( !( obj is Grid ) || ( int )e.NewValue < 0 )
return;
Grid grid = (Grid)obj;
grid.RowDefinitions.Clear( );
for ( int i = 0; i < ( int )e.NewValue; i++ )
grid.RowDefinitions.Add( new RowDefinition( ) {
Height = new GridLength( 1, GridUnitType.Star )
} );
}
#endregion
#region ColumnCount Property
/// <summary>
/// Adds the specified number of Columns to ColumnDefinitions.
/// Default Width is Auto
/// </summary>
public static readonly DependencyProperty ColumnCountProperty =
DependencyProperty.RegisterAttached(
"ColumnCount", typeof(int), typeof(GridItemsControl),
new PropertyMetadata(-1, ColumnCountChanged));
// Get
public static int GetColumnCount( DependencyObject obj ) {
return ( int )obj.GetValue( ColumnCountProperty );
}
// Set
public static void SetColumnCount( DependencyObject obj, int value ) {
obj.SetValue( ColumnCountProperty, value );
}
// Change Event - Add the Columns
public static void ColumnCountChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e ) {
if ( !( obj is Grid ) || ( int )e.NewValue < 0 )
return;
Grid grid = (Grid)obj;
grid.ColumnDefinitions.Clear( );
for ( int i = 0; i < ( int )e.NewValue; i++ )
grid.ColumnDefinitions.Add( new ColumnDefinition( ) {
Width = new GridLength( 1, GridUnitType.Star )
} );
}
#endregion
protected override DependencyObject GetContainerForItemOverride( ) {
ContentPresenter container =
(ContentPresenter) base.GetContainerForItemOverride();
if ( ItemTemplate == null ) {
return container;
}
FrameworkElement
content = (FrameworkElement)ItemTemplate.LoadContent();
BindingExpression
rowBinding = content.GetBindingExpression(Grid.RowProperty),
columnBinding = content.GetBindingExpression(Grid.ColumnProperty);
if ( rowBinding != null ) {
container.SetBinding( Grid.RowProperty, rowBinding.ParentBinding );
}
if ( columnBinding != null ) {
container.SetBinding( Grid.ColumnProperty, columnBinding.ParentBinding );
}
return container;
}
}
这是 window 的 XAML,我一直在其中测试此控件:
<Window
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:QMPQuestionTester"
xmlns:Controls="clr-namespace:TriviaEngine.Controls;assembly=TriviaEngine"
xmlns:Components="clr-namespace:WPFTools.Components;assembly=WPFTools"
xmlns:Converters="clr-namespace:WPFTools.Classes.Converters;assembly=WPFTools"
x:Class="QMPQuestionTester.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ListViewModel/>
</Window.DataContext>
<Window.Resources>
<Converters:MathConverter x:Key="Math"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="5"/>
<ColumnDefinition/>
<ColumnDefinition Width="5"/>
<ColumnDefinition/>
<ColumnDefinition Width="5"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button
x:Name="btnFirst" Content="First" Grid.Row="2" Click="Nav"/>
<Button
x:Name="btnPrev" Content="Prev." Grid.Row="2" Grid.Column="2" Click="Nav"/>
<Button
x:Name="btnNext" Content="Next" Grid.Row="2" Grid.Column="4" Click="Nav"/>
<Button
x:Name="btnLast" Content="Last" Grid.Row="2" Grid.Column="6" Click="Nav"/>
<Components:GridItemsControl
DataContext="{Binding Current}"
ItemsSource="{Binding}"
AlternationCount="{Binding Count}"
Grid.ColumnSpan="7">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
Components:GridItemsControl.RowCount="{
Binding Count,
Converter={StaticResource ResourceKey=Math},
ConverterParameter=/2}"
Components:GridItemsControl.ColumnCount="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Grid.Row="{Binding
Path = (ItemsControl.AlternationIndex),
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource Math},
ConverterParameter=/2}"
Grid.Column="{Binding
Path = (ItemsControl.AlternationIndex),
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource Math},
ConverterParameter=%2}"
Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</Components:GridItemsControl>
</Grid>
</Window>
(看起来像这样)
这是我用来绑定内容行和列值失败的 MathConverter 的代码:
public class MathConverter : IValueConverter {
public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
if ( parameter == null )
return value;
double
Foo,
Bar = ( double )System.Convert.ChangeType( value, typeof( double ) );
switch ( ( ( string )( parameter ) ).ToCharArray( )[ 0 ] ) {
case '%':
Foo = Bar % double.Parse(
( ( string )( parameter ) ).TrimStart( new char[ ] { '%' } ) );
break;
case '*':
Foo = Bar * double.Parse(
( ( string )( parameter ) ).TrimStart( new char[ ] { '*' } ) );
break;
case '/':
Foo = Bar / double.Parse(
( ( string )( parameter ) ).TrimStart( new char[ ] { '/' } ) );
break;
case '+':
Foo = Bar + double.Parse(
( ( string )( parameter ) ).TrimStart( new char[ ] { '+' } ) );
break;
case '-':
if ( ( ( string )( parameter ) ).Length > 1 ) {
Foo = Bar - double.Parse(
( ( string )( parameter ) ).TrimStart( new char[ ] { '-' } ) );
} else Foo = Bar * -1.0D;
break;
default:
return DependencyProperty.UnsetValue;
}
return System.Convert.ChangeType( Foo, targetType );
}
这是 Window 的其余代码:
public partial class MainWindow : Window {
private Dictionary<Button, Action> NavCMDs;
public MainWindow( ) {
InitializeComponent( );
this.NavCMDs = new Dictionary<Button, Action>( ) {
{ this.btnFirst, ( ) => ( this.DataContext as ListViewModel ).First( ) },
{ this.btnPrev, ( ) => ( this.DataContext as ListViewModel ).Previous( ) },
{ this.btnNext, ( ) => ( this.DataContext as ListViewModel ).Next( ) },
{ this.btnLast, ( ) => ( this.DataContext as ListViewModel ).Last( ) },
};
}
private void Nav( object sender, RoutedEventArgs e ) {
this.NavCMDs[ sender as Button ]( );
}
}
这就是问题所在:
第一个当然什么也没有显示,因为ListViewModel的Current值默认为null。说得通。但是在第二个中,单击“下一步”后,所有元素都堆积在一起,这就是问题所在。
我对问题是什么有一些想法:
AlternationCount="{Binding Count}"
Count
不是 IEnumerable
的 属性(Current 是什么)。
我也试过 AlternationCount="{Binding Path=(Count( ))}"
但结果相同。
我很确定问题出在我实现 Viewmodel 的方式和我如何绑定以获得 AlternationCount 以便我可以在网格中布置元素之间的某个地方......但到目前为止因为我可以接受这个 - 除了将 Current 更改为 List(我尝试过但也没有用),我没有想法。
我在这里做错了什么?如何让 ItemsControl 智能地布置列表的内容?
我修改了 viewModel 以简化视图绑定
private IEnumerable<string> _Current;
public IEnumerable<string> Current
{
get { return this._Current; }
set
{
this._Current = value;
_currentCount = _Current.Count();
this.OnPropertyChanged("Current");
this.OnPropertyChanged("CurrentCount");
this.OnPropertyChanged("CurrentItems");
}
}
private int _currentCount;
public int CurrentCount
{
get { return _currentCount; }
}
// or create a class instead of anonymous objects
public IEnumerable<object> CurrentItems
{
get { return Current.Select((item, idx) => new { Item = item, Row = idx / 2, Column = idx % 2 }); }
}
和 ItemsControl 标记
<Components:GridItemsControl
ItemsSource="{Binding CurrentItems}"
AlternationCount="{Binding CurrentCount}"
Grid.ColumnSpan="7">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
Components:GridItemsControl.RowCount="{
Binding Path=CurrentCount,
Converter={StaticResource ResourceKey=Math},
ConverterParameter=/2}"
Components:GridItemsControl.ColumnCount="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Grid.Row="{Binding Path = Row}"
Grid.Column="{Binding Path = Column}"
Text="{Binding Item}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</Components:GridItemsControl>
看来问题是为每个项目计算 Grid.Row
和 Grid.Column
。
我有一个字符串列表,足以代表我需要布置的内容。
我也到处读到,为了完成我需要做的事情,我最好的选择是 ItemsControl
。
问题是 ItemsControl
与开箱即用的 Grid
不兼容。
幸运的是,我发现 a few articles 的代码帮助我指明了正确的方向。
不幸的是,这不是我需要的一切。
这是我正在使用的 DataContext(我很确定我的问题在于我实现它的方式):
public class ListViewModel : INotifyPropertyChanged {
private IEnumerable<IEnumerable<string>> _Items;
public ReadOnlyCollection<IEnumerable<string>> Items {
get { return this._Items.ToList( ).AsReadOnly( ); }
}
private IEnumerable<string> _Current;
public IEnumerable<string> Current {
get { return this._Current; }
set {
this._Current = value;
this.OnPropertyChanged( "Current" );//.DontBlock( ).Wait( );
}
}
public ListViewModel( ) {
this._Items = new List<IEnumerable<string>>( );
List<string> stringsList;
for ( int x = 0; x < 10; x++ ) {
stringsList = new List<string>( );
for ( int y = x * 4; y < 4 + ( x * 4 ); y++ )
stringsList.Add( y.ToString( ) );
( this._Items as List<IEnumerable<string>> ).Add( stringsList );
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged( string p ) {
if ( this.PropertyChanged != null )
this.PropertyChanged( this, new PropertyChangedEventArgs( p ) );
}
public void First( ) { this.Current = this._Items.First( ); }
public void Previous( ) {
int i = this.Items.IndexOf( this.Current );
if ( i <= 0 )
this.Current = this.Items.Last( );
else
this.Current = this.Items[ i - 1 ];
}
public void Next( ) {
int i = this.Items.IndexOf( this.Current );
if ( i + 1 >= this.Items.Count || i < 0 )
this.Current = this.Items.First( );
else
this.Current = this.Items[ i + 1 ];
}
public void Last( ) {
this.Current = this.Items.Last( );
}
}
这是 GridItemsControl 的代码,我从链接文章中找到的相关代码,呃,Frakensteined 一起:
public class GridItemsControl : ItemsControl {
#region RowCount Property
/// <summary>
/// Adds the specified number of Rows to RowDefinitions.
/// Default Height is Auto
/// </summary>
public static readonly DependencyProperty RowCountProperty =
DependencyProperty.RegisterAttached(
"RowCount", typeof(int), typeof(GridItemsControl),
new PropertyMetadata(-1, RowCountChanged));
// Get
public static int GetRowCount( DependencyObject obj ) {
return ( int )obj.GetValue( RowCountProperty );
}
// Set
public static void SetRowCount( DependencyObject obj, int value ) {
obj.SetValue( RowCountProperty, value );
}
// Change Event - Adds the Rows
public static void RowCountChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e ) {
if ( !( obj is Grid ) || ( int )e.NewValue < 0 )
return;
Grid grid = (Grid)obj;
grid.RowDefinitions.Clear( );
for ( int i = 0; i < ( int )e.NewValue; i++ )
grid.RowDefinitions.Add( new RowDefinition( ) {
Height = new GridLength( 1, GridUnitType.Star )
} );
}
#endregion
#region ColumnCount Property
/// <summary>
/// Adds the specified number of Columns to ColumnDefinitions.
/// Default Width is Auto
/// </summary>
public static readonly DependencyProperty ColumnCountProperty =
DependencyProperty.RegisterAttached(
"ColumnCount", typeof(int), typeof(GridItemsControl),
new PropertyMetadata(-1, ColumnCountChanged));
// Get
public static int GetColumnCount( DependencyObject obj ) {
return ( int )obj.GetValue( ColumnCountProperty );
}
// Set
public static void SetColumnCount( DependencyObject obj, int value ) {
obj.SetValue( ColumnCountProperty, value );
}
// Change Event - Add the Columns
public static void ColumnCountChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e ) {
if ( !( obj is Grid ) || ( int )e.NewValue < 0 )
return;
Grid grid = (Grid)obj;
grid.ColumnDefinitions.Clear( );
for ( int i = 0; i < ( int )e.NewValue; i++ )
grid.ColumnDefinitions.Add( new ColumnDefinition( ) {
Width = new GridLength( 1, GridUnitType.Star )
} );
}
#endregion
protected override DependencyObject GetContainerForItemOverride( ) {
ContentPresenter container =
(ContentPresenter) base.GetContainerForItemOverride();
if ( ItemTemplate == null ) {
return container;
}
FrameworkElement
content = (FrameworkElement)ItemTemplate.LoadContent();
BindingExpression
rowBinding = content.GetBindingExpression(Grid.RowProperty),
columnBinding = content.GetBindingExpression(Grid.ColumnProperty);
if ( rowBinding != null ) {
container.SetBinding( Grid.RowProperty, rowBinding.ParentBinding );
}
if ( columnBinding != null ) {
container.SetBinding( Grid.ColumnProperty, columnBinding.ParentBinding );
}
return container;
}
}
这是 window 的 XAML,我一直在其中测试此控件:
<Window
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:QMPQuestionTester"
xmlns:Controls="clr-namespace:TriviaEngine.Controls;assembly=TriviaEngine"
xmlns:Components="clr-namespace:WPFTools.Components;assembly=WPFTools"
xmlns:Converters="clr-namespace:WPFTools.Classes.Converters;assembly=WPFTools"
x:Class="QMPQuestionTester.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ListViewModel/>
</Window.DataContext>
<Window.Resources>
<Converters:MathConverter x:Key="Math"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="5"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="5"/>
<ColumnDefinition/>
<ColumnDefinition Width="5"/>
<ColumnDefinition/>
<ColumnDefinition Width="5"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button
x:Name="btnFirst" Content="First" Grid.Row="2" Click="Nav"/>
<Button
x:Name="btnPrev" Content="Prev." Grid.Row="2" Grid.Column="2" Click="Nav"/>
<Button
x:Name="btnNext" Content="Next" Grid.Row="2" Grid.Column="4" Click="Nav"/>
<Button
x:Name="btnLast" Content="Last" Grid.Row="2" Grid.Column="6" Click="Nav"/>
<Components:GridItemsControl
DataContext="{Binding Current}"
ItemsSource="{Binding}"
AlternationCount="{Binding Count}"
Grid.ColumnSpan="7">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
Components:GridItemsControl.RowCount="{
Binding Count,
Converter={StaticResource ResourceKey=Math},
ConverterParameter=/2}"
Components:GridItemsControl.ColumnCount="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Grid.Row="{Binding
Path = (ItemsControl.AlternationIndex),
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource Math},
ConverterParameter=/2}"
Grid.Column="{Binding
Path = (ItemsControl.AlternationIndex),
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource Math},
ConverterParameter=%2}"
Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</Components:GridItemsControl>
</Grid>
</Window>
(看起来像这样)
这是我用来绑定内容行和列值失败的 MathConverter 的代码:
public class MathConverter : IValueConverter {
public object Convert( object value, Type targetType, object parameter, CultureInfo culture ) {
if ( parameter == null )
return value;
double
Foo,
Bar = ( double )System.Convert.ChangeType( value, typeof( double ) );
switch ( ( ( string )( parameter ) ).ToCharArray( )[ 0 ] ) {
case '%':
Foo = Bar % double.Parse(
( ( string )( parameter ) ).TrimStart( new char[ ] { '%' } ) );
break;
case '*':
Foo = Bar * double.Parse(
( ( string )( parameter ) ).TrimStart( new char[ ] { '*' } ) );
break;
case '/':
Foo = Bar / double.Parse(
( ( string )( parameter ) ).TrimStart( new char[ ] { '/' } ) );
break;
case '+':
Foo = Bar + double.Parse(
( ( string )( parameter ) ).TrimStart( new char[ ] { '+' } ) );
break;
case '-':
if ( ( ( string )( parameter ) ).Length > 1 ) {
Foo = Bar - double.Parse(
( ( string )( parameter ) ).TrimStart( new char[ ] { '-' } ) );
} else Foo = Bar * -1.0D;
break;
default:
return DependencyProperty.UnsetValue;
}
return System.Convert.ChangeType( Foo, targetType );
}
这是 Window 的其余代码:
public partial class MainWindow : Window {
private Dictionary<Button, Action> NavCMDs;
public MainWindow( ) {
InitializeComponent( );
this.NavCMDs = new Dictionary<Button, Action>( ) {
{ this.btnFirst, ( ) => ( this.DataContext as ListViewModel ).First( ) },
{ this.btnPrev, ( ) => ( this.DataContext as ListViewModel ).Previous( ) },
{ this.btnNext, ( ) => ( this.DataContext as ListViewModel ).Next( ) },
{ this.btnLast, ( ) => ( this.DataContext as ListViewModel ).Last( ) },
};
}
private void Nav( object sender, RoutedEventArgs e ) {
this.NavCMDs[ sender as Button ]( );
}
}
这就是问题所在:
第一个当然什么也没有显示,因为ListViewModel的Current值默认为null。说得通。但是在第二个中,单击“下一步”后,所有元素都堆积在一起,这就是问题所在。
我对问题是什么有一些想法:
AlternationCount="{Binding Count}"
Count
不是 IEnumerable
的 属性(Current 是什么)。
我也试过 AlternationCount="{Binding Path=(Count( ))}"
但结果相同。
我很确定问题出在我实现 Viewmodel 的方式和我如何绑定以获得 AlternationCount 以便我可以在网格中布置元素之间的某个地方......但到目前为止因为我可以接受这个 - 除了将 Current 更改为 List(我尝试过但也没有用),我没有想法。
我在这里做错了什么?如何让 ItemsControl 智能地布置列表的内容?
我修改了 viewModel 以简化视图绑定
private IEnumerable<string> _Current;
public IEnumerable<string> Current
{
get { return this._Current; }
set
{
this._Current = value;
_currentCount = _Current.Count();
this.OnPropertyChanged("Current");
this.OnPropertyChanged("CurrentCount");
this.OnPropertyChanged("CurrentItems");
}
}
private int _currentCount;
public int CurrentCount
{
get { return _currentCount; }
}
// or create a class instead of anonymous objects
public IEnumerable<object> CurrentItems
{
get { return Current.Select((item, idx) => new { Item = item, Row = idx / 2, Column = idx % 2 }); }
}
和 ItemsControl 标记
<Components:GridItemsControl
ItemsSource="{Binding CurrentItems}"
AlternationCount="{Binding CurrentCount}"
Grid.ColumnSpan="7">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
Components:GridItemsControl.RowCount="{
Binding Path=CurrentCount,
Converter={StaticResource ResourceKey=Math},
ConverterParameter=/2}"
Components:GridItemsControl.ColumnCount="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Grid.Row="{Binding Path = Row}"
Grid.Column="{Binding Path = Column}"
Text="{Binding Item}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</Components:GridItemsControl>
看来问题是为每个项目计算 Grid.Row
和 Grid.Column
。