2011年7月31日日曜日

Life cycle of Windows Phone applications

I found that the application life cycle of Windows Phone applications is not so simple as I expected. Here is a pseudo code to fulfill the requirements. See Execution Model for Windows Phone (http://msdn.microsoft.com/en-us/library/ff769557%28v=vs.92%29.aspx) for details.


private void Application_Launching(object sender, LaunchingEventArgs e)
{
    // Launching to Running state.

    // If application persistent state is available, it should be restored.
    // However, it should not be done here to avoid consuming too much time
    // on launching.
}

private void Application_Activated(object sender, ActivatedEventArgs e)
{
    if (e.IsApplicationInstancePreserved)
    {
        // Activated from Dormant state.
    }
    else
    {
        // Activated from Tombstoned state.

        // Restore the application transient state which was saved
        // during the last call of Application_Deactivated() method.
        var state = PhoneApplicationService.Current.State;
        RestoreApplicationTransientState(state);
    }
}

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
    // Deactivated to Dormant state.

    // Save the application transient state which may be used later
    // by Application_Activated() method in case this application is
    // relaunched from Tombstoned state.
    var state = PhoneApplicationService.Current.State;
    SaveApplicationTransientState(state);

    // Save the application persistent state to isolated storage
    // because this application may be terminated without being
    // activated, and in such a case, Application_Closing() is
    // not called.
    SaveApplicationPersistentState();
}

private void Application_Closing(object sender, ClosingEventArgs e)
{
    // Save the application persistent state to isolated storage
    // to reuse it later on next invocation.
    SaveApplicationPersistentState();
}


protected override void OnNavigatedTo(NavigationEventArgs e)
{
    // If this page instance is new.
    //
    // The variable _isNewPageInstance here is an instance variable
    // and initialized to true in this page's constructor.
    if (_isNewPageInstance)
    {
        if (0 < State.Count)
        {
            // Activated from Tombstoned state.

            // Use the page state data to build up this page.
            // The data was saved during the last call of
            // OnNavigatedFrom() method which was executed
            // when this application entered Dormant state.
            RestorePageState(State);
        }
        else
        {
            // Normal forward page navigation. Build up this page
            // from scratch.
            InitializePageState(State);
        }

        _isNewPageInstance = false;
    }
    else
    {
        // Activated from Dormant state, or
        // normal page backward navigation.

        // There is nothing to do because the page state remains
        // intact in memory.
    }
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    if (e.NavigationMode == NavigationMode.Back)
    {
        // Normal backward page navigation.

        // No need to save the page state because this page
        // instance is about to be discarded.
    }
    else
    {
        // Save the page state to reuse it later when this
        // application is activated from Tombstoned state.
        SavePageState(State);
    }
}


Windows Phoneアプリケーションのライフサイクル

Windows Phone アプリケーションのライフサイクルは、期待していたほど単純なものではなかった。要求事項を満たすための擬似コードは下記の通り。詳細は Execution Model for Windows Phone (http://msdn.microsoft.com/en-us/library/ff769557%28v=vs.92%29.aspx) を参照のこと。

private void Application_Launching(object sender, LaunchingEventArgs e)
{
    // Launching to Running state.

    // もしもアプリケーションの永続状態が利用可能であれば、復元する。
    // ただし、起動時に多くの時間を消費しないように、復元はここでは
    // おこなわない。
}

private void Application_Activated(object sender, ActivatedEventArgs e)
{
    if (e.IsApplicationInstancePreserved)
    {
        // Activated from Dormant state.
    }
    else
    {
        // Activated from Tombstoned state.

        // 最後の Application_Deactivated() メソッドの呼び出しの際に
        // 保存されたアプリケーションの一時状態を復元する。
        var state = PhoneApplicationService.Current.State;
        RestoreApplicationTransientState(state);
    }
}

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
    // Deactivated to Dormant state.

    // このアプリケーションが Tombstoned 状態から再起動された場合に
    // Application_Activated() メソッドで使用することになる、アプリ
    // ケーションの一時状態を保存する。
    var state = PhoneApplicationService.Current.State;
    SaveApplicationTransientState(state);

    // Activate されることなくアプリケーションが終了させられることも
    // ありうるので(そのケースでは Application_Closing() は呼ばれない)、
    // アプリケーションの永続状態を isolated storage に保存しておく。
    SaveApplicationPersistentState();
}

