Как настроить вертикальную WPF WrapPanel, чтобы использовать как можно больше столбцов

У меня есть ItemsControl с WrapPanel как ItemsPanel. ItemsControl находится внутри ScrollViewer.

В зависимости от ширины окна ItemsControl / WrapPanel должно иметь больше столбцов, чтобы лучше использовать экран (и чтобы пользователю приходилось меньше прокручивать).

Если я установил для WrapPanel значение Orientation = Horizontal, он будет работать, как ожидалось. Но если я установил ориентацию по вертикали, ItemsControl / WrapPanel покажет только один столбец.

Разница в том, что мне нравится, чтобы столбцы были похожи на газетные, а именно:

A   E   I
B   F   J
C   G
D   H

Но если для WrapPanel установлено значение Horizontal, столбцы будут похожи на:

A   B   C
D   E   F
G   H   I
J

Как я могу настроить WrapPanel на такое поведение?

Конечно, я мог ограничить высоту. Конечно, я мог бы реализовать это с помощью некоторой умной логики, которая учитывает количество элементов из источника, а также ширину окна. Но это было бы сложно и, вероятно, беспорядочно, что никто другой никогда бы не понял (или даже я, если я пересмотрю это через пару лет).

Надеюсь, что-то подобное уже существует в WPF.


Вот псевдо-XAML-код

<ScrollViewer>
    <StackPanel>

        <!-- Some other stuff -->

        <ItemsControl ItemsSource="{Binding … }">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel Orientation="Vertical" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid Width="300"><!-- … --></Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

        <!-- Some more other stuff -->

    </StackPanel>
</ScrollViewer>

Он работает с Orientation = Horizontal (но тогда ячейки находятся в неправильном порядке), но показывает только один столбец, когда Orientation = Vertical.


person Martini Bianco    schedule 18.01.2021    source источник


Ответы (2)


Я считаю, что Orientation="Vertical" - это тот вариант, который вы ищете. Он попытается заполнить столбец перед переносом в новый столбец.

A __D __G
| | | | | 
B | E | .
| | | | .
C_| F_| .->

Проблема, с которой вы сталкиваетесь, заключается в том, что WrapPanel разрешено StackPanel неограниченное количество вертикального пространства макета и, как следствие, не нужно переносить.

Чтобы устранить эту проблему, измените свой StackPanel на WrapPanel или другой элемент управления, не допускающий бесконечного пространства макета.

Пример

<WrapPanel>
    <ItemsControl>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel Orientation="Vertical"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>

        <Rectangle Height="50" Width="50" Fill="#F000"/>
        <Rectangle Height="50" Width="50" Fill="#F111"/>
        <Rectangle Height="50" Width="50" Fill="#F222"/>
        <!- ...  ->
        <Rectangle Height="50" Width="50" Fill="#F111"/>
        <Rectangle Height="50" Width="50" Fill="#F000"/>
    </ItemsControl>    
</WrapPanel>

Результат

Пример результата проекта

person D M    schedule 18.01.2021
comment
Но это своего рода проблема. ScrollViewer, вероятно, по определению предоставит неограниченное пространство. И если окно слишком маленькое, мне нужны полосы прокрутки. Но, конечно, полосы прокрутки должны быть вертикальными, а не горизонтальными. - person Martini Bianco; 22.01.2021
comment
Тогда лучше всего будет вычислить number of items / number of desired columns и установить MaxHeight на свой StackPanel. Вы все равно можете установить Orientation="Vertical", чтобы получить макет столбца, но контейнер не будет бесконечно высоким, поэтому столбцы будут обертываться, чтобы заполнить ваше горизонтальное пространство. - person D M; 22.01.2021

Хорошо, я придумал решение, которое представляет собой чистый XAML.

Так что в основном у меня есть сетка. С двумя рядами. Внутри сетки находятся два почти идентичных элемента управления ItemsControl с привязками и WrapPanels. У первого есть WrapPanel с Orientation = Horizontal. Он нужен только для вычисления границ для второго ItemsControl. Поэтому его видимость = скрыта. Также он распространяется только на первую строку.

Второй ItemsControl имеет WrapPanel с Orientation = Vertical. Его высота привязана к ActualHeight первого ItemsControl. Поэтому он вынужден использовать несколько столбцов, потому что высота не может быть выше высоты ItemsControl # 1. Второй ItemsControl охватывает обе строки (это важно, иначе высота может зависнуть, даже если размер окна изменится *).

Это не очень хорошее решение. Больше похоже на Грубую силу. Но конечно работает. Первый ItemsControl вычисляет внешний размер. Я устанавливаю этот размер на второй, который может распределить предметы по красивому узору.

<ScrollViewer>
    <StackPanel>

        <!-- Some other stuff -->

        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>

            <!-- first ItemsControl, to calculate the outer boundaries with --> 
            <ItemsControl ItemsSource="{Binding … }" 
                          Name="Calculating_Size"
                          Visibility="Hidden">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel Orientation="Horizontal" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Grid Width="300"><!-- … --></Grid>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>

            <!-- Second ItemsControl with fixed size for Vertical WrapPanel --> 
            <ItemsControl ItemsSource="{Binding … }" 
                          Height="{Binding ActualHeight, ElementName=Calculating_Size}"
                          Grid.RowSpan="2">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel Orientation="Vertical" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Grid Width="300"><!-- … --></Grid>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>

        <!-- Some more other stuff -->

    </StackPanel>
</ScrollViewer>



* Причина для второй строки заключается в том, что если первая WrapPanel реорганизуется из-за увеличения ширины, она по-прежнему фиксируется с высотой, потому что строка не может стать меньше, чем второй ItemsControl. Но если второй ItemsControl занимает более двух строк, первая строка может стать меньше, независимо от второй ItemsControl. Второй сразу после этого становится меньше. Но это кажется лишним шагом.

person Martini Bianco    schedule 22.01.2021