Ich habe die letzten Wochen Beratung unter anderem zum Thema Windows Workflow Foundation gemacht. Dabei sollte das Workflow Designer-Control für die Anpassung der WFs genutzt werden. Im Großen und Ganzen ein super Tool und sehr interessant und mächtig, allerdings hatte die Demo, die wir benutzt haben einige ärgerliche Probleme. Eine Version mit ausführlicher Anleitung kann beim Artikel „Windows Workflow Foundation: Everything About Re-Hosting the Workflow Designer“ heruntergeladen werden.
1. Bei der Verwendung von Umlauten, bekommt die Anwendung arge Probleme, hier müssen einige Methoden in der Loader-Klasse angepasst werden.
In der Methode PerformFlush sollte das Encoding für den Writer gesetzt werden, hier der Auszug:244 if (rootActivity != null )
245 {
246 using ( XmlWriter xmlWriter = XmlWriter . Create(
247 this . xoml
248 , new XmlWriterSettings () { Encoding = Encoding . UTF8, OmitXmlDeclaration = false }))
249 {
250 WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer ();
251 xomlSerializer . Serialize(xmlWriter, rootActivity);
252 }
253 }
Außerdem habe ich noch die Methode PerformLoad angepasst, hier der Auszug:193 Activity rootActivity = null ;
194 using ( StreamReader sr = new StreamReader ( this . xoml, Encoding . UTF8, true ))
195 {
196 using ( XmlReader reader = XmlReader . Create( this . xoml))
197 {
198 WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer ();
199 try
200 {
201 rootActivity = xomlSerializer . Deserialize(serializationManager, reader) as Activity ;
202 }
203 catch ( Exception ex)
204 {
205 Trace . WriteLine(ex);
206 }
207 }
208 if (rootActivity != null && designerHost != null )
209 {
210 AddObjectGraphToDesignerHost(designerHost, rootActivity);
211 Type companionType = rootActivity . GetValue( WorkflowMarkupSerializer . XClassProperty) as Type ;
212 if (companionType != null )
213 SetBaseComponentClassName(companionType . FullName);
214 }
215 }
Bei der letzen Änderung bin ich mir nicht sicher, aber dennoch der Hinweis. Die Methode GetRootActivity in der Helper-Klasse könnte ebenfalls zu Problemen führen, auch hier eine kleine Anpassung:52 internal static Activity GetRootActivity( string fileName, IServiceProvider serviceProvider)
53 {
54 Activity rootActivity = null ;
55 using ( StreamReader sr = new StreamReader (fileName, Encoding . UTF8, true ))
56 {
57 using ( XmlReader reader = XmlReader . Create(sr
58 , new XmlReaderSettings () { XmlResolver = new System . Xml . XmlUrlResolver () }))
59 {
60 WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer ();
61 DesignerSerializationManager ser = new DesignerSerializationManager (serviceProvider);
62 ser . CreateSession();
63 rootActivity = xomlSerializer . Deserialize(ser, reader) as Activity ;
64 }
65 }
66 return rootActivity;
67 }
2. Die Nutzung einer Konfigurationsdatei für die möglichen Controls finde ich mehr als ungünstig, hier sollte man die AddToolboxEntries-Methode der ToolboxService-Klasse anpassen, in dem alle Assemblies (aus dem aktuellen und dessen Unterverzeichnis) durchlaufen werden und geprüft wird, ob Klassen mit der Basis-Klasse „Activity“ vorhanden sind. Außerdem nicht vergessen die Standard Activities zu laden. Das Durchlaufen der Assemblies im Ordner ist in den Code-Zeilen nicht vorhanden.
546 Assembly assembly = typeof (System . Workflow . Activities . ActiveDirectoryRole ) . Assembly;
547 LoopAssemblyTypes(lb, assembly);
548 assembly = typeof ( TerminateActivity ) . Assembly;
549 LoopAssemblyTypes(lb, assembly);
550 //VS2008 required
551 assembly = typeof ( SendActivity ) . Assembly;
552 LoopAssemblyTypes(lb, assembly);
Hier die aufgerufene Methode (Außerdem wird der Assembly-Name als Titel verwendet):555 private void LoopAssemblyTypes( ListBox lb, Assembly assembly)
556 {
557 Type [] assemblyTypes = assembly . GetTypes();
558 bool groupSet = false ;
559 foreach ( Type assemblyType in assemblyTypes)
560 {
561 if (assemblyType . IsAbstract || ! assemblyType . IsClass || ! assemblyType . IsSubclassOf( typeof ( Activity )))
562 {
563 Debug . WriteLine( string . Format( "Class '{0}' not a valid Activity!" , assemblyType . FullName), "ToolboxService.LoopAssemblyTypes" );
564 }
565 else
566 {
567 Trace . WriteLine( string . Format( "Loading '{0}' into toolbox!" , assemblyType . FullName), "ToolboxService.LoopAssemblyTypes" );
568 SelfHostToolboxItem item = new SelfHostToolboxItem (assemblyType, assemblyType . FullName);
569 tbItems . Add(item);
570 if ( ! groupSet)
571 {
572 lb . Items . Add(assembly . FullName . Split( new char [] { ',' }, 2 )[ 0 ]);
573 groupSet = true ;
574 }
575 lb . Items . Add(item);
576 }
577 }
578 }
3. Übergeben der Typen an den TypeProvider dazu die Methode Initialize des Loaders erweitern. (Wird der TypeProvider nicht erweitert mit den Assemblies und dieser auch überall übergeben, so kann das der Grund sein, warum definierte Workflows nicht geladen werden. (Es gibt auch keine Fehlermeldung)
102 foreach ( SelfHostToolboxItem toolboxItem in toolbox . Items)
103 {
104 typeProvider . AddAssembly(toolboxItem . Compon entClass . Assembly);
105 }
106 host . AddService( typeof ( ITypeProvider ), typeProvider, true );
4. Außerdem habe ich einen ToolTip in die Anwendung integriert, der das DescriptionAttribute auswertet und ausgibt. Dazu muss kräftig in die ToolboxService-Klasse eingegriffen werden.
Die erste Änderung in der OnListBoxMouseMove692 int currentIndex = listBox . IndexFromPoint( new Point (e . X, e . Y));
693 if (currentIndex >= 0 )
694 {
695 SelfHostToolboxItem item = listBox . Items[currentIndex] as SelfHostToolboxItem ;
696 if (item == null || string . IsNullOrEmpty(item . Tooltip))
697 {
698 this . tooltip = null ;
699
700 currentHighlightItemIndex = Int32 . MinValue;
701 }
702 else
703 {
704 {
705 // ToolTip setzen
706 if (currentHighlightItemIndex != currentIndex)
707 {
708 this . tooltip = item . Tooltip;
709 currentHighlightItemIndex = currentIndex;
710 this . tp . Show(tooltip, this , new Point (e . X, e . Y));
711 }
712 }
713 }
714 }
Auf Klassenebene sind die 3 Fields definiert717 ToolTip tp = new ToolTip ();
718 string tooltip;
719 int currentHighlightItemIndex;
Außer den beschriebenen Anpassungen kann man sich durchaus auch noch kräftig in der OnDrawItem-Methode austoben, hier kann man die Anzeige den eigenen Wünschen anpassen.
Man muss/sollte in seinen Workflows-Assemblies unbedingt das XmlnsDefinitionAttribute verwenden und einen für sich eindeutigen Namespace definieren. Mittels des Attributes wird die Auflösung der Namepsaces im Xml (Xoml-Workflow) und der CLR gesteuert. Kommt es zu Problemen beim Laden der Workflows im Designer, obwohl der TypeProvider überall mitgegeben wird, so kann man sich mit einem Trick weiterhelfen. Das XmlnsDefinitionAttribute wird auf den CLR-Namespace gesetzt, zum Beispiel xmlns:cv="clr-namespace:DE.CapeVision.Workflow.MainActivities;assembly=CapeVisionMainActivityLib". Viel mehr Details dazu unter XAML Namespaces and Namespace Mapping.
3 Kommentare:
Nur um zu unterstreichen, dass die Änderung in "GetRootActivity" sehr wichtig ist: Genau an dieser Stelle ist vor dem Anwenden dieser "Patches" bei mir der Workflow-Designer unmittelbar nach der Eingabe von Umlauten in irgendeine Property abgeschmiert ;-)
Danke für die Info svenni. Ich hatte zum Glück alle Stellen gesucht und angepasst.
Es ist echt schade, dass die WF-Designer diese UTF-8 Fehler im Original hat, das finden der problematischen Stellen ist leider sehr nervig und langwierig.
Viel Spass beim Designer nutzen.
Kommentar veröffentlichen