From 7db33fa99c05af6d1e3f28ac01b149d6b19c067c Mon Sep 17 00:00:00 2001 From: Gustavo Martin Date: Tue, 8 Jul 2014 06:58:53 +0200 Subject: [PATCH] WeatherInformation: Preserving state (pages and application) See: http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff967548(v=vs.105).aspx http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff967547(v=vs.105).aspx --- .../WeatherInformation/App.xaml.cs | 154 ++++++++++++++++++++- .../WeatherInformation/MainPage.xaml.cs | 62 ++++++++- .../ViewModels/SettingsViewModel.cs | 16 ++- 3 files changed, 224 insertions(+), 8 deletions(-) diff --git a/WindowsPhone/WeatherInformation/WeatherInformation/App.xaml.cs b/WindowsPhone/WeatherInformation/WeatherInformation/App.xaml.cs index 3d50baf..e6e1356 100644 --- a/WindowsPhone/WeatherInformation/WeatherInformation/App.xaml.cs +++ b/WindowsPhone/WeatherInformation/WeatherInformation/App.xaml.cs @@ -10,6 +10,8 @@ using System.Threading; using System.Globalization; using WeatherInformation.Resources; using WeatherInformation.ViewModels; +using System.IO.IsolatedStorage; +using System.IO; namespace WeatherInformation { @@ -19,9 +21,19 @@ namespace WeatherInformation // Use "qps-PLOC" to deploy pseudolocalized strings. // Use "" to let user Phone Language selection determine locale. // public static String appForceCulture = "qps-PLOC"; - public static String appForceCulture = "en"; + private const String appForceCulture = "en"; private static MainViewModel viewModel = null; + // Declare a private variable to store application state. + private string _applicationDataObject; + + // Declare an event for when the application data changes. + public event EventHandler ApplicationDataObjectChanged; + + // Declare a public property to store the status of the application data. + // Pages should register this event (if they want to receive + public string ApplicationDataStatus { get; set; } + /// /// MainViewModel estático que usan las vistas con el que se van a enlazar. /// @@ -38,6 +50,30 @@ namespace WeatherInformation } } + // Declare a public property to access the application data variable. + public string ApplicationDataObject + { + get { return _applicationDataObject; } + set + { + if (value != _applicationDataObject) + { + _applicationDataObject = value; + OnApplicationDataObjectChanged(EventArgs.Empty); + } + } + } + + // Create a method to raise the ApplicationDataObjectChanged event. + protected void OnApplicationDataObjectChanged(EventArgs e) + { + EventHandler handler = ApplicationDataObjectChanged; + if (handler != null) + { + handler(this, e); + } + } + /// /// Proporcionar acceso sencillo al marco raíz de la aplicación telefónica. /// @@ -82,6 +118,87 @@ namespace WeatherInformation } } + public void GetDataAsync() + { + // Call the GetData method on a new thread. + Thread t = new Thread(new ThreadStart(GetData)); + t.Start(); + } + + private void GetData() + { + // Check the time elapsed since data was last saved to isolated storage. + TimeSpan TimeSinceLastSave = TimeSpan.FromSeconds(0); + if (IsolatedStorageSettings.ApplicationSettings.Contains("DataLastSavedTime")) + { + DateTime dataLastSaveTime = (DateTime)IsolatedStorageSettings.ApplicationSettings["DataLastSavedTime"]; + TimeSinceLastSave = DateTime.Now - dataLastSaveTime; + } + + // Check to see if data exists in isolated storage and see if the data is fresh. + // This example uses 30 seconds as the valid time window to make it easy to test. + // Real apps will use a larger window. + IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication(); + if (isoStore.FileExists("myDataFile.txt") && TimeSinceLastSave.TotalSeconds < 30) + { + // This method loads the data from isolated storage, if it is available. + StreamReader sr = new StreamReader(isoStore.OpenFile("myDataFile.txt", FileMode.Open)); + string data = sr.ReadToEnd(); + sr.Close(); + + ApplicationDataStatus = "data from isolated storage"; + ApplicationDataObject = data; + } + else + { + // SHOULD I CHECK HERE IF THERE ARE COORDINATES AND IN THAT CASE CALL OPENWEATHERMAP? :/ I THINK SO!!! + // MOVE THE MainViewModel.LoadData METHOD TO THIS PLACE!!!! + + // Otherwise, it gets the data from the web. + //HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("http://windowsteamblog.com/windows_phone/b/windowsphone/rss.aspx")); + //request.BeginGetResponse(HandleWebResponse, request); + } + } + + private void HandleWebResponse(IAsyncResult result) + { + // Put this in a try block in case the web request was unsuccessful. + try + { + // Get the request from the IAsyncResult. + // HttpWebRequest request = (HttpWebRequest)(result.AsyncState); + + // Read the response stream from the response. + //StreamReader sr = new StreamReader(request.EndGetResponse(result).GetResponseStream()); + //string data = sr.ReadToEnd(); + + // Use the Dispatcher to call SetData on the UI thread, passing the retrieved data. + //Dispatcher.BeginInvoke(() => { SetData(data, "web"); }); + ApplicationDataStatus = "data from web."; + //ApplicationDataObject = data; + } + catch + { + // If the data request fails, alert the user. + ApplicationDataStatus = "Unable to get data from Web."; + ApplicationDataObject = ""; + } + } + + // TODO: temporary file :/ + private void SaveDataToIsolatedStorage(string isoFileName, string value) + { + using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication()) + using (IsolatedStorageFileStream fileStream = isoStore.OpenFile(isoFileName, FileMode.OpenOrCreate)) + using (StreamWriter sw = new StreamWriter(isoStore.OpenFile(isoFileName, FileMode.OpenOrCreate))) + { + fileStream.Flush(true); + sw.Write(value); + } + + IsolatedStorageSettings.ApplicationSettings["DataLastSaveTime"] = DateTime.Now; + } + // Código para ejecutar cuando la aplicación se inicia (p.ej. a partir de Inicio) // Este código no se ejecutará cuando la aplicación se reactive private void Application_Launching(object sender, LaunchingEventArgs e) @@ -90,12 +207,29 @@ namespace WeatherInformation // Código para ejecutar cuando la aplicación se activa (se trae a primer plano) // Este código no se ejecutará cuando la aplicación se inicie por primera vez + // Coming from TOMBSTONED or DORMANT private void Application_Activated(object sender, ActivatedEventArgs e) { // Asegurarse de que el estado de la aplicación se restaura adecuadamente - if (!App.MainViewModel.IsDataLoaded) + // It seems as if here I should store global data to the whole application because this event + // is just called here (never from classes implementing PhoneApplicationPage) + //if (!App.MainViewModel.IsDataLoaded) + //{ + // App.MainViewModel.LoadData(); + //} + + if (e.IsApplicationInstancePreserved) { - App.MainViewModel.LoadData(); + ApplicationDataStatus = "application instance preserved."; + return; + } + + // Check to see if the key for the application state data is in the State dictionary. + if (PhoneApplicationService.Current.State.ContainsKey("ApplicationDataObject")) + { + // If it exists, assign the data to the application member variable. + ApplicationDataStatus = "data from preserved state."; + ApplicationDataObject = PhoneApplicationService.Current.State["ApplicationDataObject"] as string; } } @@ -103,6 +237,15 @@ namespace WeatherInformation // Este código no se ejecutará cuando la aplicación se cierre private void Application_Deactivated(object sender, DeactivatedEventArgs e) { + // If there is data in the application member variable... + if (!string.IsNullOrEmpty(ApplicationDataObject)) + { + // Store it in the State dictionary. + PhoneApplicationService.Current.State["ApplicationDataObject"] = ApplicationDataObject; + + // Also store it in isolated storage, in case the application is never reactivated. + SaveDataToIsolatedStorage("myDataFile.txt", ApplicationDataObject); + } } // Código para ejecutar cuando la aplicación se cierra (p.ej., al hacer clic en Atrás) @@ -110,6 +253,11 @@ namespace WeatherInformation private void Application_Closing(object sender, ClosingEventArgs e) { // Asegurarse de que el estado de la aplicación requerida persiste aquí. + // The application will not be tombstoned, so save only to isolated storage. + if (!string.IsNullOrEmpty(ApplicationDataObject)) + { + SaveDataToIsolatedStorage("myDataFile.txt", ApplicationDataObject); + } } // Código para ejecutar si hay un error de navegación diff --git a/WindowsPhone/WeatherInformation/WeatherInformation/MainPage.xaml.cs b/WindowsPhone/WeatherInformation/WeatherInformation/MainPage.xaml.cs index e695bc7..4f9bcf2 100644 --- a/WindowsPhone/WeatherInformation/WeatherInformation/MainPage.xaml.cs +++ b/WindowsPhone/WeatherInformation/WeatherInformation/MainPage.xaml.cs @@ -1,30 +1,47 @@ -using System; +using Microsoft.Phone.Controls; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.IO.IsolatedStorage; using System.Linq; -using System.Net; +using System.Text; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Navigation; -using Microsoft.Phone.Controls; -using Microsoft.Phone.Shell; using WeatherInformation.Resources; +using WeatherInformation.ViewModels; namespace WeatherInformation { public partial class MainPage : PhoneApplicationPage { + private MainViewModel _mainViewModel; + private bool _isNewPageInstance = false; + // Constructor public MainPage() { InitializeComponent(); + // Establecer el contexto de datos del control ListBox control en los datos de ejemplo DataContext = App.MainViewModel; + + _isNewPageInstance = true; + + // Set the event handler for when the application data object changes. + (Application.Current as WeatherInformation.App).ApplicationDataObjectChanged += + new EventHandler(MainPage_ApplicationDataObjectChanged); + + // Código de ejemplo para traducir ApplicationBar //BuildLocalizedApplicationBar(); } + // Cargar datos para los elementos MainViewModel protected override void OnNavigatedTo(NavigationEventArgs e) { @@ -32,6 +49,43 @@ namespace WeatherInformation { App.MainViewModel.LoadData(); } + + + // If _isNewPageInstance is true, the page constructor has been called, so + // state may need to be restored. + if (_isNewPageInstance) + { + // If the application member variable is not empty, + // set the page's data object from the application member variable. + if ((Application.Current as WeatherInformation.App).ApplicationDataObject != null) + { + UpdateApplicationDataUI(); + } + else + { + // Otherwise, call the method that loads data. + //statusTextBlock.Text = "getting data..."; + (Application.Current as WeatherInformation.App).GetDataAsync(); + } + } + + // Set _isNewPageInstance to false. If the user navigates back to this page + // and it has remained in memory, this value will continue to be false. + _isNewPageInstance = false; + } + + // The event handler called when the ApplicationDataObject changes. + void MainPage_ApplicationDataObjectChanged(object sender, EventArgs e) + { + // Call UpdateApplicationData on the UI thread. + Dispatcher.BeginInvoke(() => UpdateApplicationDataUI()); + } + void UpdateApplicationDataUI() + { + // Set the ApplicationData and ApplicationDataStatus members of the ViewModel + // class to update the UI. + // dataTextBlock.Text = (Application.Current as ExecutionModelApplication.App).ApplicationDataObject; + // statusTextBlock.Text = (Application.Current as ExecutionModelApplication.App).ApplicationDataStatus; } private void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e) diff --git a/WindowsPhone/WeatherInformation/WeatherInformation/ViewModels/SettingsViewModel.cs b/WindowsPhone/WeatherInformation/WeatherInformation/ViewModels/SettingsViewModel.cs index ce56db2..c9f488f 100644 --- a/WindowsPhone/WeatherInformation/WeatherInformation/ViewModels/SettingsViewModel.cs +++ b/WindowsPhone/WeatherInformation/WeatherInformation/ViewModels/SettingsViewModel.cs @@ -14,7 +14,8 @@ namespace WeatherInformation.ViewModels { // TODO: to use this class or something like that in every place of this application where the settings must be retrieved!!! // (do not copy-paste code!!!) - public class SettingsViewModel + // TODO: IMHO INotifyPropertyChanged does not do anything useful in this class... NotifyPropertyChanged has no effect :( + public class SettingsViewModel : INotifyPropertyChanged { // Settings private readonly IsolatedStorageSettings _settings; @@ -57,6 +58,7 @@ namespace WeatherInformation.ViewModels { Save(); } + NotifyPropertyChanged(_languageSelectionSettingKeyName); } } @@ -75,6 +77,7 @@ namespace WeatherInformation.ViewModels { Save(); } + NotifyPropertyChanged(_temperatureUnitsSelectionSettingKeyName); } } @@ -93,6 +96,7 @@ namespace WeatherInformation.ViewModels { Save(); } + NotifyPropertyChanged(_forecastDayNumbersSelectionSelectionSettingKeyName); } } @@ -183,5 +187,15 @@ namespace WeatherInformation.ViewModels _settings.Save(); } + + public event PropertyChangedEventHandler PropertyChanged; + private void NotifyPropertyChanged(string propertyName) + { + PropertyChangedEventHandler handler = PropertyChanged; + if (null != handler) + { + handler(this, new PropertyChangedEventArgs(propertyName)); + } + } } } -- 2.1.4