Remplissage de formulaires
Élaborez des flux de travail pour capturer des données et créer des documents
Rick Spiewak
Cet article aborde les sujets suivants :
- Création d'activités personnalisées
- Interface avec Microsoft Office
- Transmission des données aux activités du flux de travail
- Extraction des données du flux de travail dans des documents Office
|
Cet article utilise les technologies suivantes :
Windows Workflow Foundation, Office System 2007, Visual Basic
|

Sommaire
Un flux de travail décrit une méthode d'automatisation des processus métier. Un flux de travail peut être utilisé pour gérer les requêtes des clients, traiter les déclarations de sinistre ou faire valoir des actions d'un fonds de placement. Les flux de travail peuvent démarrer lorsqu'un document arrive dans le courrier, lorsqu'une requête est reçue d'un site Web ou lorsqu'un client appelle un centre d'assistance. Des tâches prédéfinies peuvent alors être attribuées pour être réalisées, soit de façon automatisée, soit par une intervention humaine.
Par le passé, les tentatives de fournir ce type d'automation ont généralement reposé sur des systèmes de flux de travail monolithiques, autour desquels était construit le processus métier. Windows® Workflow Foundation (WF) fournit une approche basée sur les composants qui vous permet de créer des flux de travail afin de prendre en charge un processus métier, plutôt que l'inverse.
Les processus métier impliquant un flux de travail nécessitent souvent l'utilisation ou la création de documents liés au processus. Cette exigence peut survenir, par exemple, lorsqu'une demande (par exemple d'emprunt ou de rachat d'actions) a été approuvée ou a été refusée pendant le processus de flux de travail. Elle peut survenir après l'examen par un programme (automatiquement) ou par un validateur (manuellement). Une lettre peut devoir être écrite ou un relevé de soldes sous forme de tableur peut devoir être généré.
Dans cet article, je parlerai des techniques d'intégration des applications Microsoft® Office avec WF. J'aborderai l'utilisation de formulaires Infopath® et autres documents Office pour capturer des données, expliquerai comment transmettre des données à des activités ciblées et comment utiliser ces champs de données pour les décisions ainsi que pour créer ou remplir de nouveaux documents Office.
J'ai utilisé Visual Studio® 2008 pour tester un certain nombre de ces flux de travail et de scénarios d'intégration d'Office, notamment l'énumération des champs nommés, l'extraction de leurs contenus et le remplissage de documents à partir de modèles. J'ai pu écrire des activités de flux de travail personnalisées pour prendre en charge la saisie de données, le remplissage de divers types de documents et l'affichage des documents finis. J'ai également ajouté la prise en charge du concepteur pour ces activités afin que leur apparence soit différente des autres activités de flux de travail.
Conception générale
Toute conception pour l'intégration de flux de travail et d'Office possède trois composants fondamentaux : l'insertion des données dans le flux de travail, l'utilisation des données pour créer ou mettre à jour des documents Office et le stockage ou le renvoi des documents de sortie. Pour prendre en charge ces besoins, j'ai créé une série d'interfaces permettant la prise en charge d'une poignée de tâches de flux de travail discrètes par dessus les documents et applications Office sous-jacents.
La première de ces tâches consistait à insérer les noms des champs de données dans les documents Office. Même si j'utiliserai le terme générique « champ » pour décrire ceux-ci, dans le cas de Microsoft Word, il s'agit de signets. Microsoft Excel® utilise des plages nommées, les champs Infopath sont des nœuds XML et PowerPoint® possède des noms pour les formes. (Avant Office System 2007, il n'existait aucun moyen de renommer les formes dans l'interface utilisateur. Dans PowerPoint 2007, vous pouvez ouvrir le Volet Sélection pour le faire).
Vient ensuite la tâche de remplissage de ces champs avec les données. J'ai choisi de faire cela en acceptant un dictionnaire d'entrées Dictionary(Of String, String) et de faire correspondre le contenu du dictionnaire aux champs du document. J'ai en outre implémenté l'interface IDisposable afin de pouvoir nettoyer les objets COM.
Pour l'implémentation d'activités de remplissage des documents Office, j'ai utilisé une classe de base nommée OfficeFormFill. Celle-ci prend en charge le motif supprimable et inclut un concepteur partagé qui confère une uniformité visuelle aux activités. À partir de cette classe de base, j'ai ensuite dérivé mes classes d'activités personnalisées.
Le flux de travail repose sur les activités personnalisées nommées WordFormFill, ExcelFormFill, PowerPointFormFill et DataEntryActivity. Le but de DataEntryActivity est simplement de permettre l'introduction de variables liées aux flux de travail qui ne sont pas spécifiques à un document dans la structure de dictionnaire générique utilisée par les diverses activités OfficeFormFill. Elle utilise à cet effet un document Office comme modèle, énumérant les champs nommés et les présentant à l'utilisateur pour remplissage. Le modèle peut provenir de toute classe de document prise en charge, quels que soient le ou les documents cibles finaux à créer et à remplir par le flux de travail.
Insertion des données dans le flux de travail
L'une des premières difficultés auxquelles j'ai été confronté lors de l'intégration de documents Office dans les flux de travail consistait à faire apparaître les données contenues dans un document d'entrée depuis l'intérieur du flux de travail. Le paradigme standard du flux de travail repose sur la connaissance préalable des noms des propriétés associées aux activités. Les propriétés de dépendance peuvent être promues afin d'être visibles par le flux de travail ainsi que par d'autres activités. J'ai considéré ceci comme trop rigide, car cela nécessiterait que la conception globale du flux de travail soit liée à des champs spécifiques du document d'entrée. Dans le cas de l'intégration d'Office, l'activité de flux de travail sert de proxy générique pour tout document Office. Il n'est pas réaliste d'essayer de déterminer au préalable les noms des champs du document, car ceci nécessiterait une activité de flux de travail personnalisée pour les différents types de documents.
En examinant la façon dont les arguments sont transmis dans les flux de travail et par les activités d'extension, vous pouvez voir qu'ils sont transmis en tant que dictionnaire Dictionary(Of String, Object) générique. Le service d'exécution de Workflow Foundation lie alors les éléments du dictionnaire aux propriétés du flux de travail. Pour remplir des champs dans un document Office, vous avez besoin de deux informations : le nom du champ et la valeur à insérer. La stratégie générale que j'ai adoptée par le passé consistait à énumérer les champs nommés dans le document et de les faire correspondre ensuite au dictionnaire de paramètres d'entrée WF d'après leur nom. Si un champ de document correspond à une clé du dictionnaire, il est transmis au paramètre d'entrée du même nom. Dans ce cas, cependant, ce n'était pas la façon dont je pensais qu'ils devaient être gérés, car les propriétés devraient être créées sur le flux de travail pour chacun des champs de document potentiels qui pourrait être utilisé.
Plutôt que de nommer et promouvoir les propriétés d'une activité pour qu'elles correspondent aux champs du document, j'ai décidé d'utiliser un dictionnaire Dictionary(Of String, String) générique pour transmettre celles-ci. J'ai nommé ce paramètre Fields et l'utilise dans chacune des activités de flux de travail. La clé est utilisée pour faire correspondre le nom de champ. La valeur est utilisée pour remplir le contenu du champ. Ce dictionnaire Fields est ensuite transmis en tant que l'une des entrées dans les paramètres transmis au flux de travail. Par conséquent, les véritables paramètres, du moins en ce qui concerne l'intégration d'Office, sont contenus dans un dictionnaire au sein d'un dictionnaire (voir la figure 1).

