[WF 4 (10)] アクティビティ デザイナー (Custom Activity Designer)

環境 : Visual Studio 2010 Beta 2 (.NET Framework 4)

WF 4

  1. そのアイデアとベースクラスの変更
  2. コードいらずのワークフロー (入門)
  3. ワークフローのコードと XAML の内部
  4. フローチャート (FlowCharts)
  5. アクティビティ (基礎)
  6. アクティビティコンテキスト (context) と変数 (Variable) の意義
  7. アクティビティにおけるさまざまな実行管理
  8. アクティビティのデリゲート
  9. アクティビティの非同期実行
  10. アクティビティデザイナー
  11. ブックマーク
  12. Workflow Extensions と 永続化、トラッキング
  13. デザイナーリホスティングとカスタムアプリケーション

こんにちは。

これまで、アクティビティの内部動作を中心に説明してきましたが、今回は、第 2 回でも使用したように、実際に使いやすい UI (ユーザーインタフェース) を持った Activity の Activity Designer の実装方法について説明します。

アクティビティデザイナーを作成する (基礎)

例えば、第 5 回で作成した ReadLine アクティビティ (下記) にアクティビティデザイナーを付与していきましょう。

public sealed class ReadLine : CodeActivity
{
    public InArgument<string> LineText { get; set; }
    public OutArgument<string> InputText { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        Console.WriteLine(context.GetValue(this.LineText));
        string inputText = Console.ReadLine();
        context.SetValue(InputText, inputText);
    }
}

まず、プロジェクトを右クリックして、[追加] – [新しい項目] で、[アクティビティデザイナー] を選択します。(System.Activities.Presentation.dll など、必要な dll 参照がドッと追加されます。)

すると、普通の WPF アプリケーションの構築時と同様、デザイナーがあがってきますので、ここで WPF を使ってデザインをおこないます。

しかし、ここでは、XAML を直接編集しましょう。というのは、第 2 回の Sequence アクティビティなどで使用した「アクティビティ用の入れ物」(子アクティビティを入れるコントロール) や、「VB 式を入れる入れ物」などのコントロールは、残念ながら、左のツールボックス上には入っていないためです。(ここでは、Beta 2 版を使用しています。正式出荷版では、是非既定で入っていることを期待したいです。。。なお、MSDN サブスクリプションを購入されている方は、既に英語版の Visual Studio 2010 RC を入手できますので確認してみてください。)

まず、上記の XAML 上に、必要な XML 名前空間を追加しておきましょう。(下記の太字)

<sap:ActivityDesigner x:Class="ActivityDesignerDemo.ReadLineDesigner"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
    xmlns:sad="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sadc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation">

    . . .

つぎに、デザインをおこなっていきます。
WF では、WF のアクティビティデザイナーで使用できる専用の以下のコントロールを提供しています。

  • ExpressionTextBox コントロール
    WriteLine アクティビティなど多くのアクティビティで使用されている、VB 式を入れられるコントロールです
  • WorkflowItemPresenter コントロール
    この中に、ドラッグアンドドロップで単一のアクティビティを入れることができるコントロールです
  • WorkflowItemsPresenter コントロール
    Suquence アクティビティのように、複数のアクティビティをドラッグアンドドロップで入れることができるコントロールです (セパレータとして使用する図形なども指定できます)

今回は、上記のコード (カスタムの ReadLine アクティビティ) の通り、「LineText」という InArgument と、「InputText」という OutArgument を入力させたいので、以下のように ExpressionTextBox コントロールを 2 つ配置します。

<sap:ActivityDesigner x:Class="ActivityDesignerDemo.ReadLineDesigner"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
    xmlns:sad="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sadc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation">
    <sad:ActivityDesigner.Resources>
        <ResourceDictionary>
            <sadc:ArgumentToExpressionConverter x:Key="ArgumentToExpressionConverter" />
        </ResourceDictionary>
    </sad:ActivityDesigner.Resources>
    <Grid>
        <StackPanel>
            <sapv:ExpressionTextBox
              Expression="{Binding Path=ModelItem.LineText,
                Mode=TwoWay,
                Converter={StaticResource ArgumentToExpressionConverter},
                ConverterParameter=In}"
              OwnerActivity="{Binding Path=ModelItem}"
              MinLines="1" MaxLines="1" MinWidth="50" />
            <TextBlock>=======</TextBlock>
            <sapv:ExpressionTextBox
              UseLocationExpression="True"
              Expression="{Binding Path=ModelItem.InputText,
                Mode=TwoWay,
                Converter={StaticResource ArgumentToExpressionConverter},
                ConverterParameter=Out}"
              OwnerActivity="{Binding Path=ModelItem}"
              MinLines="1" MaxLines="1" MinWidth="50" />
        </StackPanel>
    </Grid>
</sap:ActivityDesigner>

デザイナーで表示をおこなうと、下図のように表示されるはずです。

