.NET1.1のListViewでオーナードロー、オーナーデータ

.NET Framework 1.1のListViewで、オーナーデータをやりたいという要求とオーナードローをやりたいという要求が同じ日に自分の所にまわってきて。
そんな対応をしていた、今日はListViewの日(・∀・)


面倒なことに、.NET Framework 1.1だとどちらにも標準対応してないんですよね(´・ω・`)
.NET Framework 2.0ならVirtualModeプロパティやOwnerDrawプロパティがあるわけですが。


で、やるとしたらWin32的な処理でやるか、いっそ.NET 2.0にしてしまうか。


オーナーデータの方は、前に自分が作ったListView表示の簡易ツールで、大量のデータ*1を扱いたいと言う内容だったわけですけが。
最大でも数千件程度のデータを想定したテスト用のツールだったので、単純にListViewItemの追加*2
を行うだけの実装にしていたため、大量データだと何十秒も固まるという状況に(´Д`)


まあ、これは正規に顧客へリリースするプログラムでも無いし、内部で使うためのものなので、.NET 2.0化することで対応しちゃいました(゚∀゚)
前にMSDNかどこかで.NET 1.1用のListViewオーナーデータ拡張を見かけた気もするんですが、探すのも面倒だったし.NET 2.0化するほうが楽だと思ったりして。


で、もう一つの要求について。
オーナードローの方はというと、こっちは厄介なことに顧客へリリースするプログラムであって、要件を満たすためにはListViewのオーナードローが必要なものだったりして。
勿論、仕様の方を調整するとか、グリッド系のコンポーネントに切り替えるっていう手はあるわけですが。
ただ、顧客*3が変更管理に関してとってもお役所的な作業を必要とされるタイプということもあって、とりあえずListViewの設計のままでどうにかできないかというお話でした。


だから、推敲フェーズで技術リスクをちゃんと検証してないプロジェクトはダメなんだよ!*4
しかも細かいことに『だけ』五月蠅い顧客の案件で、実装スキルの無い人間が適当な設計書を書いているから、後になって実現できないとか騒ぐ事に(`ω´*)


で、環境も.NET 1.1用と決まっているモノだから2.0化して対応というわけにも行かず、とりあえずWin32的な対処方法を検討してみることにしたのでそのメモ。


まずは、CDDS_PREPAINTとかNM_CUSTOMDRAWとかCDRF_DODEFAULTのenumを定義。
RECT、NMHDR、NMCUSTOMDRAW、MEASUREITEMSTRUCTのstructなんかも定義。


あとは以下のようなCustomDrawHandlerと、そのイベントOnCustomDrawを持つListView派生クラスを作って、WndProcをoverrideすることでオーナードローに対応してみました。

// カスタムドロー用ハンドラ
public delegate int CustomDrawHandler(int idCtrl, ref NMCUSTOMDRAW nmcd);

// カスタムドロー対応ListView
public class CustomDrawListView : System.Windows.Forms.ListView
{
    public event CustomDrawHandler OnCustomDraw;

    private int itemHeight = 13;

    public int ItemHeight
    {
        get { return( this.itemHeight ); }
        set { this.itemHeight = value; }
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.Style |= LVS_OWNERDRAWFIXED;
            return( cp );
        }
    }

    protected override void WndProc(ref Message msg)
    {
        if ( msg.Msg == (int)OCM.OCM_MEASUREITEM )
        {
            unsafe
            {
                MEASUREITEMSTRUCT* pmis =  (MEASUREITEMSTRUCT*)msg.LParam.ToPointer();
                pmis->itemHeight = this.itemHeight;
            }
            return;
        }

        if ( OnCustomDraw != null )
        {
            if ( msg.Msg == (int)OCM.OCM_NOTIFY )
            {
                NMHDR hrd = (NMHDR)msg.GetLParam( typeof(NMHDR) );
                
                if ( hrd.code == (int)NM.NM_CUSTOMDRAW )
                {	
                    NMLVCUSTOMDRAW nmlvcd = (NMLVCUSTOMDRAW)msg.GetLParam( typeof(NMLVCUSTOMDRAW) );

                    msg.Result = (IntPtr)OnCustomDraw( (int)msg.WParam.ToInt32(), ref nmlvcd );

                    return;
                }
            }
        }

        base.WndProc(ref msg);
    }
}

1行にマルチラインの文字表示したいということで、行の高さも設定できるようにOCM_MEASUREITEMもハンドリングしています。


で、描画の詳細はイベントの実装先に委譲しちゃっていますが(´Д`;)
後は担当者が実装するということですが…、大丈夫かしら?
とりあえず「.NET 1.1におけるListViewでのオーナードローの仕組み」までは用意してみたわけですが、面倒なのはここからよね〜。


まあ、一般的にはこれから新規に.NET 1.1の開発ってことも無いだろうし、今更必要の無い情報でしたね(´ω`)

*1:10万行とか

*2:もちろnBeginUpdate()、EndUpdate()はしています

*3:っというかその前のコンサルの人達

*4:…って別にRUPしてるわけではないんですけどね(´ω`)