列表框-内容的大小,直到满足最大行数 [英] Listbox - Size to content until Max number of rows is met

查看:88
本文介绍了列表框-内容的大小,直到满足最大行数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想拥有一个可以调整其内容大小的ListBox,直到满足某些MaxRow属性为止.因此,如果MaxRow值为3,它将以以下方式运行.

I'd like to have a ListBox that sizes to its content, until some MaxRow property is met. So, if the MaxRow value was 3, it would behave in the following manner.

Items.Count == 0-> SizeToContent

Items.Count == 0 -> SizeToContent

Items.Count == 1-> SizeToContent

Items.Count == 1 -> SizeToContent

Items.Count == 2-> SizeToContent

Items.Count == 2 -> SizeToContent

Items.Count == 3-> SizeToContent

Items.Count == 3 -> SizeToContent

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

Items.Count == 4 -> limit height to 3 rows and enable scrollbar

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

Items.Count == 5 -> limit height to 3 rows and enable scrollbar etc etc

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

I thought the correct way to do this would be to use a custom Panel (as seen below), but this doesn't seem to work.

我怎么能做到这一点?

<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);
    }
}

这是我要实现的示例

Edit : Here is an example of what I'm trying to achieve

这是我最后使用的解决方案(基于PushPraj的回答)

Edit : The is the solution I used in the end (based on the answer from 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());
    }
}

推荐答案

我试图通过附加的属性来解决它,因此将其作为行为

I attempted to solve it via attached properties hence making this as behavior

样本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.我使这个数字变得灵活,可以轻松适应各种情况

note that I have added ListBoxHelper.AutoSizeItemCount="3" to ListBox. I have made this number flexible for easy adaptation for various scenarios

ListBoxHelper类

ListBoxHelper class

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

结果是一个自动高度的列表框,最多可指定指定数量的项目.

result is a auto-height ListBox up to the specified number of items.

此解决方案假定所有项目的大小均相同.最后,对于这个问题,这不是一个完美的解决方案,我可以说这是一种解决方法.还值得一提的是,它只能在运行时调整大小,而在设计时无效.

this solution assumes same size for all the items. finally it is not a perfect solution for the problem I can say it is a workaround. also worth to note that it will only be able to re-size during run-time, at design time it is not effective.

尝试一下,看看它与您的需求有多接近.

give this a try and see how close is this with your needs.

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

if you know the item height in advance then a better solution would be using the MaxHeight property

这篇关于列表框-内容的大小,直到满足最大行数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