Créer des éléments de façon dynamique

Nous allons maintenant aborder une nouvelle façon de créer tous les éléments que nous avons vu auparavant, depuis le bouton jusqu’aux différents layouts. En effet, tous ces éléments sont des objets C#, et nous pouvons donc les initialiser et spécifier leurs propriétés directement depuis la MainWindow.xaml.cs. Si au départ l’intérêt de cette approche semble inexistant, nous allons voir que se priver de la simplicité du XAML nous permettra d’aller beaucoup plus loin dans notre utilisation de WPF.

1. Quelques éléments du Chapitre 2

a. Le Bouton

Tout ce que nous ferons maintenant concernera la fenêtre MainWindow.xaml.cs, et s’écrira donc en C#. Comme nous l’avons dit plus haut, il est possible de créer un bouton sans utiliser notre fenêtre XAML. Pour cela il suffit de déclarer un nouvel objet de la classe Button :

Button b = new Button();

Comme vous pouvez le voir, l’élément Button a un constructeur qui ne prend aucun argument. Comment donc lui associer un contenu ? Comment peut-on lui attribuer une taille ? Le Button étant un objet, nous le ferons donc en utilisant ses propriétés. Par exemple, si je veux que mon bouton ait le texte Hello World en contenu, il me suffit d’inscrire ce texte dans la propriété Content :

b.Content = "Hello World";

De même pour lui fixer une taille, il faut donner une valeur aux propriétés Height et Width :

b.Height = 150;
b.Width = 150;

Enfin, on peut donner une position à notre bouton grâce à la propriété HorizontalAlignment (respectivement VerticalAlignment). Pour ce faire, vous avez deux options :

b.HorizontalAlignment = VALEUR;

Avec VALEUR un entier entre 0 et 3 (0 : gauche, 1 : centre, 2 : droite, 3 : stretch). Ou bien vous pouvez utiliser HorizontalAlignment.Left (respectivement .Center, .Right et .Stretch).

b.HorizontalAlignment = HorizontalAlignment.Left;

Maintenant, créez à votre tour un bouton dans votre fenêtre C#, attribuez-lui une taille, un contenu et spécifiez-lui un alignement.
Maintenant que tout est prêt lancez votre code et… Et rien ne s’affiche, étrange non ?
En réalité c’est normal, nous n’avons pas fait le lien entre l’élément que nous avons créé, ici le bouton, et la fenêtre que nous affichons en sortie.
En effet, sur la fenêtre MainWindow.xaml, nous placions directement notre bouton dans la fenêtre XAML, mais ici ce n’est pas le cas.
Il faut attribuer un contenu à la fenêtre pour que celle-ci affiche autre chose qu’une fenêtre blanche.
Pour ce faire, comme nous sommes dans la partial class MainWindow, et que l’élément this se réfère donc à l’instance de la classe MainWindow, il faut écrire ceci :

this.Content = b;

Si vous exécutez le code maintenant, vous allez voir que cette fois-ci vous obtiendrez bien le résultat désiré. A noter que l’objet MainWindow a d’autres propriétés comme Height, Width, Background…

Dans une Grid appelée x :Name=Grille, créez dynamiquement 4 boutons, un dans chaque angles avec un numéro précis, comme dans l’image qui suit :

image1ex

Correction

b. Le TextBlock

De la même façon, le TextBlock est un objet et se déclare comme on déclare un bouton :

TextBlock txtbl = new TextBlock();

Pour attribuer un texte au TextBlock, il suffit de donner une valeur à sa propriété Text :

txtbl.Text = "TextBlock créée sans XAML";

Evidemment, on peut aussi attribuer une taille au TextBlock (comme pour tous les autres éléments, mais, l’exemple ayant été donné pour le bouton, nous ne nous y arrêterons plus). Arrêtons-nous maintenant sur les propriétés du texte.

Nous pouvons spécifier la police de texte que nous voulons utiliser comme suit :

txtbl.FontFamily = new FontFamily("Nom_De_La_Police");

Nous pouvons aussi spécifier sa taille :

txtbl.FontSize = VALEUR;

Avec VALEUR un entier qui déterminera la taille de la police.

Nous pouvons aussi déterminer l’épaisseur de la police :

txtbl.FontWeight = FontWeights.WEIGHT_POLICE;

