如何使用网格动态布置ItemsControl中的列表项? [英] How can I dynamically lay out list items within an ItemsControl using a Grid?

查看:36
本文介绍了如何使用网格动态布置ItemsControl中的列表项?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个字符串列表,可以满足我需要布置的所有字符串.

I have a list of a list of strings which is an adequate stand in for what I need to lay out.

我也到处都读到,为了完成我需要做的事情,我最好的选择是ItemsControl.

I've also read everywhere that in order to accomplish what I need to do, my best bet is an ItemsControl.

问题在于ItemsControl在开箱即用的情况下不能很好地发挥作用.

The problem is that ItemsControl doesn't play nice with Grid out of the box.

幸运的是,我找到了 a 文章与有助于我指出正确方向的代码.

Fortunately I've found a few articles with code that has helped get me pointed in the right direction.

不幸的是,这不是我所需要的.

Unfortunately, it's not everything I need.

这是我正在使用的DataContext(并且我可以肯定的是,我的问题在于实现它的方式):

This is the DataContext with which I am working ( and I am pretty certain that my problem lies therein with the manner in which I have implemented it ) :

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

这是我从链接文章中找到的相关代码中把Frankenstein合并在一起的GridItemsControl的代码:

This is the code for the GridItemsControl that I have, er, Frakensteined together from the relevant code that I have found within the linked articles :

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

这是我一直在其中测试此控件的窗口的XAML:

This is the XAML for the window in which I have been testing this control :

<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的代码:

This is the code for the MathConverter that I am using to fail at binding the contents row and column values :

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的其余代码:

And this is the rest of the code for the 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.说得通.但是在第二步中,单击下一步"后,所有元素都堆积在一起,这就是问题所在.

In the first, of course, nothing is displayed because the ListViewModel Current value is null by default. Makes sense. But in the second, after clicking Next, all the elements get piled on top of each other, and that's the problem.

对于什么是问题,我有个主意:

I have an inkling of an idea as to what the problem is :

AlternationCount="{Binding Count}"

Count不是IEnumerable的属性(这是Current的属性). 我也尝试过AlternationCount="{Binding Path=(Count( ))}",但是产生了相同的结果.

Count is not a property of an IEnumerable ( which is what Current is ). I've also tried AlternationCount="{Binding Path=(Count( ))}" but that yielded the same results.

我很确定问题出在实现Viewmodel的方式和如何绑定以获得AlternateCount的方式之间,以便我可以在网格中布置元素……但是就我而言可以接受这一点-除了将Current更改为List(我尝试过并且也没有用)之外,我也没有主意.

I'm pretty certain the problem lies somewhere between the way that I have implemented the Viewmodel and how I am binding to get the AlternationCount so that I can lay out the elements in the grid... but that's as far as I can take this - Besides changing Current to a List ( which I tried and that did not work either ), I'm out of ideas.

我在这里做错了什么?如何使ItemsControl智能地布置列表的内容?

What am I doing wrong here? How can I make the ItemsControl intelligently lay out the contents of the list?

推荐答案

我修改了viewModel来简化视图绑定

I modified viewModel to simplify view bindings

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

and ItemsControl markup

<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.RowGrid.Column.

it seems the problem was calculating Grid.Row and Grid.Column for each item.

这篇关于如何使用网格动态布置ItemsControl中的列表项?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