Exemple d'application métier avec Silverlight 3 et .NET RIA Services - Partie 2 : L'accès aux données et la requête de données riches

| Brad Abrams | Yannick Aristidi (Traduction originale) | Deepin Prayag (Traduction revisitée) |

Cet article fait partie d'une série de traductions d'articles de Brad Abrams sur le développement d'applications métier avec Silverlight 3 et .NET RIA Services.

Retrouvez l'ensemble des articles de la série sur cette page : Exemple d'application métier avec Silverlight 3 et .NET RIA Services.

Commentez cet article : Commentez ♪

- Titre : Exemple d'application métier avec Silverlight 3 et .NET RIA Services - Partie 2 : L'accès aux données et la requête de données riches

- Auteurs : Brad Abrams, Yannick Aristidi (Traduction originale), Deepin Prayag (Traduction revisitée)

- Parution : 18 novembre 2011

- Mise à jour : 1 février 2012

- Licence : Copyright ® 2011 Brad Abrams. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Traduction

- L'accès aux données et la requête de données riches

- Préparation à l'accès aux données

- Requête de données riches

- Validation de la mise à jour des données

- Conclusion

- Liens

- Remerciements

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 2: Rich Data Query.

L'accès aux données et la requête de données riches

On continue notre discussion sur Silverlight 3 et la mise à jour de .NET RIA Services. J'ai mis à jour l'exemple de ma présentation du Mix09 intitulée « building business applications with Silverlight 3 ».

Vous pouvez regarder

Animation en format "flv" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://deepin.developpez.com/traductions/videos/t40f.flv

La démo requiert les éléments suivants (tout est 100 % gratuit) :

- Visual Studio 2008 Express SP1 (qui inclut SQL Server 2008 Express)

- Silverlight 3

- .NET RIA Services

Vous pouvez, de plus, télécharger les fichiers de la démo complète et jeter un œil à l'application en cours d'exécution.

Dans cet article, nous allons parler de l'accès aux données et la requête de données riches.

Préparation à l'accès aux données

Cette section ne fait pas partie de l'article original. Elle est juste là à but didactique afin de préparer l'application pour l'accès aux données.

Tout d'abord, récupérez la base NORTHWND.MDF disponible ici.

Ensuite, placez-la dans le répertoire MonApplication.web/AppData.

Puis, effectuez un clic droit sur MonApplication.web et ajoutez un nouvel item.

Allez dans Data et sélectionnez ADO.NET Entity Data Model puis nommez-le Northwind.edmx.

Une nouvelle fenêtre apparait vous demandant de choisir quel type de modèle vous désirez. Sélectionnez Generate from database.

Dans la fenêtre d'option, dans Data Source, sélectionnez Change et choisissez Microsoft SQL Server Database File.

Cliquez sur le bouton Browse de Database File Name et sélectionnez la base NORTHWND.MDF.

Vous pouvez cliquer sur le bouton Test Connection pour vous assurer que tout se passe bien.

Cliquez sur OK, et une nouvelle fenêtre apparait. Cliquez alors sur Next.

Sur la fenêtre suivante, vous allez sélectionner la table qui vous intéresse, en l'occurrence ici, il faut cocher SuperEmployees (dbo). Validez votre choix en cliquant sur Finish.

À ce stade, il est alors nécessaire de compiler à nouveau la solution afin de tout prendre en compte. Pour cela, faites un clic droit sur votre solution et sélectionnez Build Solution.

Voilà, vous avez créé votre couche d'accès aux données.

Requête de données riches

Maintenant, une fois que tout est en place, nous allons pouvoir parler de la requête de données. Quasiment toutes les applications métier traitent des données. Voyons comment le faire avec .NET RIA Services. Nous allons démarrer avec le projet web. Pour cet exemple, j'ai utilisé un modèle de données Entity Framework, mais RIA Services fonctionne très bien avec tout type de données issues d'objets ou de fichier XML, de services web au LINQ to SQL. Pour commencer, nous allons construire un modèle Entity Framework pour nos données. Pour cette procédure pas à pas, nous allons démarrer de manière simple…

J'ai mis à jour la pluralisation du nom de l'entité

