如何在ListView和WrapPanel模板中实现均匀的水平间距?

时间:2016-09-22 13:14:30

标签: c# wpf listview

继承人的例子:

<Window x:Class="ListViewItemSpacing.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:c="clr-namespace:ListViewItemSpacing.Controls"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ListViewItemSpacing"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListView FlowDirection="LeftToRight" Background="#222">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}" />
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
            <ListView.Items>
                <Rectangle Fill="#27f" Width="100" Height="100"/>
                <Rectangle Fill="#27f" Width="100" Height="100"/>
                <Rectangle Fill="#27f" Width="100" Height="100"/>
                <Rectangle Fill="#27f" Width="100" Height="100"/>
                <Rectangle Fill="#27f" Width="100" Height="100"/>
                <Rectangle Fill="#27f" Width="100" Height="100"/>
            </ListView.Items>
        </ListView>
    </Grid>
</Window>

XAML生成6个蓝色方块,第一行为4,下一行为2。经过4个第一个蓝色方块后,右侧有一些空间。

换句话说 - 它看起来像左对齐的文字。

我希望它看起来像对齐文本,所以我希望调整方块之间的水平间距以左右对齐ListView,空间均匀分布在元素之间,而不是右侧。

尽管看起来很简单,但我不知道该怎么做。我不知道从哪里开始。例如 - 我可以看到默认添加一些间距。默认情况下很丑,因为水平间距远大于垂直间距,但它不够大,无法将物品对齐左右两侧。但间距来自哪里?如何手动更改?我不想把这些元素搞得一团糟。真实世界app中的元素来自不同的模块,这些模块应该分开。

最明显的想法是处理SizeChanged的{​​{1}}事件并手动调整水平间距,但是间距是什么?我的意思是如何在代码后面访问项目间距?

澄清:所需的渲染应该仍然包含6个相同的蓝色方块,但前4个对齐左右控制边缘,其余2个对齐左边和前面的行方块。

默认外观如下:

ListView

期望的样子是这样的:

----------------
|[] [] [] []   | 
|[] []         |
----------------

2 个答案:

答案 0 :(得分:1)

感谢RachelDTig我终于做到了。似乎没有任何面板可以对齐像.slide-transition { transition: all .4s cubic-bezier(.25,.1,.25,1); } .slide-enter { transform: translate3d(25%, 0, 0); opacity: 0; } .slide-leave { transform: translate3d(-25%, 0, 0); opacity: 0; } 这样的元素,但可以使用可自定义的WrapPanel。显然 - HorizontalContentAlignment就是我在这里所需要的。

根据Rachel建议我尝试了this,但它并不支持HorizontalContentAlignment = HorizontalAlignment.Stretch值。

所以我添加了支持,其中提供的元素宽度彼此相等:

HorizontalAlignment.Stretch