Figure 1 Transmission de champs au flux de travail
Private WithEvents workflowRuntime As WorkflowRuntime = Nothing
Private workflowInstance As WorkflowInstance = Nothing
Private WithEvents waitHandle As New AutoResetEvent(False)
Public Sub main()
Dim WorkflowType As Type = GetType(Workflow4)
Dim Params As New Dictionary(Of String, Object)
Dim Fields As New Dictionary(Of String, String)
Params.Add("Fields", Fields)
' Start the workflow
workflowRuntime = New WorkflowRuntime
workflowInstance = _
workflowRuntime.CreateWorkflow(WorkflowType, Params)
workflowInstance.Start()
waitHandle.WaitOne()
Je voulais que toutes les activités de flux de travail soient utiles dans une large mesure et que plus d'une stratégie soit disponible pour spécifier le document ou modèle d'entrée. Pour atteindre cet objectif, toutes les activités ont également en commun le paramètre InputDocument. Il s'agit d'une propriété de dépendance et elle peut donc être promue selon les besoins du flux de travail. Elle contient le chemin vers un document ou modèle d'entrée. Cependant, le code permet également l'utilisation d'un paramètre Field dont le nom correspond au nom de l'activité s'il contient un chemin vers un document ou un modèle adapté à l'application Office cible.
Tout véritable flux de travail possède une source de données d'entrée. Pour la démonstration, j'ai utilisé une activité DataEntry, qui peut déduire son modèle (les champs et les valeurs par défaut) de n 'importe quel type de document Office pris en charge. Elle affiche un formulaire d'entrée, qui permet à l'utilisateur de spécifier une activité cible. Il peut s'agir d'une activité OfficeFormFill particulière ou d'une activité composite. Mon exemple est une activité IfElseActivity. Le code doit seulement avoir connaissance de la propriété Fields générale utilisée par toutes les activités OfficeFormFill.
Traitement des données
Comme je l'ai fait remarquer précédemment, chacun des types de document Office possède sa propre collection de champs nommés. Chacune des activités dérivées d'OfficeFormFill a été écrite pour prendre en charge un type de document particulier. Bien qu'il soit certainement possible de combiner des fonctions qui référencent plusieurs types de document (DataEntryActivity en était un exemple), il existe un inconvénient : si vous référencez l'un des assemblys PIA (Primary Interop Assemblies) d'Office dans votre code et qu'il n'est pas présent sur le système sur lequel votre code est déployé, une exception peut être levée, même si le chemin d'exécution n'utilise pas le PIA spécifique. Par exemple, une instruction Select Case ou If qui semble contourner le composant absent se traduira malgré tout par une exception. C'est pourquoi il est judicieux d'isoler les appels aux composants Office. Si vous décidez de créer une activité combinée, veillez à conserver cette isolation. Bien entendu, testez toujours votre application dans tous les environnements cibles potentiels.
Les activités qui prennent en charge chacun des types de document Office suivent toutes le même motif. Si la propriété InputDocument est fournie, elle est utilisée comme chemin vers le document ou modèle. Si la propriété InputDocument est null, l'activité examine la propriété Fields à la recherche d'une clé correspondant au nom de l'activité. Si celle-ci est trouvée, elle est examinée pour voir si elle contient un chemin avec un suffixe correspondant au type de document que gère l'activité. Si ces conditions sont remplies, la propriété InputDocument est définie sur cette valeur.
Chaque entrée correspondante de la collection Fields est utilisée pour remplir le champ correspondant dans le document. Le résultat est placé dans le document de sortie. Soit il est transmis en tant que propriété de dépendance correspondante (OutputDocument), soit il se trouve dans la collection Fields sous la forme de l'entrée Output KeyValuePair. Dans tous les cas, si le document de sortie ne possède pas de suffixe, un suffixe par défaut approprié lui est ajouté. Ceci permet à la même valeur d'être potentiellement utilisée pour créer différents types de document ou même plusieurs documents de types différents.
Le document de sortie sera stocké au chemin spécifié. Dans la plupart des environnements de flux de travail, il s'agira d'un partage réseau ou d'une bibliothèque de documents SharePoint®. Par souci de simplicité, j'ai utilisé un chemin local.
Chacune des activités possède également un champ nommé Visible, afin d'éventuellement afficher le document pendant qu'il est rempli. La figure 2 présente le code de remplissage d'un document Word. Notez en particulier la ligne :
WordFiller = New WordInterface(InputDocument, Fields)

