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

226
02 апреля 2018, 21:01

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

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

Answer 1

У вас размещение элементов - часть бизнес-логики, поэтому алгоритм поместим в 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<PlaceVm>();
    }
    public ObservableCollection<PlaceVm> 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();
    }
}

Он просто умножает входное значение на коэффициент.

Теперь разметка:

<Grid Margin="5">
    <Grid.Resources>
        <c:ScaleConverter x:Key="conv" Coeff="20"/>
    </Grid.Resources>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <Viewbox>
        <ItemsControl ItemsSource="{Binding Storage.Places}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas IsItemsHost="True"
                            Height="{Binding Storage.Height, Converter={StaticResource conv}}"
                            Width="{Binding Storage.Width, Converter={StaticResource conv}}"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemContainerStyle>
                <Style>
                    <Setter Property="Canvas.Top" Value="{Binding Y, Converter={StaticResource conv}}"/>
                    <Setter Property="Canvas.Left" Value="{Binding X, Converter={StaticResource conv}}"/>
                </Style>
            </ItemsControl.ItemContainerStyle>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border Height="{Binding Box.Height, Converter={StaticResource conv}}"
                            Width="{Binding Box.Width, Converter={StaticResource conv}}"
                            BorderThickness="1" BorderBrush="Black"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Viewbox>
    <StackPanel Grid.Column="1" Margin="5,0,0,0">
        <TextBox Text="{Binding Width}"/>
        <TextBox Text="{Binding Height}"/>
        <Button VerticalAlignment="Top" Padding="10,2"
                Content="Add" Command="{Binding AddCommand}"/>
    </StackPanel>
</Grid>

Я разместил ItemsControl в ViewBox, чтобы он был растянут на всё доступное пространство, вы же можете поместить его в ScrollViewer.

READ ALSO
ASP.NET Identity валидация пользователя

ASP.NET Identity валидация пользователя

Создал класс для валидации пользователяМетод ValidateAsync возвращает, что ошибок нет (Errors

133
VS C# chart несколько несвязанных линии c типом SeriesChartType.Spline

VS C# chart несколько несвязанных линии c типом SeriesChartType.Spline

Имеется один chart на форме, нужно начертить на нём несколько линии c ChartType = spline, таким образом что бы конец одного сплайна не соединялся с началом...

153
Как объединить несколько окон в однои окне С# WPF

Как объединить несколько окон в однои окне С# WPF

У меня есть проект на WPF С#, а точнее заготовки для приложения такие как регистрация, авторизация и основной контент у виде файлов з форматомxaml

154