UiPath Studioで使用するカスタムアクティビティの作成方法 つづき(GUI構築)



さて [UiPath Studioで使用するカスタムアクティビティの作成方法](https://qiita.com/masatomix/items/1f63513e80313a99faeb) で、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/4eb1ca70-5860-1d6a-1f9c-ee992d8fa939.png)

こんな感じのUiPath 用アクティビティを作ろうとしていましたが、そのつづきです。


前回までで、GUIを持たないアクティビティ
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/193f55f2-9407-bd06-f9f1-baf75dc0ea0c.png)
が、Hello world するところまでをやりました。


今回は

- C#のコード側でXAMLを指定して、アクティビティとGUIの紐付け
- そのGUIをXAMLで構築
- パスを連結する実際のロジック作成
- バージョンをインクリメントしてnupkgの再パッケージング

をやっていきます。


## Visual Studio 側での作業

前回作成済みの Visual Studio のプロジェクトを使用します。もしくは、前回の成果物は
https://github.com/masatomix/UiPath_Path/tree/0.0.1 に配置してあります。

### アクティビティとGUIの紐付け
C#のコード側でXAMLを指定して、アクティビティとGUIの紐付けをおこないます。

パスを連結するCombineアクティビティの実体は C#の Combine.csでした。このクラスに**GUIはこのXAMLで構築しますよっていう属性(JavaでいうAnnotationですかねコレ)を追記**します。具体的には以下の通り。

```C#:Combine.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.ComponentModel;

namespace Path
{
    [Designer(typeof(CombineDesigner))]   // 追記
    public sealed class Combine : CodeActivity
    {
        // 文字列型のアクティビティ入力引数を定義します
        public InArgument<string> Text { get; set; }

        public OutArgument<String> result { get; set; }  // 追記

        // アクティビティが値を返す場合は、CodeActivity<TResult> から派生して、
        // Execute メソッドから値を返します。
        protected override void Execute(CodeActivityContext context)
        {
            // テキスト型の入力引数のランタイム値を取得します
            string text = context.GetValue(this.Text);
            result.Set(context, text + " Hello world.");  // 追記
        }
    }
}
```

GUIクラス(デザイナのクラス)は CombineDesigner ですよっていう属性

```C#
[Designer(typeof(CombineDesigner))]  
```
を追記しました。


### ロジック作成
パスを連結する実際のロジックを作成します。
またまた Combine.csに追記ですが、具体的には以下の通り。

```C#:Combine.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.ComponentModel;

namespace Path
{

    [Designer(typeof(CombineDesigner))]
    public sealed class Combine : CodeActivity
    {
        [Category("Input")]
        [RequiredArgument]
        [Description("stringの配列で、連結したいファイルパスを記述します。")]
        public InArgument<String[]> pathArray { get; set; }

        [Category("Output")]
        public OutArgument<String> result { get; set; }

        // アクティビティが値を返す場合は、CodeActivity<TResult> から派生して、
        // Execute メソッドから値を返します。
        protected override void Execute(CodeActivityContext context)
        {
            var paths = pathArray.Get(context);
            var combine = System.IO.Path.Combine(paths);
            result.Set(context, combine);
        }
    }
}

```

先ほどのDesigner属性に加え、プロパティに属性を追加しています。

ひとつめは

```C#
[Category("Input")] // UiPath Studio上の Inputカテゴリに表示するって意味
[RequiredArgument] // UiPath Studio上 必須のプロパティ
[Description("stringの配列で、連結したいファイルパスを記述します。")] // UiPath Studioに表示される説明
public InArgument<String[]> pathArray { get; set; }  // パラメタはstring配列
```

ふたつめは

```C#
[Category("Output")]  // UiPath Studio上の Outputカテゴリに表示するって意味
public OutArgument<String> result { get; set; } // 戻り値はstring
```

ロジックは以下の通り。単純に、引数のpathArrayからstring配列を取り出しC#のSystem.IO.Path のメソッドでパスを生成してresult変数に結果をセットしました。

```C#
protected override void Execute(CodeActivityContext context)
{
  var paths = pathArray.Get(context);
  var combine = System.IO.Path.Combine(paths);
  result.Set(context, combine);
}
```

以上でアクティビティの実体であるCombine.csの作成は完了です。


### nupkgの再パッケージング
GUIをいじる前に復習を兼ねて、バージョンをインクリメントしてから再度ビルドして、nupkgを再パッケージングします。


アセンブリ情報のバージョンをインクリメントするため、ソリューションエクスプローラを右クリックして プロパティを選択
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/9d1359e8-1934-85b9-0eed-0bdb8137c06b.png)

アセンブリ情報を選択
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/fe0900fe-a9de-f0e3-d5dc-d77db5472a0f.png)


アセンブリバージョンが、0.0.1.0になっていると思いますので、0.0.2.0へ変更します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/f7ce20f2-13b2-2de7-7fb5-fa9ae9e17364.png)


