Ich werde heute mal etwas nostalgisch in der Vergangenheit herumrühren und hier kurz beschreiben, wie man ein ItemTemplate für Visual Studio 2008 baut und individualisiert. Das heißt, wir legen mittels eines Templates zwei cs-Dateien an, eine resx-Datei und wir schreiben in irgendein Kommentar automatisiert das Geburtsdatum des Autors. Ja, ihr habt richtig gelesen. 2008. Asche über mein Haupt. Ich werd das aber demnächst für 2010 nachholen… :-) Aber da 2008 immer noch sehr häufig benutzt wird, dachte ich mir, ich mach das jetzt mal.
ItemTemplates oder auch ProjectTemplates sind die Vorlagen, die man in Visual Studio findet, wenn man ein Neues Projekt oder ein Neues Element hinzufügen möchte. Hier können auch eigene Templates geschrieben werden.

Wenn man ein neues Element hinzufügt so werden mindestens eine Datei, oder auch mehrere Dateien oder Ordner, dem Projekt hinzugefügt. In unserem Beispiel möchten wir eine Kommando-Klasse (z.B. SpeichernKommando) und ein Interface in einem separaten Use-Case-Ordner hinzufügen. Zusätzlich wollen wir noch eine Ressourcendatei anlegen. Danach wollen wir weitere Benutzereingaben entgegennehmen und ebenfalls in den Dateien hinterlegen. Hier kurz noch mal meine beiden cs-Dateien, die als Vorlage für ein Kommando dienen sollen.
SpeichernKommando.cs
1: namespace WizardBeispiel
2: {
3: public class SpeichernKommando : ISpeichernKommando
4: {
5: public SpeichernKommando()
6: {
7:
8: }
9:
10: public bool IsAvailable()
11: {
12: return true;
13: }
14:
15: public void Execute()
16: {
17: throw new NotImplementedException();
18: }
19: }
20: }
ISpeichernKommando.cs
1: namespace WizardBeispiel
2: {
3: public interface ISpeichernKommando
4: {
5: bool IsAvailable();
6: void Execute();
7: }
8: }
Zuerst erstellen wir uns eine Grundlage. Klick auf Datei –> Vorlage Exportieren
Dann befolgt ihr den Wizard. Wählt Symbolvorlage (für deutsche VS, englisch Item Template)
![clip_image001[5] clip_image001[5]](http://dotnet-forum.de/blogs/nicofranze/clip_image0015_thumb_360B8CB1.jpg)
Wählt die zu exportierende Datei aus. Obwohl hier Checkboxen sind und man ein additives Verhalten vermuten könnte, ist dem leider nicht so. Trotzdem können wir später mehr als nur eine Datei hinzufügen. Jetzt wählen wir einfach erstmal nur das Kommando.
![clip_image001[7] clip_image001[7]](http://dotnet-forum.de/blogs/nicofranze/clip_image0017_thumb_4BA5C54E.jpg)
Wählt anschließend noch einige Assemblies, die ihr gern immer mit dabei haben möchtet, wenn ihr das Template auswählt. Auch hier könnt ihr später eigene Assemblies angeben.
![clip_image001[9] clip_image001[9]](http://dotnet-forum.de/blogs/nicofranze/clip_image0019_thumb_224E7D50.jpg)
Im letzten Schritt könnt ihr ein Symbol wählen, eine Beschreibung anpassen und den Exportordner einsehen. Die Daten für das Template selbst können später alle noch verändert werden.
![clip_image001[11] clip_image001[11]](http://dotnet-forum.de/blogs/nicofranze/clip_image00111_thumb_197E7504.jpg)
Nachdem dieser Schritt ausgeführt wurde, entsteht eine zip-Datei in Eigene Dateien\Visual Studio 2008\My Exported Templates\. In meinem Beispiel beinhaltet diese 3 Dateien.
-
__TemplateIcon.ico
-
MyTemplate.vstemplate
-
SpeichernKommando.cs
Die Bilddatei zeigt das Bildchen an in Visual Studio, die cs-Datei ist die Datei, die später wirklich importiert werden soll und in der .vstemplate-Datei steht genau drin, was wohin importiert werden soll. Im Übrigen geht das hier natürlich mit VB genauso gut wie mit C#. Da es sich ja nun hier nicht mehr um das SpeichernKommando direkt handelt sondern um eine Vorlage für alle möglichen Kommandos, benennen wir die Datei einfach mal um in „Kommando.cs“. Der Name der Datei muss auch in der vsTemplate-Datei umbenannt werden. Die vsTemplate-Datei sieht nun wie folgt aus:
1: <VSTemplate Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">
2: <TemplateData>
3: <DefaultName>WizardBeispiel.cs</DefaultName>
4: <Name>WizardBeispiel</Name>
5: <Description>Beschreibung zu meinem Template</Description>
6: <ProjectType>CSharp</ProjectType>
7: <SortOrder>10</SortOrder>
8: <Icon>__TemplateIcon.ico</Icon>
9: </TemplateData>
10: <TemplateContent>
11: <References>
12: <Reference>
13: <Assembly>System</Assembly>
14: </Reference>
15: </References>
16: <ProjectItem SubType="Code" TargetFileName="$fileinputname$.cs" ReplaceParameters="true">Kommando.cs</ProjectItem>
17: </TemplateContent>
18: </VSTemplate>
Wenn man sich hier unsere cs-Datei noch einmal anschaut, dann wird man feststellen, dass alle „personalisierten“ Informationen mit Platzhaltern versehen wurden. Der Namespace, der Name sowie der Konstruktor der Klasse beinhalten nun kryptische Platzhalter wie $rootnamespace$ oder $safeitemname$. Somit ist sichergestellt, dass später die Klasse im richtigen Namespace liegt, den richtigen Namen hat und auch alles wieder sofort kompilierbar ist. Der Parameter „" ReplaceParameters="true"” sorgt dafür, dass diese Platzhalter auch wirklich ersetzt werden.
Jetzt fehlen uns ja noch weitere Dateien. Wir kopieren unsere Interface-Datei auch noch in den Ordner, wo wir gerade unsere vsTemplate-Datei entpackt haben. Außerdem kopieren wir noch eine resx-Datei hinein. Wenn ihr keine habt, erzeugt in Visual Studio irgendwo schnell eine Neue und fügt diese ein. Die .Designer.cs-Datei brauchen wir hier nicht, die erzeugen wir nachher neu. Meine wird Resource.resx heißen.
Wir haben ja im Wizard gesagt, das Template soll gleich wieder nach Visual Studio importiert werden. „Importiert“ heißt kopiert, und zwar nach C:\Dokumente und Einstellungen\nfranze\Eigene Dateien\Visual Studio 2008\Templates\ItemTemplates. Befindet sich diese zip-Datei in diesem Ordner, so ist die Vorlage schon hinzugefügt, wenn man ein neues Element dem Projekt hinzufügen möchte.
![clip_image001[13] clip_image001[13]](http://dotnet-forum.de/blogs/nicofranze/clip_image00113_thumb_3DC35986.jpg)
Auch das Icon findet sich wieder. Wenn wir nun dieses WizardBeispiel-Element einmal testweise hinzufügen zum Projekt, so erscheint unsere Datei, nur mit dem soeben neu eingegebenen Namen. Sollte die System-Assembly noch nicht als Verweis vorliegen, so würde sie mit hinzugefügt werden. So wie es in unserem Template angegeben ist.
Aber nun weiter in unserer vstemplate-Datei. Wir fügen nun zwei neue Zeilen ein und ändern eine vorhandene noch etwas ab, damit die Dateien in unserem UC-Ordner landen. Mit den beiden neuen Dateien möchten wir zusätzlich zum Kommando noch unsere Ressourcendatei sowie unser Interface hinzufügen. Unsere Datei sieht nun wie folgt aus.
1: <TemplateContent>
2: <References>
3: <Reference>
4: <Assembly>System</Assembly>
5: </Reference>
6: </References>
7: <ProjectItem SubType="Code" TargetFileName="UC$fileinputname$\$fileinputname$.cs" OpenInEditor="true" ReplaceParameters="true">Kommando.cs</ProjectItem>
8: <ProjectItem SubType="Code" TargetFileName="UC$fileinputname$\I$fileinputname$.cs" ReplaceParameters="true">IKommando.cs</ProjectItem>
9: <ProjectItem SubType="Code" CustomTool="ResXFileCodeGenerator" TargetFileName="UC$fileinputname$\$fileinputname$Ressource.resx" ReplaceParameters="true">Resource.resx</ProjectItem>
10: </TemplateContent>
Diese Zeilen besagen nun folgendes. Erstens werden alle drei Dateien in einem neuen Unterordner erstellt, der mit UC anfängt und weiterhin den Namen der Datei beinhaltet (ohne Endung). Wenn wir also ein Element hinzufügen und es „SpeichernKommando.cs“ nennen, so entsteht automatisch erstmal ein Ordner namens „UCSpeichernKommando“. Dieser Ordner enthält drei Dateien. Eine SpeichernKommando.cs, eine ISpeichernKommando.cs sowie eine SpeichernKommandoRessource.resx.
Das OpenInEditor gibt an, welche Datei danach im Editor geöffnet werden soll. Ich finde das Kommando besser als z.B. das Interface oder die Ressourcendatei. Hier kann natürlich jeder machen was er will. Im Parameter Subtype steht drin, welcher Editor zum Öffnen benutzt werden soll.
Bei der Ressourcendatei ist es wichtig noch das CustomTool anzugeben. Dieses erzeugt automatisch die gewünschte .Designer.cs und ordnet sie der Ressourcendatei unter. Bei Ressourcendateien heißt das CustomTool, welches die Ressourcen.Designer.cs-Datei generiert „ResXFileCodeGenerator“. Solltet ihr andere Anwendungsfälle haben, so erzeugt euch einfach einmal eine entsprechende Datei ganz regulär in Visual studio und schaut euch die Dateieigenschaften an. Dort ist das CustomTool eingetragen, welches ihr hier in der Vorlage benutzen könnt.
![clip_image001[15] clip_image001[15]](http://dotnet-forum.de/blogs/nicofranze/clip_image00115_thumb_3A61C1DE.jpg)
So, mein Ordner besteht nun aus folgenden Dateien.
-
__TemplateIcon.ico
-
MyTemplate.vstemplate
-
Kommando.cs
-
IKommando.cs
-
Ressource.resx
Bevor wir aber weitermachen müssen wir noch unser Interface anpassen. Aktuell sieht es so aus:
1: namespace WizardBeispiel
2: {
3: public interface ISpeichernKommando
4: {
5: bool IsAvailable();
6: void Execute();
7: }
8: }
Hier ist noch nicht ein Platzhalter drin. Wir ändern es in folgendes ab
1: namespace $rootnamespace$.UC$fileinputname$
2: {
3: public interface $safeitemname$
4: {
5: bool IsAvailable();
6: void Execute();
7: }
8: }
Nun befindet sich auch das Interface im richtigen Namespace (auch im Unterordner) und hat auch den richtigen Namen. Der gleiche Namespace sollte auch in dem Kommando selbst gewählt werden. Hier braucht man kein großes I vor den Platzhalter machen, das macht VS anscheinend automatisch.
In unserer Kommando.cs sollte das Interface aber auch benutzt werden. (Man beachte hier aber das I)
public class $safeitemname$ : I$safeitemname$
Diese Dateien zippen wir nun und kopieren sie nach „C:\Dokumente und Einstellungen\nfranze\Eigene Dateien\Visual Studio 2008\Templates\ItemTemplates“. Schon können wir in Visual Studio „Neues Element“ hinzufügen und unsere Vorlage auswählen. Probieren wir das mal.
![clip_image001[17] clip_image001[17]](http://dotnet-forum.de/blogs/nicofranze/clip_image00117_thumb_773643B0.jpg)
Et voilà
![clip_image001[19] clip_image001[19]](http://dotnet-forum.de/blogs/nicofranze/clip_image00119_thumb_48FC47F6.jpg)
Und es kann sofort wieder kompiliert werden… Doll. Jetzt kommt der eigentliche Spaß. Ich möchte dass im Kommando automatisch das Geburtsdatum des Autors steht. Da Visual Studio das nicht erraten kann, muss es während des Hinzufügens irgendwo angegeben werden. Wir definieren in unserem Kommando die Stelle, wo wir das gern hinhaben möchten. Bei mir sieht das nun so aus, dass ich es über meiner Klasse eingefügt hab:
// $username$ hat Geburtstag am $geburtsdatum$
public class $safeitemname$ : I$safeitemname$
{
Den Platzhalter $username$ gibt es schon, den Platzhalter $geburtsdatum$ werden wir nachher selbst ersetzen. Im Übrigen gibt es folgende vordefinierte Platzhalter (Quelle: http://msdn.microsoft.com/en-us/magazine/cc188697.aspx):
|
Parameter
|
Beschreibung
|
|
itemname
|
The user-provided name from the dialog. This value is often used for naming classes and files.
|
|
safeitemname
|
The same as itemname but with all unsafe characters removed.
|
|
safeitemrootname
|
The name of the root item, which can be used for multi-item templates. For example, if you have partial classes, or a code-beside or code-behind scenario, you can use this item in the secondary files when you need the safeitemname of the main item being created.
|
|
projectname
|
The user-provided name of the project for a project template.
|
|
safeprojectname
|
The user-provided name of the project for a project template with all unsafe characters removed.
|
|
rootnamespace
|
The root namespace of the project for an item template, which can be used to replace the namespace in a project item source file.
|
|
guid[1-10]
|
A GUID that can be used as a unique identifier for uses such as project GUID in the project file. You can specify up to 10 different GUID parameters using the syntax: guidx, where x is a number between 1 and 10.
|
|
time
|
The current time in the format DD/MM/YYYY HH:MM:SS.
|
|
year
|
The current four-digit year.
|
|
username
|
The Windows username of the logged-in user.
|
|
userdomain
|
The Windows domain of the logged-in user.
|
|
machinename
|
The name of the machine where the template is being used.
|
|
clrversion
|
The version of the common language runtime being used.
|
|
registered-organization
|
The registered organization of the user based on the system settings.
|
|
wizarddata
|
A single string that can be any XML or string data included in the WizardData element in a template metadata file.
|
Zuerst erzeugen wir ein neues Visual Studio Projekt vom Typ Klassenbibliothek. Als Verweise fügen wir folgende ein:
Die vorgefertigte Klasse Class1 nutzen wir gleich. Wir nennen sie „Wizard“ und implementieren das Interface „IWizard“ im namespace Microsoft.VisualStudio.TemplateWizard.
Damit diese Assembly benutzt werden kann, muss sie in den GAC. Damit sie da rein kann, muss sie signiert werden. Das geht in den Projekteigenschaften -> Tab „Signierung“. Dort Checkbox „Assembly signieren“ anhaken und in der Combobox bei Schlüsseldatei „Neu“ auswählen. In dem Fenster was erscheint, gebt ihr irgendeinen Namen für die Daten an z.B. „key“ und ein passwort braucht ihr hier nicht. Das sieht nun so aus
![clip_image001[21] clip_image001[21]](http://dotnet-forum.de/blogs/nicofranze/clip_image00121_thumb_3FC00CB5.jpg)
Als nächstes fügen wir einen winzigen Dialog (Windows Form) hinzu. Ich nenne den Dialog „DataDialog“. Auf meinem Dialog platziere ich einfach nur einen DateTimePicker. So sieht das nun bei mir aus. Wehe hier schreibt irgendeiner ins Kommentar, dass der Dialog scheiße aussieht, oder er verzweifelt den „OK“-Button sucht… Das Ausschmücken des Dialoges überlasse ich der Phantasie des Lesers.
![clip_image001[23] clip_image001[23]](http://dotnet-forum.de/blogs/nicofranze/clip_image00123_thumb_24A73DA7.jpg)
Ich mache nun einfach auf die Schnelle eine Property in den Dialog, der den Wert des Datetimepickers zurückgibt. Später muss der Dialog einfach nur mittels „x“ oben rechts geschlossen werden.
1: public DateTime Datum
2: {
3: get { return dateTimePicker1.Value; }
4: set { dateTimePicker1.Value = value; }
5: }
In dem Wizard entferne ich überall die throw NotImplementedExceptions und in der Methode RunStarted werden wir den Dialog anzeigen und selbst einen dieser Platzhalter ersetzen. Die Parameter der Methode „RunStarted“ sind automationObject, welches das DTE-Objekt ist, falls jemand hier noch mit dem CodeDOM oder anderen Visual Studio Features weiterarbeiten möchte. Weiterhin gibt es ein Dictionary. Dieses beinhaltet alle Platzhalter und ihre Ersetzungen. Hier brauchen wir unser Geburtsdatum nur noch hinzufügen. Mein code sieht wie folgt aus:
1: public void BeforeOpeningFile(EnvDTE.ProjectItem projectItem)
2: {
3: }
4:
5: public void ProjectFinishedGenerating(EnvDTE.Project project)
6: {
7: }
8:
9: public void ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem)
10: {
11: }
12:
13: public void RunFinished()
14: {
15: }
16:
17: public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
18: {
19: DataDialog dialog = new DataDialog();
20: dialog.ShowDialog();
21:
22: replacementsDictionary.Add("$geburtsdatum$", dialog.Datum.ToShortDateString());
23: }
24:
25: public bool ShouldAddProjectItem(string filePath)
26: {
27: return true;
28: }
Ok, so, nun ham wa ja schon viel geschafft. Wir kompilieren alles und werden nun unsere Assembly in den GAC einfügen. Das geht wie folgt. Im Startmenu „Microsoft Visual Studio 2008“->“Visual Studio Tools“->“Visual Studio 2008-Eingabeaufforderung“. Wichtig ist, IM ADMINISTRATORMODUS STARTEN (rechte Maustaste als Admin ausführen). Folgende Zeile eingeben:
Gacutil /i „C:\Dokumente und Einstellungen\nfranze\Eigene Dateien\Visual Studio 2008\Projects\ItemWizard\ItemWizard\ItemWizard\bin\Debug\ItemWizard.dll“
Wobei ihr natürlich hoffentlich euren Pfad nehmt… ;-)
Als letzte kleine Änderung müssen wir unseren Wizard nun auch noch bekannt machen in unserem Template. Dafür bearbeiten wir die vstemplate-Datei wie folgt.
1: <VSTemplate Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">
2: <TemplateData>
3: ...
4: </TemplateData>
5: <TemplateContent>
6: ...
7: </TemplateContent>
8: <WizardExtension>
9: <Assembly>ItemWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=859062e4da6d855b</Assembly>
10: <FullClassName>ItemWizard.Wizard</FullClassName>
11: </WizardExtension>
12: </VSTemplate>
So, nun haben wirs. Nehmt euch wieder euren Ordner mit den ganzen Dateien drin, macht wieder eine zip draus und kopiert die Datei ins ItemTemplate-Verzeichnis. Und dann probiert mal, ob das so klappt.
Solltet ihr euch manchmal wundern, dass eure Änderungen im Wizard nicht übernommen werden, wenn ihr neu kompiliert und die Datei neu dem GAC zufügt, so startet mal Visual Studio neu.
Weiterhin habe ich einige Dokumentationen gesehen, in denen bei <WizardExtension><Assembly> immer nur der Assemblyname angegeben wurde, ohne Version und PublicKeyToken. Dies kann manchmal folgenden Fehler verursachen:
„Fehler: Diese Vorlage hat versucht, die nicht vertrauenswürdige Komponente „ItemWizard“ hinzuzufügen. Weitere Informationen zu diesem Problem und zum Aktivieren dieser Vorlage finden Sie in der Dokumentation über das Anpassen von Projektvorlagen.“
![clip_image001[25] clip_image001[25]](http://dotnet-forum.de/blogs/nicofranze/clip_image00125_thumb_7C285B92.jpg)
So, und nun, liebe Entwicklungsleiter. Schreibt Vorlagen für eure Mitarbeiter und lasset sie sie benutzen. Jeder hat die gleichen Startvoraussetzungen, alle arbeiten mit gleichen Schemen, dies könnte ein großer Qualitätssprung in eurem Quellcode sein.