这基本上是Tig's解决方案,添加了/// <summary> /// <see cref="Panel"/> like <see cref="WrapPanel"/> which supports <see cref="HorizontalContentAlignment"/> property. /// </summary> public class AlignableWrapPanel : Panel { /// <summary> /// <see cref="HorizontalAlignment"/> property definition. /// </summary> public static readonly DependencyProperty HorizontalContentAlignmentProperty = DependencyProperty.Register( "HorizontalContentAlignment", typeof(HorizontalAlignment), typeof(AlignableWrapPanel), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.AffectsArrange) ); /// <summary> /// Gets or sets the horizontal alignment of the control's content. /// </summary> [BindableAttribute(true)] public HorizontalAlignment HorizontalContentAlignment { get { return (HorizontalAlignment)GetValue(HorizontalContentAlignmentProperty); } set { SetValue(HorizontalContentAlignmentProperty, value); } } /// <summary> /// Measures the size in layout required for child elements and determines a size for the <see cref="AlignableWrapPanel"/>. /// </summary> /// <param name="constraint">The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available.</param> /// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns> protected override Size MeasureOverride(Size constraint) { var curLineSize = new Size(); var panelSize = new Size(); var children = base.InternalChildren; for (var i = 0; i < children.Count; i++) { var child = children[i] as UIElement; // Flow passes its own constraint to children child.Measure(constraint); var sz = child.DesiredSize; if (curLineSize.Width + sz.Width > constraint.Width) { //need to switch to another line panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width); panelSize.Height += curLineSize.Height; curLineSize = sz; if (sz.Width > constraint.Width) { // if the element is wider then the constraint - give it a separate line panelSize.Width = Math.Max(sz.Width, panelSize.Width); panelSize.Height += sz.Height; curLineSize = new Size(); } } else { //continue to accumulate a line curLineSize.Width += sz.Width; curLineSize.Height = Math.Max(sz.Height, curLineSize.Height); } } // the last line size, if any need to be added panelSize.Width = Math.Max(curLineSize.Width, panelSize.Width); panelSize.Height += curLineSize.Height; return panelSize; } /// <summary> /// Positions child elements and determines a size for a <see cref="AlignableWrapPanel"/>. /// </summary> /// <param name="arrangeBounds">The final area within the parent that this element should use to arrange itself and its children.</param> /// <returns>The actual size used.</returns> protected override Size ArrangeOverride(Size arrangeBounds) { var firstInLine = 0; var curLineSize = new Size(); var accumulatedHeight = 0.0; var children = InternalChildren; for (var i = 0; i < children.Count; i++) { var sz = children[i].DesiredSize; if (curLineSize.Width + sz.Width > arrangeBounds.Width) { //need to switch to another line ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, i); accumulatedHeight += curLineSize.Height; curLineSize = sz; if (sz.Width > arrangeBounds.Width) { //the element is wider then the constraint - give it a separate line ArrangeLine(accumulatedHeight, sz, arrangeBounds.Width, i, ++i); accumulatedHeight += sz.Height; curLineSize = new Size(); } firstInLine = i; } else { //continue to accumulate a line curLineSize.Width += sz.Width; curLineSize.Height = Math.Max(sz.Height, curLineSize.Height); } } if (firstInLine < children.Count) ArrangeLine(accumulatedHeight, curLineSize, arrangeBounds.Width, firstInLine, children.Count); return arrangeBounds; } /// <summary> /// Arranges elements within a line. /// </summary> /// <param name="y">Line vertical coordinate.</param> /// <param name="lineSize">Size of the items line.</param> /// <param name="boundsWidth">Width of the panel bounds.</param> /// <param name="start">Index of the first child which belongs to the line.</param> /// <param name="end">Index of the last child which belongs to the line.</param> private void ArrangeLine(double y, Size lineSize, double boundsWidth, int start, int end) { var children = InternalChildren; var x = 0.0; var stretchOffset = 0.0; if (HorizontalContentAlignment == HorizontalAlignment.Center) x = (boundsWidth - lineSize.Width) / 2; else if (HorizontalContentAlignment == HorizontalAlignment.Right) x = (boundsWidth - lineSize.Width); else if (HorizontalAlignment == HorizontalAlignment.Stretch) { var childWidth = children[start].DesiredSize.Width; // warning, this works only when all children have equal widths int n = (int)boundsWidth / (int)childWidth; if (children.Count > n) { var takenWidth = n * childWidth; var spaceWidth = boundsWidth - takenWidth; stretchOffset = spaceWidth / (n - 1); } } for (var i = start; i < end; i++) { var child = children[i]; child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineSize.Height)); x += child.DesiredSize.Width + stretchOffset; } } } 对齐。

以下为此测试XAML:

Stretch

它并不完美,但它完美地完成了特定的工作。如果子元素的大小不同,它将无法正常工作 - 在这种情况下,<Window x:Class="ListViewItemSpacing.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:c="clr-namespace:CustomControls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:ListViewItemSpacing" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="MainWindow" Width="525" Height="250" mc:Ignorable="d"> <Grid> <ListView Background="#222"> <ListView.ItemsPanel> <ItemsPanelTemplate> <c:AlignableWrapPanel Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}" HorizontalContentAlignment="Stretch" /> </ItemsPanelTemplate> </ListView.ItemsPanel> <ListView.Items> <Rectangle Width="100" Height="100" Fill="#27f" /> <Rectangle Width="100" Height="100" Fill="#27f" /> <Rectangle Width="100" Height="100" Fill="#27f" /> <Rectangle Width="100" Height="100" Fill="#27f" /> <Rectangle Width="100" Height="100" Fill="#27f" /> <Rectangle Width="100" Height="100" Fill="#27f" /> </ListView.Items> </ListView> </Grid> </Window> 应计算为具有从takenWidthstart的索引的子项的总和。您还应该为最后一行指定不同的条件。