で、再ビルドです。ビルドはStudio上で「ビルド >> ソリューションのクリーン」と「ビルド >> ソリューションのクリーン」を選択。


最後にnupkgのパッケージングは、nuget.exe です。下記のコマンドを実行。

```
Z:\git\Path\Path> nuget.exe pack Path.csproj -Prop Configuration=Release
'Path.csproj' からパッケージをビルドしています。
MSBuild auto-detection: using msbuild version '15.8.169.51996' from 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\bin'.
'V:\git\Path\Path\bin\Release' のファイルをパックしています。
メタデータに 'Path.nuspec' を使用しています。
Successfully created package 'V:\git\Path\Path\ActivitiesPathUtils.0.0.2.nupkg'.
警告: NU5101: The assembly 'lib\ActivitiesPathUtils.dll' is placed directly under 'lib' folder. It is recommended that assemblies be placed inside a framework-specific folder. Move it into a framework-specific folder.
警告: NU5102: The value "Summary of changes made in this release of the package." for ReleaseNotes is a sample value and should be removed. Replace it with an appropriate value or remove it and rebuild your package.

V:\git\Path\Path>

```

新しいバージョンとしてさっきインクリメントした 0.0.2のnupkgである、ActivitiesPathUtils.0.0.2.nupkg が出来たと表示されればOKですね。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/41102152-6c0b-66ff-8bc7-619313585f0e.png)
うん実際にありそうです。




### UiPath Studio側でアクティビティを更新
UiPath Studio側で、アクティビティをUpdateして、UiPath Studioの再起動を行います。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/e4551b67-3d26-b829-7bc5-60581abbb43d.png)


アクティビティが更新されたので、アクティビティペインにあるCombineアクティビティをまたまたドラッグアンドドロップしてみます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/e012599c-d828-e7fd-b2fa-1f839d0a19d8.png)


想定通り、InputカテゴリとOutputカテゴリが追加され、それぞれにプロパティが定義されました。

さてアクティビティを実行してみます。結果のキャプチャが薄くてわかりにくいですが、 c:\temp\hogehoge  が得られました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/a0a6cb35-5a08-f59b-f7ed-bee1c894dc4e.png)




### アクティビティのGUIを、XAMLで構築
最後というかようやくですが、、アクティビティのGUIをCombineDesigner.xaml で構築します。

初期状態はこんな感じでした。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/e247e0c8-ff56-ff09-0f3f-77b72899e131.png)
コードの初期状態はこう。

```xml:CombineDesigner.xaml
<sap:ActivityDesigner x:Class="Path.CombineDesigner"
    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">
    <Grid>
        
    </Grid>
</sap:ActivityDesigner>
```

初期状態は上記の通りGridに何も書いていないのですが、ここに「VB式を入れることが可能なコントロール、ExpressionTextBox」などを貼り付けていきます。
(参考: [アクティビティ デザイナー (Custom Activity Designer)](https://tsmatz.wordpress.com/2010/02/10/wf-4-10-custom-activity-designer/) )

具体的にはこんな感じ。。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/bcf9c733-d21c-21d8-a294-b73d874a0152.png)
コードはこんな感じ。


```xml:CombineDesigner.xaml
<sap:ActivityDesigner x:Class="Path.CombineDesigner"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="clr-namespace:System;assembly=mscorlib"
    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
    xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation" >

    <sap:ActivityDesigner.Resources>
        <ResourceDictionary>
            <sapc:ArgumentToExpressionConverter x:Key="ArgumentToExpressionConverter" />
        </ResourceDictionary>
    </sap:ActivityDesigner.Resources>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="3*"/>
            <ColumnDefinition Width="7*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Label Content="pathArray" />
        <sapv:ExpressionTextBox 
        Expression="{Binding ModelItem.pathArray, 
            ConverterParameter=In, 
            Converter={StaticResource ArgumentToExpressionConverter}, 
            Mode=TwoWay}"
        ExpressionType="{x:Type s:String[]}"
        HintText="Enter a string array"
        OwnerActivity="{Binding ModelItem}"
        MaxLines="1" Grid.Column="1" />
    </Grid>
</sap:ActivityDesigner>

```

Gridタグでレイアウトを定義し、左側に「pathArray」というラベルを、右側に ExpressionTextBox を配置します。


ExpressionTextBox タグでは、配置されるテキストボックスの定義情報を記述していきますが、

```xml
Expression="{Binding ModelItem.pathArray, 
            ConverterParameter=In, 
            Converter={StaticResource ArgumentToExpressionConverter}, 
            Mode=TwoWay}"
```

の **ModelItem.pathArray** が、Combine.csの `public InArgument<String[]> pathArray ` にマッピングを定義してるっぽいです。

```xml
ExpressionType="{x:Type s:String[]}"
```

は、その型を指定しているようです。

このようにして**GUI上のテキストボックスの値と、UiPath Studio上のプロパティペインにあるテキストボックスの相互バインディング**をしている事が分かります。

GUIの構築は以上です。。


### nupkgの再パッケージング
さあ、再度ビルド。バージョンを 0.0.3にして再度nupkgを作成、そして UiPath Studioに配置しましょう。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/dd888362-5c56-35e4-9812-3c7853cc53ec.png)
おお、、シーケンスのところのアクティビティのGUIにテキストボックスが表示されました！GUIでの編集がプロパティペインのところに反映されること、逆に、プロパティペインで修正した内容がGUI上のテキストボックスにも反映されること、を確認しておきましょう。

