列表框-内容的大小,直到满足最大行数 [英] Listbox - Size to content until Max number of rows is met
问题描述
我想拥有一个可以调整其内容大小的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屋!