WPF ListBox的WrapPanel会裁剪长分组

3

我创建了一个ListBox来按组显示项目,当它们不能适应ListBox面板的高度时,这些组会从右到左换行。因此,在listbox中,每个组的高度是随意的(例如,group 1的高度是group 2的两倍),组将类似于此:

[ 1 ][ 3 ][ 5 ]
[   ][ 4 ][ 6 ]
[ 2 ][   ]

以下 XAML 代码能够正确实现自动换行,并当 ListBox 中的项超出右侧边缘时,允许水平滚动条出现。
<ListBox> 
  <ListBox.ItemsPanel> 
    <ItemsPanelTemplate> 
      <StackPanel Orientation="Vertical"/> 
    </ItemsPanelTemplate> 
  </ListBox.ItemsPanel> 

  <ListBox.GroupStyle> 
    <ItemsPanelTemplate> 
      <WrapPanel Orientation="Vertical" 
                 Height="{Binding Path=ActualHeight, 
                          RelativeSource={RelativeSource 
                            FindAncestor, 
                            AncestorLevel=1, 
                            AncestorType={x:Type ScrollContentPresenter}}}"/> 
    </ItemsPanelTemplate> 
  </ListBox.GroupStyle> 
</ListBox>

问题出现在一组项目的长度超过了WrapPanel的高度。在这种情况下,垂直滚动条没有出现以查看被截断的项目组,而是直接将该组中的项目裁剪。我猜测这是WrapPanel中Height绑定的副作用 - 滚动条认为没有必要启用。
是否有任何方法可以启用滚动条或解决此问题的其他方式?
4个回答

2
通过将WrapPanel的Height属性设置为ScrollContentPresenter的高度,它将不会垂直滚动。但是,如果您删除该绑定,它将永远不会换行,因为在布局过程中,它有无限的高度可以布局。
我建议创建自己的面板类以获得所需的行为。具有单独的依赖属性,可以将所需的高度绑定到该属性,以便在测量和排列步骤中使用该属性来计算目标高度。如果任何一个子元素高于所需高度,请使用该子元素的高度作为目标高度来计算换行。
以下是一个可实现此功能的示例面板:
public class SmartWrapPanel : WrapPanel
{
    /// <summary>
    /// Identifies the DesiredHeight dependency property
    /// </summary>
    public static readonly DependencyProperty DesiredHeightProperty = DependencyProperty.Register(
        "DesiredHeight",
        typeof(double),
        typeof(SmartWrapPanel),
        new FrameworkPropertyMetadata(Double.NaN, 
            FrameworkPropertyMetadataOptions.AffectsArrange |
            FrameworkPropertyMetadataOptions.AffectsMeasure));

    /// <summary>
    /// Gets or sets the height to attempt to be.  If any child is taller than this, will use the child's height.
    /// </summary>
    public double DesiredHeight
    {
        get { return (double)GetValue(DesiredHeightProperty); }
        set { SetValue(DesiredHeightProperty, value); }
    }

    protected override Size MeasureOverride(Size constraint)
    {
        Size ret = base.MeasureOverride(constraint);
        double h = ret.Height;

        if (!Double.IsNaN(DesiredHeight))
        {
            h = DesiredHeight;
            foreach (UIElement child in Children)
            {
                if (child.DesiredSize.Height > h)
                    h = child.DesiredSize.Height;
            }
        }

        return new Size(ret.Width, h);
    }

    protected override System.Windows.Size ArrangeOverride(Size finalSize)
    {
        double h = finalSize.Height;

        if (!Double.IsNaN(DesiredHeight))
        {
            h = DesiredHeight;
            foreach (UIElement child in Children)
            {
                if (child.DesiredSize.Height > h)
                    h = child.DesiredSize.Height;
            }
        }

        return base.ArrangeOverride(new Size(finalSize.Width, h));
    }
}

这几乎是我需要的完美解决方案。它允许垂直滚动发生,但停止水平滚动。我稍微修改了一下(修改后的代码在下一个答案中显示),现在它完美地工作了。谢谢,Abe。 - mike

2

这是稍作修改的代码 - 所有功劳归于之前发布它的Abe Heidebrecht - 它允许水平和垂直滚动。唯一的变化是MeasureOverride的返回值需要是base.MeasureOverride(new Size(ret.width, h))。

// Original code : Abe Heidebrecht
public class SmartWrapPanel : WrapPanel
{
  /// <summary>
  /// Identifies the DesiredHeight dependency property
  /// </summary>
  public static readonly DependencyProperty DesiredHeightProperty = DependencyProperty.Register(
    "DesiredHeight",
    typeof(double),
    typeof(SmartWrapPanel),
    new FrameworkPropertyMetadata(Double.NaN, 
            FrameworkPropertyMetadataOptions.AffectsArrange |
            FrameworkPropertyMetadataOptions.AffectsMeasure));

  /// <summary>
  /// Gets or sets the height to attempt to be.  If any child is taller than this, will use the child's height.
  /// </summary>
  public double DesiredHeight
  {
    get { return (double)GetValue(DesiredHeightProperty); }
    set { SetValue(DesiredHeightProperty, value); }
  }

  protected override Size MeasureOverride(Size constraint)
  {
    Size ret = base.MeasureOverride(constraint);
    double h = ret.Height;

    if (!Double.IsNaN(DesiredHeight))
    {
      h = DesiredHeight;
      foreach (UIElement child in Children)
      {
        if (child.DesiredSize.Height > h)
          h = child.DesiredSize.Height;
      }
    }

    return base.MeasureOverride(new Size(ret.Width, h));
  }

  protected override System.Windows.Size ArrangeOverride(Size finalSize)
  {
    double h = finalSize.Height;

    if (!Double.IsNaN(DesiredHeight))
    {
      h = DesiredHeight;
      foreach (UIElement child in Children)
      {
        if (child.DesiredSize.Height > h)
          h = child.DesiredSize.Height;
      }
    }

    return base.ArrangeOverride(new Size(finalSize.Width, h));
  }
}

0
我认为你说得对,这可能与绑定有关。如果移除绑定会发生什么?如果你想填满整个列表框的高度,那么考虑绑定到MinHeight,或尝试使用VerticalAlignment属性。

0

感谢您的回答,David。

当绑定被移除时,不会发生任何包装。 WrapPanel将每个组放入单个垂直列中。

绑定旨在强制WrapPanel实际包装。 如果未设置绑定,则WrapPanel假定高度为无限,并且永远不会换行。

绑定到MinHeight会导致空列表框。 我可以看到VerticalAlignment属性可能似乎是解决方案,但对齐本身会防止发生任何包装。 当绑定和对齐一起使用时,对齐对问题没有影响。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接