2011年7月26日火曜日

LongListSelectorが空表示になる問題の回避方法 (Windows Phone)

// 2011/8/21:
// 以前の回避方法だとまだ問題が発生することが分かったので、内容修正。

Windows Phone 用の Silverlight Toolkit (http://silverlight.codeplex.com/) に含まれる LongListSelector は、LongListSelector のインスタンスが含まれるページ (PhoneApplicationPage) から他のページに遷移した後にまた戻ってきたときに、内容が空になってしまうという不具合がある。この問題は、LongListSelector の ScrollTo() を使って強制的に再描画をトリガーすることで回避できる。

参考: WP7 LongListSelector? Be aware of its problems
http://mikaelkoskinen.net/post/wp7-longlist-selector-problems-and-fixing-them.aspx

ScrollTo() が不適当な場合は、ItemsSource の再設定でも再描画をかけることができる。ただし、他のページから戻ってきたとき、LongListSelector インスタンスのロードが終わる前に ItemsSource を設定すると、(既に ItemsSource が設定済みの場合) LongListSelector.Balance() メソッドが NullPointerException を投げてしまう。そのため、ItemsSource を再設定する場合は、LongListSelector の Loaded イベントのハンドラーでおこなう必要がある。

参考: LongListSelector NullReferenceException in Balance()
http://silverlight.codeplex.com/workitem/8376

// 2011/8/21:
// それでもまだLongListSelector.Balance()が問題を起こすようなので、
// LongListSelector.ItemsSourceの再設定は避けたほうがよさそう。

最終的には、対象となる LongListSelector インスタンスの Loaded イベントのハンドラーとして、次のようなメソッドを指定することになる。

// A "Loaded" event handler for LongListSelector.
private void RedrawLongListSelector(object sender, RoutedEventArgs e)
{
    // LongListSelector has a bug where the content of it may
    // become empty when we navigate back to a page which contains
    // the LongListSelector instance.
    //
    //   See http://mikaelkoskinen.net/post/wp7-longlist-selector-problems-and-fixing-them.aspx
    //
    // Therefore, we need to redraw it.
    //
    // The reason that redrawing must be written here in an event handler
    // for Loaded is that LongListSelector throws a NullPointerException
    // if ItemsSource is changed before it gets loaded (if ItemsSource
    // has already been set).
    //
    //   See http://silverlight.codeplex.com/workitem/8376
    //
    // 2011/8/21:
    // Because LongListSelector still throws an exception, it seems better
    // to avoid re-assigning data to LongListSelector.ItemsSource.

    LongListSelector longListSelector = sender as LongListSelector;

    if (longListSelector == null)
    {
        // This should not happen.
        return;
    }

    if (longListSelector.Visibility == Visibility.Collapsed)
    {
        // The LongListSelector instance is hidden.
        // No need to redraw it now.
        return;
    }

    // Get the current selected item.
    var selectedItem = longListSelector.SelectedItem;

    if (selectedItem != null)
    {
        // Redraw the LongListSelector by scrolling to the item.
        longListSelector.ScrollTo(selectedItem);
        return;
    }

    // Get items in the current view. An empty list may be returned
    // if scrolling is happening too quickly. In addition, since the new
    // Silverlight Toolkit for Windows Phone (2011 August), this
    // method has been marked as obsolete and always returns an
    // empty list, the document says.
    var items = longListSelector.GetItemsInView();

    if (items != null && items.Count != 0)
    {
        // Redraw the LongListSelector by scrolling to the item.
        longListSelector.ScrollingTo(items.ElementAt(0));
        return;
    }

    // No item in the current view. Nothing can be done.
    return;

    // 2011/8/21:
    // Removed the following code because it seems re-assigning
    // data to LongListSelector.ItemsSource causes a problem
    // in some contexts.

/*
// Get the current ItemsSource.
    var itemsSource = longListSelector.ItemsSource;

    if (itemsSource == null)
    {
        // No content. No need to redraw.
        return;
    }

    // Set dummy data to force the LongListSelector to redraw itself.
    longListSelector.ItemsSource = new ObservableCollection<object>();

    // Assign the ItemsSource again.
    longListSelector.ItemsSource = itemsSource;
*/
}