Techracho

[完全版] 究極のC#プログラミング

このエントリーをはてなブックマーク Share
2009.12.07    C#, 読書関係, 馬場      baba   

いろいろな本を読むと言っても、エンジニアとして専門分野を強化していくことをサボっては元も子もありません。
雑誌や技術書のインプットは減らさず、総量を増やしていくように心がけています。

最近はWeb以外の案件も少しずつ増えてきて、今後のWindows開発でメインとなるC#力を強化しようと思って本書を購入しました。

[完全版] 究極のC#プログラミング ~新スタイルによる実践的コーディング

著者/訳者:川俣 晶

出版社:技術評論社( 2009-05-22 )

定価:¥ 3,129

Amazon価格:¥ 3,129

大型本 ( 408 ページ )

ISBN-10 : 4774138622

ISBN-13 : 9784774138626


C#3.0以降はきちんと仕様を追いかけていなかったのですが、バージョンアップの素晴らしさに感動を覚えました。
よく見る表面的な「新機能LINQ!」みたいな記事では分からない、設計思想と数々の機能の連携による解説で、C#3.0の魅力が十二分に伝わってきます。

もともとC#は大好きな言語ですが、本書を読んでさらに大好きになりました。早速、C#2.0で書いていて不満があった部分を3.0で書き直してみたり、色々実験中です。

C#プログラマーやJavaからC#へ移行する方に、自信を持っておすすめできる良書です。

4.0ではdynamic型と名前付オプション引数で、さらにすごいことになるみたいですね。

WPFでプロジェクトリソースからアイコン指定

このエントリーをはてなブックマーク Share
2009.11.28    C#, Windows, 馬場   タグ: —    baba   

引き続きWPFの小ネタです。

WPFウィンドウのアイコンは、System.Windows.Media.ImageSource型です。
プロジェクトのリソースにアイコンを指定すると(プロジェクト右クリック→プロパティ→リソースから追加)、System.Drawing.Icon型になります。

これらは直接変換できないようですが、以下のようにStreamを介せば変換できます。ただ、もっとスマートな方法が欲しいですね・・・
そもそも、WPFではアイコンをこのように指定することが希なのかも知れません。

System.IO.MemoryStream s = new System.IO.MemoryStream();
(Properties.Resources.myicon as System.Drawing.Icon).Save(s);
window.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(s);

WPFで任意のタイミングでシステムメニューを表示

このエントリーをはてなブックマーク Share
    C#, Windows, 馬場   タグ: —    baba   

WPFはWindowsFormsに比べ、デザインを柔軟に設定できて良いですね。
また、デザインとロジックの分離がとてもやりやすいです。Visual Studio 2008のエディタがこなれていないのと、Bindingマークアップ拡張が分かりにくいのが普及を妨げている感がありますが(?)、おすすめです。

さて、大した内容では無いのですが、WPFで任意のタイミングでシステムメニューを表示させる方法をご紹介します。
WPFでと言いながらWinAPIのお話で、かなり有名な内容ですが、まあ一応。

システムメニュー

システムメニュー