Avec WEIGHT_POLICE la nature de la police de caractère, par exemple FontWeights.UltraBold.

Nous pouvons aussi choisir la couleur du texte comme suit :

txtbl.Foreground = Brushes.COLOR;

Avec COLOR la couleur du texte par exemple Brushes.AliceBlue.

De même, on peut spécifier la couleur de fond de notre TextBlock comme suit :

txtbl.Background = Brushes.COLOR;

Enfin, on peut choisir la couleur des bords de notre TextBox :

txtbl.BorderBrush = Brushes.COLOR;

Ainsi que choisir l’épaisseur des bords :

txtbl.BorderThickness = new Thickness(VALEUR);

Ou en spécifiant chaque valeur une par une :

txtbl.BorderThickness = new Thickness(VALEUR1, VALEUR2, VALEUR3, VALEUR4);

Reproduisez à l’aide du TextBlock et en dynamique le résultat suivant :

image2

image3

Correction

c. La TextBox

On déclare l’objet TextBox comme suit :

TextBox txtbx = new TextBox();

On y insère du texte avec la propriété Text :

txtbx.Text = "TextBox créée sans XAML";

Si notre texte dépasse de la TextBox, on peut choisir de le faire retourner à la ligne :

txtbx.TextWrapping = TextWrapping.Wrap;

Reproduisez à l’aide du TextBlock et en dynamique le résultat suivant :

image4

image5

Correction

d. La CheckBox

On déclare l’objet CheckBox comme suit :

CheckBox cbx = new CheckBox();

Nous avons déjà évoqué comment attribuer du texte et choisir les caractéristiques de la police plus haut. On peut aussi attribuer à la CheckBox la propriété IsThreeState abordée au chapitre 2 :

cbx.IsThreeState = true;

En suivant le modèle de l’image, créez un bouton qui demande un chiffre pour ajouter à la fenêtre le même nombre de checkbox.

image6ex

Correction

2. Quelques éléments du Chapitre 3

a. La Grid

On déclare un Grid comme suit :

Grid myGrid = new Grid();

Pour pouvoir créer une colonne, respectivement une ligne, il faut créer un objet :

ColumnDefinition colDef1 = new ColumnDefinition();
myGrid.ColumnDefinitions.Add(colDef1);

Imaginons que nous créions le TextBlock suivant :

TextBlock txt = new TextBlock();
txt.Text = "TextBlock";
txt.FontSize = 20;
txt.FontWeight = FontWeights.Bold;

Notre Grid a pour l’instant trois colonnes et quatre lignes :

ColumnDefinition colDef2 = new ColumnDefinition();
ColumnDefinition colDef3 = new ColumnDefinition();
ColumnDefinition colDef1 = new ColumnDefinition();
myGrid.ColumnDefinitions.Add(colDef1);
myGrid.ColumnDefinitions.Add(colDef2);
myGrid.ColumnDefinitions.Add(colDef3);
RowDefinition rowDef1 = new RowDefinition();
RowDefinition rowDef2 = new RowDefinition();
RowDefinition rowDef3 = new RowDefinition();
RowDefinition rowDef4 = new RowDefinition();
myGrid.RowDefinitions.Add(rowDef1);
myGrid.RowDefinitions.Add(rowDef2);
myGrid.RowDefinitions.Add(rowDef3);
myGrid.RowDefinitions.Add(rowDef4);

On choisit la localization du TextBlock dans notre Grid comme suit :

Grid.SetColumn(txt, 0);
Grid.SetRow(txt, 0);

Evidemment, on peut toujours utiliser les propriétés ColumnSpan et RowSpan comme suit :

Grid.SetColumnSpan(txt, 3);

Enfin, on ajoute l’élément à notre Grid comme suit :

myGrid.Children.Add(txt);

Essayez d’obtenir grâce à la grille et en dynamique le résultat suivant :

image13

Correction

b. Le Stack Panel

Nous pouvons déclarer des layouts, qui sont eux aussi des objets C#. Pour construire un Stack Panel, il faut écrire la ligne suivante :

StackPanel stp = new StackPanel();

Nous rappelons que nous pouvons choisir si notre Stack Panel sera vertical ou horizontal avec la propriété Orientation :

stp.Orientation = Orientation.Horizontal;

