複数選択可能なListViewコントロール

いや、もちろんCompact Frameworkでなければ…ListViewのMultiSelectプロパティをTrueにするだけ ;)

なので、ListViewコントロールを見ると…ないよ :shock: でたぁ~また、Compact Frameworkにはない :arrow:

要するにチェックボックス付きがあるだろう…ってのがMS様の言い分なのかな :?: 仕方ないので、Google先生に訊いてみるが…なかなか答えてくれない :cry:

そんな中、CodeProjectで見つけたのはCompact Frameworkとは全く関係ない…以下のXPTableというListView拡張で、何かの機会に使うためにメモ :)

あ、Compact Frameworkでした。調べてると同じことで質問してる人は居るんだけど…なかなか望んだ回答がでていないなぁ~と思っていると以下のページを発見。

ページの中にuuencodeされたMultiSelectListView.zipというファイルが貼り付けられてるなぁ~内容はわからないけど、どにかくエディタに貼り付けて…改行コードをLFで保存して、Cygwinのuudecodeでファイルを取り出してみた。なんか、uudecodeなんて使うの超ひさしぶりだなぁ~なんて思いつつ。

でてきた、MultiSelectListView.zipを展開すると…おぉ、VB.netのコードだったのね。量からみて、大きくないのはわかっていたので中身を見てみる。

なるほどポイントは、P/Invokeを使ってSetWindowLongを呼び出して、ウィンドウ(ListView)のLVS_SINGLESEL属性を外してやることだけなのね :idea: わかってみれば、なるほどなんだけど…思いつかなかったなぁ。

と言うことで、C#に書き直したポイントだけのコードをペタっとしておきます ;) ありゃ、わざわざGetCaptureしてウィンドウハンドルを取らなくてもListViewのプロパティから取れますね :arrow:

    private const int GWL_STYLE = -16;
    private const int LVS_SINGLESEL = 0x4;
 
    [DllImport("coredll.dll")]
    static extern IntPtr GetCapture();
    [DllImport("coredll.dll", SetLastError = true)]
    private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
    [DllImport("coredll.dll", SetLastError = true)]
    private static extern UInt32 GetWindowLong(IntPtr hWnd, int nIndex);
 
    private void EnableMultiSelect(ListView listView)
    {
        listView.Capture = true;
        IntPtr hWnd = GetCapture();
        listView.Capture = false;
        uint style = GetWindowLong(hWnd, GWL_STYLE);
        SetWindowLong(hWnd, GWL_STYLE, (int)(style & ~LVS_SINGLESEL));
    }

このEnableMultiSelectメソッドをListViewを使うフォームのコンストラクタあたりで、複数選択させたいListViewを引数にして呼べば幸せになれるかも知れません。

さらに、CtrlキーやShiftキーを同時押下するのが面倒(ソフトウェアキーボードだもんね)なんて場合は、ListViewのフォーカスイン時とフォーカスアウト時にCtrlキーを制御してフォーカスのある間はCtrlキーを押した状態にしておけば…さらに幸せになれるかも知れません ;)

Ctrlキーを制御するコードもついでなのでペタっと。

    private const byte VK_CONTROL = 0x11;
    private const int KEYEVENTF_KEYDOWN = 0x0;
    private const int KEYEVENTF_KEYUP = 0x2;
 
    [DllImport("coredll.dll", EntryPoint = "keybd_event", SetLastError = true)]
    internal static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
 
    private void FocusIn()
    {
        keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYDOWN, 0);
    }
 
    private void FocusOut()
    {
        keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);
    }

あ、あと選択されている項目を取り出すコードもペタっとしとかなきゃ。

    ListView.SelectedIndexCollection collection = listViewMulti.SelectedIndices;
    int count = collection.Count; // 選択されている個数がcountに
    foreach (int i in collection)
    {
        // iに選択されているリストのインデックスが順次入る(当然0からの値)
    }

追記 2008-11-10 21:24:25
Ctrlキーの同時押しですが…FocusInとFocusOut時の制御では、やはり制御しきれないみたいです :|
結局、ListViewのWinProcをフックしてWM_LBUTTONDOWN時にMK_CONTROLビットをORすることで同時に押されている状態としました。
さらに、WM_LBUTTONDBLCLKとWM_KILLFOCUSメッセージを無視するようにすると…さらに幸せになれるかも知れません。

さらに追記 2008-11-13 01:13:25
WinProcのフックですが…『WndProcHooker クラスを使用する』のやり方を忠実に実装しました。当初、安易な方法で実装したら…最小化、最大化などを行うと思わぬ挙動をして半泣き。
なので、結局カスタムコントロール化してEnableMultiSelectと同様の処理をコンストラクタで行うようにして、ようやく最終形ができあがりました。
確かにカスタムコントロール内で全て閉じたのでコードは綺麗になったけど…超面倒ですね :|

Be Sociable, Share!

TrackBack URL :

No comments yet. Be the first.

Leave a reply