In my Metro application, I have a data source containing a certain number of items (say 25). I have a ListView that presents those items. My problem is that the ListView hav
My final solution was to combine the suggestions of @NovitchiS and @JesuX.
I created a stack panel override, and listened to the LayoutUpdated event. My final solution:
class HeightLimitedStackPanel : StackPanel
{
public HeightLimitedStackPanel() : base()
{
this.LayoutUpdated += OnLayoutUpdated;
}
double GetSizeOfVisibleChildren(double parentHeight)
{
double currentSize = 0;
bool hasBreaked = false;
for (int i = 0; i < Children.Count; i++)
{
var child = Children[i];
if (currentSize + child.DesiredSize.Height > parentHeight)
{
hasBreaked = true;
break;
}
currentSize += child.DesiredSize.Height;
}
if (hasBreaked) return currentSize;
return parentHeight;
}
double ParentHeight
{
get
{
ItemsPresenter parent = VisualTreeHelper.GetParent(this) as ItemsPresenter;
if (parent == null)
return 0;
return parent.ActualHeight;
}
}
double previousHeight = 0;
int previousChildCount = 0;
protected void OnLayoutUpdated(object sender, object e)
{
double height = ParentHeight;
if (height == previousHeight && previousChildCount == Children.Count) return;
previousHeight = height;
previousChildCount = Children.Count;
this.Height = GetSizeOfVisibleChildren(height);
}
}
ListView inherits from ItemsControl, so one more optimized solution consists in injecting custom panel (overriding measure by custom clipping display) in ItemsPanel
something like this(sorry, i did not try to compile):
protected override Size MeasureOverride(Size constraint)
{
if (this.VisualChildrenCount <= 0)
return base.MeasureOverride(constraint);
var size = ne Size(constraint.Width,0);
for(int i = 0; i < this.visualChildrenCount; i++)
{
child.Measure(size);
if(size.height + child.desiredSize > constraint.height)
break;
size.Height += child.DesiredSize;
}
return size;
}
The answer from @JesuX is the better approach -- if done correctly. The following ListView
subclass works fine for me:
public sealed class IntegralItemsListView : ListView
{
protected override Size MeasureOverride(Size availableSize)
{
Size size = base.MeasureOverride(availableSize);
double height = 0;
if (Items != null)
{
for (int i = 0; i < Items.Count; ++i)
{
UIElement itemContainer = (UIElement)ContainerFromIndex(i);
if (itemContainer == null)
{
break;
}
itemContainer.Measure(availableSize);
double childHeight = itemContainer.DesiredSize.Height;
if (height + childHeight > size.Height)
{
break;
}
height += childHeight;
}
}
size.Height = height;
return size;
}
}
One caveat -- if you plop an IntegralItemsListView
into a Grid
, it will have
VerticalAlignment="Stretch"
by default, which defeats the purpose of this class.
Also: If the items are of uniform height, the method can obviously be simplified:
protected override Size MeasureOverride(Size availableSize)
{
Size size = base.MeasureOverride(availableSize);
size.Height = (int)(size.Height / ItemHeight) * ItemHeight;
return size;
}