Хорошо, второй раз это очарование. Основываясь на скриншоте вашего макета, я сразу могу сделать вывод, что вам нужна WrapPanel
, панель макета, которая позволяет элементам заполняться до тех пор, пока не достигнет края, после чего оставшиеся элементы переходят на следующую строку. Но вы все еще хотите использовать ItemsControl
, чтобы получить все преимущества привязки данных и динамической генерации. Поэтому для этого мы будем использовать свойство ItemsControl.ItemsPanel
, которое позволяет нам указать панель, в которую будут помещены элементы. Давайте снова начнем с кода:
public partial class Window1 : Window
{
public ObservableCollection<Field> Fields { get; set; }
public Window1()
{
InitializeComponent();
Fields = new ObservableCollection<Field>();
Fields.Add(new Field() { Name = "Username", Length = 100, Required = true });
Fields.Add(new Field() { Name = "Password", Length = 80, Required = true });
Fields.Add(new Field() { Name = "City", Length = 100, Required = false });
Fields.Add(new Field() { Name = "State", Length = 40, Required = false });
Fields.Add(new Field() { Name = "Zipcode", Length = 60, Required = false });
FieldsListBox.ItemsSource = Fields;
}
}
public class Field
{
public string Name { get; set; }
public int Length { get; set; }
public bool Required { get; set; }
}
Здесь мало что изменилось, но я отредактировал примеры полей, чтобы лучше соответствовать вашему примеру. Теперь давайте посмотрим, где происходит волшебство - XAML для Window
:
<Window x:Class="DataBoundFields.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataBoundFields"
Title="Window1" Height="200" Width="300">
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<Grid>
<ListBox x:Name="FieldsListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}" VerticalAlignment="Center"/>
<TextBox Width="{Binding Length}" Margin="5,0,0,0"/>
<Label Content="*" Visibility="{Binding Required, Converter={StaticResource BoolToVisConverter}}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"
Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=ActualHeight}"
Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=ActualWidth}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
Во-первых, вы заметите, что ItemTemplate
немного изменился. Метка по-прежнему привязана к свойству name, но теперь ширина текстового поля привязана к свойству length (поэтому вы можете иметь текстовые поля различной длины). Кроме того, я добавил «*» во все обязательные поля, используя упрощенный BoolToVisibilityConverter
(код которого вы можете найти где угодно, и я не буду публиковать здесь).
Главное, на что нужно обратить внимание - это использование WrapPanel
в ItemsPanel
свойстве ListBox
. Это говорит ListBox
, что любые элементы, которые он генерирует, должны быть помещены в горизонтальную упаковку (это соответствует вашему скриншоту). Что делает эту работу еще лучше, так это привязка к высоте и ширине на панели - это говорит о том, что «сделайте эту панель того же размера, что и мое родительское окно». Это означает, что когда я изменяю размер Window
, WrapPanel
соответствующим образом корректирует его размер, что улучшает расположение элементов.