WPF列表框布局:多列

时间:2011-01-18 08:53:07

标签: c# wpf xaml

我有一个包含CheckBoxes的ListBox(WPF)。我正在使用配置屏幕。下面的示意图:

alt text

现在我要添加一个“Test 5”CheckBox。我的垂直空间有限,所以我希望它在水平方向上显示,如下所示:

alt text

可以修改ListBox布局,以便CheckBoxes像这样排列吗?

6 个答案:

答案 0 :(得分:18)

<ListBox Name="CategoryListBox"
         ScrollViewer.HorizontalScrollBarVisibility="Disabled"
         ItemsSource="{Binding Path=RefValues,
                UpdateSourceTrigger=PropertyChanged}"
                SelectionMode="Multiple">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
        <DataTemplate >
            <StackPanel Orientation="Horizontal"
                        MinWidth="150" MaxWidth="150"
                        Margin="0,5, 0, 5" >
                <CheckBox
                    Name="checkedListBoxItem"
                    IsChecked="{Binding
                            RelativeSource={RelativeSource FindAncestor,
                            AncestorType={x:Type ListBoxItem} },
                            Path=IsSelected, Mode=TwoWay}" />
                <ContentPresenter
                    Content="{Binding
                            RelativeSource={RelativeSource TemplatedParent},
                            Path=Content}"
                    Margin="5,0, 0, 0" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

或简单如下:

<Grid>
    <ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel IsItemsHost="True" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBoxItem>listbox item 1</ListBoxItem>
        <ListBoxItem>listbox item 2</ListBoxItem>
        <ListBoxItem>listbox item 3</ListBoxItem>
        <ListBoxItem>listbox item 4</ListBoxItem>
        <ListBoxItem>listbox item 5</ListBoxItem>
    </ListBox>
</Grid>

答案 1 :(得分:12)

我遇到了类似的问题,eibhrum的回答给了我一些想法。我使用了以下代码,我认为这也是你需要的。我使用 UniformGrid 而不是WrapPanel。

<ListBox HorizontalAlignment="Stretch" 
      ItemsSource="{Binding Timers}" 
      >
   <ListBox.ItemContainerStyle>
      <Style TargetType="ListBoxItem">
         <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
      </Style>
   </ListBox.ItemContainerStyle>

      <ListBox.ItemsPanel>
         <ItemsPanelTemplate>
            <!-- UNIFORM GRID HERE -->
            <UniformGrid Columns="3" IsItemsHost="True" 
               HorizontalAlignment="Stretch"/>
         </ItemsPanelTemplate>
      </ListBox.ItemsPanel>

      <ListBox.ItemTemplate>
         <DataTemplate>
            <Border>
               <StackPanel Orientation="Vertical" >
                  <TextBlock Text="{Binding Label}" TextWrapping="Wrap"/>
                  <Separator Margin="5,0,10,0"/>
               </StackPanel>
            </Border>
         </DataTemplate>
      </ListBox.ItemTemplate>

   </ListBox>

答案 2 :(得分:4)

我知道这是一篇较老的帖子,但在我试图解决同样的问题时,我偶然发现了一个相当简单的方法:http://social.technet.microsoft.com/wiki/contents/articles/19395.multiple-columns-in-wpf-listbox.aspx

只需添加绑定数据源(或根据需要添加项目)。

<ListBox Name="myLB" ScrollViewer.HorizontalScrollBarVisiblity="Disabled">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Columns="2" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

答案 3 :(得分:0)

具有多列和ListBoxItem的ListBox方向是垂直的。 ListBox已修复高度和自动宽度。添加ListBoxItem时,ListBox将自动增加宽度。

<ListBox Height="81" VerticalAlignment="Top" HorizontalAlignment="Left" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Orientation="Vertical"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBoxItem Content="1"/>
    <ListBoxItem Content="2"/>
    <ListBoxItem Content="3"/>
    <ListBoxItem Content="4"/>
    <ListBoxItem Content="5"/>
    <ListBoxItem Content="6"/>
    <ListBoxItem Content="7"/>
    <ListBoxItem Content="8"/>
    <ListBoxItem Content="9"/>
    <ListBoxItem Content="10"/>
</ListBox>

具有多列和ListBoxItem的ListBox方向是水平的。 ListBox已修复宽度和自动高度。添加ListBoxItem时,ListBox将自动增加高度。