再次感谢,有点陌生人:)

答案 1 :(得分:0)

假设我们不知道列数。 windows xaml和代码隐藏的完整代码(修复类名以匹配您自己的类名)。试试这个:

<强>的Xaml:

<Window x:Class="SO39640127.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:so39640127="clr-namespace:SO39640127"
        mc:Ignorable="d"
        Title="MainWindow" 
        Height="{Binding LayoutHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
        Width="{Binding LayoutWidth,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
        d:DataContext="{d:DesignInstance so39640127:MainWindow}">
    <Grid>
        <ListView Background="#222" VerticalContentAlignment="Top">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid HorizontalAlignment="Center" VerticalAlignment="Top" 
                                 Columns="{Binding LayoutColumns, UpdateSourceTrigger=PropertyChanged}" 
                                 Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"></UniformGrid>
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
            <ListView.Items>
                <Rectangle Fill="#27f" Width="{Binding ItemWidth}" Height="{Binding ItemHeight}" Margin="{Binding VerticalSpacing}"/>
                <Rectangle Fill="#27f" Width="{Binding ItemWidth}" Height="{Binding ItemHeight}" Margin="{Binding VerticalSpacing}"/>
                <Rectangle Fill="#27f" Width="{Binding ItemWidth}" Height="{Binding ItemHeight}" Margin="{Binding VerticalSpacing}"/>
                <Rectangle Fill="#27f" Width="{Binding ItemWidth}" Height="{Binding ItemHeight}" Margin="{Binding VerticalSpacing}"/>
                <Rectangle Fill="#27f" Width="{Binding ItemWidth}" Height="{Binding ItemHeight}" Margin="{Binding VerticalSpacing}"/>
                <Rectangle Fill="#27f" Width="{Binding ItemWidth}" Height="{Binding ItemHeight}" Margin="{Binding VerticalSpacing}"/>
            </ListView.Items>
        </ListView>
    </Grid>
</Window>

<强>代码隐藏:

public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private double itemHeight;
        private double itemWidth;
        private int layoutColumns;
        private int layoutHeight;
        private int layoutWidth;
        private Thickness verticalSpacing;

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            InitializeDesign();
        }

        public double ItemWidth
        {
            get { return itemWidth; }
            set
            {
                if (value.Equals(itemWidth))
                    return;
                itemWidth = value;
                OnPropertyChanged();
            }
        }

        public double ItemHeight
        {
            get { return itemHeight; }
            set
            {
                if (value.Equals(itemHeight))
                    return;
                itemHeight = value;
                OnPropertyChanged();
            }
        }

        public int LayoutColumns
        {
            get { return layoutColumns; }
            set
            {
                if (value == layoutColumns)
                    return;
                layoutColumns = value;
                OnPropertyChanged();
            }
        }

        public int LayoutWidth
        {
            get { return layoutWidth; }
            set
            {
                if (value == layoutWidth)
                    return;
                layoutWidth = value;
                OnPropertyChanged();
                UpdateCalculations();
            }
        }

        public int LayoutHeight
        {
            get { return layoutHeight; }
            set
            {
                if (value == layoutHeight)
                    return;
                layoutHeight = value;
                OnPropertyChanged();
                UpdateCalculations();
            }
        }

        public Thickness VerticalSpacing
        {
            get { return verticalSpacing; }
            set
            {
                if (value.Equals(verticalSpacing))
                    return;
                verticalSpacing = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void UpdateCalculations()
        {
            //Calculate the # of elements that fit on the list view
            var totalItems = (int) (LayoutWidth/ItemWidth);
            LayoutColumns = totalItems;
        }

        private void InitializeDesign()
        {
            LayoutWidth = 525;
            LayoutHeight = 350;

            ItemWidth = 100;
            ItemHeight = 100;

            VerticalSpacing = new Thickness(3);
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

我通过物品中的边距实现垂直间距。您可以尝试调整大小,以便根据窗口的尺寸查看列的正确调整方式。另外,我正在使用一点MVVM来进行绑定以进行演示。你当然应该相应地调整它。

希望这有帮助!