Maintenant, la question qui se pose est : comment allons-nous accéder à ces données depuis notre client Silverlight ? Traditionnellement, la plupart des applications métier démarrent avec un modèle d'application 2-tiers. Ceci est source de nombreux problèmes en termes de flexibilité et d'évolutivité. De plus, cela ne fonctionne tout simplement pas avec l'architecture client Silverlight / web.

Du coup, les développeurs sont plutôt plongés dans le monde du n-tiers. Il est très facile, avec .NET RIA Services, de créer des services n-tiers qui soient flexibles et évolutifs, bâtis sur WCF et ADO.NET Data Services.

Ces .NET RIA Services modélisent la logique de notre application UI-tiers et encapsulent l'accès à nos données diverses tel que les données POCO (Plain Old CLR Object), les services Cloud comme Azure, S3, etc. via REST et autre. L'un des points intéressants de cette technologie est que l'on peut migrer d'une base de données SQL Server à un service Azure distant sans avoir à changer quoi que ce soit dans la logique de notre application.

Voyons maintenant à quel point il est aisé de créer ces .NET RIA Services.

Effectuez un clic droit sur le projet serveur (MonApplication.web) et sélectionnez ajouter une nouvelle classe Domain Service. Nommez la nouvelle classe SuperEmployeeDomainService.cs.

Dans l'assistant, sélectionnez votre source de données (ici NORTHWNDEntities). Notez que nous aurions pu choisir une classe Linq2Sql, une classe POCO, etc. Veillez également à cocher les cases Enable client access, SuperEmployees, Enable editing et Generate associated classes for metada.

Dans la classe nouvellement créée, nous avons une ébauche de toutes les méthodes CRUD pour accéder à nos données. Vous devrez bien évidemment les personnaliser pour votre application. Pour les prochaines étapes, nous allons utiliser la méthode GetSuperEmployees(), donc, nous allons devoir la modifier comme suit :

Maintenant, nous allons basculer côté client. Assurez-vous de compiler la solution pour que vous puissiez y accéder côté client directement. Ces projets sont liés parce que nous avons choisi « ASP.NET enable » dans l'assistant de nouveau projet.

Effectuez un glisser-déposer du Datagrid de la boite à outils dans la vue Home.xaml, juste après les TextBlock et avant la fermeture du StackPanel.

Modifiez alors le xaml ainsi ajouté comme suit :

Maintenant, dans le code behind (c'est-à-dire, dans le fichier Home.xaml.cs), ajoutez la clause using MonApplication.Web. Notez qu'il est intéressant que MonApplication.Web soit définie sur le serveur. Vous pouvez maintenant accéder au proxy client du serveur DomainService localement.

À la ligne 1, nous avons créé notre SuperEmployeeDomainContext. Il s'agit du SuperEmployeeDomainService côté client. Notez la convention d'appellation.

À la ligne 2, nous lions les données à la datagrid que nous avons créée précédemment. Enfin, à la ligne 3, nous chargeons les données grâce à la méthode GetSuperEmployees() que nous avons définie sur le serveur. Notez que tout cela se fait de façon asynchrone et que nous n'avons pas à nous préoccuper de la complexité de l'asynchronisation.

Et voilà le résultat ! Nous avons toutes nos entrées, mais avec un vrai site web, ne voulons-nous pas pouvoir paginer et faire en sorte que le serveur trie et filtre les résultats ? Voyons comment procéder.

Tout d'abord, effacez totalement les lignes de code que nous avons ajouté en code behind (c'est-à-dire, les trois lignes dans notre fichier Home.xaml.cs). Puis, dans la vue Home.xaml, ajoutez un DomainDataSource en faisant un glisser-déposer de la boite à outils.

Puis, éditez le code nouvellement inséré. Ajoutez en premier lieu l'espace de nom de votre application.

En 1, nous avons ajouté l'espace de nom.

En 2, nous appelons la méthode GetSuperEmployeesQuery du DomianContext spécifié en 4.

En 3, nous réglons la taille du chargement à 20. Ceci signifie que nous allons télécharger les données par groupe de 20.

Maintenant, nous allons lier tout cela au Datagrid et afficher un petit indicateur de chargement.

Pour ce faire, nous allons tout d'abord ajouter un espace de nom afin d'accéder à l'indicateur de progression.

Puis nous allons modifier notre vue comme suit :

