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 OnListBoxMouseMove
692
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 definiert
717
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.