Панель виртуализации, которая не обрезает элементы - PullRequest
5 голосов
/ 23 февраля 2012

Таким образом, выглядит очень глупо иметь коллекцию без хрома, если элементы обрезаются / обрезаются в конце области прокрутки.

Я хочу создать виртуальную панель для коллекций (ItemsControl / ListBox), которая рисует только целые элементы, но не отдельные элементы.Например:

 ______________
|              |
|______________|
 ______________
|              |
|______________|
 ______________
|              |

enter image description here

Я не хочу отображать 3-й частичный контейнер, если нет места для отображения целого элемента / контейнера.В примерах третий элемент был обрезан из-за недостатка места.

Есть предложения?Должен ли я попытаться заново изобрести колесо (создать свой собственный VirtualizingWholeItemPanel)?

РЕДАКТИРОВАТЬ :

Microsoft пояснила, что VirtualizingPanel.ScrollUnit не предназначена для выполнения этой функции вообще.Похоже, что VirtualizingPanel.ScrollUnit служит очень похоже на старую CanContentScroll на ScrollViewer.

1 Ответ

4 голосов
/ 23 февраля 2012

У меня есть вспомогательный метод, который я использую, чтобы определить, виден ли элемент управления частично или полностью в родительском контейнере. Вероятно, вы можете использовать его с Converter для определения видимости элементов.

Ваш конвертер должен будет либо рассчитать родительский контейнер из элемента пользовательского интерфейса (в моем блоге есть набор помощников по визуальному дереву , которые могут помочь в этом, если хотите), либо это может быть MultiConverter который принимает как элемент пользовательского интерфейса, так и родительский контейнер в качестве параметров.

ControlVisibility ctrlVisibility= 
    WPFHelpers.IsObjectVisibleInContainer(childControl, parentContainer);

if (ctrlVisibility == ControlVisibility.Full 
    || isVisible == ControlVisibility.FullHeightPartialWidth)
{
    return Visibility.Visible;
}
else
{
    return = Visibility.Hidden;
}

Код для определения видимости элемента управления в его родительском элементе выглядит следующим образом:

public enum ControlVisibility
{
    Hidden,
    Partial,
    Full,
    FullHeightPartialWidth,
    FullWidthPartialHeight
}


/// <summary>
/// Checks to see if an object is rendered visible within a parent container
/// </summary>
/// <param name="child">UI element of child object</param>
/// <param name="parent">UI Element of parent object</param>
/// <returns>ControlVisibility Enum</returns>
public static ControlVisibility IsObjectVisibleInContainer(
    FrameworkElement child, UIElement parent)
{
    GeneralTransform childTransform = child.TransformToAncestor(parent);
    Rect childSize = childTransform.TransformBounds(
        new Rect(new Point(0, 0), new Point(child.ActualWidth, child.ActualHeight)));

    Rect result = Rect.Intersect(
        new Rect(new Point(0, 0), parent.RenderSize), childSize);

    if (result == Rect.Empty)
    {
        return ControlVisibility.Hidden;
    }
    if (Math.Round(result.Height, 2) == childSize.Height 
        && Math.Round(result.Width, 2) == childSize.Width)
    {
        return ControlVisibility.Full;
    }
    if (result.Height == childSize.Height)
    {
        return ControlVisibility.FullHeightPartialWidth;
    }
    if (result.Width == childSize.Width)
    {
        return ControlVisibility.FullWidthPartialHeight;
    }
    return ControlVisibility.Partial;
}

Редактировать

Провел несколько тестов и, по-видимому, конвертер запускается до того, как элементы управления будут фактически отображены. Как хак, он будет работать, если вы используете MultiConverter и передадите ему ActualHeight элемента управления, что заставит преобразователь пересмотреть оценку при визуализации элемента управления.

Вот конвертер, который я использовал:

public class TestConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        FrameworkElement child = values[0] as FrameworkElement;
        var parent = VisualTreeHelpers.FindAncestor<ListBox>(child);

        ControlVisibility ctrlVisibility =
            VisualTreeHelpers.IsObjectVisibleInContainer(child, parent);

        if (ctrlVisibility == ControlVisibility.Full
            || ctrlVisibility == ControlVisibility.FullHeightPartialWidth)
        {
            return Visibility.Visible;
        }
        else
        {
            return Visibility.Hidden;
        }
    }

    public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}

Я использовал XAML, который вы опубликовали в своем вопросе, и просто добавил неявный стиль для ListBoxItem в .Resources

<ListBox.Resources>
    <Style TargetType="{x:Type ListBoxItem}">
        <Setter Property="Visibility">
            <Setter.Value>
                <MultiBinding Converter="{StaticResource Converter}">
                    <Binding RelativeSource="{RelativeSource Self}" />
                    <Binding RelativeSource="{RelativeSource Self}" Path="ActualHeight" />
                </MultiBinding>
            </Setter.Value>
        </Setter>
    </Style>
</ListBox.Resources>
...