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月31日日曜日
2011年7月29日金曜日
Binding ActualHeight and ActualWidth (Silverlight)
As reported to Microsoft Connect:
and as described in the API document:
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:
And the implementation is here:
ActualHeight と ActualWidth をバインドする (Silverlight)
Microsoft Connect に報告されているように、
また、API ドキュメントに記述されているように、
ActualHeight と ActualWidth はデータバインディングに適していない。そのため、回避策として、実際のサイズが変更されたときに何かをしたい場合は SizeChanged イベントを使うようにと、マイクロソフトは言っている。しかし、私たちが欲しいのは、単にデータバインディングに適した ActualHeight と ActualWidth であり、そしてそのための実装をここにあげる。
後で実装を見ることになる SizeChange クラスは、IsEnabled、ActualHeight、ActualWidth という三つのアタッチトプロパティ (attached property) を定義している。SizeChange.IsEnabled="True" がプロパティとして FrameworkElement に与えられると、その FrameworkElement の実際の高さと実際の幅を、SizeChange.ActualHeight と SizeChange.ActualWidth を通じて得ることができるようになる。
実装を見る前に、何を実現しようとしているかを理解するために、使用例を見よう。
使用例:
そして実装は下記のとおり。
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=Trueif (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)
{
}
if (selectedItem != null)
{
return;
}
if (items != null && items.Count != 0)
{
return;
}
/*
// Get the current ItemsSource.
}
// 以前の回避方法だとまだ問題が発生することが分かったので、内容修正。
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;
*/ }
2011年6月28日火曜日
Show GIF images in Windows Phone applications
JPEG and PNG are supported by BitmapImage of Silverlight, but GIF is not supported. However, with ImageTools available at CodePlex, GIF images can be handled.
(1) Download ImageTools from CodePlex.
(2) Add DLLs found in bin\Phone folder to your project as necessary.
(3) Register the GIF decoder in initialization code.
(4) Construct an ExtendedImage instance based on GIF data.
(5) Use as BitmapImage.
The above is an example for the case where input GIF data is available as stream, but if available as Uri, it seems to be enough to simply set the Uri to UriSource property of ExtendedImage. A code example for that case can be found at the following.
http://imagetools.codeplex.com/documentation
A class named ImageConverter implementing IValueConverter is also provided. It seems to be able to be used in XAML files.
Windows PhoneアプリでGIFを表示する
Silverlight の BitmapImage では JPEG と PNG はサポートされているものの、GIF はサポートされていない。しかし、CodePlex で公開されている ImageTools を使用すると、GIF 画像を扱うことができる。
(1) ImageTools を CodePlex からダウンロードする。
(2) 解凍後の bin\Phone フォルダ以下にある DLL のうち、必要なものを適宜プロジェクトに追加する。
(3) 初期化コードで GIF デコーダを登録する。
(4) GIF データをもとに ExtendedImage のインスタンスを構築する。
(5) BitmapImage として利用する。
上記は、入力 GIF データがストリームとして得られる場合の例だが、Uri として得られる場合は、単純に ExtendedImage の UriSource プロパティに Uri を設定するだけでよさそう。その場合のコード例は次の場所にある。
http://imagetools.codeplex.com/documentation
IValueConverter を実装した ImageCoverter というクラスもあるので、XAML の中でも使えそう。
(1) Download ImageTools from CodePlex.
http://imagetools.codeplex.com/
The format of the binary + documentation package is .rar, so utility software that can extract RAR files is required.
(2) Add DLLs found in bin\Phone folder to your project as necessary.
ImageTools.dll
ImageTools.IO.Gif.dll
ImageTools.Utils.dll
(3) Register the GIF decoder in initialization code.
ImageTools.IO.Decoders.AddDecoder<GifDecoder>();
(4) Construct an ExtendedImage instance based on GIF data.
// Create an empty ExtendedImage instance.ExtendedImage exImage = new ExtendedImage();
// Create an instance of GIF decoder.GifDecoder gifDecoder = new GifDecoder();
// Get a stream of GIF data from somewhere.Stream gifStream = ...;
// Interpret the stream as GIF and set the result// to the ExtendedImage instance.gifDecoder.Decode(exImage, gifStream);
(5) Use as BitmapImage.
// Get a BitmapImage instance from somewhere.BitmapImage bitmap = ...;
// Specify the ExtendedImage constructed based on GIF data// as source of the BitmapImage.using (var stream = exImage.ToStream())
{
bitmap.SetSource(stream);
}
The above is an example for the case where input GIF data is available as stream, but if available as Uri, it seems to be enough to simply set the Uri to UriSource property of ExtendedImage. A code example for that case can be found at the following.
http://imagetools.codeplex.com/documentation
A class named ImageConverter implementing IValueConverter is also provided. It seems to be able to be used in XAML files.
Windows PhoneアプリでGIFを表示する
Silverlight の BitmapImage では JPEG と PNG はサポートされているものの、GIF はサポートされていない。しかし、CodePlex で公開されている ImageTools を使用すると、GIF 画像を扱うことができる。
(1) ImageTools を CodePlex からダウンロードする。
http://imagetools.codeplex.com/
バイナリ+ドキュメントのパッケージが .rar 形式なので、RAR を扱える解凍ソフトが別途必要。
(2) 解凍後の bin\Phone フォルダ以下にある DLL のうち、必要なものを適宜プロジェクトに追加する。
ImageTools.dll
ImageTools.IO.Gif.dll
ImageTools.Utils.dll
(3) 初期化コードで GIF デコーダを登録する。
ImageTools.IO.Decoders.AddDecoder<GifDecoder>();
(4) GIF データをもとに ExtendedImage のインスタンスを構築する。
// 空の ExtendedImage のインスタンスを作成する。ExtendedImage exImage = new ExtendedImage();
// GIF デコーダのインスタンスを作成する。GifDecoder gifDecoder = new GifDecoder();
// GIF データのストリームをどこかから持ってくる。Stream gifStream = ...;
// ストリームを GIF として解釈し、デコード結果を ExtendedImage にセットする。gifDecoder.Decode(exImage, gifStream);
(5) BitmapImage として利用する。
// BitmapImage インスタンスをどこかから持ってくる。BitmapImage bitmap = ...;
// BitmapImage のソースとして、GIF データをもとに構築した// ExtendedImage を指定する。using (var stream = exImage.ToStream())
{
bitmap.SetSource(stream);
}
上記は、入力 GIF データがストリームとして得られる場合の例だが、Uri として得られる場合は、単純に ExtendedImage の UriSource プロパティに Uri を設定するだけでよさそう。その場合のコード例は次の場所にある。
http://imagetools.codeplex.com/documentation
IValueConverter を実装した ImageCoverter というクラスもあるので、XAML の中でも使えそう。
2011年6月10日金曜日
Windows Phoneアプリをローカライズする
1. リソースの準備
1.1 デフォルト言語用リソースファイルを追加する
1.2 サポート対象言語用リソースファイルを追加する
1.3 リソースファイルをPublicアクセス可能にする
1.4 リソースファイルにエントリーを追加する
2. リソースの設定
2.1 デフォルトのカルチャーを定義する
2.2 サポート対象言語を宣言する
3. リソースにアクセスするための準備
3.1 リソースクラスにアクセスするためのアクセッサークラスを作成する
3.2 アクセッサークラスのインスタンスを生成し、参照できるよう、App.xaml を編集する
4. リソースの利用
4.1 データバインディングを使い、アクセッサークラス経由でリソースを利用する
以上でローカライズの作業は終了。詳細は How to: Build a Localized Application for Windows Phone を参照のこと。
なお、リソースファイルを自動生成するためのコマンド ResGen が、自動生成するクラスのコンストラクタを internal ではなく public にしてくれれば、リソースクラスにアクセスするためのアクセッサークラスをわざわざ作成する必要はない:
しかし、この問題はまだ解消されていない。
1.1 デフォルト言語用リソースファイルを追加する
- Visual Studio でプロジェクトを開く。
- Solution Explorer でプロジェクト名を右クリックし、Add → New Item... と進む。
- Add New Item ダイアログで Resources File を選択、適当なファイル名(例:AppResources.resx)を付けて Add ボタンをクリックする。
1.2 サポート対象言語用リソースファイルを追加する
- 上記と同じ方法でリソースファイルを追加する。ファイル名にはカルチャーとランゲージを含めておく。
日本語の例: AppResources.ja-JP.resx
1.3 リソースファイルをPublicアクセス可能にする
- Solution Explorer でリソースファイルを開く。
- AccessModifier リストボックスで Public を選択する。
- 同じ処理をすべてのリソースファイルに対しておこなう。
1.4 リソースファイルにエントリーを追加する
例:
AppResources.resx (en-US用) に次のエントリーを追加
Name=ApplicationDescriptionText
Value=This is an i18n-aware application.
AppResources.ja-JP.resx (ja-JP用) に次のエントリーを追加
Name=ApplicationDescriptionText
Value=これは国際化対応したアプリケーションです。
2. リソースの設定
2.1 デフォルトのカルチャーを定義する
- Solution Explorer でプロジェクト名を右クリックし、Properties を選ぶ。
- Application タブで、Assembly Information ボタンをクリックする。
- Neutral Language でデフォルトのカルチャー(例: English (United States))を選択し、OK ボタンをクリックする。
2.2 サポート対象言語を宣言する
- プロジェクトを閉じ、プロジェクトファイル(*.csproj)をテキストエディターで開く。
- <SupportedCultures> タグを見つけ、そこに、セミコロン区切りでサポート対象言語をリストアップする。
日本語の例: <SupportedCultures>ja-JP;</SupportedCultures>
3. リソースにアクセスするための準備
3.1 リソースクラスにアクセスするためのアクセッサークラスを作成する
namespace MyApplication
{
public class AppResourcesProvider
{
public AppResourcesProvider()
{
}
private static AppResources appResources_ = new AppResources();
public AppResources AppResources { get { return appResources_; } }
}
}
3.2 アクセッサークラスのインスタンスを生成し、参照できるよう、App.xaml を編集する
Application タグに xmlns:local を追加し、Application.Resources タグに local:AppResourcesProvider を追加する。
<Application
x:Class="MyApplication.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:local="clr-namespace:MyApplication"
>
<!--Application Resources-->
<Application.Resources>
<local:AppResourcesProvider x:Key="AppResourcesProvider" />
</Application.Resources>
<Application.ApplicationLifetimeObjects>
<!--Required object that handles lifetime events for the application-->
<shell:PhoneApplicationService
Launching="Application_Launching" Closing="Application_Closing"
Activated="Application_Activated" Deactivated="Application_Deactivated"/>
</Application.ApplicationLifetimeObjects>
</Application>
4. リソースの利用
4.1 データバインディングを使い、アクセッサークラス経由でリソースを利用する
例:リソース ApplicationDescriptionText の値を TextBlock に設定する場合
<TextBlock Name="ApplicationDescription"
Text="{Binding Source={StaticResource AppResourcesProvider},
Path=AppResources.ApplicationDescriptionText}" />
以上でローカライズの作業は終了。詳細は How to: Build a Localized Application for Windows Phone を参照のこと。
なお、リソースファイルを自動生成するためのコマンド ResGen が、自動生成するクラスのコンストラクタを internal ではなく public にしてくれれば、リソースクラスにアクセスするためのアクセッサークラスをわざわざ作成する必要はない:
http://connect.microsoft.com/VisualStudio/feedback/details/628281/silverlight-wp7-resource-files-and-binding
しかし、この問題はまだ解消されていない。
2011年6月9日木曜日
Windows Phoneのタイルにアプリケーションタイトルを表示させない
Windows Phone のタイルにアプリケーションタイトルを表示させないようにするには、WMAppManifest.xml の PrimaryToken の Title を空にする。これで、Office や XBOX のタイルのように、画像だけでタイルの見た目を構成することができる。
WMAppManifest.xml を直接編集しなくても、下記の手順でもいけそう。
・・・と思ったが、実際にやってみると、既に Title: に文字列が入っている状態から空文字列にしようとすると、Visual Studio が「An empty string is not allowed for Title」という警告ポップアップを出し、空文字列にすることを許してくれない(← @shinoblogavi さんからの指摘)。空文字列にしたい場合は、やはり直接 WMAppManifest.xml を編集しなければならないようだ。
WMAppManifest.xml を直接編集しなくても、下記の手順でもいけそう。
- Visual Studio でプロジェクトを開く。
- Solution Explorer でプロジェクト名を右クリックし、Propertiesを選択する。
- Application タブの Tile options のところで、Title: を空にする。
・・・と思ったが、実際にやってみると、既に Title: に文字列が入っている状態から空文字列にしようとすると、Visual Studio が「An empty string is not allowed for Title」という警告ポップアップを出し、空文字列にすることを許してくれない(← @shinoblogavi さんからの指摘)。空文字列にしたい場合は、やはり直接 WMAppManifest.xml を編集しなければならないようだ。
2011年5月27日金曜日
How to solve "Invalid cross-thread access" error in Silverlight
In Silverlight, an UnauthorizedAccessException with the message "Invalid cross-thread access" is thrown when jobs that should be done in the UI thread are tried to be done in other threads.
To make the UI thread do something, the instance of Dispatcher class of the UI thread (there is one Dispatcher instance per Thread) needs to be obtained and jobs for the UI thread should be passed to BeginInvoke method of the instance. For example, given a variable named 'dispatcherInstance' holds an instance of Dispatcher class, the following code will work.
dipatcherInstance.BeginInvoke(() => DoMyJob());
There are some ways to access an instance of Dispatcher class. DependencyObject class has a property named 'Dispatcher' and it is the simplest way to use it. Within classes which are derived from DependencyObject class (because UIElement class is derived from DependencyObject class, as a result, all sub classes of UIElement are included), using the property, jobs can be passed to the dispatcher by the code like below.
Dispatcher.BeginInvoke(() => DoMyJob());
To access the Dispatcher instance of the UI thread from where the Dispatcher property is not available, System.Windows.Deployment class can be used like the following.
Deployment.Current.Dispatcher.BeginInvoke(() => DoMyJob());
Note that because an UnauthorizedAccessException may be raised when it is tried to access the Dispatcher instance of the UI thread via the property 'Application.Current.RootVisual' (because RootVisual belongs to the UI thread), it is better to use Deployment class.
------
"Invalid cross-thread access" 問題の解決の仕方 (Silverlight)
Silverlight において、UI スレッドで処理すべきことをそれ以外のスレッドからおこなおうとすると、Invalid cross-thread access というメッセージを伴う UnauthorizedAccessException が発生してしまう。
UI スレッドに処理をさせたい場合は、UI スレッド用の Dispatcher クラスのインスタンスを取得して(UI スレッドに限らず、それぞれのスレッドが、Dispatcher クラスのインスタンスを一つだけ持っている)、そのインスタンスの BeginInvoke メソッドに、UI スレッドで実行してほしい処理を渡せばよい。例えば、dispatcherInstance という変数に Dispatcher クラスのインスタンスが入っているとした場合、次のように記述する。
dipatcherInstance.BeginInvoke(() => DoMyJob());
Dispatcher クラスのインスタンスにアクセスする方法は幾つかある。DependencyObject には、その名も Dispatcher というプロパティーがあるので、それを利用するのが一番簡単である。DependencyObject から派生するクラス(UIElement クラスは DependencyObject クラスから派生しているので、結果的に、これには UIElement から派生する全てのクラスが含まれる)内のコードでは、このプロパティーを利用して次のように記述することで、Dispatcher に処理を渡すことができる。
Dispatcher.BeginInvoke(() => DoMyJob());
Dispatcher プロパティーにアクセスできない場所から UI スレッドの Dispatcher インスタンスを取得したい場合は、System.Windows.Deployment クラスを使用して、次のように記述する。
Deployment.Current.Dispatcher.BeginInvoke(() => DoMyJob());
なお、Application.Current.RootVisual というプロパティー経由で UI スレッドの Dispatcher にアクセスしようとすると、UnauthorizedAccessException が発生しうるので(RootVisual は UI スレッドに属しているので)、Deployment クラスの方を使うのがよい。
To make the UI thread do something, the instance of Dispatcher class of the UI thread (there is one Dispatcher instance per Thread) needs to be obtained and jobs for the UI thread should be passed to BeginInvoke method of the instance. For example, given a variable named 'dispatcherInstance' holds an instance of Dispatcher class, the following code will work.
dipatcherInstance.BeginInvoke(() => DoMyJob());
There are some ways to access an instance of Dispatcher class. DependencyObject class has a property named 'Dispatcher' and it is the simplest way to use it. Within classes which are derived from DependencyObject class (because UIElement class is derived from DependencyObject class, as a result, all sub classes of UIElement are included), using the property, jobs can be passed to the dispatcher by the code like below.
Dispatcher.BeginInvoke(() => DoMyJob());
To access the Dispatcher instance of the UI thread from where the Dispatcher property is not available, System.Windows.Deployment class can be used like the following.
Deployment.Current.Dispatcher.BeginInvoke(() => DoMyJob());
Note that because an UnauthorizedAccessException may be raised when it is tried to access the Dispatcher instance of the UI thread via the property 'Application.Current.RootVisual' (because RootVisual belongs to the UI thread), it is better to use Deployment class.
------
"Invalid cross-thread access" 問題の解決の仕方 (Silverlight)
Silverlight において、UI スレッドで処理すべきことをそれ以外のスレッドからおこなおうとすると、Invalid cross-thread access というメッセージを伴う UnauthorizedAccessException が発生してしまう。
UI スレッドに処理をさせたい場合は、UI スレッド用の Dispatcher クラスのインスタンスを取得して(UI スレッドに限らず、それぞれのスレッドが、Dispatcher クラスのインスタンスを一つだけ持っている)、そのインスタンスの BeginInvoke メソッドに、UI スレッドで実行してほしい処理を渡せばよい。例えば、dispatcherInstance という変数に Dispatcher クラスのインスタンスが入っているとした場合、次のように記述する。
dipatcherInstance.BeginInvoke(() => DoMyJob());
Dispatcher クラスのインスタンスにアクセスする方法は幾つかある。DependencyObject には、その名も Dispatcher というプロパティーがあるので、それを利用するのが一番簡単である。DependencyObject から派生するクラス(UIElement クラスは DependencyObject クラスから派生しているので、結果的に、これには UIElement から派生する全てのクラスが含まれる)内のコードでは、このプロパティーを利用して次のように記述することで、Dispatcher に処理を渡すことができる。
Dispatcher.BeginInvoke(() => DoMyJob());
Dispatcher プロパティーにアクセスできない場所から UI スレッドの Dispatcher インスタンスを取得したい場合は、System.Windows.Deployment クラスを使用して、次のように記述する。
Deployment.Current.Dispatcher.BeginInvoke(() => DoMyJob());
なお、Application.Current.RootVisual というプロパティー経由で UI スレッドの Dispatcher にアクセスしようとすると、UnauthorizedAccessException が発生しうるので(RootVisual は UI スレッドに属しているので)、Deployment クラスの方を使うのがよい。
登録:
投稿 (Atom)