おつかれさまでした。
長かったですが UiPath Studioで稼働するカスタムアクティビティのGUIを、Visual Studioで作成することができましたね。。


## ソースコード
https://github.com/masatomix/UiPath_Path/tree/0.0.3
に配置しました。

ちなみに[前回記事](https://qiita.com/masatomix/items/1f63513e80313a99faeb) 完了時点のコードは
https://github.com/masatomix/UiPath_Path/tree/0.0.1
です

## 関連リンク
- [UiPath Studioで使用するカスタムアクティビティの作成方法](https://qiita.com/masatomix/items/1f63513e80313a99faeb) 事前の環境構築とかは、前回記事にご参照ください
- [【UiPath】カスタムアクティビティの作成（事前準備と実践初級）](https://qiita.com/UmegayaRollcake/items/c9ff9a01b101ba9193fc)  いつも参考にさせていただいています。感謝します！
- [【UiPath】カスタムアクティビティのプロパティを見える化する](https://qiita.com/UmegayaRollcake/items/ca6108560e3b1e183780) 感謝！
- [プライベートなnugetパッケージを作る](https://qiita.com/Temarin/items/f8847bf787c161e874e6)
- [UiPath Studio 2018.3 Early access - Beta release をさわってみた2 (カスタムアクティビティ)](https://qiita.com/masatomix/items/ae619eed7699e677338d) 2018.3方式のカスタムアクティビティ作成は、Visual Studio方式を置き換えるモノではなさそうとわかったきっかけ。。
- [UiPath/Community.Activities](https://github.com/UiPath/Community.Activities) UiPathの中のヒト(?)が公開してるサンプル
- [アクティビティ デザイナー (Custom Activity Designer)](https://tsmatz.wordpress.com/2010/02/10/wf-4-10-custom-activity-designer/) 
- [Windows Workflow Foundation × UiPath カスタムアクティビティ × 自分的作成時の注意点](https://www.kinakomotitti.net/entry/2018/07/19/201340)

以下、後日もすこし調べたさいに情報源になったサイトのリンクです

- [カスタム アクティビティ デザイナーでの ExpressionTextBox の使用](https://docs.microsoft.com/ja-jp/dotnet/framework/windows-workflow-foundation/samples/using-the-expressiontextbox-in-a-custom-activity-designer) このサイトにあるリンク：[Windows Communication Foundation (WCF) and Windows Workflow Foundation (WF) Samples for .NET Framework 4](https://www.microsoft.com/en-us/download/details.aspx?id=21459)　からサンプルをダウンロードすると、MultiAssignDesigner.xaml　ってファイルがあって、、ソレが参考になります。
- [ScriptActivityDesigner.xaml](https://github.com/UiPath/Community.Activities/blob/master/Activities/Script/UiPath.Script.Activities.Design/ScriptActivityDesigner.xaml) FileDialog をだすサンプル
- [カスタム アクティビティ デザイナーおよびテンプレートの使用](https://docs.microsoft.com/ja-jp/dotnet/framework/windows-workflow-foundation/using-custom-activity-designers-and-templates) Collapsed/Expanded のやり方や、ComboBoxのサンプルなど。


## 記事目次
- [UiPath Studioで使用するカスタムアクティビティの作成方法](https://qiita.com/masatomix/items/1f63513e80313a99faeb)
- [UiPath Studioで使用するカスタムアクティビティの作成方法 つづき(GUI構築)](https://qiita.com/masatomix/items/e75598f73b0e019044ac) 今回の記事
- [UiPathのカスタムアクティビティの配布について(Orchestrator接続なし版)](https://qiita.com/masatomix/items/9206873c2959451ac01e)  次回の記事
- [UiPathのカスタムアクティビティの配布について(Orchestrator接続あり版)](https://qiita.com/masatomix/items/af71fa799d53d3cbc08a)
-  [UiPathのカスタムアクティビティの配置場所について(アクティビティ開発者・UiPath開発者・Robot向け)](https://qiita.com/masatomix/items/b8988fcc2bd7046825f2)
- https://github.com/masatomix/UiPath_Path ソースのMaster Branch