private void Application_Closing(object sender, ClosingEventArgs e)
{
    // 次回の起動時に再利用できるよう、アプリケーションの永続状態を
    // isolated storage に保存する。
    SaveApplicationPersistentState();
}

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    // このページインスタンスが新しい場合
    //
    // 変数 _isNewPageInstance はインスタンス変数で、このページの
    // コンストラクタで true に初期化される。
    if (_isNewPageInstance)
    {
        if (0 < State.Count)
        {
            // Activated from Tombstoned state.

            // ページの状態データを使ってこのページを構築する。
            // データは、このアプリケーションが Dormant state に
            // 移行するときに実行された OnNavigatedFrom() メソッド
            // の呼び出し中に保存されたもの。
            RestorePageState(State);
        }
        else
        {
            // 通常の前方ページ遷移。このページを一から構築する。
            InitializePageState(State);
        }

        _isNewPageInstance = false;
    }
    else
    {
        // Activated from Dormant state, もしくは通常の後方ページ遷移。

        // ページの状態はメモリ上にそのまま残っているので、ここでは
        // 特にすることはない。
    }
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    if (e.NavigationMode == NavigationMode.Back)
    {
        // 通常の後方ページ遷移

        // このページインスタンスは破棄されようとしているところなので、
        // ページの状態を保存する必要はない。
    }
    else
    {
        // 後でアプリケーションが Tombstoned 状態から復帰したときに 
        // 再利用するため、ページの状態を保存しておく。
        SavePageState(State);
    }
}

2011年7月29日金曜日

Binding ActualHeight and ActualWidth (Silverlight)

As reported to Microsoft Connect:

  Silverlight Grid/StackPanel/Canvas ActualHeight and AcutalWidth is 0
  http://connect.microsoft.com/VisualStudio/feedback/details/522231/silverlight-grid-stackpanel-canvas-actualheight-and-acutalwidth-is-0

and as described in the API document:

  FrameworkElement.ActualHeight Property
  http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.actualheight%28v=VS.95%29.aspx

  FrameworkElement.ActualWidth Property
  http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.actualwidth%28v=VS.95%29.aspx

ActualHeight and ActualWidth are not suitable for data binding. Therefore, as a workaround, Microsoft suggests that SizeChanged event be used if we want to do something when actual size changes. However, all we want are just data-binding-suitable ActualHeight and ActualWidth, and here I show an implementation for that purpose.

The class, SizeChange, whose implementation I show later, defines 3 attached properties named "IsEnabled", "ActualHeight" and "ActualWidth", respectively. If SizeChange.IsEnabled="True" is given as a property to a FrameworkElement, actual height and actual width of the FrameworkElement can be obtained through SizeChange.ActualHeight and SizeChange.ActualWidth.

Before diving into the implementation, let's see a usage example to understand what we want to realize.

Usage Example:

<...
    // At an appropriate place, define a namespace prefix for
    // the namespace under whicn SizeChange class is contained.
    //
    // The definition below assumes that SizeChange class exists
    // under the namespace "MyApp.UI" and specifies "local_ui"
    // as a prefix for the namespace.
    xmlns:local_ui="clr-namespace:MyApp.UI"
    >

    // Give a SizeChange.IsEnabled="True" to a FrameworkElement.
    // By doing this, actual height and actual width of this
    // element can be bound through SizeChange.ActualHeight and
    // SizeChange.ActualWidth.
    <Grid local_ui:SizeChange.IsEnabled="True" x:Name="grid1">
        ...
    </Grid>

    // Bind the data-binding-suitable attached property.
    //
    // Here, Text property of the TextBlock instance is bound
    // to SizeChange.ActualHeight attached-property of the Grid
    // instance named "grid1".
    //
    // As a result, The TextBlock shows the actual height of
    // the Grid instance, and the shown value is refreshed
    // whenever the actual size of the Grid instance is changed.
    //
    // Note that "Path=(local_ui:SizeChange.ActualHeight)"
    // is used instead of "Path=ActualHeight".
    <TextBlock Text="{Binding ElementName=grid1,
                              Path=(local_ui:SizeChange.ActualHeight)}"/>