[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

private void Button_Click(object sender, MouseButtonEventArgs e)
{
	//マウス座標を取得し、lParamに加工
	int x = System.Windows.Forms.Control.MousePosition.X;
	int y = System.Windows.Forms.Control.MousePosition.Y;
	IntPtr lParam = new IntPtr(x | y << 16);

	//Windowハンドルを取得し、0x313メッセージを送信
	IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
	PostMessage(hwnd, 0x313, IntPtr.Zero, lParam);
}

要するに0×313を送るだけです。これはWinUser.hにも書かれていない隠し定数ですが、いろんなところで紹介されて逆に有名な値です。0×313。

画像をJPEGに変換するソフト

このエントリーをはてなブックマーク Share
2009.11.27    C#, Windows, 馬場      baba   

Webアプリなどで、JPEGしかアップロードできないことがよくあります。

そのためだけに高機能なソフトを起動するのは重いし、操作も複雑です。

そこで、JPEGに変換するだけのソフトを作成しました。

JPEGにします

アイコンにPNGやBitmap、GIFなどの画像をドラッグすると、JPEGファイルが作成されます。
起動してウィンドウにドラッグしてもOKです。
拡張子が間違っているファイル(PNGなのに.jpgと付いているなど)も、ファイル自体が壊れていなければJPEGに変換できます。

詳細データ

  • プログラム作成時間:15分
  • アイコン作成時間:20分
  • 動作環境:.NET Framework 2.0以上(Windows Vista以降ならOK。XPの場合、以下からインストールしてください)
    マイクロソフトのページ

一応お決まりの。昼休みに作って試験的に公開しているものなので、使って不具合が生じてもBPSや馬場は責任をとれませんのでご注意ください。もちろん、上手く動かないなどのフィードバックは大歓迎です。

C#でListBoxの項目が無い時の背景描画処理を変更する

このエントリーをはてなブックマーク Share
2009.03.11    C#, Windows, 馬場   タグ: , , —    baba   

C#でListBoxの描画処理を変更したい場合、当然ながらOwnerDrawを使います。
DrawMode = DrawMode.OwnerDrawFixed;

これで
protected override void OnDrawItem(DrawItemPaintArgs e) {}
を定義すれば、項目は自由に描画できます。

背景描画も変更したい場合、ウィンドウプロシージャをオーバーライドして、

protected override void WndProc(ref Message m)
{
  if (m.Msg == 0x14) //0x14:WM_ERASEBKGND
  {
    PaintBackground();//任意の描画処理
    return;
  }
  base.WndProc(ref m);
}

とすれば良いです。

ただしこれだと、項目が0個の時に正しく背景が描画されません。どんな処理を書いても、必ずBackColorの単色塗りつぶしになってしまいます。

メッセージを追っていくと、どうやら項目が0個の時にはWM_ERASEBKGNDが呼ばれないみたいです。
ListBoxは内部的に複数のコントロールから出来ているようで、項目が0個でも、ウィンドウを移動しているとたまにWM_ERASEBKGNDが呼ばれたり、よく分からない挙動をします。

再描画時に必ずWM_PAINTは呼ばれるので、このときに背景描画処理をしてしまえば良さそうです。
しかし、単にWM_PAINT時に描画しても、その後に上から単色塗りつぶしが行われて、期待した結果は得られません。
WM_PAINT時に背景描画を行ってからreturn;してしまうと、描画は上手くいくのですが、無限に再描画が発生してCPUを食い尽くしてしまいます。これは、無効領域が有効化されないためです。

要するに、WinAPIで直接描画するのと同じように、BeginPaintをやって、EndPaintで無効領域を有効化してしまえば良いわけです。

[StructLayout(LayoutKind.Sequential)]
unsafe public struct PAINTSTRUCT
{
  public IntPtr hdc;
  public bool fErase;
  public RECT rcPaint;
  public bool fRestore;
  public bool fIncUpdate;
  public fixed byte rgbReserved[32];
}

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
  public int left;
  public int top;
  public int right;
  public int bottom;
}

[DllImport("user32.dll")]
public static extern IntPtr BeginPaint(IntPtr hWnd, out PAINTSTRUCT ps);

[DllImport("user32.dll")]
public static extern IntPtr EndPaint(IntPtr hWnd, ref PAINTSTRUCT ps);

protected override void WndProc(ref Message m)
{
  if (m.Msg == 0xf && Items.Count == 0) //0xf:WM_PAINT
  {
    PAINTSTRUCT ps;
    BeginPaint(Handle, out ps);
    
    PaintBackground(); //任意の描画処理
    
    EndPaint(Handle, ref ps);
    return;
  }
  base.WndProc(ref m);
}