Lorsque l'on fait un glisser-déposer d'un contrôle sur la vue, l'espace de nom est automatiquement ajouté. Ajoutons donc le DataPager de cette manière.

À la ligne 27, nous voyons le Datagrid qui est lié à la propriété DDS.Data. Puis, nous voyons un DataPager à la ligne 31, qui est lié à la même source de données. Ceci nous fournit l'interface de pagination. Notez qu'à la ligne 31 nous choisissons d'afficher dix enregistrements à la fois. Finalement, nous englobons l'ensemble dans un contrôle Activity afin d'afficher la progression.

Ce qui est pratique avec les contrôles Activity, Datagrid et DataPager, c'est que l'on peut les utiliser avec n'importe quelle source de données comme les services WCF, les services REST, etc.

Appuyez sur F5 et voyez le résultat.

Notez que nous chargeons vingt enregistrements à la fois, mais que nous n'en affichons que dix. Ainsi, si nous avançons d'une seule page, cela sera géré uniquement par le client, mais si nous avançons davantage, nous faisons un appel serveur et nous téléchargeons vingt enregistrements de plus. Notez également que le tri fonctionne aussi. Et où se trouve le code qui gère le tri ? Avons-nous écrit le code côté client ou serveur ? Pas du tout, ceci n'est que la magie de LINQ, tout cela se fait tout seul et le résultat tombe.

Nous pouvons déjà ajouter une fonction de groupement. Pour cela, nous allons tout d'abord ajouter un espace de nom comme suit :

Puis, nous ajoutons le code suivant :

Et voilà le résultat :

Maintenant, ajoutons un filtrage. Tout d'abord, ajoutons un Label et un TextBox.

Enfin, ajoutons ces filtres à notre DomainDataSource :

Lorsque nous appuyons sur F5, nous obtenons une boite de filtre et lorsque l'on tape quelque chose dedans, nous faisons un filtrage des résultats côté serveur.

Maintenant, supposons que nous voulons créer un champ de saisie automatique plutôt qu'une simple boite de texte. La première chose que nous devons faire est d'obtenir toutes les origines possibles. Notez que nous devons les obtenir à partir du serveur (le client risque en effet de ne pas toutes les avoir dans la mesure où nous chargeons les données par groupe de vingt, que nous effectuons une pagination, etc.). Pour ce faire, nous allons ajouter une méthode dans notre DomainService. Dans le fichier SuperEmployeeDomainService.cs, ajoutons tout d'abord cette classe :

Puis la méthode dans la classe SuperEmployeeDomainService qui retourne les origines :

Dans notre vue Home.xaml, remplaçons le TextBox par un AutoCompleteBox comme ceci :

Puis ajoutons le code behind pour charger tout ça :

Lançons le tout avec F5 et nous devrions obtenir cela :

Validation de la mise à jour des données

Maintenant, nous avons certes une application qui nous permet d'afficher les données de manière sophistiquée, mais les applications métier nécessitent également d'autoriser la mise à jour de données. Voyons comment procéder. Tout d'abord, remplaçons le xaml ci-dessous, sous le DomainDataService. Ceci nous fournira une belle vue master-detail.

(Vous pouvez télécharger le fichier Home.xaml).

<navigation:Page xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"
x:Class="MonApplication.Home"
xmlns:dataControls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data.DataForm.Toolkit"
xmlns:input="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:navControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:activity="clr-namespace:System.Windows.Controls;assembly=ActivityControl"
xmlns:datagroup="clr-namespace:System.Windows.Data;assembly=System.Windows.Ria.Controls"
xmlns:App="clr-namespace:MonApplication.Web"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
Title="Home"
NavigationCacheMode="Enabled"
Style="{StaticResource PageStyle}">

<Grid x:Name="LayoutRoot">
<ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}" >

<StackPanel x:Name="ContentStackPanel"   Style="{StaticResource ContentStackPanelStyle}" >

<riaControls:DomainDataSource x:Name="dds"
AutoLoad="True"
QueryName="GetSuperEmployeesQuery"
LoadSize="20">
<riaControls:DomainDataSource.DomainContext>
<App:SuperEmployeeDomainContext/>
</riaControls:DomainDataSource.DomainContext>

<riaControls:DomainDataSource.GroupDescriptors>
<datagroup:GroupDescriptor PropertyPath="Publishers" />
</riaControls:DomainDataSource.GroupDescriptors>