Nous pouvons donner une taille à notre Stack Panel comme nous le faisons pour tous les éléments :

stp.Width = VALEUR;
stp.Height = VALEUR;

Une fois notre Stack Panel construit, nous pouvons lui insérer des éléments. Créons tout d’abord un TextBlock :

TextBlock txtbl = new TextBlock();
txtbl.Text = "TextBlock Without XAML";
txtbl.Background = Brushes.AliceBlue;

Maintenant que nous avons notre TextBlock, nous pouvons l’ajouter à notre Stack Panel :

stp.Children.Add(txtbl);

Repdroduisez l’empilement de bouton de l’image avec un Stack Panel que vous remplissez dynamiquement. Vous devrez utiliser une boucle for pour créer et ajouter ces boutons.

image8

Correction

c. Le Wrap Panel

Nous pouvons créer un Wrap Panel comme suit :

WrapPanel wp = new WrapPanel();

Comme pour le Stack Panel, on peut choisir son orientation :

wp.Orientation = Orientation.CHOIX_ORIENTATION;

Avec CHOIX_ORIENTATION qui peut prendre les valeurs Horizontal et Vertical.

Bien entendu, nous pouvons aussi choisir où se situera notre Wrap Panel dans la fenêtre grâce aux propriétés HorizontalAlignment et VerticalAlignment :

wp.HorizontalAlignment = HorizontalAlignment.Left;
wp.VerticalAlignment = VerticalAlignment.Top;

D’autres propriétés sont également disponibles, telle la propriété Background :

wp.Background = Brushes.COLOR_NAME;

Si nous reprenons notre TextBlock de tout à l’heure, nous pouvons l’ajouter à notre Wrap Panel comme suit :

wp.Children.Add(txtbl);

Essayez d’obtenir avec Wrap Panel et en dynamique le résultat suivant :

image10

Correction

d. Le Dock Panel

On peut déclarer un objet Dock Panel comme suit :

DockPanel dcpnl = new DockPanel();

Comme nous l’avons vu, le Dock Panel permet de choisir si l’élément que nous plaçons sera en haut, en bas, à gauche ou à droite dans notre fenêtre. Cela complique donc un petit peu sa création. Cet objet a lui aussi des propriétés de taille, de Background, de bordure et d’épaisseur de bordure, voici quelques exemples :

b.Height = 25;
b.BorderBrush = Brushes.AliceBlue;
b.BorderThickness = new Thickness(5);

Nous devons ensuite ajouter cette bordure à notre Dock Panel, en choisissant sa future position :

DockPanel.SetDock(b, Dock.POSITION);

Avec POSITION qui a pour valeur Top, Bottom, Right et Left. Si nous reprenons de nouveau notre TextBlock de tout à l’heure, nous devons l’associer à la bordure que nous venons de créer :

b.Child = txtbl;

Enfin, nous ajoutons le tout à notre Dock Panel comme suit :

dcpnl.Children.Add(b);

Il est important de se souvenir que l’ordre dans lequel nous ajoutons les éléments dans notre Dock Panel va influer sur leur disposition dans notre fenêtre (pour rappel voir la première partie de ce chapitre).

Enfin, nous pouvons utiliser la propriété LastChildFill qui spécifie si le dernier élément placé remplit toute la place restante (dans ce cas sa valeur est true) ou non :

dcpnl.LastChildFill = true;

Par défaut cette propriété a pour valeur true.

Essayez d’obtenir avec Dock Panel et en dynamique le résultat suivant :

image11

Correction

e. Le Canvas

On déclare un objet Canvas comme suit :

Canvas cv = new Canvas();

Evidemment, les figures que nous avions vues dans la première partie de ce chapitre, comme le rectangle ou l’ellipse sont elles aussi des objets :

Rectangle rect = new Rectangle();
rect.Width = 100;
rect.Height = 75;
rect.Fill = Brushes.AliceBlue;
rect.Stroke = Brushes.Black;
rect.StrokeThickness = 5;

On peut choisir la position précise de l’élément grâce aux propriétés SetRight, SetLeft, SetTop, SetBottom :

Canvas.SetBottom(rect, 100);
Canvas.SetRight(rect, 10);

Enfin, on ajoute notre figure au Canvas comme suit :

cv.Children.Add(rect);