注意点としては、PAINTSTRUCT構造体が固定長の配列を持つため、unsafeブロックで囲み、fixedキーワードで配列を静的確保しています(この記述は古いC#ではだめみたいです)。当然、ビルドオプションで「unsafeコードを許可する」にチェックを入れておいてください。

この手法を使う副作用として、VistualStudioのフォームデザイナが使えなくなります。unsafeコードを使うと、例外を吐いてだめになるみたいです。
しかたないので、これらのコードを全部#ifで囲み、条件付きコンパイルで普段は無効にしておき、リリース時に有効にすることで回避できます。これらのコードを無効にしても、背景が単色になるだけで、開発には支障がないはずです。

ここまでやるとC#で書く意味があるのか疑問になりますが、どうしてもリストボックスの背景を変更したいときの参考になれば幸いです。

ListBoxでOutOfMemoryException

このエントリーをはてなブックマーク Share
2009.02.23    C#, Windows, 馬場   タグ: , , , —    baba   

.NETでListBoxにobjectをAddするとき、OutOfMemoryException 「リストボックスに含まれる項目が多すぎます」が発生することがあります。

これは、ToString() の結果がnullになるオブジェクトを挿入したときに起こります。
エラーメッセージがわかりにくいです。
ToString()の結果に変数を返すようなオブジェクトを作るときは、確実に値が入るようにしないとまずいということですね。

C#で便利な例外処理コード

このエントリーをはてなブックマーク Share
2009.02.10    C#, Windows, 馬場   タグ: , , —    baba   

C#でプログラムを書いているとき、エラーが発生したらとりあえずthrowしますよね?
その例外は、どこかでcatchして、メッセージボックスなりエラーログなりで出力しなくてはいけません。
忘れると、例外発生時に情報無しで落ちてしまって、情報収集がしにくくなります。

しかし、開発中にきちんとしたエラー処理を書くのも面倒なので、とりあえず捕捉しなかったエラーは全部1カ所で処理したいものです。
単純にApplication.Run()をtry-catchで囲っても、正しく処理できません。
この場合は、ApplicationのThreadException と ThreadのUnhandledException を使います。

[参考]
適切に処理されなかった例外をキャッチするには?

下記のサンプルコードのようにすると、catchされなかった例外はメッセージボックスを表示し、同じディレクトリのerror.txtに詳細が記録されます。
開発中でエラーポリシーも詳細が決まっていないとき、とりあえずこのような処理を入れておくと、デバッグモード以外で起動してもエラー詳細が見られて便利です。


//サンプルコード
[STAThread]
static void Main()
{
    //エラーハンドラを登録
    Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
    Thread.GetDomain().UnhandledException += new UnhandledExceptionEventHandler(Program_UnhandledException);

    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

static void Program_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    Exception ex = e.ExceptionObject as Exception;
    if (ex != null)
    {
        ShowError(ex, "UnhandledException");
    }
}

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
    ShowError(e.Exception, "ThreadException");
}

static void ShowError(Exception ex, string title)
{
    MessageBox.Show("プログラム中で補足されなかったエラーが発生しました。詳細はエラーログをごらん下さい。", title);

    StreamWriter stream = new StreamWriter("error.txt", true);    
    stream.WriteLine("[" + title + "]");
    stream.WriteLine("[message]\r\n" + ex.Message);
    stream.WriteLine("[source]\r\n" + ex.Source);
    stream.WriteLine("[stacktrace]\r\n" + ex.StackTrace);
    stream.WriteLine();
    stream.Close();
}

System.Windows.Forms.Timer が動かない

このエントリーをはてなブックマーク Share
    C#, Windows, ネットワーク, 馬場   タグ: , —    baba   

C#でスレッドを使うときの注意です。

System.Windows.Forms.Timer はお手軽でよく使いますが、このTimerはUIスレッドでのみ動くためなのか、別スレッドから呼ぶと動かないみたいです。

System.Threading.Thread や System.Threading.Timer、TcpListener::BeginReceive() などで作ったスレッド内で System.Windows.Forms.Timer を作ってStart()メソッドを呼んでも、Tickはいくら待っても呼ばれません。


//テストコード
Thread thread = new Thread(new ThreadStart(delegate {
    System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer { Interval = 1000 };
    timer.Tick += new EventHandler(delegate { MessageBox.Show("HOGE");});
    timer.Start();
}));
thread.Start();

上記のテストコードで、メッセージボックスは永久に出ません。
System.Windows.Forms.Timer を System.Threading.Timer に変えれば動くので、これは System.Windows.Forms.Timer の制約のようです。