ここでは、Expression=”{Binding Path=ModelItem.LineText}” が重要です。
まず、「ModelItem」は約束語で、これから関連付けをおこなう上記の ReadLine アクティビティだと思ってください。つまり、この ExpressionTextBox コントロールに入力された値は、上記の ReadLine アクティビティの LineText 引数とバインドされます。
InputText も同様にバインドをおこなっていますが、InputText のような OutArgument では、UseLocationExpression=”True” を指定しておいてください。(この指定をおこなうと、左辺値の Expression がコントロールに適用されます。)

さいごに、上記のアクティビティデザイナーとアクティビティの関連付けをおこないます。簡単です ! クラス属性を使うだけです。

. . .

using System.ComponentModel;

. . .

[Designer(typeof(ReadLineDesigner))]
public sealed class ReadLine : NativeActivity
{
    . . .

このアクティビティをワークフローで使用すると、下図の通り、入力した結果に “Hello,” が付加されて出力されます。

実行結果 (下図) :

従来の WF では、GDI を使って描画をおこなう必要があり、大変面倒でしたが、WF 4 では、このように、WPF を使って簡単にアクティビティデザイナーを適用することができます。

 

WorkflowItemsPresenter を使用する

今度は、第 7 回で最初に作成した MySequenceActivity に Activity Designer を適用してみましょう。

まず、デザイナーで外部から子アクティビティを定義 (挿入) できるようにしておくため、第 7 回で作成した MySequenceActivity を下記 (太字) の通り変更しておいてください。

public sealed class MySequenceActivity : NativeActivity
{
    public Collection<Activity> Activities { get; set; }
    public MySequenceActivity()
    {
        Activities = new Collection<Activity>();
    }

    Variable<Int32> CurrentIndex = new Variable<Int32>();

    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        //metadata.SetImplementationChildrenCollection(Activities);
        metadata.SetChildrenCollection(Activities);
        metadata.AddImplementationVariable(CurrentIndex);
        base.CacheMetadata(metadata);
    }

    protected override void Execute(NativeActivityContext context)
    {
        MyExecute(context, null);
    }

    // コールバック用に、別メソッドにしました . . .
    private void MyExecute(NativeActivityContext context, object sender)
    {
        int currentIndex = this.CurrentIndex.Get(context);
        if (currentIndex == Activities.Count)
            return;
        Activity next = Activities[currentIndex];
        context.ScheduleActivity(next, new CompletionCallback(MyExecute));
        this.CurrentIndex.Set(context, currentIndex + 1);
    }
}

基本的な手順と注意点はさきほどと同じです。(XML 名前空間も同様に参照しておいてください。)
今度は、前述したように WorkflowItemsPresenter を使用するため、Activity Designer の XAML を下記の通り記述します。

<sap:ActivityDesigner x:Class="ActivityDesignerDemo.MySequenceActivityDesigner"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
    xmlns:sad="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sadc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation">
  <Grid>
    <StackPanel>
      <TextBlock>ここに、アクティビティをドロップ</TextBlock>
      <sad:WorkflowItemsPresenter
        Items="{Binding Path=ModelItem.Activities}">
        <sad:WorkflowItemsPresenter.SpacerTemplate>
          <DataTemplate>
            <Ellipse Width="10" Height="10" Fill="Black"/>
          </DataTemplate>
        </sad:WorkflowItemsPresenter.SpacerTemplate>
        <sad:WorkflowItemsPresenter.ItemsPanel>
          <ItemsPanelTemplate>
            <StackPanel Orientation="Vertical"/>
          </ItemsPanelTemplate>
        </sad:WorkflowItemsPresenter.ItemsPanel>
      </sad:WorkflowItemsPresenter>
    </StackPanel>
  </Grid>
</sap:ActivityDesigner>

今回は、MySequenceActivity の Activities コレクションに WorkflowItemsPresenter をバインドするため、Items=”{Binding Path=ModelItem.Activities}” と指定しています。
また、上記で、<sad:WorkflowItemsPresenter.SpacerTemplate /> は、複数のアクティビティを挿入した際のアクティビティ間 (アクティビティとアクティビティの間) の表示方法 (表示する図形など) を指定しています。今回は、直径 10 の円が作成されます。
また、<StackPanel Orientation=”Vertical”/> により、各アクティビティは縦に並ぶことになります。Parallel アクティビティのように横に並べたいときには、Orientation=”Horizontal” とします。

さて、あとは、前述の通り、このアクティビティデザイナーとアクティビティのバインドを属性で指定しますが、今回は、下記の通り、ContentProperty 属性も指定するようにしてください。(この指定をおこなうことで、出力されるマークアップで、<MySequenceActivity /> 要素の子要素が Activities プロパティに相当することを指示しています。)

. . .

using System.Windows.Markup;
...

[Designer(typeof(MySequenceActivityDesigner))]
[ContentProperty("Activities")]
public sealed class MySequenceActivity : NativeActivity
{
    . . .

このアクティビティをワークフローで使用すると、下図の通り、黒丸で区切られ、縦に並んだ形で子アクティビティが表示されることになります。

実に簡単ですね。

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s