【问题标题】:WPF Listbox layout: multiple columnsWPF列表框布局:多列
【发布时间】:2018-12-19 05:53:25
【问题描述】:

我有一个包含复选框的列表框 (WPF)。我正在使用的是在配置屏幕中。示意图如下:

现在我想添加一个“测试 5”复选框。我的垂直空间有限,所以我希望它出现在水平方向,如下图:

可以修改 ListBox 布局,使 CheckBox 像这样排列吗?

【问题讨论】:

  • 也许 Listbox 可以做到这一点,但为什么不是 WrapPanel?你真的需要 SelectedItem 吗?
  • 您可以使用网格作为列表框的 ItemsPanel:scottlogic.co.uk/blog/colin/2010/11/…
  • @Henk,将WrapPanel 设置为ListBoxItemsPanel 即可。
  • @decyc,我知道,但我仍然首先质疑对 LB 的需求/愿望。
  • @Henk:是的,我同意这一点。但是,我仍然觉得有必要使用ItemsControl 进行数据绑定,而不是直接使用面板。

标签: c# wpf xaml


【解决方案1】:
<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>

【讨论】:

    【解决方案2】:

    我遇到了类似的问题,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>
    

    【讨论】:

    • 这比使用WrapPanel 的解决方案效果更好,因为可以限制水平扩展(通过Columns 属性)。
    • @Andrzej Gis,一个绝妙的解决方案!
    【解决方案3】:

    我知道这是一篇较旧的帖子,但我在尝试解决同样的问题时偶然发现了一种相当简单的方法: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>
    

    【讨论】:

    • 我喜欢你包含一个链接,但我试图了解你的答案与@gisek 留下的答案有何不同。如果存在显着差异(我承认我可能只是错过了它),您可能需要指出那是什么。
    【解决方案4】:

    具有多列且 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>
    

    【讨论】:

      【解决方案5】:

      如果您需要在多个区域(在我的例子中是多个窗口)之间流动行,您可以使用自定义面板实现。

      示例用法:

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

      结果:

      完整的演示实现是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;
          }
      }
      

      【讨论】:

        【解决方案6】:

        我的解决方案:使用具有垂直方向的 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>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-02-13
          • 1970-01-01
          • 2011-03-15
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多