列表框 - 大小到内容,直到满足最大行数

时间:2014-08-09 22:15:32

标签: wpf listbox panel

我想要一个大小与其内容相匹配的ListBox,直到满足某些MaxRow属性。因此,如果MaxRow值为3,则其行为方式如下。

Items.Count == 0 - > SizeToContent

Items.Count == 1 - > SizeToContent

Items.Count == 2 - > SizeToContent

Items.Count == 3 - > SizeToContent

Items.Count == 4 - >将高度限制为3行并启用滚动条

Items.Count == 5 - >将高度限制为3行并启用滚动条 等等

我认为正确的方法是使用自定义面板(如下所示),但这似乎不起作用。

我怎么能实现这个目标?

<ListBox ItemsSource="{Binding Items}"
         HorizontalContentAlignment="Stretch">
        <ListBox.Template>
            <ControlTemplate>
                <ScrollViewer VerticalScrollBarVisibility="Auto">
                    <l:LimitingStackPanel VerticalAlignment="Top"
                                          IsItemsHost="True"/>
                </ScrollViewer>
            </ControlTemplate>
        </ListBox.Template>
    </ListBox>


public class LimitingStackPanel : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        Size measuredSize = new Size(0, 0);
        int count = 0;
        foreach(UIElement item in InternalChildren)
        {
            item.Measure(availableSize);
            measuredSize.Width = Math.Max(measuredSize.Width, item.DesiredSize.Width);

            if(++count <= 4)
            {
                measuredSize.Height += item.DesiredSize.Height;
            }
        }

        return measuredSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        double y = 0;
        foreach (UIElement item in InternalChildren)
        {
            double height = item.DesiredSize.Height;
            item.Arrange(new Rect(0, y, finalSize.Width, height));
            y += height;
        }

        return new Size(finalSize.Width, y);
    }
}

编辑:这是我想要实现的一个例子 enter image description here

编辑:这是我最终使用的解决方案(基于PushPraj的答案)

<DockPanel>
    <UniformGrid Columns="2"
                 DockPanel.Dock="Top">
        <Button Content="Add"
                Click="OnAddClick" />
        <Button Content="Remove"
                Click="OnRemoveClick" />
    </UniformGrid>

    <StackPanel>
        <ListBox l:ListBoxHelper.AutoSizeItemCount="3"
                 ItemsSource="{Binding ItemsA}" />
        <ListBox l:ListBoxHelper.AutoSizeItemCount="3"
                 ItemsSource="{Binding ItemsB}" />
        <ListBox l:ListBoxHelper.AutoSizeItemCount="3"
                 ItemsSource="{Binding ItemsC}" />
        <ListBox l:ListBoxHelper.AutoSizeItemCount="3"
                 ItemsSource="{Binding ItemsD}" />
        <ListBox l:ListBoxHelper.AutoSizeItemCount="3"
                 ItemsSource="{Binding ItemsE}" />
        <ListBox l:ListBoxHelper.AutoSizeItemCount="3"
                 ItemsSource="{Binding ItemsF}" />
    </StackPanel>
</DockPanel>

public class ListBoxHelper : DependencyObject
{
    public static int GetAutoSizeItemCount(DependencyObject obj)
    {
        return (int)obj.GetValue(AutoSizeItemCountProperty);
    }

    public static void SetAutoSizeItemCount(DependencyObject obj, int value)
    {
        obj.SetValue(AutoSizeItemCountProperty, value);
    }

    public static readonly DependencyProperty AutoSizeItemCountProperty =
        DependencyProperty.RegisterAttached("AutoSizeItemCount", typeof(int), typeof(ListBoxHelper), new PropertyMetadata(0, OnAutoSizeItemCountChanged));

    static void OnAutoSizeItemCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var listBox = d as ListBox;

        // we set this to 0.0 so that we ddon't create any elements
        // before we have had a chance to modify the scrollviewer
        listBox.MaxHeight = 0.0;

        listBox.Loaded += OnListBoxLoaded;
    }

    static void OnListBoxLoaded(object sender, RoutedEventArgs e)
    {
        var listBox = sender as ListBox;

        var sv = Helper.GetChildOfType<ScrollViewer>(listBox);
        if(sv != null)
        {
            // limit the scrollviewer height so that the bare minimum elements are generated
            sv.MaxHeight = 1.0;

            var vsp = Helper.GetChildOfType<VirtualizingStackPanel>(listBox);
            if(vsp != null)
            {
                vsp.SizeChanged += OnVirtualizingStackPanelSizeChanged;
            }
        }

        listBox.MaxHeight = double.PositiveInfinity;
    }

    static void OnVirtualizingStackPanelSizeChanged(object sender, SizeChangedEventArgs e)
    {
        var vsp = sender as VirtualizingStackPanel;
        var lb = (ListBox)ItemsControl.GetItemsOwner(vsp);
        int maxCount = GetAutoSizeItemCount(lb);
        vsp.ScrollOwner.MaxHeight = vsp.Children.Count == 0 ? 1 : ((FrameworkElement)vsp.Children[0]).ActualHeight * maxCount;
    }
}

public static class Helper
{
    public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
    {
        if (depObj == null) return null;

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            var child = VisualTreeHelper.GetChild(depObj, i);
            var result = (child as T) ?? GetChildOfType<T>(child);
            if (result != null) return result;
        }
        return null;
    }
}

public partial class MainWindow : Window
{
    public ObservableCollection<string> ItemsA { get; private set; }
    public ObservableCollection<string> ItemsB { get; private set; }
    public ObservableCollection<string> ItemsC { get; private set; }
    public ObservableCollection<string> ItemsD { get; private set; }
    public ObservableCollection<string> ItemsE { get; private set; }
    public ObservableCollection<string> ItemsF { get; private set; }