<ListBox Width="81" VerticalAlignment="Top" HorizontalAlignment="Left" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBoxItem Content="1"/>
    <ListBoxItem Content="2"/>
    <ListBoxItem Content="3"/>
    <ListBoxItem Content="4"/>
    <ListBoxItem Content="5"/>
    <ListBoxItem Content="6"/>
    <ListBoxItem Content="7"/>
    <ListBoxItem Content="8"/>
    <ListBoxItem Content="9"/>
    <ListBoxItem Content="10"/>
</ListBox>

答案 4 :(得分:0)

如果需要在多个区域(在我的情况下为多个窗口)之间流动行,则可以使用自定义面板实现。

用法示例:

<Grid>
    <Grid.Resources>
        <ItemsPanelTemplate x:Key="ItemsPanelTemplate">
            <local:SharedLayoutStackPanel IsItemsHost="True"/>
        </ItemsPanelTemplate>

        <local:SharedLayoutCoordinator x:Key="slc" ItemsSource="{Binding Path=MyItems}" />
    </Grid.Resources>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>
    <Border Grid.Column="0" Margin="10,10,5,10" BorderBrush="Black" BorderThickness="1">
        <ListBox 
            local:SharedLayoutCoordinator.Region="{Binding Source={StaticResource slc}, Path=[0]}" 
            ItemsPanel="{StaticResource ResourceKey=ItemsPanelTemplate}" />
    </Border>
    <Border Grid.Column="1" Margin="5,10" BorderBrush="Black" BorderThickness="1">
        <ListBox 
            local:SharedLayoutCoordinator.Region="{Binding Source={StaticResource slc}, Path=[1]}" 
            ItemsPanel="{StaticResource ResourceKey=ItemsPanelTemplate}" />
    </Border>
    <Border Grid.Column="2" Margin="5,10,10,10" BorderBrush="Black" BorderThickness="1">
        <ListBox 
            local:SharedLayoutCoordinator.Region="{Binding Source={StaticResource slc}, Path=[2]}" 
            ItemsPanel="{StaticResource ResourceKey=ItemsPanelTemplate}" />
    </Border>
</Grid>

结果:

Window showing three listboxes displaying portions of the same list in columns with scrolling only on the last one

完整的演示实现为available on Github,但是关键部分在下面。

SharedLayoutCoordinator.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace MultiRegionListBox
{
    internal class SharedLayoutCoordinator : DependencyObject
    {
        private List<SharedLayoutRegion> Regions = new List<SharedLayoutRegion>();
        public SharedLayoutRegion this[int index]
        {
            get
            {
                var slr = new SharedLayoutRegion(this, index);
                for (int i = 0; i < Regions.Count; i++)
                {
                    if (Regions[i].Index > index)
                    {
                        Regions.Insert(i, slr);
                        return slr;
                    }
                }
                Regions.Add(slr);
                return slr;
            }
        }

        public object ItemsSource
        {
            get { return (object)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(object), typeof(SharedLayoutCoordinator), new PropertyMetadata(null));

        public static SharedLayoutRegion GetRegion(DependencyObject obj)
        {
            return (SharedLayoutRegion)obj.GetValue(RegionProperty);
        }

        public static void SetRegion(DependencyObject obj, SharedLayoutRegion value)
        {
            obj.SetValue(RegionProperty, value);
        }

        public static readonly DependencyProperty RegionProperty =
            DependencyProperty.RegisterAttached("Region", typeof(SharedLayoutRegion),
                typeof(SharedLayoutCoordinator), new PropertyMetadata(null, Region_Changed));

        private static void Region_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var itemsControl = (ItemsControl)d;
            var newController = (SharedLayoutRegion)e.NewValue;

            if (newController == null)
            {
                return;
            }

            itemsControl.SetBinding(ItemsControl.ItemsSourceProperty, new Binding(nameof(ItemsSource)) { Source = newController.Coordinator });
        }

        public static SharedLayoutRegion GetParentSharedLayoutController(DependencyObject obj)
        {
            while (obj != null)
            {
                if (obj is ItemsControl ic)
                {
                    var slc = GetRegion(ic);
                    if (slc != null)
                    {
                        return slc;
                    }
                }
                obj = VisualTreeHelper.GetParent(obj);
            }

            return null;
        }

        public IEnumerable<SharedLayoutRegion> GetPreceedingRegions(SharedLayoutRegion region)
        {
            return Regions.Where(r => r.Index < region.Index);
        }

        internal SharedLayoutRegion GetNextRegion(SharedLayoutRegion region)
        {
            var idx = Regions.IndexOf(region);
            if (idx + 1 < Regions.Count)
            {
                return Regions[idx + 1];
            }
            return null;
        }

        internal SharedLayoutRegion GetPreviousRegion(SharedLayoutRegion region)
        {
            var idx = Regions.IndexOf(region);
            if (idx > 0)
            {
                return Regions[idx - 1];
            }
            return null;
        }
    }

    internal class SharedLayoutRegion
    {
        private Action InvalidateMeasureCallback;

        public SharedLayoutRegion(SharedLayoutCoordinator coord, int index)
        {
            this.Coordinator = coord;
            this.Index = index;
        }

        public SharedLayoutCoordinator Coordinator { get; }
        public int Index { get; }

        public SharedLayoutStackPanel Panel { get; set; }
        public bool IsMeasureValid
            => !(Panel == null || !Panel.IsMeasureValid || Panel.IsMeasureMeaningless);

        internal bool CanMeasure(Action invalidateMeasure)
        {
            if (Coordinator.GetPreceedingRegions(this).All(pr => pr.IsMeasureValid))
            {
                return true;
            }

            this.InvalidateMeasureCallback = invalidateMeasure;
            return false;
        }

        public int StartOfRegion => Coordinator.GetPreviousRegion(this)?.EndOfRegion ?? 0;
        public int CountInRegion { get; set; }
        public int EndOfRegion => CountInRegion + StartOfRegion;

        public bool HasNextRegion => Coordinator.GetNextRegion(this) != null;

        internal void OnMeasure()
        {
            var nextRegion = Coordinator.GetNextRegion(this);
            if (nextRegion != null && nextRegion.InvalidateMeasureCallback != null)
            {
                nextRegion.InvalidateMeasureCallback();
                nextRegion.InvalidateMeasureCallback = null;
            }
        }
    }
}

