Workflow Foundation も xaml を利用しているので XAML Advent Calendar 2013 に参加してみました。他の方々に比べると異様な感じを醸し出しているのですが、まあ諦めてください。
まず Workflow Foundation での xaml ですが、2つの使われる場面があります。一つは、アクティビティデザイナーで利用するデザイナーの定義。もう一つはワークフロー自体の定義になります。
DataGridView や DataRepeater にこだわりつつ
時代は Workflow Foundation などと言い続けていたら
これからは LogicFlow だろ! と思い始めてきた
色々浅く広くやっていく古い技術者の Blog
Workflow Foundation も xaml を利用しているので XAML Advent Calendar 2013 に参加してみました。他の方々に比べると異様な感じを醸し出しているのですが、まあ諦めてください。
まず Workflow Foundation での xaml ですが、2つの使われる場面があります。一つは、アクティビティデザイナーで利用するデザイナーの定義。もう一つはワークフロー自体の定義になります。
※これは VB Advent Calendat 2011 用の投稿です
ネット上で調べ物をしていると C# で書かれている記事に出会う事が多々あると思います。基本的なロジックは、コード変換を行っているサービスを利用するなど C# を知らない人でもどうにかする方法が用意されています。ですがコードだけではなくxaml 上でも C# と VB では違いがあります。
前回の LocBAML を利用した記事にあるように、現在拙作の WFDesignerExpress を多言語対応(といっても機械翻訳での対応ですが……)を行っています。その際に非常に感じたのは、クラスやプロパティに対して属性を設定するのではなく、IRegisterMetadata.Register メソッドにて属性の設定を行うようにするのが、WPF や WF でのプログラミングを行う際に適している、というところです。
アプリケーションの多言語化対応というのは、人によっては必要な作業だと思います。MSDN でもローカライズ関係の資料が色々と掲載されており、その中の一つの方法として LocBaml ツールを利用したローカライズがあります。
アクティビティを色々作成している中で、次のようなケースがありました。
アクティビティのプロパティとしてファイル名を受け取るとして、そのファイル名で指定された画像をデザイナ上でプレビュー表示をする、というものです。その際にプロパティが String で指定されているのであればまだ簡単なのですが、アクティビティの場合ほとんどのプロパティは InArgument(T) を指定することもあり、値の変換には少し方法を変える必要があります。
メッセージボックスを表示するアクティビティを作っていて、デザイナ上でアイコンやボタンの設定をラジオボタンにて選択させようとした時、MessageBoxIcon 列挙値を変換するような IValueConverter を用意してあげれば、ラジオボタンにバインドができて非常に楽になります。ただし、その際に少しだけ注意する点がありました。
元ネタは海外の BBS から。ファイル名を指定するようなアクティビティを作ろうとした場合、やはり [ファイルを開く] ダイアログを利用して指定させたいというのはあると思います。そのような場合には以下のような感じで実装します。
・DialogPropertyValueEditor を継承したクラスを用意する
1: Imports System.Activities2: Imports System.Activities.Presentation3: Imports System.Windows.Forms4:5: Public Class FileBrowserDialogPropertyValueEditor6: Inherits PropertyEditing.DialogPropertyValueEditor7:8: Private _res As New ExecuteOtherWorkflowActivityDesigner9:10: Public Sub New()11: Me.InlineEditorTemplate = TryCast(_res.Resources("FileBrowserInlineEditorTemplate"), DataTemplate)12: End Sub13:14: Public Overrides Sub ShowDialog(ByVal propertyValue As System.Activities.Presentation.PropertyEditing.PropertyValue, ByVal commandSource As System.Windows.IInputElement)15:16: Using dlg As New OpenFileDialog17: With dlg18: .AddExtension = True19: .CheckFileExists = True20: .Multiselect = False21: .Filter = "ワークフロー(*.xaml)|*.xaml|全てのファイル(*.*)|*.*"22:23: If .ShowDialog = DialogResult.OK Then24: propertyValue.StringValue = dlg.FileName25: End If26: End With27: End Using28:29: End Sub30:31: End Class
このクラスはプロパティダイアログ上での入力時にどのような挙動をさせるかを記述するクラスとなっています。コンストラクタで、InlineEditorTemplate プロパティに何やら DataTemplate を設定していますが、これがプロパティダイアログ上で表示されるものとなります。今回は面倒だったので、アクティビティで利用するデザイナ上に定義したものを呼び出していますw
1: <DataTemplate x:Uid="DataTemplate_1" x:Key="FileBrowserInlineEditorTemplate">2: <Border x:Uid="Border_1" BorderThickness ="1">3: <Grid>4: <Grid.ColumnDefinitions>5: <ColumnDefinition Width="1*"/>6: <ColumnDefinition Width="Auto"/>7: </Grid.ColumnDefinitions>8: <TextBox Grid.Column="0" Text="{Binding StringValue}"/>9: <sapp:EditModeSwitchButton Grid.Column="1"/>10: </Grid>11: </Border>12: </DataTemplate>13:
そのテンプレートの内容はこのような感じにしてあります。テキストボックスはそのままとして EditModeSwitchButton が [...] ボタンの表示を行います。余談ですがこのテンプレートをアクティビティ側に適用した場合、何故か VS によく出てくるピンボタンに変化します。
・アクティビティの対象となるプロパティに Editor 属性を付加する
このように作成したクラスを対象となるプロパティに Editor 属性を利用して設定します。
1: <Browsable(True)>2: <Editor(GetType(FileBrowserDialogPropertyValueEditor), GetType(DialogPropertyValueEditor))>3: Public Property WorkflowFile As String
このような感じで設定します。
このように実装したアクティビティを利用するとこのように表示されます。
WorkflowFile プロパティの欄にテキストボックスとボタンが表示されているのが見えると思います。このボタンをクリックすると [ファイルを開く] ダイアログが表示され、そこで選択したファイル名がプロパティの値として設定されるようになります。
このようにアクティビティのプロパティについては、プロパティダイアログ上での入力もカスタマイズできる手段が提供されています。
ActivityDesigner を使ってカスタムアクティビティを作成する際、コンボボックスで値を指定したくなることが多々あります。通常の文字とか数字とかでしたら、ExpressionTextBox でそのまま入力するだけでも構わないのですが、内容によっては特定の選択肢に限定したい場合がどうしても出てきます。
このような場合、Activity と ActivityDesigner だけでは不足で、通常ユーザーコントロールを作成する場合にも用いる TypeConverter 的なものを用意する必要があります。実際には IValueConverter インターフェースを継承したクラスです。
1: Imports System.Activities2: Imports System.Windows.Data3: Imports System.Activities.Presentation.Model4: Imports Microsoft.VisualBasic.Activities5: Imports System.Activities.Expressions6: Imports System.Windows.Controls7:8: Public Class ComboBoxItemConverter9: Implements IValueConverter10:11: Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert12:13: Dim mItem = CType(value, ModelItem)14: If mItem IsNot Nothing Then15: Dim inArgs = TryCast(mItem.GetCurrentValue, InArgument(Of String))16: If inArgs IsNot Nothing Then17: Dim expr = inArgs.Expression18: Dim liter = TryCast(expr, Literal(Of String))19: If liter IsNot Nothing Then Return "\" + liter.Value + "\"20:21: Dim vbExpr = CType(expr, VisualBasicValue(Of String))22: If vbExpr IsNot Nothing Then Return vbExpr.ExpressionText23:24: End If25: End If26: Return Nothing27: End Function28:29: Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack30:31: Dim itemCount = CType(DirectCast(value, ComboBoxItem).Content, String)32: Dim vbArgs As New VisualBasicValue(Of String)(itemCount)33: Dim inArgs As New InArgument(Of String)(vbArgs)34: Return inArgs35:36: End Function37: End Class38:
ロジックとしてはこのような形になります。Convert メソッドはアクティビティの値を別のものに変換するロジックを、ConvertBack メソッドはデザイナ上の値をアクティビティの値に変換するロジックとなります。
これを準備した上で、上記 Conber t用クラスを xaml 上にてリソースに定義します。
1: <sad:ActivityDesigner.Resources>2: <ResourceDictionary>3: <c:ComboBoxItemConverter x:Key="comboBoxItemConverter"/>4: </ResourceDictionary>5: </sad:ActivityDesigner.Resources>
このような形で定義した上でデザイナ上の要素を記述します。
1: <ComboBox SelectedValue="{Binding Path=ModelItem.TargetType, Mode=TwoWay, Converter={StaticResource comboBoxItemConverter}}" Name="comboBox1" ItemsSource="{Binding}">2: <ComboBoxItem>SQL Server</ComboBoxItem>3: <ComboBoxItem>OLE DB</ComboBoxItem>4: </ComboBox>5:
このようにコンボボックスの SelectedValue プロパティに対して、Converter を利用するように記述します。後はアクティビティ側でこれらを定義したデザイナを利用するように System.ComponentModel.Designer 属性を用いて設定します。
実際の利用イメージは上記のようになります。アクティビティデザイナ上で定義しているので、ワークフローデザイナ上で直接入力可能になっているのがわかると思います。
よく参考にしている海外 MSDN ブログで見つけたものです。ActivityDesginer は XAML で作成するのでややわかりにくい部分があるのですが、プロパティの一覧を眺めていると大体想像できるかと思います。
1: <sad:ActivityDesigner.Icon>2: <DrawingBrush>3: <DrawingBrush.Drawing>4: <ImageDrawing ImageSource="pack://application:,,,/Images/commnet-out.png">5: <ImageDrawing.Rect>6: <Rect Location="0,0" Size="16,16" ></Rect>7: </ImageDrawing.Rect>8: </ImageDrawing>9: </DrawingBrush.Drawing>10: </DrawingBrush>11: </sad:ActivityDesigner.Icon>
今回の例は同一ソリューションにビルドアクションを Resources として組み込んだ png 画像を設定した例です。ソリューションの構成としては、[ソリューション]直下に images と言う名前のフォルダを作成、その中に comment-out.png という画像ファイルを用意している形です。
参考にしていた Blog では書き方が若干異なりますが同一の意味合いとなります。