ページ

2011年5月25日水曜日

[WP7] LongListSelector でちょっとはまった

Silverlight for Windows Phone Toolkit に入っている LongListSelector はお手軽にグループ化したリストが作れて便利ですが、ちょっとはまったので記録として。

もっとも単純な形だと、

public class Book
{
    public string Category { get; set; }
    public string Name { get; set; }
}

こんなクラスがあったとすると、

var books = new Book[] {
    new Book() { Category = "Cat1", Name = "Book1" },
    new Book() { Category = "Cat1", Name = "Book2" },
    new Book() { Category = "Cat2", Name = "Book3" },
};

this.LongListSelector1.ItemsSource = from book in books
                                     group book by book.Category into c
                                     select c;

こんな風にすればグループ化して表示できます。

と思ってたんですが、これじゃダメなんですね。これだとグループヘッダーのところに何も表示されません。
この場合、ItemsSource には System.Linq.IGouping<TKey, TElement> のコレクションを渡していて、Key がグループヘッダー、グループの中身は GetEnumerator() で取得することになります。実際に group by で返ってくるのは IGrouping を実装した System.Linq.Lookup.Grouping クラス(リファレンスには載ってない)なんですが、どうやらこれの Key プロパティとうまくデータバインドできない様子。

Toolkit のサンプルを見ると

public class PublicGrouping<TKey, TElement> : IGrouping<TKey, TElement>
{
    private readonly IGrouping<TKey, TElement> _internalGrouping;

    public PublicGrouping(IGrouping<TKey, TElement> internalGrouping)
    {
        _internalGrouping = internalGrouping;
    }

    public override bool Equals(object obj)
    {
        PublicGrouping<TKey, TElement> that = obj as PublicGrouping<TKey, TElement>;

        return (that != null) && (this.Key.Equals(that.Key));
    }

    public override int GetHashCode()
    {
        return Key.GetHashCode();
    }

    #region IGrouping<TKey,TElement> Members

    public TKey Key
    {
        get { return _internalGrouping.Key; }
    }

    #endregion

    #region IEnumerable<TElement> Members

    public IEnumerator<TElement> GetEnumerator()
    {
        return _internalGrouping.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _internalGrouping.GetEnumerator();
    }

    #endregion
}

こんなクラスを自前で用意して

this.LongListSelector1.ItemsSource = from book in books
                                     group book by book.Category into c
                                     select new PublicGrouping<string, Book>(c);

と言う風に包んであげています。
なぜそんな事に?

var q = from book in books
        group book by book.Category into c
        select c;
var group = q.First();
var key = group.Key;                                    // ちゃんと Key の内容を取り出せる
var propertyInfo = group.GetType().GetProperty("Key");    // null が返る!

こんな風に試してみました。
System.Linq.Lookup.Grouping クラスは DependectyObject ではありませんから、LongListSelector は内部でリフレクションを使ってデータバインドしてると思います。なので、GetProperty(“Key”) で PropertyInfo が取れないとバインドしようが無いと思いますが、実際に試してみると null が返ってきちゃいます。どうやら Grouping クラスは internal なのでリフレクションでアクセスできない様子。
ちなみに、まったく同じコードを .NET Framework 4 のコンソールアプリケーションと Silverlight 4 で試すとまったく問題なく PropertyInfo が取得できます。Silverlight と WP7 は null になるのかと思ったら、WP7 だけが動作が違うんですね。

分かってしまえばどうということは無いんですけど、ちょっとはまりました。
(というか、最初から public に Grouping クラスを用意しておいてくれたらよかったのに)

0 件のコメント:

コメントを投稿