自作のListViewItemを作成する

ListViewを使ってると「なーんでわざわざデータ用のクラスを作ったのにstringの配列なんぞに変換せにゃならんのじゃ」と結構色んなタイミングで感じることがあります。

いや別に、入れる分にはいいんですけど、取り出す際にTextプロパティ見てSubItemsプロパティ見てstringからデータ用のクラスに変換して、って操作が面倒。Selectしてもいいけど、listView.Items.Cast<Hoge>()で済ませられるなら済ませたい。ついでにlistView.Items.Add(new Hoge())で済ませたい。

というわけで、ListViewItemを継承してみましょう。

コード

ちょうど今メールに関するあれこれを個人的にやっていて、作ってみたもの。

public class MailData
{
    public long Id { get; set; }
    public string From { get; set; }
    public string Title { get; set; }
    public string Date { get; set; }
}

public class ListMailData : ListViewItem
{
    public MailData InnerData { get; private set; }

    public ListMailData(MailData data)
    {
        InnerData = data;
        base.Text = data.From;
        base.SubItems.Add(data.Title);
        base.SubItems.Add(data.Date);
    }
}

えー、正直あんまり…というか、全然スマートじゃないです。自分自身あんまり納得いってないです。結局取り出すときはlistView.Items.Cast<ListMailData>().Select(x => x.InnerData);としないといけませんし、Addもnew ListMailDataしないといけないです。

ただ、MailDataの方でListViewItemを継承するともっとスマートじゃなくなります。

いいわけ

データオブジェクトの方で(つまり、MailDataの方で)ListViewItemを継承しようとなると、SubItemsの扱いが死ぬほど面倒になります。

かといって、ListViewSubItemを継承したクラスも作るの???となると、なんかそれも嫌です。理想としては、データオブジェクトのプロパティそのものがTextとSubItemsの関係になっていて欲しいです。

で、じゃあプロパティのsetでそれを再現してみようとなると、こんな感じになります。

public class TestMailData : ListViewItem
{
    public long Id { get; set; }

    private string _From;
    public string From
    {
        get { return _From; }
        set
        {
            _From = value;
            base.Text = value;
        }
    }

    private string _Title;
    public string Title
    {
        get { return _Title; }
        set
        {
            _Title = value;
            base.SubItems.Add(value);
        }
    }

    private string _Date;
    public string Date
    {
        get { return _Date; }
        set
        {
            _Date = value;
            base.SubItems.Add(_Date);
        }
    }
}

これ、Fromはともかく、TitleとDateをsetする時の順番によってSubItemsの順番も変わってしまいます。それはいくらなんでもダメでしょう。

次に考えたのはこれ。ListViewそのものが画面に表示されるときに、いくらなんでもSubItemsを一回も見ないってことはないだろう。という発想。

public class TestMailData : ListViewItem
{
    public long Id { get; set; }
    public string Title { get; set; }
    public string Date { get; set; }

    private string _From;
    public string From
    {
        get { return _From; }
        set
        {
            _From = value;
            base.Text = value;
        }
    }

    public new ListViewSubItemCollection SubItems
    {
        get
        {
            base.SubItems.Add(Title);
            base.SubItems.Add(Date);
            return base.SubItems;
        }
    }
}

プロパティをオーバーライドすればいけるんじゃね?と思ったんですが、これじゃあgetが呼ばれるたびにSubItemsが増えるし、それを対策したとしても、そもそもListViewItemのSubItemsプロパティは呼ばれません。baseのListViewItemがprivateで持っている、SubItemsプロパティのgetが返す隠蔽されたオブジェクトを直接使っています。なので、オーバーライドしても意味がないです。

最後に考えたのがこれ。

public class TestMailData : ListViewItem
{
    public long Id { get; private set; }
    public string From { get; private set; }
    public string Title { get; private set; }
    public string Date { get; private set; }

    public TestMailData(long id, string from, string title, string date)
    {
        Id = id;

        From = from;
        base.Text = from;

        Title = title;
        base.SubItems.Add(title);

        Date = date;
        base.SubItems.Add(date);
    }
}

コンストラクタで全部指定。確実ですが、完全敗北です。まぁ確かに、全項目が揃ってない状態でListViewItemを作られても困るんだけども、このクラスを作るときに一発で全プロパティの値を取ってこれなかったら非常に取り回しにくくなります。却下。

で、冒頭の形に落ち着いたわけです。

まとめ

いい加減WPF、っつーかXAML覚えたいですね。