Страницы

Поиск по вопросам

воскресенье, 9 июня 2019 г.

WPF: замостить контейнер элементами

Здравствуйте, сделал элемент UserControl, который может иметь разные высоту и ширину. Необходимо создать алгоритм который будет при добавлении этого элемента в ScrollViewer(или другой элемент) располагать его в месте наиболее подходящем для него, т. е., например, вот тут:

было бы правильно, если бы "серый" элемент был между синим и зелёным. Ширина ScrollViewer фиксированная.


Ответ

У вас размещение элементов - часть бизнес-логики, поэтому алгоритм поместим в VM.
Я написал такой простой класс, представляющий ящик:
class BoxVm : Vm { public int Width { get; } public int Height { get; } public BoxVm(int width, int height) { Width = width; Height = height; } }
И класс, представляющий размещение ящика:
class PlaceVm : Vm { public int X { get; } public int Y { get; } public BoxVm Box { get; } public PlaceVm(int x, int y, BoxVm box) { X = x; Y = y; Box = box; } public bool IsIntersects(PlaceVm place) { return !(X >= place.X + place.Box.Width || X + Box.Width <= place.X || Y >= place.Y + place.Box.Height || Y + Box.Height <= place.Y); } }
Также в нем есть метод для определения пересекаются ли 2 размещения.
Теперь класс, представляющий склад:
class StorageVm : Vm { public int Width { get; } public int Height { get; } public StorageVm(int width, int height) { Width = width; Height = height; Places = new ObservableCollection(); }
public ObservableCollection Places { get; }
public PlaceVm AddBox(BoxVm box) { for (int y = 0; y <= Height - box.Height; ++y) for (int x = 0; x <= Width - box.Width; ++x) { var place = new PlaceVm(x, y, box); if (!Places.Any(p => p.IsIntersects(place))) { Places.Add(place); return place; } } throw new InvalidOperationException("Невозможно разместить!"); } }
Надеюсь, здесь вам всё понятно. Если у вас сетка не ограничена снизу, то просто замените цикл for по y на цикл while, ну и проверку, влазит ли элемент по ширине, надо будет вынести перед циклом и бросать исключение в ней, а не как у меня в конце.
Теперь главная VM:
class MainVm : Vm { int width; public int Width { get => width; set => Set(ref width, value); }
int height; public int Height { get => height; set => Set(ref height, value); }
public StorageVm Storage { get; } public ICommand AddCommand { get; }
public MainVm() { Storage = new StorageVm(10, 10); AddCommand = new DelegateCommand(_ => Storage.AddBox(new BoxVm(Width, Height))); } }
Здесь тоже всё просто - один склад и одна команда для добавления ящика на склад.
Теперь GUI. Нам потребуется конвертер из ячеек в экранные размеры, он очень простой:
class ScaleConverter : IValueConverter { public int Coeff { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (int)value * Coeff; }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
Он просто умножает входное значение на коэффициент.
Теперь разметка:




Комментариев нет:

Отправить комментарий