Вопрос

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 have a size that allows it to display, say, 6.5 items, so that the last item it displays is cut in half. If the resolution changes, it might display 4 items, or 8.2 items, or whatever. What I'd like is that the ListView shows exactly the number of items that fits in the height of the control, instead of clipping the last item.

Right now, I see two possible half-solutions, none of which is optimal:

  1. Set the height of the ListView to a fixed height that is a multiple of the item size. This does not scale with changes in resolution.

  2. Limit the number of items in the data source. This does not scale either.

So my question is, how can I get the ListView to only display complete items (items where all edges are inside the viewport/listview), and hide the rest?

Это было полезно?

Решение 2

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;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top