Figure 2 Remplissage d'un document Word
<STAThread()> Protected Overrides Function Execute _
(ByVal executionContext As ActivityExecutionContext) _
As ActivityExecutionStatus
Dim Status As ActivityExecutionStatus
' Open the target document or template
If String.IsNullOrEmpty(Me.InputDocument) Then
If Me.Fields _
IsNot Nothing AndAlso Me.Fields.ContainsKey(Me.Name) Then
' Use a field named for *this* activity if it is a document
Dim NameValue As String = Fields(Me.Name)
Select Case System.IO.Path.GetExtension(NameValue).ToLowerInvariant
Case ".doc", ".docx", ".dot", ".dotx"
InputDocument = NameValue
Case Else
Throw New ArgumentException( _
"Input Document Invalid or Missing")
End Select
End If
End If
' Create or open the required document from an input
' document or template
WordFiller = New WordInterface(InputDocument, Fields)
' Production workflow may not want to show the document
If Me.Fields.ContainsKey("Visible") Then
Boolean.TryParse(Me.Fields("Visible"), WordFiller.Visible)
End If
' Get success or failure from the Word Interface class
Dim Success As Boolean = WordFiller.FillInDocument()
WordApp = WordFiller.WordApp
If Success Then
' Find the target output document
If String.IsNullOrEmpty(Me.OutputDocument) Then
If Me.Fields.ContainsKey("Output") Then
Dim NameValue As String = Fields("Output").ToString
If NameValue.EndsWith(".doc", _
StringComparison.CurrentCultureIgnoreCase) _
OrElse NameValue.EndsWith(".docx", _
StringComparison.CurrentCultureIgnoreCase) Then
' Set the document property to the provided name
Me.OutputDocument = NameValue
Else 'Force .doc suffix
Me.OutputDocument = NameValue & ".doc"
End If
End If
End If
' Save the output
WordApp.ActiveDocument.SaveAs(Me.OutputDocument)
Cleanup()
Status = ActivityExecutionStatus.Closed
executionContext.CloseActivity()
Else
Cleanup()
Status = ActivityExecutionStatus.Closed
executionContext.CancelActivity(Me)
End If
Return Status
End Function
C'est ici que l'instance de la classe WordInterface est construite et transmet le chemin vers le document à utiliser comme modèle, avec les données de champ. Celles-ci ont simplement été stockées dans les propriétés correspondantes afin d'être utilisées par les méthodes de la classe.
La classe WordInterface fournit la fonctionnalité de remplissage du document cible (voir la figure 3). Si le document d'entrée est un modèle, une nouvelle instance du document est créée. S'il s'agit d'un document, il est ouvert en lecture seule pour empêcher tout remplacement accidentel. Cette classe hérite également sa propriété Visible de la classe de base. Notez que ce code ne contient pas de vérification d'erreur, par souci de brièveté.