And the implementation is here:

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace MyApp.UI
{
    // Declare SizeChange class as a sub class of DependencyObject
    // because we need to register attached properties.
    public class SizeChange : DependencyObject
    {
        #region Attached property "IsEnabled"

        // The name of IsEnabled property.
        public const string IsEnabledPropertyName = "IsEnabled";

        // Register an attached property named "IsEnabled".
        // Note that OnIsEnabledChanged method is called when
        // the value of IsEnabled property is changed.
        public static readonly DependencyProperty IsEnabledProperty
            = DependencyProperty.RegisterAttached(
                IsEnabledPropertyName,
                typeof(bool),
                typeof(SizeChange),
                new PropertyMetadata(false, OnIsEnabledChanged));

        // Getter of IsEnabled property. The name of this method
        // should not be changed because the dependency system
        // uses it.
        public static bool GetIsEnabled(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsEnabledProperty);
        }

        // Setter of IsEnabled property. The name of this method
        // should not be changed because the dependency system
        // uses it.
        public static void SetIsEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(IsEnabledProperty, value);
        }

        #endregion

        #region Attached property "ActualHeight"

        // The name of ActualHeight property.
        public const string ActualHeightPropertyName = "ActualHeight";

        // Register an attached property named "ActualHeight".
        // The value of this property is updated When SizeChanged
        // event is raised.
        public static readonly DependencyProperty ActualHeightProperty
            = DependencyProperty.RegisterAttached(
                ActualHeightPropertyName,
                typeof(double),
                typeof(SizeChange),
                null);

        // Getter of ActualHeight property. The name of this method
        // should not be changed because the dependency system
        // uses it.
        public static double GetActualHeight(DependencyObject obj)
        {
            return (double)obj.GetValue(ActualHeightProperty);
        }

        // Setter of ActualHeight property. The name of this method
        // should not be changed because the dependency system
        // uses it.
        public static void SetActualHeight(DependencyObject obj, double value)
        {
            obj.SetValue(ActualHeightProperty, value);
        }

        #endregion

        #region Attached property "ActualWidth"

        // The name of ActualWidth property.
        public const string ActualWidthPropertyName = "ActualWidth";

        // Register an attached property named "ActualWidth".
        // The value of this property is updated When SizeChanged
        // event is raised.
        public static readonly DependencyProperty ActualWidthProperty
            = DependencyProperty.RegisterAttached(
                ActualWidthPropertyName,
                typeof(double),
                typeof(SizeChange),
                null);

        // Getter of ActualWidth property. The name of this method
        // should not be changed because the dependency system
        // uses it.
        public static double GetActualWidth(DependencyObject obj)
        {
            return (double)obj.GetValue(ActualWidthProperty);
        }

        // Setter of ActualWidth property. The name of this method
        // should not be changed because the dependency system
        // uses it.
        public static void SetActualWidth(DependencyObject obj, double value)
        {
            obj.SetValue(ActualWidthProperty, value);
        }

        #endregion

        // This method is called when the value of IsEnabled property
        // is changed. If the new value is true, an event handler is
        // added to SizeChanged event of the target element.
        private static void OnIsEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            // The given object must be a FrameworkElement instance,
            // because we add an event handler to SizeChanged event
            // of it.
            var element = obj as FrameworkElement;

            if (element == null)
            {
                // The given object is not an instance of FrameworkElement,
                // meaning SizeChanged event is not available. So, nothing
                // can be done for the object.
                return;
            }

            // If IsEnabled=True
            if (args.NewValue != null && (bool)args.NewValue == true)
            {
                // Attach to the element.
                Attach(element);
            }
            else
            {
                // Detach from the element.
                Detach(element);
            }
        }

        private static void Attach(FrameworkElement element)
        {
            // Add an event handler to SizeChanged event of the element
            // to take action when actual size of the element changes.
            element.SizeChanged += HandleSizeChanged;
        }

        private static void Detach(FrameworkElement element)
        {
            // Remove the event handler from the element.
            element.SizeChanged -= HandleSizeChanged;
        }

        // An event handler invoked when SizeChanged event is raised.
        private static void HandleSizeChanged(object sender, SizeChangedEventArgs args)
        {
            var element = sender as FrameworkElement;

            if (element == null)
            {
                return;
            }

            // Get the new actual height and width.
            var width  = args.NewSize.Width;
            var height = args.NewSize.Height;

            // Update values of SizeChange.ActualHeight and
            // SizeChange.ActualWidth.
            SetActualWidth(element, width);
            SetActualHeight(element, height);
        }
    }
}


