Какой существует способ расположить элементы (блоки) в две колонки?
При этом, если высота кончается, то добавляется Scroll
Идея в том, чтобы расположить элементы в две колонки, не учитывая высоту самого большого блока. Т.е. использование UniformGrid не особо подходит. Желаемый результат покажу на картинке.
Помимо стандартных средств WPF, можно использовать telerik (может в нём уже существует готовый компонент).
Вот такой код я пробую написать, но видимо надо создавать кастомную Panel
<ItemsControl ItemsSource="{Binding ModuleCategories}"
Margin="32,40"
IsTabStop="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="modularity:MyType">
<Border BorderThickness="1"
BorderBrush="LightGray"
Margin="5"
Padding="5">
//Тут Grid, разделённый на строки
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Алгоритм заполнения хотелось вы видеть такой: Заполняется первый столбец, затем когда кончается высота, заполняется второй; когда заполняется и второй столбец, но не все блоки ещё появились, то увеличивается высота панели, и следующий блок добавляется в первый столбец и так далее.
Это был мой первоначальный вариант. Но со временем он мне показался сложным и я подумал о том, чтобы заполнять так, чтобы каждый следующий блок ставился в самый низкий столбец
Ответы (1 шт):
Пишем панель:
class SimpleColumnStackPanel : Panel
{
public int Columns
{
get => (int)GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(nameof(Columns),
typeof(int), typeof(SimpleColumnStackPanel), new PropertyMetadata(1));
// Этап подсчета занимаемого места
protected override Size MeasureOverride(Size availableSize)
{
return ArrangeInternal(availableSize, isMeasureStep: true);
}
// Этап размещения элементов
protected override Size ArrangeOverride(Size finalSize)
{
return ArrangeInternal(finalSize, isMeasureStep: false);
}
private Size ArrangeInternal(Size size, bool isMeasureStep)
{
// Меньше чем 1 колонка быть не должно
var columns = Math.Max(1, Columns);
// Ширина колонки
var columnWidth = size.Width / columns;
// Здесь будем хранить текущую высоту каждого столбца
var heights = new double[columns];
// Перебираем все элементы
foreach (FrameworkElement child in Children)
{
// Выбираем самую низкую колонку на данный момент
var column = IndexOfMin(heights);
if (isMeasureStep)
{
// Запрашиваем измерение желаемого размера
child.Measure(new Size(columnWidth, size.Height));
}
else
{
// Размещаем элемент
var x = size.Width * column / columns;
var y = heights[column];
child.Arrange(new Rect(x, y, columnWidth, child.DesiredSize.Height));
}
// Увеличиваем колонку на высоту элемента
heights[column] += child.DesiredSize.Height;
}
// Берем максимальную высоту колонки
var maxHeight = heights.Max();
// Если элементов не много — растянем панель по высоте на все доступное пространство
if (!isMeasureStep && size.Height > maxHeight) maxHeight = size.Height;
return new Size(size.Width, maxHeight);
}
private int IndexOfMin(double[] source)
{
var index = 0;
for (var i = 1; i < source.Length; ++i)
if (source[i] < source[index]) index = i;
return index;
}
}
Используем ее:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="true">
<ScrollViewer>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<c:SimpleColumnStackPanel Columns="2"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="5" BorderBrush="Red" BorderThickness="1" Height="{Binding Value}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Caption}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Для теста я использовал такую простую вью-модель:
class MainVm
{
public List<Item> Items { get; } = new List<Item>
{
new Item("1", 123),
new Item("2", 248),
new Item("3", 203),
new Item("4", 97),
new Item("5", 33),
new Item("6", 27),
new Item("7", 116),
new Item("8", 38),
new Item("9", 42)
};
}
class Item
{
public string Caption { get; }
public int Value { get; }
public Item(string caption, int value)
=> (Caption, Value) = (caption, value);
}
Результат:

