2012年11月8日木曜日
[Excel]コネクタの小技
ドキュメントはわかりやすさが肝。
わかりやすさという点で図形ほど直観的なものはない。(絵心にもよるが。。。)
Excelで図形をレイアウトするのは意外と時間がかかってしまう。
例えば1つの図形に複数のコネクタを接続した場合、重なってどの線がどの矢印かわからなくなってしまう。
こんなケース。
こんな感じ↓に見える。
A ->C
B<->C
が、実際は↓こう。
A ->C
B<- C
コネクタを外してわかるようにしても、
Cをずらすと線が離れちゃう。
そんなときの小技。
まずCの図形の端に小さい図形を配置し、最背面に移動させる
Cと小さい図形をグループ化
これで完了。
裏にいる小さい図形とコネクタを結べば
Cを移動させても矢印がついてきてくれる。
予め、沢山小さいやつを配置したのをひな形として作っておけば、
ドキュメントをきれいにスピーディーに作ることが出来るようになる。
例えるなら千手観音の働きだ、、、おもしろくなかった。
[C#][WPF][Silverlight]XAMLをロードしてプロパティ設定し、保存する
デザイナーさんに数百枚に及ぶの地図をXAMLで作成してもらい、埋め込まれている大量のカスタムコントロールのプロパティを設定することになった。デザイナーさんにプロパティ設定も一緒にお願いしたかったんだけど、それだけ工数がかかると費用もかかるっちゅうわけでなんとかならんか考えてみた。
ツールを作ろう。そう、京都に行こうみたいなのりだ。
XAML を読み込んで Excel とかで管理している情報からプロパティを埋め込んでに保存するやつ。
うーーーーん、ロードは XamlReader で読み込める。
でも Silverlight って XamlWriter なかったよなぁ。
よく考えてみると、XAML って要は XML じゃんという驚愕の新事実に気づく。
XML ファイルとしてロードして、プロパティ、つまり属性を追加して値を書き込めばいん
じゃねぇ?っていうことで、テストプロを作ってみた。
string file = @"D:\hogehoge.xaml";
string controlName = "hogehoge";
string propertyName = "HogeProperty";
string value = "ほげほげ";
// カスタムコントロールの x:Name を検索するのに必要
XNamespace x = "http://schemas.microsoft.com/winfx/2006/xaml";
// ロード
XElement root = XElement.Load(file);
// 全要素検索
foreach (var element in root.Descendants())
{
// 対象のコントロール名と一致したら、属性を設定(x:Nameっていう文字列にするとException!)
if (element.Attribute(x + "Name") != null && element.Attribute(x + "Name").Value == controlName)
{
element.SetAttributeValue(propertyName, value);
break;
}
}
// XML宣言は無しでタブによるインデント
var settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.Indent = true;
settings.IndentChars = "\t";
// 保存
using (var writer = XmlWriter.Create(file, settings))
{
root.Save(writer);
}
おーけーおーけー、これで数百万円分の工数を稼げたな。
XML の操作なので Windowsアプリで作ればよくて、わざわざ Silverlight でブラウザ外
実行する云々考える必要もないしね。
2012年9月10日月曜日
[C#]プロパティをカテゴライズ
プロパティの属性に↓こんなのをつけると、
複数のプロパティが存在する場合、アルファベット順になるのをカスタマイズしたいんだけど、出来ないのかなぁ。
名前空間「System.ComponentModel.DataAnnotations」の「DisplayAttribute 」っていう属性に Order があって、これか!と思ったけど、駄目だった。なんかいい方法ないかな。
[Category("Sample Properties")]
Visual Studio や Expression Blend のプロパティウィンドウで分類されて表示されるようになる。
複数のプロパティが存在する場合、アルファベット順になるのをカスタマイズしたいんだけど、出来ないのかなぁ。
名前空間「System.ComponentModel.DataAnnotations」の「DisplayAttribute 」っていう属性に Order があって、これか!と思ったけど、駄目だった。なんかいい方法ないかな。
2012年8月30日木曜日
コードネーム
Windows 8 はまだ出ていないが、早くもその次期 OS のコードネームが Windows Blue になるという噂が。
Windows のコードネームって一貫性がないよね。(Azureはあるけど。。。)
あるほうが個人的には愛着がわく。
Apple はネコ科で統一されてるし、Android はお菓子の名前(アルファベット順になっているのも面白い)だし。
センスが出るよね。名前って。
私が製品出すなら、間違いなくあれの名前つけるな。
豚骨とか背脂とかバリカタとか(笑)
[C#].NET Remoting で双方向通信
サーバーからクライアントに通知したい!っていうことはよくある話だけど、残念ながら.NET Remoting にはそういう機能はない。
Google先生にも聞いてみたところ、双方向やろうとしている人はいるけど、クライアントがサーバーになってるだけだったりしている。
なんとなく気に入らないので、ちょっと手間だけどクライアント側に通知する仕組みを作ってみた。手間だけど1回作っちゃえばね。
考え方は Comet と呼ばれるもの。
Web の世界では有名?なものだが、要するに応答で通知するというもの。
通常であればクライアントからの要求は即結果を返してあげるものだが、サーバー側で応答を返さずに通知したいタイミングまでひたすら待ち合わせる。
しかるべきタイミングが来たら応答を返す。クライアント側は応答が返ってきたらそれをトリガーに動き出す。
これを使う。
例として「状態変化を通知する」というお題で話を進める。
絵で書くとこんな感じ。
それぞれの役割についてざっくり説明する。
【サーバーアプリケーション】
- 状態管理クラスを生成し、リモートチャネルに登録しておく。
- 状態変化が起きたら生成している状態管理クラスの状変通知メソッドをCALL
【クライアントアプリケーション】
- 状変監視クラスを生成し、状変イベントを登録後、状変監視開始メソッドをCALL
【共通DLL】
■状態管理クラス
- 状変問合せメソッドが CALL されたら、スレッドをブロックする
- 状変通知メソッドが CALL されたら、ブロックされているスレッドを解放する
■状変監視クラス
- 状変監視開始メソッドが CALL されたら、状態管理クラスインスタンスを取得する
- 状変問合せメソッドを CALL し、結果に応じて状変イベントを発行するという処理を繰り返す
応答で通知してから次の要求を受け付けるまでに状変が発生してしまったら。たらたら;;;
まぁ、最終更新日時でも持ってやりとりするしかないね。
ほいじゃぁソースコードで表してみますか。
※結構長くて読みづらいかも。。。
※サーバーとクライアントのチャネル登録処理なんかはそれぞれのアプリケーションに実装すべきかもだけど、よく使うことになるので共通DLLに組み込んでます。
【共通DLL】
まずはサーバーとクライアントそれぞれのチャネル登録処理
/// <summary>
/// リモートサーバ
/// </summary>
public class RemoteServer
{
/// <summary>
/// TCPサーバーチャネル
/// </summary>
private static TcpServerChannel serverChannel = null;
/// <summary>
/// 要求ディクショナリ
/// </summary>
private static Dictionary<string, MarshalByRefObject> requestDictionary = new Dictionary<string, MarshalByRefObject>();
/// <summary>
/// チャネル登録
/// </summary>
/// <param name="ipAddress">IPアドレス</param>
/// <param name="port">ポート番号</param>
public static void RegisterChannel(string ipAddress, int port)
{
// 既に起動している場合は復帰する
if (serverChannel == null)
{
// セキュリティ設定
IDictionary props = new Hashtable();
props["useIpAddress"] = true;
props["port"] = port;
props["bindTo"] = new UriBuilder("", ipAddress).Host;
// TCP Channelを作成
var channel = new TcpServerChannel(props, null);
// TCPチャネル登録
ChannelServices.RegisterChannel(channel, false);
serverChannel = channel;
}
}
/// <summary>
/// リモートオブジェクト登録
/// </summary>
/// <returns>成否</returns>
public static void RegisterRemoteObject(MarshalByRefObject request, string path, Type type)
{
if (!requestDictionary.ContainsKey(path))
{
RemotingServices.Marshal(request, path, type);
requestDictionary.Add(path, request);
}
}
/// <summary>
/// チャネル登録解除
/// </summary>
public static void UnregisterChannel()
{
if (serverChannel != null)
{
string errors = "";
//==========================================================
// リモートオブジェクト登録解除
//==========================================================
try
{
foreach (MarshalByRefObject request in requestDictionary.Values)
{
RemotingServices.Disconnect(request);
}
}
catch (Exception e)
{
errors += e.Message + Environment.NewLine;
}
finally
{
requestDictionary.Clear();
}
//==========================================================
// チャネル登録解除
//==========================================================
try
{
ChannelServices.UnregisterChannel(serverChannel);
}
catch (Exception e)
{
errors += e.Message + Environment.NewLine;
}
finally
{
serverChannel = null;
GC.Collect();
}
if(!string.IsNullOrEmpty(errors))
{
throw new Exception(errors);
}
}
}
}
/// <summary>
/// リモートクライアント
/// </summary>
public class RemoteClient
{
/// <summary>
/// TCPクライアントチャネル
/// </summary>
private static TcpClientChannel clientChannel;
/// <summary>
/// IPアドレス
/// </summary>
private static string ipAddress;
/// <summary>
/// ポート番号
/// </summary>
private static int portNo;
/// <summary>
/// チャネル登録
/// </summary>
/// <returns></returns>
public static void RegisterChannel(string ip, int port)
{
if (clientChannel == null)
{
// TCP Channel を作成
var channel = new TcpClientChannel();
// リモートオブジェクトを登録
ChannelServices.RegisterChannel(channel, false);
clientChannel = channel;
ipAddress = ip;
portNo = port;
}
}
/// <summary>
/// チャネル削除
/// </summary>
public static void UnregisterChannel()
{
if (clientChannel != null)
{
ChannelServices.UnregisterChannel(clientChannel);
clientChannel = null;
ipAddress = null;
portNo = 0;
}
}
/// <summary>
/// リモートオブジェクト取得
/// </summary>
/// <param name="path">パス</param>
/// <param name="type">タイプ</param>
/// <returns>リモートオブジェクト</returns>
public static object GetRemoteObject(string path, Type type)
{
// URI生成
UriBuilder uri = new UriBuilder();
uri.Scheme = "TCP" + Uri.SchemeDelimiter;
uri.Host = ipAddress;
uri.Port = portNo;
uri.Path = path;
// オブジェクトを作成
return Activator.GetObject(type, uri.ToString());
}
}
次、状態管理
/// <summary>
/// 状態管理
/// </summary>
public class Status : MarshalByRefObject
{
/// <summary>
/// 待ち合わせ
/// </summary>
private List<AutoResetEvent> autoResetList = new List<AutoResetEvent>();
/// <summary>
/// 最終更新日時
/// </summary>
private DateTime latestChangeDateTime = DateTime.Now;
/// <summary>
/// 状態変化問い合わせ要求(For Client)
/// </summary>
/// <param name="startTime">前回更新日時</param>
/// <returns>状態更新日時</returns>
public DateTime InquiryStatusChangeRequest(DateTime startTime)
{
try
{
AutoResetEvent autoReset;
lock (this)
{
if (startTime < this.latestChangeDateTime)
{
return this.latestChangeDateTime;
}
autoReset = new AutoResetEvent(false);
autoResetList.Add(autoReset);
}
autoReset.WaitOne();
}
catch
{
}
return this.latestChangeDateTime;
}
/// <summary>
/// 状態変化通知(For Server)
/// </summary>
public void NoticeStatusChange()
{
lock (this)
{
this.latestChangeDateTime = DateTime.Now;
foreach (var autoReset in autoResetList)
{
autoReset.Set();
}
autoResetList.Clear();
}
}
/// <summary>
/// 対象のインスタンスの有効期間ポリシーを制御する
/// </summary>
/// <returns>有効期間サービス オブジェクト</returns>
public override object InitializeLifetimeService()
{
return null;
}
}
次、状変監視
/// <summary>
/// 状変チェック
/// </summary>
public class StatusCheck
{
#region インスタンスフィールド
/// <summary>
/// 起動状態
/// </summary>
private bool isStarted = false;
/// <summary>
/// 状態チェックスレッド
/// </summary>
private Thread statusCheckThread;
#endregion
#region イベント
/// <summary>
/// 状態変化イベントハンドラ
/// </summary>
public delegate void StatusChangeEventHandler();
/// <summary>
/// 状態変化イベント
/// </summary>
private event StatusChangeEventHandler statusChangeEvent;
/// <summary>
///状態変化イベントを登録または削除します。
/// </summary>
public event StatusChangeEventHandler StatusChangeEvent
{
add { statusChangeEvent += value; }
remove { statusChangeEvent -= value; }
}
#endregion
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="statusChangeCallBack"></param>
public StatusCheck(StatusChangeEventHandler statusChangeCallBack)
{
try
{
statusChangeEvent = statusChangeCallBack;
startStatusCheck();
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 状態チェックリスタート
/// </summary>
public void RestartStatusCheck()
{
try
{
if (this.isStarted)
{
throw new Exception("既に起動中です。");
}
startStatusCheck();
}
catch (Exception e)
{
throw e;
}
}
/// <summary>
/// 状態チェック停止
/// </summary>
public void StopStatusCheck()
{
this.isStarted = false;
if (this.statusCheckThread != null)
{
this.statusCheckThread.Abort();
}
}
/// <summary>
/// 状変監視開始
/// </summary>
private void startStatusCheck()
{
if (this.isStarted)
{
// 開始中の場合は復帰
return;
}
var status = (Status)RemoteClient.GetRemoteObject(RemotingConstant.PATH_STATUS, typeof(Status));
this.isStarted = true;
ThreadStart start = delegate()
{
DateTime latestDate = DateTime.MinValue;
while (this.isStarted)
{
try
{
// 状態変化問い合わせ要求
latestDate = status.InquiryStatusChangeRequest(latestDate);
// イベント通知
if (statusChangeEvent != null)
{
statusChangeEvent.BeginInvoke(null, null);
}
}
catch (Exception e)
{
break;
}
}
this.isStarted = false;
};
this.statusCheckThread = new Thread(start);
this.statusCheckThread.IsBackground = true;
this.statusCheckThread.Start();
}
}
/// <summary>
/// リモートアクセス用定数
/// </summary>
public class RemotingConstant
{
/// <summary>
/// URIパス定義(状態)
/// </summary>
public const string PATH_STATUS = "Status";
}
【サーバーアプリケーション】
初期化ね。
// チャネル登録
RemoteServer.RegisterChannel("0.0.0.0", 50000);
//状態管理
var status = new Status();
RemoteServer.RegisterRemoteObject(status, RemotingConstant.PATH_STATUS, typeof(Status));
通知処理は
// 通知
status.NoticeStatusChange();
【クライアントアプリケーション】
// チャネル登録
RemoteClient.RegisterChannel("0.0.0.0", 50000);
// 状態監視開始
var statusCheck = new StatusCheck(statusChangeEvent);
/// <summary>
/// 状態変化イベント
/// </summary>
private static void statusChangeEvent()
{
}
通知したい内容がたくさんあって、時系列で各情報を通知したければ、状態変化クラスにあたるやつが、時刻+通知情報を管理して前回通知した時刻以降に発生したものを全て通知するという処理を書くことになるが、それはサンプルを書くまでもないので省略。
登録:
投稿 (Atom)