Здравствуйте, сделал элемент 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<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.
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Создал класс для валидации пользователяМетод ValidateAsync возвращает, что ошибок нет (Errors
Имеется один chart на форме, нужно начертить на нём несколько линии c ChartType = spline, таким образом что бы конец одного сплайна не соединялся с началом...
У меня есть проект на WPF С#, а точнее заготовки для приложения такие как регистрация, авторизация и основной контент у виде файлов з форматомxaml