ActualHeight と ActualWidth をバインドする (Silverlight)

Microsoft Connect に報告されているように、

  Silverlight Grid/StackPanel/Canvas ActualHeight and AcutalWidth is 0
  http://connect.microsoft.com/VisualStudio/feedback/details/522231/silverlight-grid-stackpanel-canvas-actualheight-and-acutalwidth-is-0

また、API ドキュメントに記述されているように、

  FrameworkElement.ActualHeight プロパティ
  http://msdn.microsoft.com/ja-jp/library/system.windows.frameworkelement.actualheight%28v=VS.95%29.aspx

  FrameworkElement.ActualWidth プロパティ
  http://msdn.microsoft.com/ja-jp/library/system.windows.frameworkelement.actualwidth%28v=VS.95%29.aspx

ActualHeight と ActualWidth はデータバインディングに適していない。そのため、回避策として、実際のサイズが変更されたときに何かをしたい場合は SizeChanged イベントを使うようにと、マイクロソフトは言っている。しかし、私たちが欲しいのは、単にデータバインディングに適した ActualHeight と ActualWidth であり、そしてそのための実装をここにあげる。

後で実装を見ることになる SizeChange クラスは、IsEnabled、ActualHeight、ActualWidth という三つのアタッチトプロパティ (attached property) を定義している。SizeChange.IsEnabled="True" がプロパティとして FrameworkElement に与えられると、その FrameworkElement の実際の高さと実際の幅を、SizeChange.ActualHeight と SizeChange.ActualWidth を通じて得ることができるようになる。

実装を見る前に、何を実現しようとしているかを理解するために、使用例を見よう。

使用例:

<...
    // 適切な場所で、SizeChange クラスが含まれる名前空間用の
    // 名前空間プレフィックスを定義する。
    //
    // 下記の定義では、SizeChange クラスが名前空間 MyApp.UI
    // にあるものと仮定し、その名前空間用のプレフィックスと
    // して local_ui を指定している。
    xmlns:local_ui="clr-namespace:MyApp.UI"
    >

    // SizeChange.IsEnabled="True" を FrameworkElement に
    // 与える。これをすることにより、このエレメントの
    // 実際の高さと幅を、SizeChange.ActualHeight と
    // SizeChange.ActualWidth でバインドできるようになる。
    <Grid local_ui:SizeChange.IsEnabled="True" x:Name="grid1">
        ...
    </Grid>

    // データバインディングに適したアタッチトプロパティを
    // バインドする。
    //
    // ここでは、TextBlock インスタンスの Text プロパティを、
    // grid1 という名前の Grid インスタンスのアタッチト
    // プロパティ SizeChange.ActualHeight にバインドしている。
    //
    // 結果として、TextBlock は Grid インスタンスの実際の
    // 高さを表示し、Grid インスタンスの実際のサイズが変更
    // されるたびに表示される値が更新される。
    //
    // "Path=ActualHeight" ではなく
    // "Path=(local_ui:SizeChange.ActualHeight)" が使用されている
    // ことに注意。
    <TextBlock Text="{Binding ElementName=grid1,
                              Path=(local_ui:SizeChange.ActualHeight)}"/>