Figure 3 FillInDocument
Public Overrides Function FillInDocument() As Boolean
Dim Status As Boolean = False
_WordApp = New Word.Application
WordApp.Visible = Visible
' Check for template
Select Case Path.GetExtension(Document).ToLowerInvariant
Case ".dot", ".dotx"
_WordDocument = WordApp.Documents.Add(Document)
Case ".doc", ".docx"
_WordDocument = WordApp.Documents.Open(FileName:=Document, _
ReadOnly:=True)
End Select
' Determine dictionary variables to use
' based on bookmarks in the document matching Fields entries
If WordDocument IsNot Nothing _
AndAlso WordDocument.Bookmarks.Count > 0 Then
Dim BookMark As Word.Bookmark = Nothing
For i As Integer = 1 To WordDocument.Bookmarks.Count
BookMark = WordDocument.Bookmarks(i)
Dim BookMarkName As String = BookMark.Name
Dim rng As Word.Range = BookMark.Range
If Me.Fields.ContainsKey(BookMarkName) Then
rng.Text = Fields(BookMarkName).ToString
'This results in the bookmark being lost, it needs to be replaced
WordApp.ActiveDocument.Bookmarks.Add(BookMarkName, rng)
Else
' Handle special case(s)
Select Case BookMark.Name
Case "FullName"
rng.Text = GetFullName(Fields)
End Select
End If
Next
Status = True
Else
Status = False
End If
Return Status
End Function
J'ai ajouté un nom de champ de cas spécial nommé FullName. Si le document contient un champ de ce nom, je concatène les champs d'entrée nommés Title, FirstName et LastName pour le remplir. Puisque tous les types de document Office ont des besoins similaires, cette fonction a été déplacée dans la classe OfficeInterface avec certaines autres propriétés communes. Les classes OfficeInterface gèrent également la logique de nettoyage requise pour garantir la suppression correcte des objets COM créés pour manipuler les documents Office.
Chacune des classes OfficeFormFill possède une propriété OutputDocument. Celle-ci peut être définie directement de plusieurs façons. Dans le concepteur, une propriété peut être liée à un paramètre au niveau du flux de travail (y compris les propriétés promues à partir d'autres activités) ou à une valeur constante. Au moment de l'exécution, chacun des types OfficeFormFill examinera sa propriété OutputDocument à la recherche du chemin d'enregistrement de son document. Si celle-ci n'est pas définie, il examinera sa collection Fields à la recherche d'une clé nommée Output. Si la valeur se termine par un suffixe approprié, elle est utilisée telle quelle. Si elle ne possède pas de suffixe approprié, un suffixe est ajouté. L'activité enregistre alors le document de sortie. Ceci offre une flexibilité maximale pour le choix de l'emplacement du chemin vers le document de sortie. Ici encore, comme le suffixe est omis, la même valeur peut être utilisée par n'importe lequel des types OfficeFormFill pour enregistrer le document dans son format respectif correct. (Dans Word et PowerPoint, le suffixe que j'ai ajouté correspond à la version en mode de compatibilité. Excel fournit une propriété, Excel8CompatibilityMode, qui peut être utilisée pour distinguer le type de document, afin que vous puissiez décider directement de celui-ci).
Exemple de flux de travail
La figure 4 présente l'exemple de flux de travail que j'utiliserai pour démontrer comment fonctionne l'intégration. J'examinerai tour à tour chacune des activités et expliquerai ce qu'elles font.
Figure 4 Flux de travail de création de document (Cliquez sur l'image pour l'agrandir)
Au sommet, EnterCustomerData est une instance de la classe DataEntryActivity. Elle s'appuie sur une propriété DataEntryDocument qui a été, dans le cas présent, simplement définie au moment de la conception (voir la figure 5). Cette propriété aurait également pu être définie par le programme qui a lancé le flux de travail. DataEntryActivity présente un formulaire Windows avec un contrôle DataGridView, qui est rempli en extrayant les champs nommés de DataEntryDocument. Elle indique également le chemin vers ce document. L'utilisateur peut ajouter ou modifier les valeurs de ces champs.
Figure 5 Liaisons pour l'activité EnterCustomerData (Cliquez sur l'image pour l'agrandir)
DataEntryDocument peut être tout type de document Office pris en charge. Pour cet exemple, un modèle de document Infopath est utilisé. Pour extraire les champs du document, la classe OfficeInterface appropriée est appelée. Elle charge le document cible dans l'application appropriée et énumère les champs (et leur contenu, s'il est présent). J'ai utilisé une case à cocher, configurée par défaut, pour exclure les noms par défaut des formes dans PowerPoint. Celles-ci possèdent des noms tels que TextBox 1. Le code de DataEntryActivityForm inclut une routine pour l'exclusion des noms de champ en fonction d'une expression régulière, et celle-ci est utilisée pour éviter d'inclure ces valeurs et ces noms dans le contrôle DataGridView.
L'un des champs fournis par DataEntryActivity est TargetActivity. Il s'agit simplement du nom de l'activité dont la propriété Fields peut être remplie avec les champs recueillis depuis le document d'entrée. DataEntryActivity peut ensuite cibler toute activité dérivée d'OfficeFormFill ou toute activité CompositeActivity. Pour cet article, le champ TargetActivity est affiché rempli au préalable avec CustomerScenario.
Les données de DataEntryActivity sont transmises à l'activité cible. Lorsque la cible est une activité composite, ce processus peut nécessiter de parcourir l'arborescence des activités pour trouver la propriété Fields appropriée. J'ai créé une classe WorkflowUtilities pour contenir les routines de recherche d'activités en fonction du nom ou de la propriété voulue.
Choix du chemin à prendre
L'activité CustomerScenario est une activité IfElseActivity (qui dérive de CompositeActivity). Le type IfElseActivity exécute tour à tour chaque branche à la recherche d'une valeur de retour True. Si une branche renvoie True, les autres branches ne sont pas exécutées.
La structure de CompositeActivity se trouve dans un fichier XAML (Extensible Application Markup Language) nommé Workflow4.xoml, et sa logique se situe dans le fichier de code nommé Workflow4.xoml.vb. La première partie de l'activité CustomerScenario est illustrée à la figure 6.

Figure 6 Extrait de CustomerScenario
<IfElseActivity x:Name="CustomerScenario">
<IfElseBranchActivity x:Name="ProcessApproval">
<IfElseBranchActivity.Condition>
<CodeCondition Condition="ifElseBranchActivity1Condition" />
</IfElseBranchActivity.Condition>
<CodeActivity x:Name="codeActivity1" ExecuteCode=
"{ActivityBind Workflow4,Path=codeActivity1_ExecuteCode1}" />
<ns1:WordFormFill Description="Fill in fields in a Word document"
x:Name="WriteCustomerLetter" Fields="{x:Null}"
OutputDocument="{x:Null}"
InputDocument=" C:\MSDN Workflow\Templates\Customer Letter.doc" />
</IfElseBranchActivity>
Pour le code de la branche de traitement de la décision, une valeur de retour est définie dans la propriété Result des arguments d'événement transmis à chaque branche. La décision de continuer dans une branche particulière est prise dans le code, en fonction de la valeur du champ Status transmis à l'activité. La branche libellée ProcessApproval recherche une valeur Approved. La branche ProcessDisapproval recherche Disapproved. Aucune autre valeur n'est gérée par la branche ProcessUndecided. La figure 7 illustre le code exécuté dans la branche ProcessApproval.

Figure 7 ProcessApproval
Public Sub ifElseBranchActivity1Condition(ByVal sender As Object, _
ByVal e As ConditionalEventArgs)
' Look for status matching desired value for *this* branch
If Me.Fields.ContainsKey("Status") AndAlso _
Me.Fields("Status") = "Approved" Then
' Look for a WordFormFill activity as a child to *this* branch
Dim target As Activity = _
WorkflowUtilities.FindActivityByType( _
GetType(WordFormActivity.WordFormFill), _
(DirectCast(sender, Activity)))
If target IsNot Nothing Then
Dim TargetActivity As WordFormActivity.WordFormFill = _
DirectCast(target, WordFormActivity.WordFormFill)
TargetActivity.Fields = Me.Fields
' Set input document by using field named for target activity
TargetActivity.Fields.Add(TargetActivity.Name, Me.InputDocument)
e.Result = True
Else
e.Result = False
End If
Else
e.Result = False
End If
End Sub
Les autres branches sont semblables, mais transmettent leurs données à un type d'activité PowerPointFormFill ou ExcelFormFill. Dans un véritable processus métier, elles pourraient simplement utiliser différents modèles Word pour produire par exemple une lettre personnalisée plutôt que de s'interfacer avec des types de documents Office supplémentaires.
L'instance de WordFormFill exécutée dans cette branche possède un document (modèle) d'entrée défini au moment de la conception, comme illustré à la figure 8. Notez que la propriété Fields est initialement nulle. C'est parce qu'elle est définie par programmation.
Figure 8 Propriétés de WriteCustomerLetter (Cliquez sur l'image pour l'agrandir)
Prise en charge de lecteurs
Une partie de l'approche utilisée dans le flux de travail d'exemple repose sur la capacité d'une activité à en trouver une autre. Ceci nécessite que les activités soient des activités enfantes d'une activité CompositeActivity comprenant les principaux types de flux de travail : séquentiels, machines d'état et pilotés par règles. Cela signifie que tout flux de travail principal aura ce type de relation parmi ses activités.
La classe WorkFlowUtilities fournit des méthodes pour la recherche d'une activité descendante d'après son nom ou son type dans une arborescence d'activités. Elle fournit également des fonctions pour rechercher une activité ancêtre dotée d'une propriété particulière plus haut dans l'arborescence.
Ces méthodes utilitaires prennent en charge la recherche d'activités cibles auxquelles des paramètres peuvent être transmis, de même que la recherche de la source des paramètres d'entrée. Ainsi, par exemple, lorsqu'un paramètre TargetActivity est transmis à DataEntryActivity dans sa collection Fields, elle peut le trouver n'importe où dans l'arborescence des activités, à commencer par son propre conteneur :
Dim EntryActivity As Activity = Nothing
EntryActivity = WorkflowUtilities.FindActivityByName( _
TargetActivityName, Me.Parent)
Touches finales
L'exemple de flux de travail annonce l'achèvement de l'activité. L'activité sélectionnée par le champ Status crée le document de sortie à l'emplacement spécifié. De là, il peut être récupéré par les autres activités pour d'éventuels traitements supplémentaires.
J'ai présenté une approche de conception de base pour interfacer le flux de travail avec les applications clientes Office. En adhérant aux principes de conception orientée objets, vous pouvez créer des activités de flux de travail réutilisables et prendre en charge des classes qui répondent à une variété de besoins semblables.
Rick Spiewak est ingénieur de systèmes logiciels senior auprès de The MITRE Corporation. Il travaille avec le centre des systèmes électroniques de l'armée de l'air américaine sur la planification des missions. Il travaille avec Visual Basic depuis 1993 et Microsoft .NET Framework depuis 2002. Il était l'un des testeurs de la version bêta de Visual Studio .NET 2003.