.NETでは自作イベントを記述する処理は大体決まっていて、コピペの量産祭になることが多い。
そもそもイベントはメモリリークの原因になることが多いのでRxを使え、との議論もあるがここでは置いておいて。
大体こんな感じのコードが随所に溢れることになる。
//----イベントを宣言
public event HogeEventHandler Hoge;
protected virtual void OnHoge(HogeEventArgs e) {
if(Hoge != null) {
Hoge(this, e);
}
}
//-----イベントを発生
var e = new HogeEventArgs();
OnHoge(e);
何が嫌かというと、イベントの数だけメソッドが増えてしまうことだ。似たようなコードがずらずらと並んでいるのはとても見栄えが悪い。
そこで半分くらいはネタなのだが、以下のような方法を提案してみる。
public static class EventExtension {
public static void Fire(this EventArgs e, object sender, dynamic handler) {
if(handler != null) {
handler(sender, e);
}
}
}
dynamic型を利用しているのが格好悪いが、ハンドラを上手い感じに引数に渡す方法がこれしか見付からなかった。もっと上手い指定の方法があれば良いのだが。
これを定義しておくと、以下のように書くことが出来るようになる。
//----イベントを宣言
public event HogeEventHandler Hoge
//----イベントを発生
var e = new HogeEventArgs();
e.Fire(this, Hoge);
同様にしてCancelEventArgsを拡張してみる。
public static class EventExtension {
public static void Fire(this CancelEventArgs e, object sender, dynamic before, dynamic after, Action action) {
if(before != null) {
before(sender, e);
}
if(!e.Cancel) {
action();
if(after != null) {
after(sender, e);
}
}
}
}
これを定義しておくと、 以下のように書くことが出来るようになる。
//----イベントを宣言
public event HogeCancelEventHandler BeforeHoge, AfterHoge;
//----イベントを発生
var e = new HogeCancelEventArgs();
e.Fire(this, BeforeHoge, AfterHoge, ()=> Console.WriteLine("Hogeがキャンセルされなかったので実行したよ!"));
如何だろうか。これで個人的にはかなり楽になったのだが、他にやっている人を見掛けないのでバッドノウハウに分類される技なのかも知れない。
2015年4月15日水曜日
PropertyGridのプロパティ名をリソースを使って日本語化する
.NETで設定画面のGUIを作るとき、PropertyGridを使う人は多いだろう。
その際、例えばこんなモデルを設定したとする。
public class AppProperty {
[Category("基本情報"), Description("名前です。")]
public string Name { get; set; }
}
すると、画面には「Name」と表示される。
「Name」ならまだ良いが、これがもっと複雑なプロパティ名だった場合は、使いづらくて仕方がない。
解決策としては @IT さんの PropertyGridコントロールに表示されるプロパティ名を変更するには? に自作のAttributeを使う方法が提示されている。非常に有用な方法なのでそのままコピーして再利用されている方も多いだろう。
その方法では、こんな感じにモデルを作ることになる。
[TypeConverter(typeof(PropertyDisplayConverter))]
public class AppProperty {
[Category("基本情報"), PropertyDisplayName("名前"), Description("名前です。")]
public string Name { get; set; }
}
大抵の場合は、上記の方法で必要十分だろう。
だが、例えば以下のような場合では上記以外の方法が必要になる。
つまり、属性からではなくリソースファイルからプロパティ名を取得するようなTypeConverterを自作してやれば良い。
と言う訳で LocalizableConverter なるものを作ってみた。
public class LocalizableConverter<RESOURCES>: TypeConverter {
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) {
return new PropertyDescriptorCollection(
TypeDescriptor.GetProperties(value, attributes, true)
.Cast<PropertyDescriptor>()
.Select(original => new LocalazablePropertyDescriptor(original)).ToArray());
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context) {
return true;
}
public class LocalazablePropertyDescriptor: PropertyDescriptor {
readonly PropertyDescriptor original;
public LocalazablePropertyDescriptor(PropertyDescriptor original)
: base(original) {
this.original = original;
}
public override bool CanResetValue(object component) {
return original.CanResetValue(component);
}
public override Type ComponentType {
get { return original.ComponentType; }
}
public override object GetValue(object component) {
return original.GetValue(component);
}
public override string Description {
get { return original.Description; }
}
public override string Category {
get { return original.Category; }
}
public override bool IsReadOnly {
get { return original.IsReadOnly; }
}
public override void ResetValue(object component) {
original.ResetValue(component);
}
public override bool ShouldSerializeValue(object component) {
return original.ShouldSerializeValue(component);
}
public override void SetValue(object component, object value) {
original.SetValue(component, value);
}
public override Type PropertyType {
get { return original.PropertyType; }
}
public override string DisplayName {
get {
var name = base.DisplayName;
var prop = typeof(RESOURCES).GetProperty(name, BindingFlags.Static | BindingFlags.NonPublic);
if(prop != null) {
return (string)prop.GetValue(null);
}
return name;
}
}
}
}
ジェネリクスを利用し、型パラメータにResourcesクラスを指定して使う。
リソースにプロパティ名と同名の文字列を定義していればその値が表示され、定義していなければプロパティ名がそのまま表示されるという寸法だ。
使い方としては、こんな感じになる。
[TypeConverter(typeof(LocalizableConverter<Resources>))]
public class AppProperty {
[Category("基本情報"), Description("名前です。")]
public string Name { get; set; }
}
あとは、Properties\Resources.resxに「Name」=「名前」になるように文字列を追加してやれば良い。
その際、例えばこんなモデルを設定したとする。
public class AppProperty {
[Category("基本情報"), Description("名前です。")]
public string Name { get; set; }
}
すると、画面には「Name」と表示される。
「Name」ならまだ良いが、これがもっと複雑なプロパティ名だった場合は、使いづらくて仕方がない。
解決策としては @IT さんの PropertyGridコントロールに表示されるプロパティ名を変更するには? に自作のAttributeを使う方法が提示されている。非常に有用な方法なのでそのままコピーして再利用されている方も多いだろう。
その方法では、こんな感じにモデルを作ることになる。
[TypeConverter(typeof(PropertyDisplayConverter))]
public class AppProperty {
[Category("基本情報"), PropertyDisplayName("名前"), Description("名前です。")]
public string Name { get; set; }
}
大抵の場合は、上記の方法で必要十分だろう。
だが、例えば以下のような場合では上記以外の方法が必要になる。
- 多言語対応したアプリを作りたい
- 同じ名前のプロパティが一杯あって、同じ属性の記述を量産するのは何か違う気がする。
つまり、属性からではなくリソースファイルからプロパティ名を取得するようなTypeConverterを自作してやれば良い。
と言う訳で LocalizableConverter なるものを作ってみた。
public class LocalizableConverter<RESOURCES>: TypeConverter {
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) {
return new PropertyDescriptorCollection(
TypeDescriptor.GetProperties(value, attributes, true)
.Cast<PropertyDescriptor>()
.Select(original => new LocalazablePropertyDescriptor(original)).ToArray());
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context) {
return true;
}
public class LocalazablePropertyDescriptor: PropertyDescriptor {
readonly PropertyDescriptor original;
public LocalazablePropertyDescriptor(PropertyDescriptor original)
: base(original) {
this.original = original;
}
public override bool CanResetValue(object component) {
return original.CanResetValue(component);
}
public override Type ComponentType {
get { return original.ComponentType; }
}
public override object GetValue(object component) {
return original.GetValue(component);
}
public override string Description {
get { return original.Description; }
}
public override string Category {
get { return original.Category; }
}
public override bool IsReadOnly {
get { return original.IsReadOnly; }
}
public override void ResetValue(object component) {
original.ResetValue(component);
}
public override bool ShouldSerializeValue(object component) {
return original.ShouldSerializeValue(component);
}
public override void SetValue(object component, object value) {
original.SetValue(component, value);
}
public override Type PropertyType {
get { return original.PropertyType; }
}
public override string DisplayName {
get {
var name = base.DisplayName;
var prop = typeof(RESOURCES).GetProperty(name, BindingFlags.Static | BindingFlags.NonPublic);
if(prop != null) {
return (string)prop.GetValue(null);
}
return name;
}
}
}
}
ジェネリクスを利用し、型パラメータにResourcesクラスを指定して使う。
リソースにプロパティ名と同名の文字列を定義していればその値が表示され、定義していなければプロパティ名がそのまま表示されるという寸法だ。
使い方としては、こんな感じになる。
[TypeConverter(typeof(LocalizableConverter<Resources>))]
public class AppProperty {
[Category("基本情報"), Description("名前です。")]
public string Name { get; set; }
}
あとは、Properties\Resources.resxに「Name」=「名前」になるように文字列を追加してやれば良い。
ラベル:
.NET,
C#,
PropertyGrid,
日本語化
2014年4月16日水曜日
Windows7でリモートデスクトップ part2
前回紹介した方法では、リモートデスクトップを切断する際にわざわざバッチファイルを叩かなければならなかった。
しかも、管理者権限が必要なので毎回UACの警告が表示される。
ついでに言えば、タイムアウトなどで勝手に切断された場合はバッチファイルが実行されないため自宅のディスプレイは真っ黒のままだ。
本音を言うと、リモートデスクトップが切断されたら自動的にコンソールセッションがアクティブになって欲しい。切断するたびに特別な操作なんてやりたくないのだ。
とは言え、これ以上の方法を探しても意外と見付からない。
gpedit.mscなどで探しても、リモートデスクトップ接続時に自動的にプログラムを走らせる設定はあるが逆はない。
なので、これから紹介する方法は本ブログオリジナルの方法である。
まず最初に、リモートデスクトップの切断を検知する方法から調べてみよう。
コントロールパネルの管理者ツールからイベントビューアを起動する。
Windows7のイベントビューアはごちゃごちゃしていて見づらいが仕方がない。
アプリケーションとサービスログ > Microsoft > Windows > TerminalServices-LocalSessionManager > Operational
と、ツリーを展開していく。ここがリモートデスクトップのログの場所だ。既に何度かリモートデスクトップを利用しているなら再接続に成功したり、切断されたり、色々な種類のログが確認できるはずだ。
では、ここから切断時のログだけをピンポイントに抜き出してみよう。
カスタムビューと書かれたラベルを右クリックし、「カスタムビューの作成」を選択する。
「ログごと」にチェックが入っていることを確認し、「イベントログ」の右のコンボボックスの▼を押して
アプリケーションとサービスログ > Microsoft > Windows > TerminalServices-LocalSessionManager > Operational
を選択する。
ここまでは比較的簡単。大変なのはここからである。
「フィルター」のタブを「XML」に切り替える。
「手動でクエリを編集する」にチェックを入れる。確認が表示されるが「はい」を選ぶ。
<Select Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">*</Select>
となっている部分を
<Select Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">*[System[EventID=24 and Level=4] and UserData/EventXML/Address!='ローカル']</Select>
と書き換える。これで、ローカル「以外」から切断した場合だけを取得するようにXPath形式で指定したことになる。
「OK」を押し、フィルタに「リモートデスクトップ切断」とでも名前を付けて更に「OK」を押す。
これで屋外からリモートデスクトップで切断した時のログだけを抽出することが出来るようになったはずだ。
次に、「リモートデスクトップ切断」にログが追加される度にプログラムを起動する方法について説明する。
先程保存した「リモートデスクトップ切断」を右クリックし、「このカスタムビューにタスクを設定」を選択する。
「基本タスクの作成」で「リモートデスクトップ切断後の処理」とでも名前を付けて「次へ」をクリック。
「イベントログへの記録時」でカスタムイベントフィルターと表示されていることを確認して「次へ」をクリック。
「操作」で「プログラムの開始」を選択して「次へ」をクリック。
「プログラム/スクリプト」に「tscon」、「引数の追加」に「1 /DEST:console /PASSWORD:*****」と入力して「次へ」をクリック(*****は実際のパスワードを入れる)。
「[完了]をクリックしたときに、このタスクの[プロパティ]ダイアログを開く」にチェックを入れて「完了」をクリック。
まだまだ続く。次はプロパティダイアログにて。
「全般」タブの中で、「ユーザがログオンしているかどうかにかかわらず実行する」にチェックを入れる。 念のため「パスワードを保存しない」のチェックは外しておこう。そして「最上位の特権で実行する」にチェックを入れる。
「条件」タブの中で、「タスクを実行するためにスリープを解除する」にチェックを入れる。
「設定」タブの中で、「タスクを要求時に実行する」だけON、他のチェックは全てOFFにする。
「OK」を押してダイアログを閉じる。パスワードを聞かれた場合は入力する。
これで「リモートデスクトップ切断時に自動的にコンソールセッションをアクティブにする」ことが出来るようになった。もう×ボタンで閉じようが、タイムアウトで勝手に切断されようが関係ない。
作成したタスクはコントロールパネルの「管理ツール」→「タスクスケジューラ」の「イベントビューアータスク」で確認することが出来る。
以上!
2015/7/13
続きの記事を書きました。
しかも、管理者権限が必要なので毎回UACの警告が表示される。
ついでに言えば、タイムアウトなどで勝手に切断された場合はバッチファイルが実行されないため自宅のディスプレイは真っ黒のままだ。
本音を言うと、リモートデスクトップが切断されたら自動的にコンソールセッションがアクティブになって欲しい。切断するたびに特別な操作なんてやりたくないのだ。
とは言え、これ以上の方法を探しても意外と見付からない。
gpedit.mscなどで探しても、リモートデスクトップ接続時に自動的にプログラムを走らせる設定はあるが逆はない。
なので、これから紹介する方法は本ブログオリジナルの方法である。
まず最初に、リモートデスクトップの切断を検知する方法から調べてみよう。
コントロールパネルの管理者ツールからイベントビューアを起動する。
Windows7のイベントビューアはごちゃごちゃしていて見づらいが仕方がない。
アプリケーションとサービスログ > Microsoft > Windows > TerminalServices-LocalSessionManager > Operational
と、ツリーを展開していく。ここがリモートデスクトップのログの場所だ。既に何度かリモートデスクトップを利用しているなら再接続に成功したり、切断されたり、色々な種類のログが確認できるはずだ。
では、ここから切断時のログだけをピンポイントに抜き出してみよう。
カスタムビューと書かれたラベルを右クリックし、「カスタムビューの作成」を選択する。
「ログごと」にチェックが入っていることを確認し、「イベントログ」の右のコンボボックスの▼を押して
アプリケーションとサービスログ > Microsoft > Windows > TerminalServices-LocalSessionManager > Operational
を選択する。
ここまでは比較的簡単。大変なのはここからである。
「フィルター」のタブを「XML」に切り替える。
「手動でクエリを編集する」にチェックを入れる。確認が表示されるが「はい」を選ぶ。
<Select Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">*</Select>
となっている部分を
<Select Path="Microsoft-Windows-TerminalServices-LocalSessionManager/Operational">*[System[EventID=24 and Level=4] and UserData/EventXML/Address!='ローカル']</Select>
と書き換える。これで、ローカル「以外」から切断した場合だけを取得するようにXPath形式で指定したことになる。
「OK」を押し、フィルタに「リモートデスクトップ切断」とでも名前を付けて更に「OK」を押す。
これで屋外からリモートデスクトップで切断した時のログだけを抽出することが出来るようになったはずだ。
次に、「リモートデスクトップ切断」にログが追加される度にプログラムを起動する方法について説明する。
先程保存した「リモートデスクトップ切断」を右クリックし、「このカスタムビューにタスクを設定」を選択する。
「基本タスクの作成」で「リモートデスクトップ切断後の処理」とでも名前を付けて「次へ」をクリック。
「イベントログへの記録時」でカスタムイベントフィルターと表示されていることを確認して「次へ」をクリック。
「操作」で「プログラムの開始」を選択して「次へ」をクリック。
「プログラム/スクリプト」に「tscon」、「引数の追加」に「1 /DEST:console /PASSWORD:*****」と入力して「次へ」をクリック(*****は実際のパスワードを入れる)。
「[完了]をクリックしたときに、このタスクの[プロパティ]ダイアログを開く」にチェックを入れて「完了」をクリック。
まだまだ続く。次はプロパティダイアログにて。
「全般」タブの中で、「ユーザがログオンしているかどうかにかかわらず実行する」にチェックを入れる。 念のため「パスワードを保存しない」のチェックは外しておこう。そして「最上位の特権で実行する」にチェックを入れる。
「条件」タブの中で、「タスクを実行するためにスリープを解除する」にチェックを入れる。
「設定」タブの中で、「タスクを要求時に実行する」だけON、他のチェックは全てOFFにする。
「OK」を押してダイアログを閉じる。パスワードを聞かれた場合は入力する。
これで「リモートデスクトップ切断時に自動的にコンソールセッションをアクティブにする」ことが出来るようになった。もう×ボタンで閉じようが、タイムアウトで勝手に切断されようが関係ない。
作成したタスクはコントロールパネルの「管理ツール」→「タスクスケジューラ」の「イベントビューアータスク」で確認することが出来る。
以上!
2015/7/13
続きの記事を書きました。
ラベル:
Windows7,
リモートデスクトップ
Windows7でリモートデスクトップ part1
たまに屋外から自宅のPCを操作したくなるときがある。
そんなときに、リモートデスクトップは最もお手軽な手段の一つであろう。
自宅と同じ環境で操作したいため、屋外からは以下のコマンドでリモートデスクトップに接続することにしている。
mstsc /admin
ただ、リモートデスクトップを切断して自宅に帰ってみると、ディスプレイが真っ黒のまま何の操作も受け付けなくなっていて途方に暮れることがある。
これはコンソールセッションがアクティブになっていないことが原因である。
試しに、自宅のPCでコマンドプロンプトを起動して以下のコマンドを打ってみよう。
query session
すると、こんな感じになるのではないだろうか。
コンソールセッションがアクティブになっているのが分かる。
次に、屋外からリモートデスクトップで接続して同じコマンドを打ってみよう。
こんな感じになるはずだ。
コンソールセッションとは別のセッションがアクティブになっているのが分かる。
では、どうすれば良いか。切断時にコンソールセッションがアクティブになるようにすれば良い。
×ボタンでリモートデスクトップを閉じたり、スタートメニューからログオフや切断を選ぶ代わりに、コマンドプロンプトを管理者権限で起動し、以下のコマンドを実行する。
tscon 1 /DEST:console /PASSWORD:*****
現在アクティブなIDの1番をコンソールに接続するよ、と言う意味だ。そうすると自動的にリモートデスクトップは切断され、自宅のPCが操作可能な状態に戻る。
*****には実際のパスワードを入れよう。XPの時代は指定しなくても上手く行ったのだが、7になってからはこのオプションを指定しないと上手く行かなかった。
毎回管理者権限でコマンドプロンプトを起動してコマンドを叩くのは面倒なので、バッチファイルを作成し、そのバッチファイルのショートカットを作成し、そのショートカットのプロパティ画面の詳細設定から「管理者として実行」を有効にしておく。
あとはそのショートカットをスタートメニューなりに登録し、リモートデスクトップを切断する際にそのショートカットを実行するようにすれば良い。
実はもっと上手くやる方法があるのだが、長くなったのでその説明は次回に回そう。
そんなときに、リモートデスクトップは最もお手軽な手段の一つであろう。
自宅と同じ環境で操作したいため、屋外からは以下のコマンドでリモートデスクトップに接続することにしている。
mstsc /admin
ただ、リモートデスクトップを切断して自宅に帰ってみると、ディスプレイが真っ黒のまま何の操作も受け付けなくなっていて途方に暮れることがある。
これはコンソールセッションがアクティブになっていないことが原因である。
試しに、自宅のPCでコマンドプロンプトを起動して以下のコマンドを打ってみよう。
query session
すると、こんな感じになるのではないだろうか。
| セッション名 | ユーザ名 | ID | 状態 | 種類 | デバイス |
| services | 0 | Disc | |||
| >console | Hoge | 1 | Active |
コンソールセッションがアクティブになっているのが分かる。
次に、屋外からリモートデスクトップで接続して同じコマンドを打ってみよう。
こんな感じになるはずだ。
| セッション名 | ユーザ名 | ID | 状態 | 種類 | デバイス |
| services | 0 | Disc | |||
| >rdp-tcp#0 | Hoge | 1 | Active | rdpwd | |
| console | 13 | Conn | |||
| rdp-tcp | 65536 | Listen |
コンソールセッションとは別のセッションがアクティブになっているのが分かる。
では、どうすれば良いか。切断時にコンソールセッションがアクティブになるようにすれば良い。
×ボタンでリモートデスクトップを閉じたり、スタートメニューからログオフや切断を選ぶ代わりに、コマンドプロンプトを管理者権限で起動し、以下のコマンドを実行する。
tscon 1 /DEST:console /PASSWORD:*****
現在アクティブなIDの1番をコンソールに接続するよ、と言う意味だ。そうすると自動的にリモートデスクトップは切断され、自宅のPCが操作可能な状態に戻る。
*****には実際のパスワードを入れよう。XPの時代は指定しなくても上手く行ったのだが、7になってからはこのオプションを指定しないと上手く行かなかった。
毎回管理者権限でコマンドプロンプトを起動してコマンドを叩くのは面倒なので、バッチファイルを作成し、そのバッチファイルのショートカットを作成し、そのショートカットのプロパティ画面の詳細設定から「管理者として実行」を有効にしておく。
あとはそのショートカットをスタートメニューなりに登録し、リモートデスクトップを切断する際にそのショートカットを実行するようにすれば良い。
実はもっと上手くやる方法があるのだが、長くなったのでその説明は次回に回そう。
ラベル:
Windows7,
リモートデスクトップ
登録:
投稿 (Atom)