そして実装は下記のとおり。

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace MyApp.UI
{
    // アタッチトプロパティを登録する必要があるので、SizeChange
    // クラスを DependencyObject のサブクラスとして宣言する。
    public class SizeChange : DependencyObject
    {
        #region Attached property "IsEnabled"

        // IsEnabled プロパティの名前
        public const string IsEnabledPropertyName = "IsEnabled";

        // IsEnabled という名前のアタッチトプロパティを登録する。
        // IsEnabled プロパティの値が変更されると OnIsEnabledChanged
        // メソッドが呼ばれることに注意。
        public static readonly DependencyProperty IsEnabledProperty
            = DependencyProperty.RegisterAttached(
                IsEnabledPropertyName,
                typeof(bool),
                typeof(SizeChange),
                new PropertyMetadata(false, OnIsEnabledChanged));

        // IsEnabled プロパティのゲッター。依存関係システムが
        // 使用するので、このメソッドの名前は変更するべきではない。
        public static bool GetIsEnabled(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsEnabledProperty);
        }

        // IsEnabled プロパティのセッター。依存関係システムが
        // 使用するので、このメソッドの名前は変更するべきではない。
        public static void SetIsEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(IsEnabledProperty, value);
        }

        #endregion

        #region Attached property "ActualHeight"

        // ActualHeight プロパティの名前
        public const string ActualHeightPropertyName = "ActualHeight";

        // ActualHeight という名前のアタッチトプロパティを登録する。
        // このプロパティの値は、SizeChanged イベントが起こったときに
        // 変更される。
        public static readonly DependencyProperty ActualHeightProperty
            = DependencyProperty.RegisterAttached(
                ActualHeightPropertyName,
                typeof(double),
                typeof(SizeChange),
                null);

        // ActualHeight プロパティのゲッター。依存関係システムが
        // 使用するので、このメソッドの名前は変更するべきではない。
        public static double GetActualHeight(DependencyObject obj)
        {
            return (double)obj.GetValue(ActualHeightProperty);
        }

        // ActualHeight プロパティのセッター。依存関係システムが
        // 使用するので、このメソッドの名前は変更するべきではない。
        public static void SetActualHeight(DependencyObject obj, double value)
        {
            obj.SetValue(ActualHeightProperty, value);
        }

        #endregion

        #region Attached property "ActualWidth"

        // ActualWidth プロパティの名前
        public const string ActualWidthPropertyName = "ActualWidth";

        // ActualWidth という名前のアタッチトプロパティを登録する。
        // このプロパティの値は、SizeChanged イベントが起こったときに
        // 変更される。
        public static readonly DependencyProperty ActualWidthProperty
            = DependencyProperty.RegisterAttached(
                ActualWidthPropertyName,
                typeof(double),
                typeof(SizeChange),
                null);

        // ActualWidth プロパティのゲッター。依存関係システムが
        // 使用するので、このメソッドの名前は変更するべきではない。
        public static double GetActualWidth(DependencyObject obj)
        {
            return (double)obj.GetValue(ActualWidthProperty);
        }

        // ActualWidth プロパティのセッター。依存関係システムが
        // 使用するので、このメソッドの名前は変更するべきではない。
        public static void SetActualWidth(DependencyObject obj, double value)
        {
            obj.SetValue(ActualWidthProperty, value);
        }

        #endregion

        // このメソッドは、IsEnabled プロパティの値が変更されたときに
        // 呼び出される。新しい値が true の場合、対象のエレメントの
        // SizeChanged イベントにイベントハンドラーが追加される。
        private static void OnIsEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            // SizeChanged イベントにイベントハンドラーを追加するので、
            // エレメントは FrameworkElement のインスタンスでなければ
            // ならない。
            var element = obj as FrameworkElement;

            if (element == null)
            {
                // 与えられたオブジェクトは FrameworkElement のインス
                // タンスではないので、SizeChanged イベントが無い。
                // そのため、当オブジェクトに対してできることがない。
                return;
            }

            // IsEnabled=True の場合
            if (args.NewValue != null && (bool)args.NewValue == true)
            {
                // エレメントにアタッチする
                Attach(element);
            }
            else
            {
                // エレメントからデタッチする。
                Detach(element);
            }
        }

        private static void Attach(FrameworkElement element)
        {
            // エレメントの実際のサイズが変更されたときに処理を
            // おこなえるよう、エレメントの SizeChanged イベントに
            // イベントハンドラーを追加する。
            element.SizeChanged += HandleSizeChanged;
        }

        private static void Detach(FrameworkElement element)
        {
            // エレメントからイベントハンドラーを取り除く。
            element.SizeChanged -= HandleSizeChanged;
        }

        // SizeChanged イベントが起こった時に実行されるイベントハンドラー
        private static void HandleSizeChanged(object sender, SizeChangedEventArgs args)
        {
            var element = sender as FrameworkElement;

            if (element == null)
            {
                return;
            }

            // 実際の高さと幅を取得する。
            var width  = args.NewSize.Width;
            var height = args.NewSize.Height;

            // SizeChange.ActualHeight と SizeChange.ActualWidth の
            // 値を更新する。
            SetActualWidth(element, width);
            SetActualHeight(element, height);
        }
    }
}

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;
*/
}