- 投稿日:2020-03-18T23:47:07+09:00
UiPath Activity Creatorを使ってみる。(その3:Wizardでの各設定とUI・コードの関係について)
このドキュメントの概要
前回 はActivity Creator のWizardを使った簡単なアクティビティの作成とプロパティの設定について見ていきました。
今回は、最初にActivity Creator のWizardでの各設定が、最終的にUiPath Studio上でどのように見えるかを確認し、その後各設定によってどのようなコードが自動生成されているかを確認します。Wizardの設定とアクティビティのUIとの関係
Wizard上の設定に対して、アクティビティのUIがどのようになるかを確認します。まず下図のようにWizardを使ってActivityを作成します。赤字の番号は設定項目とUIの各項目を紐づけるためにつけています。
【注意事項】
今回は見た目の見やすさを優先して、プロパティ名にマルチバイト文字を使用しておりますが、Wizardが自動生成するコードは、一部で変数名等を大文字、小文字の違いだけで識別・自動生成している箇所があり、大文字小文字の区分がない日本語では、生成したコードにエラーを含んだ状態になります。そのため、特にプロパティ名については基本的にはASCII文字での利用をお勧めします。
Resourceファイルを使った多言語化にも対応していますので、マルチバイトの表示は基本的にそちらで対応した方が良いと思います。Wizard上でこのように設定したものが、UiPath Studioのアクティビティ上でどのよに反映されるかを示したものが下図になります。
・アクティビティパネル
Wizard上のアクティビティ定義の(1)Name欄がアクティビティ名に、(2)Description欄がヒントの表示になります。Wizard上のアクティビティ定義の(1)Name欄が表示名のデフォルトになります。また(3)Timeoutを有効にすると、TimeoutとContinueOnErrorの各プロパティが表示され有効となります。
プロパティ定義の(4)Categoryはカテゴリに、(5)のNameはそれぞれそのままプロパティの名称になります。
(6)Descriptionはテキストボックス内の説明になります。(7)(8)は表示上わからないので一旦飛ばして、(9)のRequiredを有効にすると、未入力時エラーのValidationが設定されるとともに、デザイナパネルのアクティビティ上にテキストボックスとして表示されます。設定と自動生成コードとの関係
Wizard上の設定によって自動生成コードが変化する箇所について確認します。
Activity定義のTimeout設定
まず(3)のTimeout設定ですが、無効の場合はアクティビティは
BaseAsyncCodeActivity
クラスの派生クラスとして定義されます。public class TestSum : BaseAsyncCodeActivityTimeout設定を有効にするとアクティビティは
ContinuableAsyncCodeActivity
クラスの派生クラスとして定義されます。public class TestSum : ContinuableAsyncCodeActivityこのContinuableAsyncCodeActivityは以下のようなTimeoutおよびContinueOnErrorのプロパティを持っており、これに関するメソッドも定義されているため、これらが機能する形になります。
/// <summary> /// If set, continue executing the remaining activities even if the current activity has failed. /// </summary> [LocalizedCategory(nameof(Resources.Common_Category))] [LocalizedDisplayName(nameof(Resources.ContinueOnError_DisplayName))] [LocalizedDescription(nameof(Resources.ContinueOnError_Description))] public override InArgument<bool> ContinueOnError { get; set; } [LocalizedCategory(nameof(Resources.Common_Category))] [LocalizedDisplayName(nameof(Resources.Timeout_DisplayName))] [LocalizedDescription(nameof(Resources.Timeout_Description))] public InArgument<int> TimeoutMS { get; set; } = 60000; #endregionWizard上では、Timeoutの項目とContinueOnErrorが連動して設定されますがマニュアルでコードを変更すれば、Timeoutプロパティあり、ContinueOnErrorプロパティなしの組み合わせも可能です。具体的には例えば継承するクラスを
TimeoutAsyncCodeActivity
に変更し、不足するプロパティおよびメソッドを追加すればOKです。Property定義のDirectionとType
(7)Directionおよび(8)Typeの設定ですが、下記のような形で自動生成されます。
#region Properties [LocalizedDisplayName(nameof(Resources.アクティビティ名_プロパティ名1_DisplayName))] [LocalizedDescription(nameof(Resources.アクティビティ名_プロパティ名1_Description))] [LocalizedCategory(nameof(Resources.Input_Category))] public InArgument<string> プロパティ名1 { get; set; } [LocalizedDisplayName(nameof(Resources.アクティビティ名_プロパティ名2_DisplayName))] [LocalizedDescription(nameof(Resources.アクティビティ名_プロパティ名2_Description))] [LocalizedCategory(nameof(Resources.Input_Category))] public InArgument<string> プロパティ名2 { get; set; } #endregionProperty定義のOptions - Required
(9)のRequiredを有効にすると、アクティビティ上にテキストボックスが表示されるとともに、下記のようなValidationのコードが自動生成されます。コードからもわかる通りNullチェックだけしかできません。内容的には各プロパティ定義に
[RequiredArgument]
属性をつけるのと同じようです。
なお仕様上InArgument<T>
型で入力された中身のチェックは、実行時にしかできません。protected override void CacheMetadata(CodeActivityMetadata metadata) { if (プロパティ名1 == null) metadata.AddValidationError(string.Format(Resources.ValidationValue_Error, nameof(プロパティ名1))); base.CacheMetadata(metadata); }次回はアクティビティのデザイナー部分について説明します。
(その3おわり)
- 投稿日:2020-03-18T12:03:32+09:00
キャッシュ化された関連データを返すプロパティのためのヘルパ
よくある
class Hoge{ string HogeCode{get;set;} private string __hogeName =null ; //名前を取り出す。名前は別テーブルに入ってるのでHogeCodeを元に取得 string HogeName { get{ if (__hogeName==null) { __hogeName = db.GetHogeName(HogeCode) ; // DBから名前取ったりとか } return __hogeName; } }コードが長い割に大したことやってないので毎回書くのがめんどい。
てってれー
/// <summary> /// キャッシュ化された値を格納するディクショナリ /// <ul> /// <li>キャッシュされていたらキャッシュから値を返す</li> /// <li>キャッシュされていなければpresenterを使って値をキャッシュする</li> /// </ul> /// </summary> public class NameDictionary { private Dictionary<string, object> dictionary = new Dictionary<string, object>(); /// <summary> /// 値を取得する。 /// PropertyのGetterやGet**メソッドから使われることを想定 /// CallerMemberNameが取れないとエラーになるので注意 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="presenter"></param> /// <param name="PropertyName"></param> /// <returns></returns> public T Get<T>(Func<T> presenter,[CallerMemberName] string PropertyName=null) { return Get<T>(PropertyName, presenter); } /// <summary> /// 値を取得する。 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="KeyName"></param> /// <param name="presenter"></param> /// <returns></returns> public T Get<T>(string KeyName,Func<T> presenter) { if (!dictionary.ContainsKey(KeyName)) { dictionary.Add(KeyName, presenter()); } return (T)dictionary[KeyName]; } }使い方
class Hoge{ string HogeCode{get;set;} private NameDictionary dictionary = new NameDictionary(); //名前を取り出す。名前は別テーブルに入ってるのでHogeCodeを元に取得 string HogeName => dictionary.Get<string>(()=> db.GetHogeName(HogeCode) );短くかけて大変うれしい。
注意
CallerMemberName
が取れないとエラーになるので、適宜Get<T>(string,Func<T>)
を使う。
- 投稿日:2020-03-18T11:01:56+09:00
旧人類なのでUWPのDataGridにSQLで取得した値を設定したい!
この記事は
UWPのDataGridへSQLで取得した値を設定したい旧人類のためのメモ
ざっくり方法
ちなみに旧人類なのでVisual Studio 2017で試した。
テスト用のテーブルを準備する
今回はSQLServerでデータベースを作成してUsersテーブルを準備した。
Usersテーブル
UserId UserName 1 山田 太郎 2 山田 次郎 3 山田 三郎 4 山田 四郎 5 山田 五郎 6 山田 六郎 7 山田 三四郎 8 山田 八郎 9 山田 九朗 10 山田 十郎 DataGridを使えるようにする
どうもUWPには標準でDataGridないみたい?
NuGetでMicrosoft.Toolkit.Uwp.Ui.Controls.DataGridをインストールする。
https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.Ui.Controls.DataGrid/インストールするとデザイナーのツールボックスへDataGridが追加される。
コードを書く
MainPage.xaml<Page x:Class="App1.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App1" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Custom="using:Microsoft.Toolkit.Uwp.UI.Controls" mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid> <StackPanel Grid.Column="1"> <Button Name="AddButton" Content="追加" Height="30" Margin="5,5,0,5" Click="AddButton_Click" /> <Custom:DataGrid x:Name="UsersDataGrid" AutoGenerateColumns="False" ItemsSource="{Binding}"> <Custom:DataGrid.Columns> <Custom:DataGridTextColumn Header="ID" Width="100" Binding="{Binding UserId}" /> <Custom:DataGridTextColumn Header="氏名" Width="200" Binding="{Binding UserName}" /> </Custom:DataGrid.Columns> </Custom:DataGrid> </StackPanel> </Grid> </Page>気を付ける点は
DataGridタグの
AutoGenerateColumns="False"
1
ItemsSource="{Binding}"
DataGridTextColumnタグの
Binding="{Binding XXX}"
のXXXは、SQLのSELECT句のカラム名(DataColumnのColumnName)を設定する。MainPage.xaml.csusing System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Data.SqlClient; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace App1 { public sealed partial class MainPage : Page { /// <summary> /// コンストラクター /// </summary> public MainPage() { this.InitializeComponent(); // DataTableを取得 string connectionString = "環境に合わせた接続文字列"; // 接続文字列 string commandText = "SELECT UserId, UserName FROM Users"; // Usersテーブル取得SQL SqlDataAdapter adapter = new SqlDataAdapter(commandText, connectionString); DataTable table = new DataTable(); adapter.Fill(table); // DataGridへセット UsersDataGrid.DataContext = ConvertDataTable(table); } /// <summary> /// DataTableをObservableCollectionへ変換する /// </summary> /// <param name="table">DataTable</param> /// <returns>ObservableCollectionへ変換されたDataTable</returns> private ObservableCollection<Dictionary<string, object>> ConvertDataTable(DataTable table) { ObservableCollection<Dictionary<string, object>> collection = new ObservableCollection<Dictionary<string, object>>(); foreach (DataRow row in table.Rows) { Dictionary<string, object> dic = new Dictionary<string, object>(); foreach (DataColumn column in table.Columns) { dic[column.ColumnName] = row[column]; } collection.Add(dic); } return collection; } /// <summary> /// 追加ボタンクリック /// </summary> /// <param name="sender">イベント発生元</param> /// <param name="e">Routedイベントデータ</param> private void AddButton_Click(object sender, RoutedEventArgs e) { ObservableCollection<Dictionary<string, object>> collection = (ObservableCollection<Dictionary<string, object>>)UsersDataGrid.DataContext; Dictionary<string, object> dic = new Dictionary<string, object>() { {"UserId", 0}, {"UserName", "山田 零郎"} }; collection.Insert(0, dic); } } }UsersDataGridのDataContextへ
DataTable
を設定しても期待した動作にならないので、
ObservableCollection<Dictionary<string, object>>
2に変換したものを設定する。実行結果
起動時
SQLで取得した内容が表示された!
追加ボタンクリック時
先頭に行が追加された!
最後に
結局のところ、旧人類はUWPなんて使わずにWindows Formsを使う。
- 投稿日:2020-03-18T03:43:36+09:00
【Unity】全角を2、半角を1としてbyte文字数を数える
プレイヤー名やモンスターのニックネームなど文字数の制限を行いたい場合に、全角は2、半角は1として合計30まで、といった数え方で文字数を制限したいケースの実装方法です。
よくある内容ですが、実際に実装するのはなかなか手順が多く大変でした。
要点
- Shift_JISでByteにエンコードする
- 「I18N.CJK.dll」と「I18N.dll」をプロジェクトにインポートする
Shift_JISでByteにエンコードする
通常のエンコードといえばUTF8だと思いますが、UTF8だと漢字が3byte、かなが2byteだったりするため、文字数のカウントには向いてません。
そこで、文字数のカウントではShift_JISへのエンコードを行います。
using System.Text; public static class StringEncodingExtension { public static int GetSJISByteCount(this string text) { return Encoding.GetEncoding("Shift_JIS").GetByteCount(text); } }拡張メソッドで書いておけば、適当な場所でstringのbyte文字数を数えられます。
using UnityEngine; public class StringEncodingExtension { public void Start() { string nickName = "キラーマシン2"; // >>> 13 Debug.Lot(nickName.GetSJISByteCount()); } }「I18N.CJK.dll」と「I18N.dll」をプロジェクトにインポートする
Unityエディタならコード書くだけで済みますが、実は実機でやろうとするとShift_JISエンコードがサポート対象外となってエラーになってしまいます。
対応策として、「I18N.CJK.dll」と「I18N.dll」をプロジェクトにインポートさせてあげると上手くいきました。
参考
http://baba-s.hatenablog.com/entry/2019/03/18/155000
感想
完全にぼやきですが、なんで文字数数えたいだけなのにこんな苦労しなきゃならないんだ...? って思いました。
- 投稿日:2020-03-18T00:18:03+09:00
今更ながらWPFに置き換えてみる(7)
遅い原因は単にCanvasのChildrenをClearしていなかっただけの模様。
修正したところ何とか違和感ないレベルになりました。
とはいえWindowトランジションの際はメキメキパフォーマンスが落ちるのでいったん不要なコントロールは非表示に。
NotifyIconに左上のCanvasのBitmapイメージを突っ込んでいます。
左に同じようなアイコンがもう一つありますが、そちらはForms+VB版のアイコン。同じようにGDIで描画したものを動的に入れてますが16ピクセル固定のイメージをそのまま入れているので当ノートPC(125%)のスケーリングにより20ピクセルサイズになっているため、ボケ度合いが激しいです。拡大するとより分かりやすい。
NotifyIconのもとになるCanvasのサイズを16ピクセルではなく20ピクセルで作ってるので、きれいに表示されています。16のサイズで作っちゃうと
Froms版と大して変わりませんNotifyIconへのセット手順としては
①Canvas上に作成した時計のイメージを
②CanvasごとRenderTargetBitmapにして
③RenderTargetBitmapをCopyPixelsで普通のBitmap(System.Drawing)にして
④IconHandleをとって
⑤アイコン化してセット
という流れ。private void MainLoop(object sender, EventArgs e) { //毎秒処理 if (G.DispIcon != 0) { DrawClock2(this.aCanvas); aCanvas.Arrange(new Rect(aCanvas.RenderSize)); aCanvas.Measure(aCanvas.RenderSize); var bounds = VisualTreeHelper.GetDescendantBounds(aCanvas); var RTbitmap = new RenderTargetBitmap((int)bounds.Width, (int)bounds.Height, 96.0d,96.0d,PixelFormats.Pbgra32); var dv = new DrawingVisual(); using (var dc = dv.RenderOpen()) { var vb = new VisualBrush(aCanvas); dc.DrawRectangle(vb, null, bounds); } RTbitmap.Render(dv); RTbitmap.Freeze(); //RenderTargetBitmap => bitmap var bitmap = new System.Drawing.Bitmap((int)bounds.Width, (int)bounds.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); var bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(System.Drawing.Point.Empty, bitmap.Size), System.Drawing.Imaging.ImageLockMode.WriteOnly, bitmap.PixelFormat); RTbitmap.CopyPixels(Int32Rect.Empty, bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride); //bitmap => bitmapHandler => ImageSource(参考) IntPtr Hbitmap = bitmap.GetHbitmap(); var ImageSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(Hbitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); this.image.Source = ImageSource; //bitmap =>iconHandler => icon bitmap.UnlockBits(bitmapData); IntPtr Hicon = bitmap.GetHicon(); System.Drawing.Icon icon = System.Drawing.Icon.FromHandle(Hicon); this.NotifyIcon.Icon = icon; bitmap.Dispose(); DestroyIcon(icon.Handle); } }変換が多くて非常に手間がかるやり方なんですが、stream使うのも、なんかなあという気がしてこのような手順で行くことに。きっともっとうまい方法があるに違いない。