<riaControls:DomainDataSource.FilterDescriptors>
<datagroup:FilterDescriptorCollection>
<datagroup:FilterDescriptor PropertyPath="Origin"
Operator="StartsWith">
<datagroup:ControlParameter PropertyName="Text"
RefreshEventName="TextChanged"
ControlName="originFilterBox">
</datagroup:ControlParameter>
</datagroup:FilterDescriptor>
</datagroup:FilterDescriptorCollection>
</riaControls:DomainDataSource.FilterDescriptors>

</riaControls:DomainDataSource>

<StackPanel  Orientation="Horizontal" Margin="0,5,10,0">

<activity:Activity IsActive="{Binding IsBusy, ElementName=dds}">

<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="Origine: "></TextBlock>
<input:AutoCompleteBox x:Name="originFilterBox" Width="75" Height="30"
ValueMemberBinding="{Binding Name}"
ItemTemplate="{StaticResource OriginsDataTemplate}">
</input:AutoCompleteBox>
</StackPanel>

<data:DataGrid x:Name="dataGrid1" Height="380" Width="380"
IsReadOnly="True" AutoGenerateColumns="False"
HorizontalAlignment="Left"
HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Data, ElementName=dds}">
<data:DataGrid.Columns>
<data:DataGridTextColumn Header="Nom" Binding="{Binding Name}"/>
<data:DataGridTextColumn Header="N° Employee" Binding="{Binding EmployeeID}"/>
<data:DataGridTextColumn Header="Origine" Binding="{Binding Origin}"/>
</data:DataGrid.Columns>
</data:DataGrid>

<data:DataPager PageSize="10" Width="380"
HorizontalAlignment="Left"
Source="{Binding Data, ElementName=dds}"
Margin="0,0,2,0">
</data:DataPager>

<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<Button  x:Name="SubmitButton" Content="Envoyer" Width="105" Height="28"
Click="SubmitButton_Click"></Button>
<Button x:Name="AddNewButton" Content="Ajouter..."
Width="105" Height="28"
Margin="5,0,0,0" HorizontalAlignment="Left"
Click="AddNewButton_Click"></Button>

</StackPanel>
</StackPanel>
</activity:Activity>

<StackPanel Margin="35,95,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Height="498">
<dataControls:DataForm x:Name="DataForm1" Height="393" Width="331"
VerticalAlignment="Top"
Header="Détails des employés"
CurrentItem="{Binding SelectedItem,ElementName=dataGrid1}"
HorizontalAlignment="Left">
<dataControls:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataControls:DataField>
<TextBox Text="{Binding Name, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding EmployeeID, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Origin, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Sites, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Gender, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Publishers, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<controls:DatePicker Text="{Binding LastEdit, Mode=OneWay}"></controls:DatePicker>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Issues, Mode=TwoWay}" />
</dataControls:DataField>
</StackPanel>
</DataTemplate>
</dataControls:DataForm.EditTemplate>
</dataControls:DataForm>
</StackPanel>
</StackPanel>

</StackPanel>

</ScrollViewer>
</Grid>

</navigation:Page>

Maintenant, lançons l'application avec F5 et nous obtenons alors ceci :

Si l'on tente de modifier un enregistrement dans la vue détail, nous voyons un astérisque nous signalant que cette entrée a été modifiée et quelle devra être renvoyée au serveur. Si vous éditez une ou plusieurs entrées et que vous annulez ces modifications, l'astérisque disparaitra.

Maintenant, nous devons brancher le bouton « Envoyer ». Pour ce faire, dans votre fichier Home.xaml.cs, ajoutez le délégué suivant :

Tout d'abord, nous devons valider l'élément qui est en cours d'édition, puis nous devons juste envoyer les changements. Ces changements sont regroupés par lot et renvoyés au serveur. Puis, notre méthode Update est appelée. Notez que l'astérisque disparait alors.

Voilà qui est fait, mais qu'en est-il de la validation des données ? De base, nous avons une validation de niveau type (si c'est un entier, une chaine de caractère, etc.). Par exemple, si nous remplissons le champ EmployeeID avec une chaine de caractère, nous obtenons une erreur (N.B. Pour que cela fonctionne, vous devez lancer votre projet sans débogage, en appuyant sur CTRL-F5).

