Créer une application de données simple avec WPF et Entity Framework 6
Avertissement
Si vous utilisez Visual Studio 2022, vous devez utiliser Visual Studio 2022 version 17.3 Preview 3 ou version ultérieure pour ce didacticiel.
Cette procédure pas à pas montre comment créer une application de base « formulaires sur données » dans Visual Studio. L’application utilise SQL Server LocalDB, la base de données Northwind, Entity Framework 6 (et non Entity Framework Core) et Windows Presentation Foundation pour .NET Framework (et non .NET Core ou .NET 5 ou version ultérieure). Elle montre comment effectuer la liaison de données de base avec une vue maître-détail, et elle dispose également d’un navigateur de liaison personnalisé avec des boutons pour Déplacer suivant, Déplacer précédent, Passer au début, Passer à la fin, Mettre à jour et Supprimer.
Cet article se concentre sur l’utilisation des outils de données dans Visual Studio et ne tente pas d’expliquer les technologies sous-jacentes en profondeur. Il suppose que vous disposez d’une connaissance de base de XAML, d’Entity Framework et de SQL. Cet exemple ne montre pas non plus l’architecture modèle-vue-vue modèle (MVVM), qui est standard pour les applications WPF. Toutefois, vous pouvez copier ce code dans votre propre application MVVM avec quelques modifications.
Vous trouverez le code final de ce didacticiel dans GitHub dans Échantillons de didacticiel Visual Studio - EF6.
Installer et se connecter à Northwind
Cet exemple utilise SQL Server Express LocalDB et l’exemple de base de données Northwind. Si le fournisseur de données ADO.NET pour ce produit prend en charge Entity Framework, il doit également fonctionner avec d’autres produits de base de données SQL.
Si vous n’avez pas SQL Server Express LocalDB, installez-le via le Visual Studio Installer. Dans Visual Studio Installer, vous pouvez installer la base de données locale SQL Server Express dans le cadre de la charge de travail Traitement et stockage de données, ou l’installer comme un composant seul.
Installez l’exemple de base de données Northwind en procédant comme suit :
Dans Visual Studio, ouvrez la fenêtre de l’Explorateur d’objets SQL Server. (L’Explorateur d’objets SQL Server est installé dans le cadre de la charge de travail stockage de données et du traitement dans Visual Studio Installer.) Développez le nœud SQL Server. Cliquez avec le bouton droit sur votre instance LocalDB et sélectionnez Nouvelle requête.
Une fenêtre d’éditeur de requête s’ouvre.
Copiez le script Northwind Transact-SQL dans votre Presse-papiers. Ce script T-SQL crée la base de données Northwind à partir de zéro et la remplit avec des données.
Collez le script T-SQL dans l’éditeur de requête, puis cliquez sur le bouton Exécuter.
Après un court laps de temps, la requête se termine et la base de données Northwind est créée.
Ajoutez de nouvelles connexions pour Northwind.
Configurer le projet
Dans Visual Studio, créez un projet application WPF (.NET Framework) C#.
Ajoutez le package NuGet pour Entity Framework 6. Dans l’Explorateur de solutions, sélectionnez le nœud de projet. Dans le menu principal, choisissez Projet>Gérer packs NuGet.
Dans le Gestionnaire de package NuGet, cliquez sur le lien Parcourir. Entity Framework est probablement le package en haut de la liste. Cliquez sur Installer dans le volet droit et suivez les invites. La fenêtre Sortie vous indique quand l’installation est terminée.
Vous pouvez maintenant utiliser Visual Studio pour créer un modèle basé sur la base de données Northwind.
Créer le modèle
Cliquez avec le bouton droit sur le nœud du projet dans l’Explorateur de solutions, puis choisissez Ajouter>Nouvel élément. Dans le volet gauche, sous le nœud C#, choisissez Données et, dans le volet central, choisissez modèle de données d’entité ADO.NET.
Appelez le modèle
Northwind_model
et choisissez Ajouter. L’Assistant Entity Data Model s’ouvre. Choisissez Concepteur EF dans la base de données, puis sélectionnez Suivant.Dans l’écran suivant, choisissez votre connexion Northwind LocalDB (par exemple, (localdb)\MSSQLLocalDB), spécifiez la base de données Northwind, puis cliquez sur Suivant.
Si vous ne voyez pas de connexion, choisissez Nouvelle connexion, puis dans la boîte de dialogue Choisir une source de données, choisissez Microsoft SQL Server, choisissez Continuer et, dans la boîte de dialogue Propriétés de la connexion, entrez
(localdb)\MSSQLLocalDB
et sous Sélectionner ou entrer un nom de base de données, choisissez Northwind, puis appuyez sur OK.Si vous y êtes invité, choisissez la version d’Entity Framework que vous utilisez.
Dans la page suivante de l’Assistant, choisissez les tableaux, les procédures stockées et les autres objets de base de données à inclure dans le modèle Entity Framework. Développez le nœud dbo dans l’arborescence, puis choisissez Clients, Ordres et Détails de l’ordre. Laissez les valeurs par défaut activées, puis cliquez sur Terminer.
L’Assistant génère les classes C# qui représentent le modèle Entity Framework. Les classes sont d’anciennes classes C# simples et elles sont celles que nous avons attachées à l’interface utilisateur WPF. Le fichier
.edmx
décrit les relations et autres métadonnées qui associent les classes aux objets de la base de données. Les fichiers.tt
sont des modèles T4 qui génèrent le code qui fonctionne sur le modèle et enregistre les modifications apportées à la base de données. Vous pouvez voir tous ces fichiers dans Explorateur de solutions sous le nœud Northwind_model :L’aire de conception du fichier
.edmx
vous permet de modifier certaines propriétés et relations dans le modèle. Nous n’allons pas utiliser le concepteur dans cette procédure pas à pas.Les fichiers
.tt
sont à usage général et vous devez ajuster l’un d’eux pour qu’il fonctionne avec la liaison de données WPF, ce qui nécessite ObservableCollections. Dans Explorateur de solutions, développez le nœud Northwind_model jusqu’à ce que vous trouviez Northwind_model.tt. (Vérifiez que vous n’êtes pas dans le fichier .Context.tt, qui se trouve directement sous le fichier.edmx
.)Remplacez les deux occurrences de ICollection par ObservableCollection<T>.
Remplacez la première occurrence de HashSet<T> par ObservableCollection<T> autour de la ligne 51. Ne remplacez pas la deuxième occurrence de HashSet.
Remplacez la seule occurrence de System.Collections.Generic (autour de la ligne 431) par System.Collections.ObjectModel.
Appuyez sur F5 ou Ctrl+F5 pour générer et exécuter le projet. Lorsque l’application s’exécute pour la première fois, les classes de modèle sont visibles par l’Assistant Sources de données.
Vous êtes maintenant prêt à connecter ce modèle à la page XAML afin de pouvoir afficher, parcourir et modifier les données.
Effectuer une liaison de données du modèle à la page XAML
Il est possible d’écrire votre propre code de liaison de données, mais il est beaucoup plus facile de laisser Visual Studio le faire pour vous.
Dans le menu principal, choisissez Projet>Ajouter une nouvelle source de données pour afficher l’Assistant Configuration de source de données. Choisissez Objet, car vous liez aux classes de modèle, et non à la base de données :
Développez le nœud de votre projet, puis sélectionnez Client. (Les sources pour les Ordres sont générées automatiquement à partir de la propriété de navigation Ordres dans Client.)
Cliquez sur Terminer.
Accédez à MainWindow.xaml en vue Code. Nous maintenons XAML simple pour les besoins de cet exemple. Remplacez le titre de MainWindow par quelque chose de plus descriptif et augmentez sa hauteur et sa largeur à 600 x 800 pour l’instant. Vous pouvez toujours modifier ceci ultérieurement. Ajoutez maintenant ces trois définitions de ligne à la grille principale, une ligne pour les boutons de navigation, une pour les détails du client et une pour la grille qui affiche leurs Ordres :
<Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions>
Ouvrez maintenant MainWindow.xaml afin de l’afficher dans le concepteur. Cela entraîne l’affichage de la fenêtre Sources de données en tant qu’option dans la marge de la fenêtre Visual Studio à côté de la boîte à outils. Cliquez sur l’onglet pour ouvrir la fenêtre, ou bien appuyez sur Maj+Alt+D ou choisissez Afficher>d’autres >source de données Windows. Nous allons afficher chaque propriété dans la classe Clients dans sa propre zone de texte individuelle. Tout d’abord, cliquez sur la flèche dans la zone de liste modifiable Clients, puis choisissez Détails. Ensuite, faites glisser le nœud sur la partie centrale de l’aire de conception afin que le concepteur sache que vous souhaitez qu’il se trouve dans la ligne centrale. Si vous le placez mal, vous pouvez spécifier la ligne manuellement plus loin dans le XAML (
Grid.Row="1"
). Par défaut, les contrôles sont placés verticalement dans un élément de grille, mais à ce stade, vous pouvez les organiser comme vous le souhaitez dans le formulaire. Par exemple, il peut être judicieux de placer la zone de texte Nom en haut, au-dessus de l’adresse. L’exemple d’application de cet article réorganise les champs et les réorganise en deux colonnes.Dans la vue XAML, vous pouvez maintenant voir un nouvel
Grid
élément dans la ligne 1 (la ligne du milieu) de la grille parente. La grille parente a un attributDataContext
qui fait référence à un CollectionViewSource qui a été ajouté à l’élémentWindows.Resources
. Étant donné ce contexte de données, lorsque la première zone de texte est liée à Adresse, ce nom est mappé à la propriétéAddress
dans l’objet actuelCustomer
dans leCollectionViewSource
.<Grid DataContext="{StaticResource customerViewSource}">
Lorsqu’un client est visible dans la moitié supérieure de la fenêtre, il est recommandé de voir ses Ordres dans la moitié inférieure. Vous affichez les Ordres dans un seul contrôle d’affichage de grille. Pour que la liaison de données maître-détail fonctionne comme prévu, il est important que vous liez à la propriété Ordres dans la classe Clients, et non au nœud Ordres distinct. Faites glisser la propriété Ordres de la classe Clients vers la moitié inférieure du formulaire, afin que le concepteur la place dans la ligne 2 :
Visual Studio a généré tout le code de liaison qui connecte les contrôles d’interface utilisateur aux événements du modèle. Pour afficher certaines données, il vous suffit d’écrire du code pour remplir le modèle. Tout d’abord, accédez à MainWindow.xaml.cs et ajoutez un membre de données à la classe MainWindow pour le contexte de données. Cet objet, qui a été généré pour vous, agit comme un contrôle qui effectue le suivi des modifications et des événements dans le modèle. Vous allez également ajouter des membres de données CollectionViewSource pour les clients et les Ordres, ainsi que la logique d’initialisation du constructeur associée au constructeur
MainWindow()
existant. La partie supérieure de la classe doit ressembler à ceci :public partial class MainWindow : Window { NorthwindEntities context = new NorthwindEntities(); CollectionViewSource custViewSource; CollectionViewSource ordViewSource; public MainWindow() { InitializeComponent(); custViewSource = ((CollectionViewSource)(FindResource("customerViewSource"))); ordViewSource = ((CollectionViewSource)(FindResource("customerOrdersViewSource"))); DataContext = this; }
Si ce n’est pas déjà le cas, ajoutez une directive
using
pour System.Data.Entity afin de placer la méthode d’extensionLoad
dans la portée :using System.Data.Entity;
Maintenant, faites défiler vers le bas et recherchez le gestionnaire d’événements
Window_Loaded
. Notez que Visual Studio a ajouté un objet CollectionViewSource. Cela représente l’objet NorthwindEntities que vous avez sélectionné lors de la création du modèle. Vous l’avez déjà ajouté, donc vous n’en avez pas besoin ici. Nous allons remplacer le code dansWindow_Loaded
afin que la méthode ressemble maintenant à ceci :private void Window_Loaded(object sender, RoutedEventArgs e) { // Load is an extension method on IQueryable, // defined in the System.Data.Entity namespace. // This method enumerates the results of the query, // similar to ToList but without creating a list. // When used with Linq to Entities, this method // creates entity objects and adds them to the context. context.Customers.Load(); // After the data is loaded, call the DbSet<T>.Local property // to use the DbSet<T> as a binding source. custViewSource.Source = context.Customers.Local; }
Appuyez sur F5. Vous devez voir les détails du premier client qui a été récupéré dans CollectionViewSource. Vous devez également voir leurs ordres dans la grille de données. La mise en forme n’est pas excellente. Nous allons donc l’arranger. Vous pouvez également créer un moyen d'afficher les autres enregistrements et d'effectuer des opérations créer, lire, mettre à jour et supprimer (CRUD) de base.
Ajuster la conception de la page et ajouter des grilles pour les nouveaux clients et ordres
L’organisation par défaut produite par Visual Studio n’est pas idéale pour votre application. Nous allons donc fournir ici le code XAML final à copier dans votre code. Vous avez également besoin de certains « formulaires » (qui sont en fait des grilles) pour permettre à l’utilisateur d’ajouter un nouveau client ou un nouvel ordre. Pour pouvoir ajouter un nouveau client et un ordre, vous avez besoin d’un ensemble distinct de zones de texte qui ne sont pas liées aux données de CollectionViewSource
. Vous allez contrôler la grille que l’utilisateur voit à un moment donné en définissant la propriété Visible dans les méthodes du gestionnaire. Enfin, vous ajoutez un bouton Supprimer à chaque ligne de la grille Ordres pour permettre à l’utilisateur de supprimer un Ordre individuel.
Tout d’abord, ajoutez ces styles à l’élément Windows.Resources
dans MainWindow.xaml :
<Style x:Key="Label" TargetType="{x:Type Label}" BasedOn="{x:Null}">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="3"/>
<Setter Property="Height" Value="23"/>
</Style>
<Style x:Key="CustTextBox" TargetType="{x:Type TextBox}" BasedOn="{x:Null}">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="3"/>
<Setter Property="Height" Value="26"/>
<Setter Property="Width" Value="120"/>
</Style>
Ensuite, remplacez l’intégralité de la grille externe par la majoration suivante :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="existingCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" Margin="5" Visibility="Visible" VerticalAlignment="Top" Background="AntiqueWhite" DataContext="{StaticResource customerViewSource}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="233"/>
<ColumnDefinition Width="Auto" MinWidth="397"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/>
<TextBox x:Name="customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}"
Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/>
<TextBox x:Name="companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/>
<TextBox x:Name="contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}"
Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/>
<TextBox x:Name="contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}"
Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/>
<TextBox x:Name="addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}"
Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/>
<TextBox x:Name="cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}"
Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/>
<TextBox x:Name="countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}"
Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/>
<TextBox x:Name="faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}"
Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/>
<TextBox x:Name="phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}"
Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/>
<TextBox x:Name="postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}"
Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/>
<TextBox x:Name="regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}"
Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
</Grid>
<Grid x:Name="newCustomerGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=newCustomer, UpdateSourceTrigger=Explicit}" Visibility="Collapsed" Background="CornflowerBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="233"/>
<ColumnDefinition Width="Auto" MinWidth="397"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Content="Customer ID:" Grid.Row="0" Style="{StaticResource Label}"/>
<TextBox x:Name="add_customerIDTextBox" Grid.Row="0" Style="{StaticResource CustTextBox}"
Text="{Binding CustomerID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Company Name:" Grid.Row="1" Style="{StaticResource Label}"/>
<TextBox x:Name="add_companyNameTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
Text="{Binding CompanyName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true }"/>
<Label Content="Contact Name:" Grid.Row="2" Style="{StaticResource Label}"/>
<TextBox x:Name="add_contactNameTextBox" Grid.Row="2" Style="{StaticResource CustTextBox}"
Text="{Binding ContactName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Contact title:" Grid.Row="3" Style="{StaticResource Label}"/>
<TextBox x:Name="add_contactTitleTextBox" Grid.Row="3" Style="{StaticResource CustTextBox}"
Text="{Binding ContactTitle, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Address:" Grid.Row="4" Style="{StaticResource Label}"/>
<TextBox x:Name="add_addressTextBox" Grid.Row="4" Style="{StaticResource CustTextBox}"
Text="{Binding Address, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="City:" Grid.Column="1" Grid.Row="0" Style="{StaticResource Label}"/>
<TextBox x:Name="add_cityTextBox" Grid.Column="1" Grid.Row="0" Style="{StaticResource CustTextBox}"
Text="{Binding City, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Country:" Grid.Column="1" Grid.Row="1" Style="{StaticResource Label}"/>
<TextBox x:Name="add_countryTextBox" Grid.Column="1" Grid.Row="1" Style="{StaticResource CustTextBox}"
Text="{Binding Country, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Fax:" Grid.Column="1" Grid.Row="2" Style="{StaticResource Label}"/>
<TextBox x:Name="add_faxTextBox" Grid.Column="1" Grid.Row="2" Style="{StaticResource CustTextBox}"
Text="{Binding Fax, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Phone:" Grid.Column="1" Grid.Row="3" Style="{StaticResource Label}"/>
<TextBox x:Name="add_phoneTextBox" Grid.Column="1" Grid.Row="3" Style="{StaticResource CustTextBox}"
Text="{Binding Phone, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Postal Code:" Grid.Column="1" Grid.Row="4" VerticalAlignment="Center" Style="{StaticResource Label}"/>
<TextBox x:Name="add_postalCodeTextBox" Grid.Column="1" Grid.Row="4" Style="{StaticResource CustTextBox}"
Text="{Binding PostalCode, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Region:" Grid.Column="1" Grid.Row="5" Style="{StaticResource Label}"/>
<TextBox x:Name="add_regionTextBox" Grid.Column="1" Grid.Row="5" Style="{StaticResource CustTextBox}"
Text="{Binding Region, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
</Grid>
<Grid x:Name="newOrderGrid" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5" DataContext="{Binding Path=newOrder, Mode=TwoWay}" Visibility="Collapsed" Background="LightGreen">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="233"/>
<ColumnDefinition Width="Auto" MinWidth="397"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Content="New Order Form" FontWeight="Bold"/>
<Label Content="Employee ID:" Grid.Row="1" Style="{StaticResource Label}"/>
<TextBox x:Name="add_employeeIDTextBox" Grid.Row="1" Style="{StaticResource CustTextBox}"
Text="{Binding EmployeeID, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Order Date:" Grid.Row="2" Style="{StaticResource Label}"/>
<DatePicker x:Name="add_orderDatePicker" Grid.Row="2" HorizontalAlignment="Right" Width="120"
SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
<Label Content="Required Date:" Grid.Row="3" Style="{StaticResource Label}"/>
<DatePicker x:Name="add_requiredDatePicker" Grid.Row="3" HorizontalAlignment="Right" Width="120"
SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
<Label Content="Shipped Date:" Grid.Row="4" Style="{StaticResource Label}"/>
<DatePicker x:Name="add_shippedDatePicker" Grid.Row="4" HorizontalAlignment="Right" Width="120"
SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
<Label Content="Ship Via:" Grid.Row="5" Style="{StaticResource Label}"/>
<TextBox x:Name="add_ShipViaTextBox" Grid.Row="5" Style="{StaticResource CustTextBox}"
Text="{Binding ShipVia, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
<Label Content="Freight" Grid.Row="6" Style="{StaticResource Label}"/>
<TextBox x:Name="add_freightTextBox" Grid.Row="6" Style="{StaticResource CustTextBox}"
Text="{Binding Freight, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
</Grid>
<DataGrid x:Name="ordersDataGrid" SelectionUnit="Cell" SelectionMode="Single" AutoGenerateColumns="False" CanUserAddRows="false" IsEnabled="True" EnableRowVirtualization="True" Width="auto" ItemsSource="{Binding Source={StaticResource customerOrdersViewSource}}" Margin="10,10,10,10" Grid.Row="2" RowDetailsVisibilityMode="VisibleWhenSelected">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Delete" Command="{StaticResource DeleteOrderCommand}" CommandParameter="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn x:Name="customerIDColumn" Binding="{Binding CustomerID}" Header="Customer ID" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="employeeIDColumn" Binding="{Binding EmployeeID}" Header="Employee ID" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="freightColumn" Binding="{Binding Freight}" Header="Freight" Width="SizeToHeader"/>
<DataGridTemplateColumn x:Name="orderDateColumn" Header="Order Date" Width="SizeToHeader">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn x:Name="orderIDColumn" Binding="{Binding OrderID}" Header="Order ID" Width="SizeToHeader"/>
<DataGridTemplateColumn x:Name="requiredDateColumn" Header="Required Date" Width="SizeToHeader">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding RequiredDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn x:Name="shipAddressColumn" Binding="{Binding ShipAddress}" Header="Ship Address" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="shipCityColumn" Binding="{Binding ShipCity}" Header="Ship City" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="shipCountryColumn" Binding="{Binding ShipCountry}" Header="Ship Country" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="shipNameColumn" Binding="{Binding ShipName}" Header="Ship Name" Width="SizeToHeader"/>
<DataGridTemplateColumn x:Name="shippedDateColumn" Header="Shipped Date" Width="SizeToHeader">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding ShippedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn x:Name="shipPostalCodeColumn" Binding="{Binding ShipPostalCode}" Header="Ship Postal Code" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="shipRegionColumn" Binding="{Binding ShipRegion}" Header="Ship Region" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="shipViaColumn" Binding="{Binding ShipVia}" Header="Ship Via" Width="SizeToHeader"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
Ajouter des boutons pour naviguer, ajouter, mettre à jour et supprimer
Dans les applications Windows Forms, vous obtenez un objet BindingNavigator avec des boutons permettant de parcourir les lignes d’une base de données et d’effectuer des opérations CRUD de base. WPF ne fournit pas de BindingNavigator, mais il est assez facile d’en créer un. Vous le faites avec des boutons à l’intérieur d’un StackPanel horizontal et associez les boutons à des commandes liées aux méthodes dans le code-behind.
La logique de commande comprend quatre parties : (1) les commandes, (2) les liaisons, (3) les boutons et (4) les gestionnaires de commandes dans le code-behind.
Ajouter des commandes, des liaisons et des boutons dans XAML
Tout d’abord, ajoutez les commandes dans le fichier MainWindow.xaml à l’intérieur de l’élément
Windows.Resources
:<RoutedUICommand x:Key="FirstCommand" Text="First"/> <RoutedUICommand x:Key="LastCommand" Text="Last"/> <RoutedUICommand x:Key="NextCommand" Text="Next"/> <RoutedUICommand x:Key="PreviousCommand" Text="Previous"/> <RoutedUICommand x:Key="DeleteCustomerCommand" Text="Delete Customer"/> <RoutedUICommand x:Key="DeleteOrderCommand" Text="Delete Order"/> <RoutedUICommand x:Key="UpdateCommand" Text="Update"/> <RoutedUICommand x:Key="AddCommand" Text="Add"/> <RoutedUICommand x:Key="CancelCommand" Text="Cancel"/>
Un CommandBinding mappe un événement
RoutedUICommand
à une méthode dans le code-behind. Ajoutez cet élémentCommandBindings
après la balise fermanteWindows.Resources
:<Window.CommandBindings> <CommandBinding Command="{StaticResource FirstCommand}" Executed="FirstCommandHandler"/> <CommandBinding Command="{StaticResource LastCommand}" Executed="LastCommandHandler"/> <CommandBinding Command="{StaticResource NextCommand}" Executed="NextCommandHandler"/> <CommandBinding Command="{StaticResource PreviousCommand}" Executed="PreviousCommandHandler"/> <CommandBinding Command="{StaticResource DeleteCustomerCommand}" Executed="DeleteCustomerCommandHandler"/> <CommandBinding Command="{StaticResource DeleteOrderCommand}" Executed="DeleteOrderCommandHandler"/> <CommandBinding Command="{StaticResource UpdateCommand}" Executed="UpdateCommandHandler"/> <CommandBinding Command="{StaticResource AddCommand}" Executed="AddCommandHandler"/> <CommandBinding Command="{StaticResource CancelCommand}" Executed="CancelCommandHandler"/> </Window.CommandBindings>
À présent, ajoutez
StackPanel
avec les boutons de navigation, ajouter, supprimer et mettre à jour. Tout d’abord, ajoutez ce style àWindows.Resources
:<Style x:Key="NavButton" TargetType="{x:Type Button}" BasedOn="{x:Null}"> <Setter Property="FontSize" Value="24"/> <Setter Property="FontFamily" Value="Segoe UI Symbol"/> <Setter Property="Margin" Value="2,2,2,0"/> <Setter Property="Width" Value="40"/> <Setter Property="Height" Value="auto"/> </Style>
Ensuite, collez ce code juste après le
RowDefinitions
pour l’élément externeGrid
, en haut de la page XAML :<StackPanel Orientation="Horizontal" Margin="2,2,2,0" Height="36" VerticalAlignment="Top" Background="Gainsboro" DataContext="{StaticResource customerViewSource}" d:LayoutOverrides="LeftMargin, RightMargin, TopMargin, BottomMargin"> <Button Name="btnFirst" Content="|◄" Command="{StaticResource FirstCommand}" Style="{StaticResource NavButton}"/> <Button Name="btnPrev" Content="◄" Command="{StaticResource PreviousCommand}" Style="{StaticResource NavButton}"/> <Button Name="btnNext" Content="►" Command="{StaticResource NextCommand}" Style="{StaticResource NavButton}"/> <Button Name="btnLast" Content="►|" Command="{StaticResource LastCommand}" Style="{StaticResource NavButton}"/> <Button Name="btnDelete" Content="Delete Customer" Command="{StaticResource DeleteCustomerCommand}" FontSize="11" Width="120" Style="{StaticResource NavButton}"/> <Button Name="btnAdd" Content="New Customer" Command="{StaticResource AddCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/> <Button Content="New Order" Name="btnNewOrder" FontSize="11" Width="80" Style="{StaticResource NavButton}" Click="NewOrder_click"/> <Button Name="btnUpdate" Content="Commit" Command="{StaticResource UpdateCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/> <Button Content="Cancel" Name="btnCancel" Command="{StaticResource CancelCommand}" FontSize="11" Width="80" Style="{StaticResource NavButton}"/> </StackPanel>
Ajouter des gestionnaires de commandes à la classe MainWindow
Le code-behind est minimal, à l’exception des méthodes ajouter et supprimer. La navigation s’effectue en appelant des méthodes sur la propriété View de CollectionViewSource. Le DeleteOrderCommandHandler
montre comment effectuer une suppression en cascade sur un ordre. Nous devons d’abord supprimer les Order_Details qui lui sont associées. Le UpdateCommandHandler
ajoute un nouveau client ou un nouvel ordre à la collection, ou met simplement à jour un client ou un ordre existant avec les modifications apportées par l’utilisateur dans les zones de texte.
Ajoutez ces méthodes de gestionnaire à la classe MainWindow dans MainWindow.xaml.cs. Si votre CollectionViewSource pour le tableau Clients a un nom différent, vous devez ajuster le nom dans chacune de ces méthodes :
private void LastCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
custViewSource.View.MoveCurrentToLast();
}
private void PreviousCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
custViewSource.View.MoveCurrentToPrevious();
}
private void NextCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
custViewSource.View.MoveCurrentToNext();
}
private void FirstCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
custViewSource.View.MoveCurrentToFirst();
}
private void DeleteCustomerCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
// If existing window is visible, delete the customer and all their orders.
// In a real application, you should add warnings and allow the user to cancel the operation.
var cur = custViewSource.View.CurrentItem as Customer;
var cust = (from c in context.Customers
where c.CustomerID == cur.CustomerID
select c).FirstOrDefault();
if (cust != null)
{
foreach (var ord in cust.Orders.ToList())
{
Delete_Order(ord);
}
context.Customers.Remove(cust);
}
context.SaveChanges();
custViewSource.View.Refresh();
}
// Commit changes from the new customer form, the new order form,
// or edits made to the existing customer form.
private void UpdateCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
if (newCustomerGrid.IsVisible)
{
// Create a new object because the old one
// is being tracked by EF now.
Customer newCustomer = new Customer
{
Address = add_addressTextBox.Text,
City = add_cityTextBox.Text,
CompanyName = add_companyNameTextBox.Text,
ContactName = add_contactNameTextBox.Text,
ContactTitle = add_contactTitleTextBox.Text,
Country = add_countryTextBox.Text,
CustomerID = add_customerIDTextBox.Text,
Fax = add_faxTextBox.Text,
Phone = add_phoneTextBox.Text,
PostalCode = add_postalCodeTextBox.Text,
Region = add_regionTextBox.Text
};
// Perform very basic validation
if (newCustomer.CustomerID.Length == 5)
{
// Insert the new customer at correct position:
int len = context.Customers.Local.Count();
int pos = len;
for (int i = 0; i < len; ++i)
{
if (String.CompareOrdinal(newCustomer.CustomerID, context.Customers.Local[i].CustomerID) < 0)
{
pos = i;
break;
}
}
context.Customers.Local.Insert(pos, newCustomer);
custViewSource.View.Refresh();
custViewSource.View.MoveCurrentTo(newCustomer);
}
else
{
MessageBox.Show("CustomerID must have 5 characters.");
}
newCustomerGrid.Visibility = Visibility.Collapsed;
existingCustomerGrid.Visibility = Visibility.Visible;
}
else if (newOrderGrid.IsVisible)
{
// Order ID is auto-generated so we don't set it here.
// For CustomerID, address, etc we use the values from current customer.
// User can modify these in the datagrid after the order is entered.
Customer currentCustomer = (Customer)custViewSource.View.CurrentItem;
Order newOrder = new Order()
{
OrderDate = add_orderDatePicker.SelectedDate,
RequiredDate = add_requiredDatePicker.SelectedDate,
ShippedDate = add_shippedDatePicker.SelectedDate,
CustomerID = currentCustomer.CustomerID,
ShipAddress = currentCustomer.Address,
ShipCity = currentCustomer.City,
ShipCountry = currentCustomer.Country,
ShipName = currentCustomer.CompanyName,
ShipPostalCode = currentCustomer.PostalCode,
ShipRegion = currentCustomer.Region
};
try
{
newOrder.EmployeeID = Int32.Parse(add_employeeIDTextBox.Text);
}
catch
{
MessageBox.Show("EmployeeID must be a valid integer value.");
return;
}
try
{
// Exercise for the reader if you are using Northwind:
// Add the Northwind Shippers table to the model.
// Acceptable ShipperID values are 1, 2, or 3.
if (add_ShipViaTextBox.Text == "1" || add_ShipViaTextBox.Text == "2"
|| add_ShipViaTextBox.Text == "3")
{
newOrder.ShipVia = Convert.ToInt32(add_ShipViaTextBox.Text);
}
else
{
MessageBox.Show("Shipper ID must be 1, 2, or 3 in Northwind.");
return;
}
}
catch
{
MessageBox.Show("Ship Via must be convertible to int");
return;
}
try
{
newOrder.Freight = Convert.ToDecimal(add_freightTextBox.Text);
}
catch
{
MessageBox.Show("Freight must be convertible to decimal.");
return;
}
// Add the order into the EF model
context.Orders.Add(newOrder);
ordViewSource.View.Refresh();
}
// Save the changes, either for a new customer, a new order
// or an edit to an existing customer or order.
context.SaveChanges();
}
// Sets up the form so that user can enter data. Data is later
// saved when user clicks Commit.
private void AddCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
existingCustomerGrid.Visibility = Visibility.Collapsed;
newOrderGrid.Visibility = Visibility.Collapsed;
newCustomerGrid.Visibility = Visibility.Visible;
// Clear all the text boxes before adding a new customer.
foreach (var child in newCustomerGrid.Children)
{
var tb = child as TextBox;
if (tb != null)
{
tb.Text = "";
}
}
}
private void NewOrder_click(object sender, RoutedEventArgs e)
{
var cust = custViewSource.View.CurrentItem as Customer;
if (cust == null)
{
MessageBox.Show("No customer selected.");
return;
}
existingCustomerGrid.Visibility = Visibility.Collapsed;
newCustomerGrid.Visibility = Visibility.Collapsed;
newOrderGrid.UpdateLayout();
newOrderGrid.Visibility = Visibility.Visible;
}
// Cancels any input into the new customer form
private void CancelCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
add_addressTextBox.Text = "";
add_cityTextBox.Text = "";
add_companyNameTextBox.Text = "";
add_contactNameTextBox.Text = "";
add_contactTitleTextBox.Text = "";
add_countryTextBox.Text = "";
add_customerIDTextBox.Text = "";
add_faxTextBox.Text = "";
add_phoneTextBox.Text = "";
add_postalCodeTextBox.Text = "";
add_regionTextBox.Text = "";
existingCustomerGrid.Visibility = Visibility.Visible;
newCustomerGrid.Visibility = Visibility.Collapsed;
newOrderGrid.Visibility = Visibility.Collapsed;
}
private void Delete_Order(Order order)
{
// Find the order in the EF model.
var ord = (from o in context.Orders.Local
where o.OrderID == order.OrderID
select o).FirstOrDefault();
// Delete all the order_details that have
// this Order as a foreign key
foreach (var detail in ord.Order_Details.ToList())
{
context.Order_Details.Remove(detail);
}
// Now it's safe to delete the order.
context.Orders.Remove(ord);
context.SaveChanges();
// Update the data grid.
ordViewSource.View.Refresh();
}
private void DeleteOrderCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
// Get the Order in the row in which the Delete button was clicked.
Order obj = e.Parameter as Order;
Delete_Order(obj);
}
Exécution de l'application
Pour démarrer le débogage, appuyez sur F5. Vous devez voir les données des clients et des ordres renseignées dans la grille, et les boutons de navigation doivent fonctionner comme prévu. Cliquez sur Valider pour ajouter un nouveau client ou un nouvel ordre au modèle une fois que vous avez entré les données. Cliquez sur Annuler pour annuler un nouveau client ou un nouveau formulaire d’ordre sans enregistrer les données. Vous pouvez apporter des modifications aux clients et aux ordres existants directement dans les zones de texte, et ces modifications sont écrites automatiquement dans le modèle.