Essayez d’obtenir grâce au Canvas et en dynamique le résultat suivant :

image12

Correction

3. Pourquoi utiliser la création dynamique ?

La création dynamique est un outil très puissant qui va vous permettre de modifier votre contenu XAML au cours de l’exécution de votre code.

Cela vous permettra à tout instant d’ajouter, de supprimer, de modifier un élément ou certaines de ces propriétés en fonctions des actions réalisées par l’utilisateur.

Voici un exemple de ce que l’on peut faire. Créez un nouveau projet WPF et entrez le code suivant dans la fenêtre XAML, en ayant pris soin d’affecter à la fenêtre une dimension 1000*1000 :

<Grid>
    <Grid Name="grid1" ShowGridLines="true" Width="600" Height="600" VerticalAlignment="Top" HorizontalAlignment="Right">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    </Grid>
</Grid>

Comme vous pouvez le voir, nous venons de créer une grille qui a trois lignes et trois colonnes de mêmes dimensions. Nous donnons le nom grid1 à notre grille, ce qui aura, nous le verrons, une importance par la suite. A noter que la propriété suivante permettra d’afficher les lignes et colonnes de notre grille :

myGrid.ShowGridLines = true;

Nous allons maintenant créer un premier StackPanel (n’oubliez pas de le placer à l’intérieur de la première Grid, et sous la balise fermante de la deuxième Grid) :

<StackPanel HorizontalAlignment="Left" Orientation="Vertical">
    <Button Width="125" Height="100" Click="addCol">Add Column</Button>
    <Button Width="125" Height="100" Click="addRow">Add Row</Button>
</StackPanel>

Comme vous l’avez vu dans les chapitres précédents, la propriété Click va nous permettre de créer des fonctions dans le .cs afin de réaliser un évènement lorsqu’on cliquera sur le bouton. Ajoutons maintenant un deuxième StackPanel :

<StackPanel HorizontalAlignment="Left" Orientation="Vertical" Margin="125 0 0 0">
    <Button Width="125" Height="100" Click="insertRowAt">Insert Row</Button>
    <TextBox Text="" Name="tp1" Height="100" Width="125" Background="AliceBlue" TextWrapping="Wrap" IsReadOnly="True"/>
    <Button Width="125" Height="100" Click="insertColAt">Insert Column</Button>
    <TextBox Text="" Name="tp2" Height="100" Width="125" Background="AliceBlue" TextWrapping="Wrap" IsReadOnly="True"/>
</StackPanel>

Là encore, il est important de remarquer que nous avons donné des noms à nos deux TextBox. La propriété IsReadOnly indique le fait que l’utilisateur ne peut pas écrire dans cette TextBox. Enfin, ajoutons un troisième et dernier StackPanel :

<StackPanel HorizontalAlignment="Left" Orientation="Vertical" Margin="250 0 0 0">
   <Button Width="125" Height="100" Click="addBut">Add Button</Button>
   <TextBox Text="" Width="125" Height="100" Name="txtbx" TextWrapping="Wrap" IsReadOnly="True"/>
   <TextBox Text="" Width="125" Height="100" Name="txtbx2" TextWrapping="Wrap" TextChanged="txtbx2_changed"/>
</StackPanel>

Exécutez le code. Vous devriez avoir un résultat comme suit :

image14

Nous allons maintenant créer les fonctions Click dans notre .cs qui nous permettront de réaliser les actions proposées par les boutons. Premièrement, dans la MainWindow.xaml.cs, supprimez ce qui suit :

public MainWindow()
{
    InitializeComponent();
}

Ensuite, il nous faut ajouter quelques variables :

RowDefinition rowDef1;
ColumnDefinition colDef1;
bool ligne = true;
int c1 = -1;
int c2 = -1;

Une fois que cela est fait nous allons commencer par créer la fonction qui nous permettra d’ajouter une colonne :

private void addCol(object sender, RoutedEventArgs e)
{
    colDef1 = new ColumnDefinition();
    grid1.ColumnDefinitions.Add(colDef1);
}

Remarquez que nous nous sommes servis dans cette fonction de notre variable de clase rowDef1. Remarquez aussi que nous avons pu ajouter une colonne directement à notre grille créée depuis la fenêtre XAML en se servant du nom que nous lui avions défini (grid1). On voit ici qu’on peut se référer à notre instance de Grid créée en XAML depuis notre .cs en utilisant son nom.

