I've been following the Create your first Windows Store app using C# or Visual Basic tutorials provided by Microsoft but am having some problems saving state when navigating between pages.
Create your first Windows Store app using C# or Visual Basic
Part 3: Navigation, layout, and views
Basically I've noticed that if I navigate from the main page to the photo page select a photo, navigate back to the main page and then go to the photo page again it doesn't remember the photo that was selected. I'm using the following code to navigate to the photo page from the main page.
private void photoPageButton_Click(object sender, RoutedEventArgs e)
{
this.Frame.Navigate(typeof(PhotoPage));
}
In the photo page the loadstate method is
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
if (pageState != null && pageState.ContainsKey("mruToken"))
{
object value = null;
if (pageState.TryGetValue("mruToken", out value))
{
if (value != null)
{
mruToken = value.ToString();
// Open the file via the token that you stored when adding this file into the MRU list.
Windows.Storage.StorageFile file =
await Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(mruToken);
if (file != null)
{
// Open a stream for the selected file.
Windows.Storage.Streams.IRandomAccessStream fileStream =
await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
// Set the image source to a bitmap.
Windows.UI.Xaml.Media.Imaging.BitmapImage bitmapImage =
new Windows.UI.Xaml.Media.Imaging.BitmapImage();
bitmapImage.SetSource(fileStream);
displayImage.Source = bitmapImage;
// Set the data context for the page.
this.DataContext = file;
}
}
}
}
}
The photo page save state is
protected override void SaveState(Dictionary<String, Object> pageState)
{
if (!String.IsNullOrEmpty(mruToken))
{
pageState["mruToken"] = mruToken;
}
}
I've noticed that the pagestate is always null when navigated to. Any ideas?
Enable NavigationCacheMode property of the page and add NavigationCacheMode="Enabled"
OR
Enable it by properties panel.
I did this tutorial too and I found one solution to save the state across pages navigation.
First, override the OnNavigatedFrom in order to save the file token into State Frame:
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
var state = SuspensionManager.SessionStateForFrame(this.Frame);
state["mruToken"] = mruToken;
}
Override the OnNavigatedTo in order to load the token from the state:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var state = SuspensionManager.SessionStateForFrame(this.Frame);
if (state != null && state.ContainsKey("mruToken"))
{
object value = null;
if (state.TryGetValue("mruToken", out value))
{
// the same code as LoadState to retrieve the image
}
}
}
In fact, I wrote another function to retrieve the image so it can be used in both LoadState and OnNavigatedTo methods.
private async void restoreImage(object value)
{
if (value != null)
{
mruToken = value.ToString();
// Open the file via the token that you stored when adding this file into the MRU list.
Windows.Storage.StorageFile file =
await Windows.Storage.AccessCache.StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(mruToken);
if (file != null)
{
// Open a stream for the selected file.
Windows.Storage.Streams.IRandomAccessStream fileStream =
await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
// Set the image source to a bitmap.
Windows.UI.Xaml.Media.Imaging.BitmapImage bitmapImage =
new Windows.UI.Xaml.Media.Imaging.BitmapImage();
bitmapImage.SetSource(fileStream);
displayImage.Source = bitmapImage;
// Set the data context for the page.
this.DataContext = file;
}
}
}
The problem is coming from the NavigationHelper OnNavigateTo method
public void OnNavigatedTo(NavigationEventArgs e)
{
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
this._pageKey = "Page-" + this.Frame.BackStackDepth;
if (e.NavigationMode == NavigationMode.New)
{
// Clear existing state for forward navigation when adding a new page to the
// navigation stack
var nextPageKey = this._pageKey;
int nextPageIndex = this.Frame.BackStackDepth;
while (frameState.Remove(nextPageKey))
{
nextPageIndex++;
nextPageKey = "Page-" + nextPageIndex;
}
// Pass the navigation parameter to the new page
if (this.LoadState != null)
{
this.LoadState(this, new LoadStateEventArgs(e.Parameter, null));
}
}
else
{
// Pass the navigation parameter and preserved page state to the page, using
// the same strategy for loading suspended state and recreating pages discarded
// from cache
if (this.LoadState != null)
{
this.LoadState(this, new LoadStateEventArgs(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]));
}
}
}
if (e.NavigationMode == NavigationMode.New) if always true because Frame by default creates a new instance of the Page. See Frame Class Remarks. So The LoadState event handler is always called with a null state parameter
if (this.LoadState != null)
{
this.LoadState(this, new LoadStateEventArgs(e.Parameter, null));
}
Now if you look at the complete code for PhotoPage.xaml very closely you will notice that in the page header there is this NavigationCacheMode="Enabled" that is what makes it PhotoPage works.
There no need for all that code about saving states in the Page. The Frame class does that for you when the Page sets its NavigationCacheMode.
Related
I'm developing app for Windows Phone 8.1 which have :
Page with file open picker to take photo from gallery and second Page
to sending that one photo as an e-mail attachment with message.
How i can take this one picked picture and send this with e-mail.?
I tried to look some solutions but so far without any luck.
Any suggestion please?
The code is in regular c# and xaml and I'm using Windows Phone 8.1 in Visual Studio 2015.
You can use this solution to your problem in a single step.
/// <summary>
/// Taking photo from the gallery
/// </summary>
private void SharePhotoClick(object sender, RoutedEventArgs e)
{
PhotoChooserTask ph=new PhotoChooserTask();
ph.Completed += ph_Completed;
ph.Show();
}
/// <summary>
/// Sharing the photo to social media including email
/// </summary>
void ph_Completed(object sender, PhotoResult e)
{
ShareMediaTask smt = new ShareMediaTask();
smt.FilePath = e.OriginalFileName;
smt.Show();
}
Hope this helps!
Note: Please ignore or remove answer if you are developing universal wp application.
I believe in WP8.1 you would require to implement the IContinuationManager which will help you get the image selected by the user.
First you need an event where you open up the Gallery
private void PickAFileButton_Click(object sender, RoutedEventArgs e)
{
...
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.ViewMode = PickerViewMode.Thumbnail;
openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
openPicker.FileTypeFilter.Add(".jpg");
openPicker.FileTypeFilter.Add(".jpeg");
openPicker.FileTypeFilter.Add(".png");
// Launch file open picker and caller app is suspended
// and may be terminated if required
openPicker.PickSingleFileAndContinue();
}
Once the image is selected then OnActivated() method in app.xaml.cs is called where you need continuation manager to get the data of file selected.
protected async override void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
var continuationManager = new ContinuationManager();
var rootFrame = CreateRootFrame();
await RestoreStatusAsync(args.PreviousExecutionState);
if (rootFrame.Content == null)
{
rootFrame.Navigate(typeof(MainPage));
}
var continuationEventArgs = e as IContinuationActivatedEventArgs;
if (continuationEventArgs != null)
{
Frame scenarioFrame = MainPage.Current.FindName("ScenarioFrame") as Frame;
if (scenarioFrame != null)
{
// Call ContinuationManager to handle continuation activation
continuationManager.Continue(continuationEventArgs, scenarioFrame);
}
}
Window.Current.Activate();
}
Now the continuation manager will handle the activation for you.
case ActivationKind.PickSaveFileContinuation:
var fileSavePickerPage = rootFrame.Content as IFileSavePickerContinuable;
if (fileSavePickerPage != null)
{
fileSavePickerPage.ContinueFileSavePicker(args as FileSavePickerContinuationEventArgs);
}
break;
case ActivationKind.PickFolderContinuation:
var folderPickerPage = rootFrame.Content as IFolderPickerContinuable;
if (folderPickerPage != null)
{
folderPickerPage.ContinueFolderPicker(args as FolderPickerContinuationEventArgs);
}
break;
case ActivationKind.WebAuthenticationBrokerContinuation:
var wabPage = rootFrame.Content as IWebAuthenticationContinuable;
if (wabPage != null)
{
wabPage.ContinueWebAuthentication(args as WebAuthenticationBrokerContinuationEventArgs);
}
break;
}
All the code snippets are available here https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
Once this is done you can fetch the data and save it to a file and attach that file to email.
EmailMessage email = new EmailMessage();
email.To.Add(new EmailRecipient("test#abc.com"));
email.Subject = "Test";
var file = await GetFile();
email.Attachments.Add(new EmailAttachment(file.Name, file));
await EmailManager.ShowComposeNewEmailAsync(email);
More details in this link http://developerpublish.com/windows-phone-8-1-and-windows-runtime-apps-how-to-2-send-emails-with-attachment-in-wp-8-1/
Hope this helps!
When the application runs, I want to check a condition that will determine where the page navigates to. There isn't any input from the user so there isn't an Object sender. How can I accomplish this?
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
var Albums = await App.DataModel.GetAlbum();
if (Albums != null)
{
Frame.Navigate(typeof(ViewModel.AlbumView));
}
else
{
Frame.Navigate(typeof(ViewModel.AddAlbumView));
}
}
in the App.Xaml.cs there is line in the OnAppLaunchedEvent
rootframe.Navigate(typeof(MainPage));
replace this with your condition
var Albums = await App.DataModel.GetAlbum();
if (Albums != null)
{
Frame.Navigate(typeof(ViewModel.AlbumView));
}
else
{
Frame.Navigate(typeof(ViewModel.AddAlbumView));
}
and don't forget to bring your albums and viewmodel into app.
Just as a good practice make the viewModel static and ref it in
other pages that require it . this ensures u are following a SingleTon
pattern and u dont have multiple copies of ur Viewmodel at the same time.
First, I read a list of entries from a database and display it in a ListView. When I leave the page to show details of an entry, then go back to the list, everything is ok.
Next, I open another page to add one entry to the database.
Go back to the list, reading from database shows me the correct count.
When I go to display one detail, the correct count is stored in SaveState.
Go back to the list, LoadState give the wrong count. It's the former state.
Display other details and go back now works with the old list and do not show me the added entry.
This is my code:
private void getList()
{
memoList = new List<MemoItem>();
db.loadHistory(ref memoList);
DelButton.IsEnabled = false;
System.Diagnostics.Debug.WriteLine("GetList, memoList[{0}]", memoList.Count);
}
private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
if (memoList != null)
{
e.PageState["MemoItem"] = memoList;
if (memoSelected > -1)
e.PageState["memoSelected"] = memoSelected;
System.Diagnostics.Debug.WriteLine("SaveState, memoList[{0}]", memoList.Count);
}
}
private void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
if (e.PageState != null)
{
if (e.PageState.ContainsKey("MemoItem"))
{
memoList = (List<MemoItem>)e.PageState["MemoItem"];
System.Diagnostics.Debug.WriteLine("LoadState, memoList[{0}]", memoList.Count);
if (e.PageState.ContainsKey("memoSelected"))
memoSelected = (int)e.PageState["memoSelected"];
MemoListView.ItemsSource = memoList;
MemoListView.Visibility = Visibility.Visible;
}
else
{
getList();
showList();
}
}
}
Here are the Systems.Diagnostic outputs with comments in ():
GetList, memoList[8] (first time loaded)
SaveState, memoList[8] (leave the list to display details for one entry)
LoadState, memoList[8] (come back to the ListView)
SaveState, memoList[8] (leave the list to another page and add one entry)
GetList, memoList[9] (back to the list, read the correct entry count from database)
SaveState, memoList[9] (leave the ListView to display details for one entry)
LoadState, memoList[8] (come back to the ListView loads the wrong old list)
SaveState, memoList[8] (and works with the old list...)
LoadState, memoList[8] (...)
Remark: I can't call GetList from database every time, because I have to preserve checkmarks in the list which are not contained in the database.
What is wrong in my code? How can I resolve this problem? How to invalidate the StateEvent data after availability of a new list from database?
The problem is solved now. It was actually a consequence of the back stack manipulation.
I have four basic pages in my program, for which a GoBack makes sense. But I do not want to go back deeper. (I do not think anyone wants to go back 20 or more steps, but the seemingly unlimited BackStack would allow that.The required memory consumption is not negligible.)
In order to clear the BackStack at selecting one of the basic pages, I had this:
private void NavButton_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
if (b != null && b.Tag != null)
{
Type pageType = Type.GetType(b.Tag.ToString());
if (pageType != null)
{
if (rootFrame.CurrentSourcePageType != pageType)
{
rootFrame.Navigate(pageType, rootFrame);
// No goBack for Basic Pages
if (testBasicPage(pageType))
{
while (rootFrame.BackStackDepth > 1)
{
rootFrame.BackStack.RemoveAt(0);
}
}
}
}
}
}
That worked fine until I came across the problem with SaveState / Load State.
Now I first empty the BackStack and then navigate to the target page. That works.
private void NavButton_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
if (b != null && b.Tag != null)
{
Type pageType = Type.GetType(b.Tag.ToString());
if (pageType != null)
{
if (rootFrame.CurrentSourcePageType != pageType)
{
// No goBack for Basic Pages
if (testBasicPage(pageType))
{
while (rootFrame.BackStackDepth > 0)
{
rootFrame.BackStack.RemoveAt(0);
}
}
rootFrame.Navigate(pageType, rootFrame);
}
}
}
}
I am attempting to test the Lens application feature, in which my app is supposed to navigate directly to CameraCaptureTask after a user selects the app from the LensPicker (since I do not have a viewfinder on my MainPage). Upon returning to MainPage, the CamerCaptureTask has a completed event which will display the image on the screen.
I am having an issue with a weird recurring situation where my CameraCaptureTask is repeatedly called based on the result of a QueryString value that I cannot clear before the application is tombstoned and then restarted after CameraCaptureTask completes.
LensExampleUriMapper.cs
private string tempUri;
public override Uri MapUri(Uri uri)
{
tempUri = uri.ToString();
// Look for a URI from the lens picker.
if (tempUri.Contains("ViewfinderLaunch"))
{
// Launch as a lens, launch viewfinder screen.
return new Uri("/MainPage.xaml?fromLensPicker=" + "fromLensPicker", UriKind.Relative);
}
// Otherwise perform normal launch.
return uri;
}
MainPage.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
string fromLensPicker = null;
if (NavigationContext.QueryString.TryGetValue("fromLensPicker", out fromLensPicker))
{
if (fromLensPicker == "fromLensPicker")
{
newButton_Click(null, null); //click event that calls CameraCaptureTask
fromLensPicker = null; //Temporarily nullifies value until MainPage is OnNavigatedTo after CameraCaptureTask completes
}
}
}
How might I clear the QueryString value so my application does not continuously call newButton_Click(null, null) after CameraCaptureTask completes and the app is continued?
In OnNavigatedTo of MainPage use NavigationMode to find if you are returning from CameraCaptureTask
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if(e.NavigationMode == NavigationMode.Back)
return;
// else continue further with CameraCaptureTask
}
or
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if(e.NavigationMode == NavigationMode.New)
// continue further with CameraCaptureTask
}
string fromLensPicker = null;
if (NavigationContext.QueryString.TryGetValue("fromLensPicker", out fromLensPicker))
{
if (fromLensPicker == "fromLensPicker")
{
NavigationContext.QueryString.Remove("fromLensPicker");
//...
}
}
I have created a small sample Lens application, and I would like to be able to directly navigate to the CameraCaptureTask when the Lens icon is clicked in the default camera application. In my application I am already calling the CameraCaptureTask within a button click event during normal app operations. How might I set this up to work as well from the LensPicker option?
I have been referencing
http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj662936(v=vs.105).aspx
LensExampleUriMapper.cs
private string tempUri;
public override Uri MapUri(Uri uri)
{
tempUri = uri.ToString();
// Look for a URI from the lens picker.
if (tempUri.Contains("ViewfinderLaunch"))
{
// Launch as a lens, launch viewfinder screen.
return new Uri("/MainPage.xaml", UriKind.Relative);
}
// Otherwise perform normal launch.
return uri;
}
I was thinking of passing a QueryString value in return new Uri("/MainPage.xaml", UriKind.Relative); so that in my MainPage OnNavigatedTo event I could check that QueryString value and call the CameraCaptureTask, and then just route the result to the already existing event handler I have created (which displays the resulting image in MainPage). For some reason I am getting a debugging error when trying to create the QueryString to pass, and I am unsure of why?
EDIT** No longer getting error, but an infinite loop occurs when calling CameraCaptureTask. Why?
LensExampleUriMapper.cs
private string tempUri;
public override Uri MapUri(Uri uri)
{
tempUri = uri.ToString();
// Look for a URI from the lens picker.
if (tempUri.Contains("ViewfinderLaunch"))
{
// Launch as a lens, launch viewfinder screen.
return new Uri("/MainPage.xaml?fromLensPicker=" + "fromLensPicker", UriKind.Relative);
}
// Otherwise perform normal launch.
return uri;
}
MainPage.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
string fromLensPicker = null;
if (NavigationContext.QueryString.TryGetValue("fromLensPicker", out fromLensPicker))
{
if (fromLensPicker == "fromLensPicker")
{
newButton_Click(null, null); //click event that calls CameraCaptureTask
fromLensPicker = null; //Temporarily nullifies value until MainPage is OnNavigatedTo after CameraCaptureTask completes
}
}
}
I believe that when CameraCaptureTask is called, the application is tombstoned and then resumed on MainPage, in which the QueryString value fromLensPicker == "fromLensPicker" and the entire cycle starts over again, repetitively. How might I solve this?
Use NavigationMode property in MainPage. I think you can't clear QueryString. But you can check how navigation to your page occured to know if its returning from CameraCaptureTask
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if(e.NavigationMode == NavigationMode.New)
// continue further
}
or
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if(e.NavigationMode == NavigationMode.Back)
return;
// else continue further
}
Instead of making fromLensPicker = null in MainPage.xaml.cs, I now have NavigationContext.QueryString.Remove("fromLensPicker") as referenced from WP7 Navigation with parameters
MainPage.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
string fromLensPicker = null;
if (NavigationContext.QueryString.TryGetValue("fromLensPicker", out fromLensPicker))
{
if (fromLensPicker == "fromLensPicker")
{
NavigationContext.QueryString.Remove("fromLensPicker");
//Perform Action
}
}
}