Hatena::ブログ(Diary)

当面C#と.NETな記録 このページをアンテナに追加 RSSフィード

2007/6/27 (水)

[][] enum を文字列で置き換えて ComboBox に表示する  enum を文字列で置き換えて ComboBox に表示する - 当面C#と.NETな記録 を含むブックマーク  enum を文字列で置き換えて ComboBox に表示する - 当面C#と.NETな記録 のブックマークコメント

先週は、DataBindingで大ハマリしてました。データベース知らない→ADO.NET知らない→DataBindingあまり使わないって流れで、DataBindingをよく知らないのが原因。なので、ADO.NET本を借りてきてちょっと勉強中です。以下は WinForm の DataBinding についてです。ASP.NETはまたちょっと違うらしい。

まずは ComboBox (と ListBox) の DataSource, ValueMember, DisplayMember の使い方。しかも ADO.NET なし。こいつらも DataBinding の範疇に入るのかわかりませんが、まあどっちでもいいや。

MSDNライブラリの「ListControlクラス」を見ればサンプルコードがあるので、そちらもどうぞ。リンクしようとしたら「コンテンツが見つかりません」と出た…。Googleキャッシュには確かに残ってるんだけど…。


ComboBox (と ListBox) の DataSource, ValueMember, DisplayMember の使い方

DataSource, ValueMember, DisplayMember は言葉で説明しただけではわかりづらいので、コンボボックスに「ある enum の値を文字列で置き換えて表示/選択したい」っていうシチュエーションで順を追って説明します(よくありますよね?)。

表示/選択したい enum はこれです。

public enum Gender { Male, Female }

でも、実際の表示にはこっちの文字列を使いたい。

public static readonly string[] Gender_ja_JP = { "男性", "女性" };