Une fois cette fonction réalisée, on peut facilement écrire la fonction qui permet d’ajouter une ligne. Cette tâche est d’ailleurs laissée comme exercice.

Maintenant, créons la fonction qui nous permettra d’insérer une colonne :

private void insertColAt(object sender, RoutedEventArgs e)
{
    colDef1 = new ColumnDefinition();
    grid1.ColumnDefinitions.Insert(grid1.ColumnDefinitions.Count, colDef1);
    tp2.Text = "ColumnDefinition added at index position " +                                                                                                            grid1.ColumnDefinitions.IndexOf(colDef1).ToString();
}

Nous pouvons remarquer que la propriété ColumnDefinitions.Insert permet de choisir à quelle position nous allons insérer notre élément. Là encore, nous pouvons modifier le texte de notre TextBox créée depuis notre XAML grâce à son prénom et à la propriété Text. En exercice, créez cette fonction mais qui permettra d’insérer non plus une colonne mais une ligne. Enfin, nous allons créer la fonction qui permet de rajouter un bouton à l’emplacement désiré.
Tout d’abord, créez la fonction addBut qui modifiera le texte de notre TextBox txtbx en lui affectant :

"Choisissez la ligne où insérer le bouton dans la TextBox ci-dessous"

Enfin créez la fonction qui répond à l’évènement TextChanged de la TextBox. La fonction suivante est commentée pour vous aider à sa compréhension :

private void txtbx2_changed(object sender, EventArgs e)
{
    int test; //Permet de récupérer le texte de l'utilisateur si c'est un entier
    if (int.TryParse(txtbx2.Text, out test))//Test si la string est un int
    {
        if(ligne == true)//Si nous choisissons le numéro de la ligne
        {
            if(test>=0 && test<grid1.RowDefinitions.Count)//Si l'index que nous donnons est valide
            //A noter que RowDefinitions.Count permet d'obtenir le nombre total de Rows
            {
                c1 = test;//c1 est notre index pour la ligne
                txtbx2.Text = "";//On vide notre TextBox txtbx2
                txtbx.Text = "Choisissez la colonne où insérer le bouton dans la TextBox ci-dessous";//On modifie le texte de notre TextBox txtbx
                ligne = false;//On va maintenant choisir la colonne
            }
            else
            {
                txtbx.Text = "Veuillez choisir un numéro de ligne valide";//Sinon on demande à l'utilisateur un index valide
                txtbx2.Text = "";
            }
        }
        else//Si nous choisissons le numéro de la colonne
        {
            if (test >= 0 && test < grid1.ColumnDefinitions.Count)//Si la colonne existe
            {
                c2 = test;//Notre coordonnée pour la colonne vaut test
                txtbx2.Text = "";//On vide la TextBox txtbx2
                txtbx.Text = "Bouton inséré !";//On modifie le texte de la TextBox txtbx
                ligne = true;//Si prochaine insertion, on fixe la ligne
                Button b = new Button();//On crée le bouton à insérer
                b.Content = "Bouton";//On lui affecte le texte Bouton
                b.Background = Brushes.AliceBlue;//On lui affecte un Background
                Grid.SetRow(b, c1);//On affecte la row choisie au bouton
                Grid.SetColumn(b, c2);//On affecte la column choisie au bouton
                grid1.Children.Add(b);//On ajoute notre bouton à la grille
            }
            else//Sinon le numéro n'est pas valide
            {
                txtbx.Text = "Veuillez choisir un numéro de colonne valide";
                txtbx2.Text = "";
            }
        }
    }
    else//Sinon le numéro n'est pas valide ou nous avons saisie une string
    {
        txtbx2.Text = "";
        txtbx.Text = "Veuillez choisir un nombre entier valide";
    }
}

Jouez avec le code et voyez ce que permet la création dynamique !

Evidemment, bien plus de possibilités s’offrent à vous, vous pouvez réaliser bien plus de chose que simplement ajouter des lignes, des colonnes et des boutons.

A partir d’une fenêtre similaire, ajouter autant de colonnes et lignes que demandé et ajoutez dans chaque carré un bouton rouge.

image11

Correction

Lien vers les exercices