我想要一个大小与其内容相匹配的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);
}
}
编辑:这是我想要实现的一个例子
编辑:这是我最终使用的解决方案(基于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());
}
}
答案 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达到指定的项目数。
此解决方案假定所有项目的大小相同。最后,它不是一个完美的解决方案,我可以说这是一个解决方法。另外值得注意的是,它只能在运行时重新调整大小,在设计时它无效。
尝试一下,看看这与您的需求有多接近。
如果您事先知道项目高度,那么更好的解决方案是使用MaxHeight
属性