本当はリソースから読む値とでも、いいほうに解釈しておいてください(^^;

今回は Person クラス(ってのがあると思ってください)にべた書きしました。

で、これをフォーム上のコンボボックス this.genderComboBox に結び付けるにはどうするか?

( Gender.Male, Gender_ja_JP[ 0 ] ) のような enum と文字列を一対一対応させたデータ型(なんでもいい)を作ってやり、そのコレクション(や配列)をコンボボックスの DataSource に入れてやればOKです。

ここでは KeyValuePair<string, Gender> を使います。

対応表はこのようになります。

KeyValuePair<string, Gender>[] array = new KeyValuePair<string, Gender>[] {
  new KeyValuePair<string, Gender>( Person.Gender_ja_JP[ 0 ], Gender.Male ),
  new KeyValuePair<string, Gender>( Person.Gender_ja_JP[ 1 ], Gender.Female ) };

これをコンボボックスの DataSource に入れ、DisplayMember と ValueMember で表の読み方を教えてあげればお終いです。

this.genderComboBox.DataSource = array;
this.genderComboBox.ValueMember = "Value";
this.genderComboBox.DisplayMember = "Key";

DisplayMember と ValueMember は、KeyValuePair<string, Gender> のパブリックプロパティを表しています。ValueMember が実際の値を表すプロパティDisplayMember が表示に使うプロパティDataSource はその対応表です。

あとは SelectedValue で読んでみてください。SelectedValue は enum の Gender で読み書きできます。文字列のほうではありません。MSDNライブラリによると「ValueMember にプロパティを指定しない場合、SelectedValue はオブジェクトの ToString メソッドの結果を返します。」だそうですので、ご注意を。


データバインディング時の注意

ここまでで対応付けはできたけど、データバインディングと一緒に使うときは注意が必要っぽいです。

エンティティクラスを書いて、いったんコンパイルして、VisualStudioでそのエンティティをオブジェクトデータソースとしてフォームにポトペタするってのが、データバインディングを使うときの普通なやりかたと思います。オブジェクトを詳細に設定して多数のコントロールを一度に生成できますが、この方法でコンボボックスを作るとコンボボックスのTextプロパティにバインドしたコードが生成されるようです。上述の対応付けの方法では、Textプロパティにバインドした場合にはうまく働きません!SelectedValueプロパティにバインドしないといけないようです。

何か手順を間違っているのかもしれませんが、自分で試した範囲ではそうでした。なので、フォームのコンストラクタでInitializeComponent()後に以下のように書いて対処しました。

this.genderComboBox.DataBindings.Clear();
this.genderComboBox.DataBindings.Add(
  new Binding( "SelectedValue", this.personBindingSource, "Gender", true ) );

Clearしないで両方にバインドすると、WinFormを閉じることさえできなくなるので注意。

(追記)デザイナのコンボボックスの右上の三角をクリックして出てくる「データバインド項目を使用する」を使うと、SelectedValueプロパティにバインドします。データソースで詳細をポトペタしてからこれを使うと、TextとSelectedValueの両プロパティにバインドしたコードが生成され、まともに動かないので注意。(追記終わり)


自動化してみる

ここからはおまけ。KeyValuePair<string, Gender>[] array を作るところを自動化してみました。enum 作るのも、日本語リソースを用意するのもしょうがないけど、これをくっつけるのは自動化してしまいます。対応関係を明示的に書くってのも良いことだと思うけど、やっぱり面倒なので。

using System;
using System.Collections.Generic;
using System.Reflection;

namespace BindingTest
{
  public static class EnumHelper
  {
    public static Array CreateMappingTable( Type enumType, IList<string> textList )
    {
      try
      {
        Array enumValues = Enum.GetValues( enumType );
        if ( enumValues.Length != textList.Count )
          throw new ArgumentException( "enumType に定義された要素数と textList の要素数が異なります。" );

        Type pairType = typeof( KeyValuePair<,> ).MakeGenericType( new Type[] { typeof( string ), enumType } );
        ConstructorInfo ci = pairType.GetConstructor( new Type[] { typeof( string ), enumType } );

        Array result = Array.CreateInstance( pairType, enumValues.Length );
        int i = 0;
        foreach ( object value in enumValues )
        {
          result.SetValue( ci.Invoke( new object[] { textList[ i ], value } ), i );
          i++;
        }
        return result;
      }
      catch ( ArgumentNullException e )
      {
        throw new ArgumentNullException( "enumType が null です", e );
      }
      catch ( ArgumentException e )
      {
        throw new ArgumentException( "enumType が Enum ではありません", e );
      }
    }
  }
}

また、MakeGenericTypeみたいなマニアックな世界になってしまった。型を後から指定するタイプのコードは面倒ですね…。MakeGenericType は id:siokoshou:20070509#p2 を参考にどうぞ。あとは普段あまり目にしないものが多いけど、簡単なものしか使ってないです。メソッド名がいけてないなぁ…。

これがあれば、↓が

KeyValuePair<string, Gender>[] array = new KeyValuePair<string, Gender>[] {
  new KeyValuePair<string, Gender>( Person.Gender_ja_JP[ 0 ], Gender.Male ),
  new KeyValuePair<string, Gender>( Person.Gender_ja_JP[ 1 ], Gender.Female ) };

this.genderComboBox.DataSource = array;
this.genderComboBox.ValueMember = "Value";
this.genderComboBox.DisplayMember = "Key";

こう書けます。

this.genderComboBox.DataSource = EnumHelper.CreateMappingTable(
  typeof( Gender ), Person.Gender_ja_JP );
this.genderComboBox.ValueMember = "Value";
this.genderComboBox.DisplayMember = "Key";

ラク!

enumと対応付ける文字列の要素の順番に気をつけてくださいね。enum中の要素の現れる順ではなく、値順になります。 enum E { a = 0, b = -1 } とした場合、対応する文字列のほうは string[] EText = { "b", "a" }; となります。enumの値が飛んでいても文字列のほうは値順に隙間なく並べてください。

最後に愚痴。DataSource が IDictionary<,> を受けることができればスッキリきれいなのに! IList しか受けれないようで。Dictionary がこれほどしっくりくる使いどころはそうそうないと思うんだけど…。

以上、データバインディングのお勉強してたハズが、なんか違うほうにいってしまった日記でしたw


[][] enum を ComboBox に表示する (文字列で置き換えない)  enum を ComboBox に表示する (文字列で置き換えない) - 当面C#と.NETな記録 を含むブックマーク  enum を ComboBox に表示する (文字列で置き換えない) - 当面C#と.NETな記録 のブックマークコメント

もうちょっと実験したらまた不思議なことが起きたのでメモ。

enum を直接コンボボックスに表示する場合です。一つ前のエントリとの違いは文字列で置き換えないってところ。こっちが先だろって一人つっこみしつつ…。

表示したいのはこれ。

public enum Gender { Male, Female }

そのままコンボボックスに Male や Female と表示します。フォームにこれを書くだけ。

this.genderComboBox.DataSource = Enum.GetValues( typeof( Gender ) );

ただし!前の文字列で置き換える例では、データバインディングを使うとき、コンボボックスの Text プロパティにバインドすると動かず、SelectedValue プロパティにバインドしました。

今度は逆で、この例では Text プロパティにバインドしないと動きません。

なんだかなぁ。データバインディングはドキュメントも少ないくせにややこしい。

2007/5/2 (水)

[] ContextMenuStrip の Left と Top  ContextMenuStrip の Left と Top - 当面C#と.NETな記録 を含むブックマーク  ContextMenuStrip の Left と Top - 当面C#と.NETな記録 のブックマークコメント

ContextMenuStrip は Location がないのに、Left と Top はあって、しかもその値はクライアント座標じゃなくてスクリーン座標だった…。ずいぶんはまった(T-T)

MSDNにちゃんと書いておいてよ…。

やろうとしてたことは、DataGridView のセルで ContextMenuStrip を出したときに、どのセルから呼ばれたのか調べようとして、http://oshiete1.goo.ne.jp/qa2740989.html を見つけて真似しようとしてたんですけどね。

もっとスマートな方法はないんでしょうか?

2007/2/25 (日)

[][] DataGridView + SortableBindingList<T> = 楽  DataGridView + SortableBindingList = 楽 - 当面C#と.NETな記録 を含むブックマーク  DataGridView + SortableBindingList = 楽 - 当面C#と.NETな記録 のブックマークコメント

久しぶりにC#、WinFormの話題。ただいまC#。WPF時代到来ですがWinFormです。

DataGridView にオブジェクトを載せると、列のヘッダをクリックしての自動ソートができなくてがっかりします。これをカイゼン!

VisualStudio2005 は、データベースのフロントエンドのようなアプリをコードを書かずに作れてしまう(無料の Express であっても!)めちゃくちゃスゴイ機能があります。本格的なアプリの礎にも、ちょっとしたツールにも便利に使えます。なにこの IDE の進化っぷりは!

データベースだと列ヘッダをクリックすると▼▲みたいなのが表示されて、列の内容でソートできます。コーディングレスでここまでできます。でも、オブジェクト(というより型ですが)を入れるとコードを書かないとソートできません。データベースは自動なのに、差別です!数行書けばいいだけなんだけど、データベースは書かなくてもいいのにって思うとものっっすごく面倒に感じます(^^;


カスタム データ バインド (第 2 部)

http://www.microsoft.com/japan/msdn/columns/winforms/winforms02182005.aspx

このMSDNの古い記事に、どんな型でもソート可能にする SortableBindingList<T> が載っています!すばらしい!VisualStudio で自動生成される「DataGridView - BindingSource - オブジェクト」の形を「DataGridView - BindingSource - SortableBindingList<T> - オブジェクト」とするだけで、どんな型でも自動ソート対応にしてしまいます。のハズなんですが、実は動きません…。

この記事のサンプルでは、なぜか記事本文に書いてあるいくつかのメソッドの実装が欠けてます。さらに、β時代のコードなのでそのままではビルドできず、直してビルドしてもシリアライズの形式が変わったのか、添付データは読めないのでゲームは遊べませんので、あしからず。

欠けてる部分の実装は http://www.thescripts.com/forum/thread441915.html の #7 で Bart Mermuys さんが投稿しています。ApplySortCore() が改悪されてるのが気になりますが。MSDN記事の肝はここに載っている2クラスなので、これだけ見れば元記事のサンプルは見なくてもOKです。ただし、MSDN記事のサンプルコードについてるEULAは目を通すのを忘れずに。

実際に使うには PropertyComparer<T> がプロパティ値の null を考慮してなかったりするので、もうちょっと堅牢にしたほうがいいですね。SortableBindingList<T> クラスに、以下の2行を加えると便利です。

public SortableBindingList() { }
public SortableBindingList( IList<T> list ) : base( list ) { }

使い方は簡単。例えば File.cs というエンティティクラスを作って DataGridView 型アプリを生成したら、BindingSource fileBindingSource が Form にできます。コンストラクタや Load あたりで this.fileBindingSource.DataSource = sortableBindingList; と入れてやればOKです。ちなみに BindingSource 抜き、つまり「DataGridView - SortableBindingList<T> - オブジェクト」形式でも動きます。このへんはまだよくわかってなかったり(-_-; にわかは語る!


後で読む。ソートとフィルタの話。


「コンピュータサイエンスは、間接層を設けることであらゆる問題が解決できるという信念に基づいた学問である。 -- Dennis DeBruler」Kent Beckがファウラーの「リファクタリング」で引用している言葉。この本で一番印象に残っている部分です(リファクタリングに関係ないしw)。この言葉、衝撃的でした。あらゆるというのは大げさだけど、ほぼその通りだし、それまでそういう視点に気づいていなかったし、気づいてなかった自分にも驚いたし。OSは偉大な間接層です。BindingSource や SortableBindingList<T> はカシコイ間接層ですね。

ちなみにこの言葉の言いだしっぺは違うっぽかったり、続きがあったり、むしろ続きを言うための前置きっぽい言葉だったのかもしれません。Wikipediaのバトラー・ランプソンの項ではデビッド・ホイーラーの言葉とされています。



今日のびっくりは「生まれ変わりはあるhttp://www.kisc.meiji.ac.jp/~metapsi/psi/7-3.htm

マジっすか!?

2006/11/26 (日)

[][] FlowLayoutPanel での DragDrop サンプルコード  FlowLayoutPanel での DragDrop サンプルコード - 当面C#と.NETな記録 を含むブックマーク  FlowLayoutPanel での DragDrop サンプルコード - 当面C#と.NETな記録 のブックマークコメント

f:id:siokoshou:20061126113438j:image

http://siokoshou.googlepages.com/

FlowLayoutPanelでのDragDrop、だいぶ前に書いてからまだだらだらやってたんですが、いい加減飽きてきました(^^;

何かの参考になることもあるかもしれないので、コードを公開します。なぜか2000行超えてます。なんでこんなにいったんだろうw MSDNのコードもご参考にどうぞ。

WindowsXPのエクスプローラで縮小版表示したときのDragDrop動作をだいたい真似てます。細かいところでちょっと違うけど、だいたいあんな感じ。


サンプルを実行するとFlowLayoutPanel上にラベルが並んでて、このラベルをマウスで並べ替えできます。Ctrl押しながらDragDropでコピーです。Link動作は未サポート。2つ起動して一方から他方に移動ももちろんできます。

カーソルの移動にあわせて、Iビームがここに入るよってナビゲートします。カーソルは半透明のソースのラベル。ドロップできないところにカーソルが乗ったらStopなカーソルに変わったり、コピー動作ではコピーを示すカーソルに変わります。コピーのカーソルは今どきっぽい(?)緑のプラス( ̄ー ̄)


制限がいろいろ…。

先に書いたとおりLink未対応。カーソル描くのがめんどかったw

同時に複数のラベルを掴むのは未サポート。次の課題はこれだなぁ。

FlowDirectionはLeftToRightのみ対応。必要なかったので。RightToLeftはNoにしか対応していません。Inheritもだめ。

(11/28追記)

一番肝心な制限を書き忘れていたので追記。各子コントロールにマージンがないとIビームが描けません!サンプルはAll=10にしています。

縦1列に並んだ場合には対応しているものの、LeftToRightでありながらFlowBreakで2列以上になるような場合も未対応です。

サンプルってことでご容赦を。

(追記ここまで)

こんなところです。


エクスプローラのDragDrop動作を調べているときに気付いたけど、一覧表示と縮小版表示でDragDropの動作が違うんですね。縮小版だとアドレスバーにドロップできてしまって、バグ?とか思ったけど実にどうでもいいことなのでスルーしておきます。


ソースはDragDropSupportフォルダの下がDragDropのコア部分。それ以外はデモコードです。ライブラリ化しようと思ったけど、制限が多くてちょっと…って状況なので、デモ部分と一緒になってます。

FlowLayoutDragDropSupportクラスがターゲット側FlowLayoutPanelのDragDrop支援クラス。使い方はデモ部分のStackPanelコードを参考にどうぞ。名前長杉…

DragDropScrollはFlowLayoutPanelのDragDrop中のスクロール支援クラス。DragDrop中にカーソルが端に近づいたらスクロールします。

DragDropSourceSupportクラスがソース側支援クラス。使い方はDropSourceLabelを参考に。

IDragDropSourceもソース側で使います。Dragソースの前後にカーソルがあると、Iビームが表示されず、マウスボタンを離してもDropしません。これを実現するために、私がソースですって名乗り出るために使っています。もっとうまい方法がないものかなぁ?


ほかはライブラリ内の内部クラス(のつもり)です。

BeamクラスはIビームを描くクラスですが、IBeamにするとインターフェイスとまぎらわしいので、苦しいけどBeamの名前にしました。Beamじゃ何のことか分かりませんねw

DragDropCursorはソースコントロールを半透明カーソルにするクラス。

FlowBase、HorizontalFlow、VerticalFlow、ZeroFlowがFlowLayoutPanel上の子コントロールのレイアウトを解析して、カーソル位置から挿入位置を割り出すクラス。stateパターン。不用意にネストクラスを使ってしまったので、ちょっと分かり辛いかも。今は反省している。

DragEnterで子コントロールのレイアウトをすべて解析して、各コントロールごとの領域をCellクラスに持ちます。DragOverではカーソル位置から下にあるCellを検索します。処理速度は良好っぽいです。


VisualStudioでデバッグする際、ドラッグ中にカーソルがタスクバーに乗ると「COMException がネイティブまたはマネージ境界を越えました」って出ます。これを止めるにはツール→オプションのデバッグ→全般の一番上にある「例外がAppDomainまたはマネージ/ネイティブの境界を超える場合にブレークする(マネージのみ)」のチェックを外せばOKです。このエラー、「FORMATETC 構造体が無効です (HRESULT からの例外: 0x80040064 (DV_E_FORMATETC))」ってやつなんですが、よく分かりません。


興味のある方は覗いてみてください。

#あ〜っ、ソリューション名が…名前空間が…最初に作ったもののままだったorz スルーしてください…

(11/28追記)制限に抜けがあったので、追記しました。

2006/11/17 (金)

[] カーソル  カーソル - 当面C#と.NETな記録 を含むブックマーク  カーソル - 当面C#と.NETな記録 のブックマークコメント

http://msdn2.microsoft.com/ja-jp/library/system.windows.forms.cursor(VS.80).aspx

によると「カラーのカーソルはサポートされていません。」ってあるけど、カラーで出たんだけど…。ん〜。

newpopsnewpops 2006/11/19 21:21
どうでしょう?少し短くなりました。

Set-Alias wc Measure-Object
ls -r -i *.cs | select FullName,Lines | %{$_.Lines = (cat $_.FullName | wc -l).Lines; $_}

ファイルのパス、ライン数をプロパティとして持つ、
カスタムオブジェクトを作成しています。

P.S.
$wc = New-Object System.Collections.HashTable は、
$wc = @{} と書けますよ。

siokoshousiokoshou 2006/11/20 13:39 あ、コメントに気付くのが遅れてすみません(>_<) おぉぉ、newpoposさんから直伝していただけるとは、光栄です!日記読んでいます。
これ、ばっちりですね! ls に Lines ってのがないのに、select できるんですか!!勉強しないと…
@{}、Perl風ですねw 大変勉強になりました。ありがとうございます。
ホント楽しいですね〜、PowerShell。

newpopsnewpops 2006/11/20 15:06
>ls に Lines ってのがないのに、select できるんですか

そうなんです。知った時は私も驚きました。(^^;
名前からは想像しづらいのですが、
Selectは指定したプロパティを持ったカスタムオブジェクトを
新規作成するコマンドレットです。

渡したオブジェクトに存在するプロパティを指定した場合は、中身はそのままコピーされ、
存在しないプロパティを指定すると、中身が空のプロパティを追加します。
#今回は、新規追加したLinesにライン数を入れています。

奥が深いですね。

siokoshousiokoshou 2006/11/20 15:50 なるほど、奥が深いですね。
Select-Objectの説明を読んでもよく理解できなかったんですが、すっきりしました。
これはPowerShell使いこなしのカギになる強力なコマンドっぽいですね。

#ところでアイコンの「>_」が(>_<)の顔文字に見えて困りますw

2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 06 | 09 | 11 | 12 |
2007 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 08 | 09 | 10 | 12 |
2009 | 01 | 03 | 04 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 07 |
2011 | 04 | 07 | 10 |
2012 | 04 | 12 |
2013 | 08 |
2014 | 03 | 08 |
2017 | 09 |