SharedLayoutStackPanel.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace MultiRegionListBox
{
    class SharedLayoutStackPanel : Panel, IScrollInfo
    {
        internal const double _scrollLineDelta = 16.0;

        public void LineUp() => SetVerticalOffset(VerticalOffset - _scrollLineDelta);
        public void LineDown() => SetVerticalOffset(VerticalOffset + _scrollLineDelta);
        public void LineLeft() => SetHorizontalOffset(HorizontalOffset - 1.0);
        public void LineRight() => SetHorizontalOffset(HorizontalOffset + 1.0);
        public void PageUp() => SetVerticalOffset(VerticalOffset - ViewportHeight);
        public void PageDown() => SetVerticalOffset(VerticalOffset + ViewportHeight);
        public void PageLeft() => SetHorizontalOffset(HorizontalOffset - ViewportWidth);
        public void PageRight() => SetHorizontalOffset(HorizontalOffset + ViewportWidth);
        public void MouseWheelUp() => SetVerticalOffset(VerticalOffset - SystemParameters.WheelScrollLines * _scrollLineDelta);
        public void MouseWheelDown() => SetVerticalOffset(VerticalOffset + SystemParameters.WheelScrollLines * _scrollLineDelta);
        public void MouseWheelLeft() => SetHorizontalOffset(HorizontalOffset - 3.0 * _scrollLineDelta);
        public void MouseWheelRight() => SetHorizontalOffset(HorizontalOffset + 3.0 * _scrollLineDelta);

        public double ExtentWidth => Extent.Width;
        public double ExtentHeight => Extent.Height;
        public double ViewportWidth => Viewport.Width;
        public double ViewportHeight => Viewport.Height;
        public double HorizontalOffset => ComputedOffset.X;
        public double VerticalOffset => ComputedOffset.Y;

        public void SetHorizontalOffset(double offset)
        {
            if (double.IsNaN(offset))
            {
                throw new ArgumentOutOfRangeException();
            }
            if (offset < 0d)
            {
                offset = 0d;
            }
            if (offset != Offset.X)
            {
                Offset.X = offset;
                InvalidateMeasure();
            }
        }

        /// <summary>
        /// Set the VerticalOffset to the passed value.
        /// </summary>
        public void SetVerticalOffset(double offset)
        {
            if (double.IsNaN(offset))
            {
                throw new ArgumentOutOfRangeException();
            }
            if (offset < 0d)
            {
                offset = 0d;
            }
            if (offset != Offset.Y)
            {
                Offset.Y = offset;
                InvalidateMeasure();
            }
        }

        public ScrollViewer ScrollOwner
        {
            get { return _scrollOwner; }
            set
            {
                if (value == _scrollOwner)
                {
                    return;
                }

                InvalidateMeasure();

                Offset = new Vector();
                Viewport = Extent = new Size();

                _scrollOwner = value;
            }
        }

        public bool CanVerticallyScroll
        {
            get { return true; }
            set { /* noop */ }
        }

        public bool CanHorizontallyScroll
        {
            get { return false; }
            set { /* noop */ }
        }

        internal bool IsMeasureMeaningless { get; private set; }
        protected override void OnVisualParentChanged(DependencyObject oldParent)
        {
            base.OnVisualParentChanged(oldParent);

            this.SLC = SharedLayoutCoordinator.GetParentSharedLayoutController(this);
            if (SLC != null)
            {
                this.SLC.Panel = this;
            }
            InvalidateMeasure();
        }

        protected override Size MeasureOverride(Size viewportSize)
        {
            if (SLC == null || !SLC.CanMeasure(InvalidateMeasure))
            {
                IsMeasureMeaningless = true;
                return viewportSize;
            }
            IsMeasureMeaningless = false;

            var extent = new Size();
            var countInRegion = 0; var hasNextRegion = SLC.HasNextRegion;
            foreach (var child in InternalChildren.Cast<UIElement>().Skip(SLC.StartOfRegion))
            {
                child.Measure(new Size(viewportSize.Width, double.PositiveInfinity));
                var childDesiredSize = child.DesiredSize;

                if (hasNextRegion && extent.Height + childDesiredSize.Height > viewportSize.Height)
                {
                    break;
                }

                extent.Width = Math.Max(extent.Width, childDesiredSize.Width);
                extent.Height += childDesiredSize.Height;
                SLC.CountInRegion = countInRegion += 1;
            }

            // Update ISI
            this.Extent = extent;
            this.Viewport = viewportSize;
            this.ComputedOffset.Y = Bound(Offset.Y, 0, extent.Height - viewportSize.Height);
            this.OnScrollChange();

            SLC.OnMeasure();

            return new Size(
                Math.Min(extent.Width, viewportSize.Width),
                Math.Min(extent.Height, viewportSize.Height));
        }

        private static double Bound(double c, double min, double max)
            => Math.Min(Math.Max(c, Math.Min(min, max)), Math.Max(min, max));

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            if (IsMeasureMeaningless)
            {
                return arrangeSize;
            }

            double cy = -ComputedOffset.Y;
            int i = 0, i_start = SLC.StartOfRegion, i_end = SLC.EndOfRegion;
            foreach (UIElement child in InternalChildren)
            {
                if (i >= i_start && i < i_end)
                {
                    child.Arrange(new Rect(0, cy, Math.Max(child.DesiredSize.Width, arrangeSize.Width), child.DesiredSize.Height));
                    cy += child.DesiredSize.Height;
                }
                else if (child.RenderSize != new Size())
                {
                    child.Arrange(new Rect());
                }

                i += 1;
            }

            return arrangeSize;
        }

        private void OnScrollChange() => ScrollOwner?.InvalidateScrollInfo();

        public Rect MakeVisible(Visual visual, Rect rectangle)
        {
            // no-op
            return rectangle;
        }

        internal ScrollViewer _scrollOwner;

        internal Vector Offset;

        private Size Viewport;

        private Size Extent;

        private Vector ComputedOffset;
        private SharedLayoutRegion SLC;
    }
}

答案 5 :(得分:0)

我的解决方案:使用具有垂直方向的WrapPanel。如果将WrapPanel的高度调整为ListBox的高度,则可以正常工作。

<ListBox ItemsSource="{Binding mySource}">
      <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
          <WrapPanel Orientation="Vertical" Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ActualHeight}" />
        </ItemsPanelTemplate>
      </ListBox.ItemsPanel>
      <ListBox.ItemTemplate>
        <DataTemplate>
          <ListBoxItem IsChecked="{Binding checked}">
            <CheckBox IsChecked="{Binding checked}" Content="{Binding Name}" />
          </ListBoxItem>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>