ネットワークでパケット受信をトリガに処理を始めるときなど、うっかりミスしないように気をつけましょう。

TcpListenerを使ったTCPサーバ

このエントリーをはてなブックマーク Share
2009.01.26    C#, Web, Windows, ネットワーク, 馬場   タグ: , —    baba   

C#のTcpListenerを使って、サーバを作っていています。

System.Net.Sockets.Socket.Available プロパティは、信用してはいけません。
下のコードは、クライアントから接続があってもたまにしか受信できません。

using (Socket client = listener.AcceptSocket())
{
  //ここにThread.Sleep(1000)とかを入れるとうまく動くことも多い・・・

  while (client.Available) //ここでいきなりfalseが返ってくる
  {
    byte[] buf = new byte[256];
    client.Receive(buf);
    Console.WriteLine(Encoding.ASCII.GetString(buf));
  }
}

この場合、次のようにするのが正しいです。

using (Socket client = listener.AcceptSocket())
{
  byte[] buf = new byte[256];
  int bytes;
  while ((bytes = client.Receive(buf)) > 0)
  {
    Console.WriteLine(Encoding.ASCII.GetString(buf));
  }
}

それから、TcpListenerのコンストラクタは第一引数にローカルIPアドレス、第二引数にポート番号を渡しますが、サーバに置いては
TcpListener listener = new TcpListener(IPAddress.Any, 45678);
のように IPAddress.Any を渡すのが正しいです。「ローカルIPアドレス」といっても、サーバマシンのアドレスでは無く、クライアントのアドレスを渡します。わかりにくい。。

オーバーライドでちょっとはまりました

このエントリーをはてなブックマーク Share
2009.01.22    C#, 馬場   タグ: , , , , , —    baba   

C#で System.Xml.XmlDocument を使っています。
XmlDocument xmlDocument = new XmlDocument();

プレフィックス付きのノードを作るとき、たとえば


<s:root xmlns:s="urn:hogehoge-hoge" >
    <s:child />
</s:root>

というXMLを作りたいとして、

XmlNode root = xmlDocument.CreateNode(XmlNodeType.Element, "s", "root", "hogehoge-hoge");
XmlNode child = xmlDocument.CreateNode(XmlNodeType.Element, "s", "child", "hogehoge-hoge");
root.AppendChild(child);

とすると動きますが、毎回NamespaceURIを指定するのは変だと思います。
しかし、最後の引数を指定しないと、prefixが出力されません。

また、
xmlDocument.Save("filename");
でファイルに保存するのは1行ですが、string型で受け取ろうと思うと、

StringWriter writer = new StringWriter();
xmlDocument.Save(writer);
string output = writer.ToString();

としないといけません。

色々不便なので、XmlDocumentを継承したExtendedXmlDocumentを作りました。命名がSXGA並にださいですが。。
このクラス内部では、prefixとnamespaceURIのHashtableを持っていて、一度指定すると以降はnamespaceURIを指定しなくても対応するものを用意してくれます。
また、ToString() をオーバーライドしてあるので、1行で文字列を受け取れます。

・・・と、ここまでは良かったんですが(前置きが長い)、
その後調子に乗ってCreateAttribute()をオーバーライドしたらはまりました。

public override XmlAttribute CreateAttribute(string prefix, string name, string value);
を作って、内部で
base.CreateAttribute(name);
を呼ぶと、StackOverflowExceptionが発生します。

どうやら、XmlDocument.CreateAttribute(string) 内部で CreateAttribute(string, string, string) を呼んでいたみたいです。
C#のオーバーライドは動的にメソッドを呼んでくれるので、派生クラスのCreateAttribute(string, string, string) が呼ばれてしまって、その内部で CreateAttribute(string) を呼ぶと無限ループ、と。

.NET Frameworkの内部コードが見られれば一瞬で解決した問題なんですが。
オーバーライドは慎重に使わないとバグの元ですね。オーバーロードと組み合わさるとたまに危険。気をつけます。

« 新しい投稿古い投稿 »

COPYRIGHT [C] 2009 BEYOND PERSPECTIVE SOLUTIONS LTD. ALL RIGHTS RESERVED.