web-dev-qa-db-fra.com

Ajouter par programme des contrôles au formulaire WPF

J'essaie d'ajouter des contrôles à un UserControl dynamiquement (par programme). J'obtiens une liste générique d'objets de ma couche métier (récupérée de la base de données), et pour chaque objet, je veux ajouter une étiquette et une zone de texte au WPF UserControl et définir la position et les largeurs pour faire joli, et j'espère tirer parti des capacités de validation WPF. C'est quelque chose qui serait facile dans la programmation Windows Forms mais je suis nouveau sur WPF. Comment puis-je faire cela (voir les commentaires pour les questions) Dites que c'est mon objet:

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

Ensuite, dans mon WPF UserControl, j'essaie de créer une étiquette et une zone de texte pour chaque objet:

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;
}
26
user210757

D'accord, la deuxième fois est le charme. Sur la base de votre capture d'écran de mise en page, je peux déduire tout de suite que ce dont vous avez besoin est un WrapPanel, un panneau de mise en page qui permet aux éléments de se remplir jusqu'à ce qu'il atteigne un bord, à quel point les éléments restants s'écoulent sur la ligne suivante . Mais vous voulez toujours utiliser un ItemsControl afin de bénéficier de tous les avantages de la liaison de données et de la génération dynamique. Donc, pour cela, nous allons utiliser le ItemsControl.ItemsPanel, qui nous permet de spécifier le panneau dans lequel les éléments seront placés. Commençons à nouveau avec le code-behind:

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; }
}

Peu de choses ont changé ici, mais j'ai modifié les champs d'exemple pour mieux correspondre à votre exemple. Voyons maintenant où la magie opère - le XAML pour le 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>

Tout d'abord, vous remarquerez que le ItemTemplate a légèrement changé. L'étiquette est toujours liée à la propriété name, mais maintenant la largeur de la zone de texte est liée à la propriété length (vous pouvez donc avoir des zones de texte de longueur variable). De plus, j'ai ajouté un "*" à tous les champs requis, en utilisant un BoolToVisibilityConverter simpliste (dont vous pouvez trouver le code n'importe où, et je ne le posterai pas ici).

La principale chose à noter est l'utilisation d'un WrapPanel dans la propriété ItemsPanel de notre ListBox. Cela indique au ListBox que tous les éléments qu'il génère doivent être poussés dans une disposition enveloppée horizontale (cela correspond à votre capture d'écran). Ce qui rend ce travail encore meilleur, c'est la liaison de la hauteur et de la largeur sur le panneau - ce qui dit, "faites de ce panneau la même taille que ma fenêtre parent." Cela signifie que lorsque je redimensionne le Window, le WrapPanel ajuste sa taille en conséquence, résultant en une meilleure mise en page pour les éléments.

24
Charlie

Il n'est pas recommandé d'ajouter des contrôles comme celui-ci. Ce que vous faites idéalement dans WPF, c'est de mettre un ListBox (ou ItemsControl) et de lier votre collection d'objets Business en tant que propriété itemsControl.ItemsSource. Maintenant, définissez DataTemplate en XAML pour votre type DataObject et vous êtes prêt à partir, c'est la magie de WPF.

Les gens issus d'un arrière-plan winforms ont tendance à faire comme vous l'avez décrit et ce n'est pas la bonne façon dans WPF.

17
Jobi Joy

J'écouterais les réponses de Charlie et Jobi, mais pour répondre directement à la question ... (Comment ajouter des contrôles et les positionner manuellement.)

Utilisez un contrôle Canvas, plutôt qu'un Grid. Les toiles donnent au contrôle une quantité infinie d'espace et vous permettent de les positionner manuellement. Il utilise des propriétés attachées pour garder une trace de la position. En code, cela ressemblerait à ceci:

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

En XAML ...

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

Vous pouvez également les positionner par rapport aux bords droit et inférieur. Si vous spécifiez à la fois un haut et un bas, le contrôle sera redimensionné verticalement avec le canevas. De même pour Gauche et Droite.

8
YotaXP