Подтвердить что ты не робот

Программно добавлять элементы управления в форму WPF

Я пытаюсь добавить элементы управления в UserControl динамически (программно). Я получаю общий список объектов из моего бизнес-уровня (извлекается из базы данных), и для каждого объекта я хочу добавить метку и текстовое поле в пользовательский элемент управления WPF и установить положение и ширину, чтобы они выглядели красиво и, надеюсь, использовать возможности проверки WPF. Это то, что было бы легко в программировании Windows Forms, но я новичок в WPF. Как это сделать (см. Комментарии к вопросам) Скажите, что это мой объект:

public class Field {
   public string Name { get; set; }
   public int Length { get; set; }
   public bool Required { get; set; }
}

Затем в моем WPF UserControl я пытаюсь создать Label и TextBox для каждого объекта:

public void createControls() {
    List<Field> fields = businessObj.getFields();

    Label label = null;
    TextBox textbox = null;

    foreach (Field field in fields) {
        label = new Label();
        // HOW TO set text, x and y (margin), width, validation based upon object? 
        // i have tried this without luck:
        // Binding b = new Binding("Name");
        // BindingOperations.SetBinding(label, Label.ContentProperty, b);
        MyGrid.Children.Add(label);

        textbox = new TextBox();
        // ???
        MyGrid.Children.Add(textbox);
    }
    // databind?
    this.DataContext = fields;
}
4b9b3361

Ответ 1

Хорошо, второй раз очарование. Основываясь на вашем скриншоте макета, я могу сразу сказать, что вам нужна панель 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 соответственно корректирует его размер, что приводит к лучшему расположению элементов. Здесь показаны два скриншота:

alt text http://img156.imageshack.us/img156/6849/wrappanelfields.png

alt text http://img12.imageshack.us/img12/2426/wrappanelfields2.png

Ответ 2

Не рекомендуется добавлять элементы управления, подобные этому. То, что вы в идеале делаете в WPF, - это поставить ListBox (или ItemsControl) и привязать вашу коллекцию бизнес-объектов как свойство itemsControl.ItemsSource. Теперь определите DataTemplate в XAML для вашего типа DataObject, и вам хорошо идти, это волшебство WPF.

Люди из фона winforms, как правило, делают так, как вы описали, и который не подходит для WPF.

Ответ 3

Я бы слушал ответы Чарли и Джоби, но ради ответа на вопрос напрямую... (Как добавить элементы управления и вручную разместить их.)

Используйте элемент управления Canvas, а не Grid. Холсты дают управление бесконечное пространство и позволяют размещать их вручную. Он использует прикрепленные свойства для отслеживания позиции. В коде это будет выглядеть так:

var tb = new TextBox();
myCanvas.Children.Add(tb);
tb.Width = 100;
Canvas.SetLeft(tb, 50);
Canvas.SetTop(tb, 20);

В XAML...

<Canvas>
  <TextBox Width="100" Canvas.Left="50" Canvas.Top="20" />
</Canvas>

Вы также можете расположить их относительно правого и нижнего краев. Указание как верхнего, так и нижнего будет иметь размер элемента управления по вертикали с холстом. Аналогично для Left и Right.