2011年09月19日

ListViewのオーナードローの問題(1)(C#メモ)

ListViewの詳細ビューで奇数行と偶数行を変えるような使い方を良く目にしますが、 これを実現するためにはオーナードローを使って自前で描画をしてやる必要があります。
これ自体はそれほど難しい事ではないです。下記のようにやれば実現出来ます。

// ListViewコントロール
public ListView listView1;

private void Form1_Load(object sender, EventArgs e)
{
    // ListViewを生成して配置する
    listView1 = new ListView();
    this.Controls.Add(listView1);

    // オーナードローの設定
    listView1.OwnerDraw = true;
    // Headerの表示をコントロールする為にイベントを追加
    listView1.DrawColumnHeader
        += new DrawListViewColumnHeaderEventHandler(
                    listView1_DrawColumnHeader);
    // Itemの表示をコントロールする為にイベントを追加
    listView1.DrawSubItem 
        += new DrawListViewSubItemEventHandler(
                    listView1_DrawSubItem);

    // プロパティの設定
    listView1.Dock = DockStyle.Fill; // 親(Form)全体に広げる
    listView1.View = View.Details;   // 詳細ビューを指定
    listView1.MultiSelect = false;   // 複数行の選択を禁止
    listView1.FullRowSelect = true;  // 項目を選択すると行全体を選択
    listView1.GridLines = true;      // グリッド線を表示
    
    // 列の作成
    listView1.Columns.Add("id", "ID", 120);
    listView1.Columns.Add("data", "データ", 120);

    // アイテムの追加
    for (int i = 1; i <= 10; i++)
    {
        string[] items = { i.ToString("d2"), "データ" + i.ToString("d2") };
        listView1.Items.Add(new ListViewItem(items));
    }
}

private void listView1_DrawSubItem(
                    object sender, 
                    DrawListViewSubItemEventArgs e)
{
    // 選択されているアイテムの描画
    if ((e.ItemState & ListViewItemStates.Selected) 
                    == ListViewItemStates.Selected)
    {
        e.Graphics.FillRectangle(
                        SystemBrushes.Highlight, 
                        e.Bounds);
        e.Graphics.DrawString(
                        e.SubItem.Text, 
                        e.Item.Font, 
                        SystemBrushes.HighlightText, 
                        e.Bounds);
        return;
    }

    // 奇数行は背景色を変更して描画
    if ((e.Item.Index % 2) != 0)
    {
        e.Graphics.FillRectangle(
                        Brushes.LightYellow, 
                        e.Bounds);
        e.Graphics.DrawString(
                        e.SubItem.Text, 
                        e.Item.Font, 
                        SystemBrushes.WindowText, 
                        e.Bounds);
        return;
    }

    // 偶数行はデフォルト描画
    {
        e.Graphics.FillRectangle(
                        SystemBrushes.Window, 
                        e.Bounds);
        e.Graphics.DrawString(
                        e.SubItem.Text, 
                        e.Item.Font, 
                        SystemBrushes.WindowText, 
                        e.Bounds);
        return;
    }
}

private void listView1_DrawColumnHeader(
                        object sender, 
                        DrawListViewColumnHeaderEventArgs e)
{
    // 描画はOSに任せる
    e.DrawDefault = true;
}

実行するとこのような感じになります。
listview-ownerdraw-1.png

ここまでは問題ないのですが、ここでツールヒントを使うように設定を変更してみます。

// Form1_Loadに下記の行を追加
listView1.ShowItemToolTips = true;   // ツールヒント表示

すると、選択された行の描画が上手くいかなくなります。
具体的には、選択された行にマウスカーソルを置くと、選択が解除されたような感じになります。

listview-ownerdraw-2.png

原因を調べてみると、どうやらDrawSubItemに渡される引数DrawListViewSubItemEventArgsのItemStateにSelectedのビットがONになっていないケースがあるようです。

DrawSubItemでItemStateをリストに描画するように改造します。

private void listView1_DrawSubItem(
                        object sender, 
                        DrawListViewSubItemEventArgs e)
{
    // 選択されているアイテムの描画
    if ((e.ItemState & ListViewItemStates.Selected) 
                    == ListViewItemStates.Selected)
    {
        e.Graphics.FillRectangle(
                        SystemBrushes.Highlight, 
                        e.Bounds);
        e.Graphics.DrawString(
                        e.ItemState.ToString(), 
                        e.Item.Font, 
                        SystemBrushes.HighlightText, 
                        e.Bounds);
        return;
    }

    // 奇数行は背景色を変更して描画
    if ((e.Item.Index % 2) != 0)
    {
        e.Graphics.FillRectangle(
                        Brushes.LightYellow, 
                        e.Bounds);
        e.Graphics.DrawString(
                        e.ItemState.ToString(), 
                        e.Item.Font, 
                        SystemBrushes.WindowText, 
                        e.Bounds);
        return;
    }

    // 偶数行はデフォルト描画
    {
        e.Graphics.FillRectangle(
                        SystemBrushes.Window, 
                        e.Bounds);
        e.Graphics.DrawString(
                        e.ItemState.ToString(), 
                        e.Item.Font, 
                        SystemBrushes.WindowText, 
                        e.Bounds);
        return;
    }
}

実行してみると、選択行は「Selected, Focused, Hot, ShowKeyboardCues」となりますが、 カーソルを上に置いてしばらくすると「0」になってしまいます。

listview-ownerdraw-3.png

なぜ、このようなことになるのか、調べてみたのですが良く分かりませんでした。 おそらくは、ツールヒントを表示する処理の中で呼び出されているのだと思いますが…。

結局、回避する方法は分からず、ツールヒントを使用しないぐらいしか対応策は考えつきませんでした。

ListViewの詳細ビューのオーナードローには、他にも問題(というか知らないとトラップにはまること)があるので、別の機会に書きたいと思います。
(実はMSDNにちゃんと書いてあるかもしれませんが…気づいてないだけで。)

良かったらクリックしてください
にほんブログ村 IT技術ブログ プログラム・プログラマーへ  人気ブログランキングへ

posted by among at 21:44 | Comment(0) | TrackBack(0) | C#
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/48035059

この記事へのトラックバック