    public MainWindow()
    {
        ItemsA = new ObservableCollection<string>(Enumerable.Repeat("Word", 0));
        ItemsB = new ObservableCollection<string>(Enumerable.Repeat("Word", 1));
        ItemsC = new ObservableCollection<string>(Enumerable.Repeat("Word", 2));
        ItemsD = new ObservableCollection<string>(Enumerable.Repeat("Word", 3));
        ItemsE = new ObservableCollection<string>(Enumerable.Repeat("Word", 4));
        ItemsF = new ObservableCollection<string>(Enumerable.Repeat("Word", 1000000));

        DataContext = this;
        InitializeComponent();
    }

    void OnAddClick(object _sender, EventArgs _args)
    {
        ItemsA.Add("new");
        ItemsB.Add("new");
        ItemsC.Add("new");
        ItemsD.Add("new");
        ItemsE.Add("new");
        ItemsF.Add("new");
    }

    void OnRemoveClick(object _sender, EventArgs _args)
    {
        ItemsA.Remove(ItemsA.LastOrDefault());
        ItemsB.Remove(ItemsB.LastOrDefault());
        ItemsC.Remove(ItemsC.LastOrDefault());
        ItemsD.Remove(ItemsD.LastOrDefault());
        ItemsE.Remove(ItemsE.LastOrDefault());
        ItemsF.Remove(ItemsF.LastOrDefault());
    }
}

1 个答案:

答案 0 :(得分:4)

我尝试通过附加属性解决它,从而将其作为行为

示例xaml

<StackPanel xmlns:l="clr-namespace:CSharpWPF">
    <ListBox l:ListBoxHelper.AutoSizeItemCount="3">
        <ListBoxItem>item 1</ListBoxItem>
    </ListBox>
    <ListBox l:ListBoxHelper.AutoSizeItemCount="3">
        <ListBoxItem>item 1</ListBoxItem>
        <ListBoxItem>item 2</ListBoxItem>
    </ListBox>
    <ListBox l:ListBoxHelper.AutoSizeItemCount="3">
        <ListBoxItem>item 1</ListBoxItem>
        <ListBoxItem>item 2</ListBoxItem>
        <ListBoxItem>item 3</ListBoxItem>
    </ListBox>
    <ListBox l:ListBoxHelper.AutoSizeItemCount="3">
        <ListBoxItem>item 1</ListBoxItem>
        <ListBoxItem>item 2</ListBoxItem>
        <ListBoxItem>item 3</ListBoxItem>
        <ListBoxItem>item 4</ListBoxItem>
    </ListBox>
    <ListBox l:ListBoxHelper.AutoSizeItemCount="3">
        <ListBoxItem>item 1</ListBoxItem>
        <ListBoxItem>item 2</ListBoxItem>
        <ListBoxItem>item 3</ListBoxItem>
        <ListBoxItem>item 4</ListBoxItem>
        <ListBoxItem>item 5</ListBoxItem>
    </ListBox>
</StackPanel>

请注意我已将ListBoxHelper.AutoSizeItemCount="3"添加到ListBox。我已经使这个号码变得灵活,可以轻松适应各种场景

ListBoxHelper类

namespace CSharpWPF
{
    public class ListBoxHelper : DependencyObject
    {
        public static int GetAutoSizeItemCount(DependencyObject obj)
        {
            return (int)obj.GetValue(AutoSizeItemCountProperty);
        }

        public static void SetAutoSizeItemCount(DependencyObject obj, int value)
        {
            obj.SetValue(AutoSizeItemCountProperty, value);
        }

        // Using a DependencyProperty as the backing store for AutoSizeItemCount.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AutoSizeItemCountProperty =
            DependencyProperty.RegisterAttached("AutoSizeItemCount", typeof(int), typeof(ListBoxHelper), new PropertyMetadata(0, OnAutoSizeItemCountChanged));

        private static void OnAutoSizeItemCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ListBox listBox = d as ListBox;
            listBox.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler((lb, arg) => UpdateSize(listBox)));
            listBox.ItemContainerGenerator.ItemsChanged += (ig, arg) => UpdateSize(listBox);
        }

        static void UpdateSize(ListBox listBox)
        {
            ItemContainerGenerator gen = listBox.ItemContainerGenerator;
            FrameworkElement element = listBox.InputHitTest(new Point(listBox.Padding.Left + 5, listBox.Padding.Top + 5)) as FrameworkElement;
            if (element != null && gen != null)
            {
                object item = element.DataContext;
                if (item != null)
                {
                    FrameworkElement container = gen.ContainerFromItem(item) as FrameworkElement;
                    if (container == null)
                    {
                        container = element;
                    }
                    int maxCount = GetAutoSizeItemCount(listBox);
                    double newHeight = Math.Min(maxCount, gen.Items.Count) * container.ActualHeight;
                    newHeight += listBox.Padding.Top + listBox.Padding.Bottom + listBox.BorderThickness.Top + listBox.BorderThickness.Bottom + 2;
                    if (listBox.ActualHeight != newHeight)
                        listBox.Height = newHeight;
                }
            }
        }
    }
}

结果是自动高度ListBox达到指定的项目数。

result

此解决方案假定所有项目的大小相同。最后,它不是一个完美的解决方案,我可以说这是一个解决方法。另外值得注意的是,它只能在运行时重新调整大小,在设计时它无效。

尝试一下,看看这与您的需求有多接近。

如果您事先知道项目高度,那么更好的解决方案是使用MaxHeight属性