如何使用网格在ItemsControl中动态布局列表项?

时间:2016-02-24 20:51:53

标签: c# wpf xaml mvvm itemscontrol

我有一个字符串列表列表,这是我需要布置的充分代表。

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

问题是ItemsControlGrid开箱即用并不好用。

幸运的是,我发现了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;
    }
}

这是我测试此控件的窗口的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>

(看起来像这样)

enter image description here

这是我用来绑定内容行和列值时失败的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 ]( );
    }
}

这就是问题所在: enter image description here enter image description here

当然,在第一个中,没有显示任何内容,因为默认情况下ListViewModel Current值为null。说得通。但是在第二步中,单击“下一步”后,所有元素都会堆叠在一起,这就是问题所在。

我对于问题是什么有一个想法:

AlternationCount="{Binding Count}"

Count不是IEnumerable的属性(属于Current)。 我也试过AlternationCount="{Binding Path=(Count( ))}",但结果相同。

我很确定问题出在我实现Viewmodel的方式和我如何绑定以获取AlternationCount之间,以便我可以在网格中布置元素......但是那个&#39 ;至于我可以采取这个 - 除了将当前改为列表(我尝试过但也没有用),我没有想法。

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

1 个答案:

答案 0 :(得分:1)

我修改了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.RowGrid.Column

enter image description here