Maintenant, voyons comment aller un peu plus loin. Pour cela, nous allons éditer SuperEmployeeDomainService.metadata.cs côté serveur. Il est important de faire cela côté serveur, ainsi le système effectue toutes les vérifications dans un premier temps pour une expérience utilisateur agréable puis une seconde fois sur le serveur pour vérifier l'intégrité des données. Lorsque notre méthode Update est appelée sur notre DomainService, nous pouvons être sûrs que la validation a été faite.

Voici quelles validations nous pouvons faire :

Maintenant, il faut reconstruire la solution pour que ces modifications soient prises en compte. Lançons l'application avec CTRL-F5 et nous voyons que nos règles de validation fonctionnent.

Notons que nous pouvons naviguer entre les erreurs et que le focus se fait sur l'erreur sélectionnée.

Voilà pour ce qui est de la validation des données. Mais, qu'en est-il sur l'ajout de données ?

Pour cela, nous allons utiliser la nouvelle fenêtre enfant (ChildWindow) de Silverlight 3.

Effectuez un clic droit sur View, Add… , New Item. Sélectionnez Silverlight Child Window et nommez-la AddNewWindow.

Lions cette fenêtre à un bouton afin de permettre son affichage. Tout d'abord, ajoutez un bouton au formulaire principal (Home.xaml) pour afficher cette nouvelle fenêtre :

Puis, liez-le en code behind (Home.xaml.cs) :

Tout d'abord, ajoutez cette clause Using :

Puis écrivons notre évènement comme suit :

Et voilà le résultat :

Nous voyons que cette nouvelle fenêtre est déjà dotée d'un bouton OK et d'un bouton Cancel qui sont déjà fonctionnels (bien qu'ils ne fassent pas grand-chose). Nous n'avons qu'à ajouter un DataForm. Pour cela, nous n'avons qu'à reprendre le même que nous avons défini pour la mise à jour des données dans notre vue master/détail. (Vous pouvez télécharger ce fichier ici : AddNewWindow.xaml)

<controls:ChildWindow x:Class="MonApplication.Views.AddNewWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:dataControls="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data.DataForm.Toolkit"
Title="AddNewWindow">
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<dataControls:DataForm x:Name="newEmployeeForm" Height="393" Width="331"
VerticalAlignment="Top"
CommandButtonsVisibility="None"
Header="Add New Super Employee"
HorizontalAlignment="Left" >
<dataControls:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataControls:DataField>
<TextBox Text="{Binding Name, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding EmployeeID, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Origin, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Sites, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Gender, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Publishers, Mode=TwoWay}" />
</dataControls:DataField>
<dataControls:DataField>
<controls:DatePicker Text="{Binding LastEdit, Mode=OneWay}"></controls:DatePicker>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text="{Binding Issues, Mode=TwoWay}" />
</dataControls:DataField>
</StackPanel>
</DataTemplate>
</dataControls:DataForm.EditTemplate>
</dataControls:DataForm>

<Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23"
HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
<Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23"
HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
</Grid>
</controls:ChildWindow>

Maintenant, en code behind, nous devons procéder à ces différentes étapes. Tout d'abord, n'omettez pas d'ajouter la clause Using :

Ajoutez cette déclaration de classe :

Initialisez le constructeur :

Et gérez le bouton OK :

Voilà le résultat :

Bien, maintenant, nous allons répercuter ce changement localement (Home.xaml.cs) :

Tout d'abord, nous allons abonner l'évènement AddNewButton_Click à un évènement qui détectera la fermeture de la fenêtre AddNewWindow :

Et nous allons créer ce délégué :

Conclusion

Notre application est totalement fonctionnelle. Notez qu'afin que les changements soient répercutés sur la base de données distante, une fois la fenêtre AddNewWindow fermée, nous devons cliquer sur le bouton Envoyer.

Ceci conclut donc la deuxième partie de cette série. Dans la troisième partie nous aborderons l'authentification.

Liens

La base de données pour cet article : northwnd.mdf

Le fichier Home.xaml : Home.xaml

Le fichier AddNewWindow.xaml : AddNewWindow.xaml

Remerciements

Je tiens ici à remercier Brad Abrams de nous avoir autorisés à traduire son article.
Je remercie également djibril pour sa relecture et ses propositions.