How to navigate to a new page from within a OnFrameNavigated handler - c#

I am porting a Windows Phone 8 app to Windows Store 8.1 and I am having some trouble with navigating to a new page when Frame.Navigate(...) is called from within a OnFrameNavigated handler.
The long story:
The App manages different "Media" objects like Books or Films. To edit the properties (title, author, etc.) of such an element the WP uses a custom "MediaEditor" control that was inspired by the DatePicker from the WP ToolKit:
Instead of manually navigating to an Editor/Picker page the Editor/Picker is capsuled in its own class. This class hooks up to the ApplicationFrame and handles Navigation to the Editor/Picker page on its own. When the ApplicationFrame navigates back to the page/content that was visible before showing the Editor/Picker the result can be processed, e.g. fire an event with the picked date.
There is one difference in my MediaEditor compared to the DatePicker:
If the MediaItem that is currently edited is related to another MediaItem (e.g. Movie A is the film version of Book A) the editing of this item can directly been started from within the MediaEditor:
call mediaEditor.Edit(MovieA) --> MediaEditor for MovieA is displayed
Select to Edit BookA from within MediaEditor --> MediaEditor for MovieA is closed and MediaEditor for BoolB is displayed
This is done by calling Edit(BookA) from within OnFrameNavigated. Thus the complete Navigation is:
call mediaEditor.Edit(MovieA) --> Frame.Navigate(typeof(MediaEditorPage), MovieA)
Select to Edit BookA --> Frame.GoBack()
Detect that BookA should be edited in OnFrameNavigated --> Frame.Navigate(typeof(MediaEditorPage), BookA)
While this works on Windows Phone this does not work on Win 8.1. Frame.Navigate(typeof(MediaEditorPage), BookA) is simply ignored. There is no OnNavigationFailed, no console output, nothing. The App navigates back to the first page (the one that original called mediaEditor.Edit(MovieA)) and nothing else happens.
What exactly is the problem here?
MediaEditor mediaEditor = new MediaEditor();
mediaEditor.Edit(bookItem);
public class MediaEditor {
private Frame applicationFrame;
private object frameContentWhenOpened;
public void Edit(MediaItem item) {
applicationFrame = Window.Current.Content as Frame;
if (applicationFrame != null) {
frameContentWhenOpened = applicationFrame.Content;
applicationFrame.Navigated += OnFrameNavigated;
applicationFrame.NavigationFailed += OnNavigationFailed;
applicationFrame.Navigate(typeof(MediaEditorPage), item);
}
}
void OnNavigationFailed(object sender, NavigationFailedEventArgs e) {
System.Diagnostics.Debug.WriteLine(e);
}
MediaEditorPage editorPage;
void OnFrameNavigated(object sender, NavigationFailedEventArgs e) {
if (e.Content == frameContentWhenOpened) {
applicationFrame.Navigated -= OnFrameNavigated;
applicationFrame.NavigationFailed -= OnNavigationFailed;
applicationFrame = null;
frameContentWhenOpened = null;
if (editorPage != null) {
if (editorPage.EditOtherItem)
// Directly start editing another Item
Edit(editorPage.OtherItem);
else
// Finish editing, fire Events, etc
}
} else {
editorPage = e.Content as MediaEditorPage ;
}
}
}

Related

return to the first frame opened in wpf xaml with C#

I am creating WPF application that proceeds in several upper frame with different data displayed for each open frame , reloaded passing an id .
If I want to go back one frame I can use this :
this.NavigationService.GoBack();
But , question , if I wanted to return to the first frame , without necessarily charging everyone I've opened in sequence, you can ?
type a method:
this.NavigationService.GoFirstFrameOpen ()
There is a similar function ? And if not, a possible solution ?
Use Frame.BackStack property.
It returns an IEnumerable that you use to enumerate the entries in back navigation history for a Frame.
You can get the containing Frame by traversing the VisualTree.
Sample :
private void Button_Click(object sender, RoutedEventArgs e)
{
var leaf = (DependencyObject)sender;
while(leaf.GetType() != typeof(Frame))
{
leaf = VisualTreeHelper.GetParent(leaf);
}
Frame f = leaf as Frame;
if (f != null)
{
JournalEntry firstItem = null;
foreach (JournalEntry item in f.BackStack)
{
firstItem = item;
break;
}
this.NavigationService.Navigate(firstItem.Source);
}
}
Addional methods if you are navigating from within the Page, then
You can have your Page class have a parameterized constructor receiving the Frame object.
You can use a while loop with going backward until you reach the first pageentry.
Use a PageFunction. A PageFunction is a Page which can return a value.
WPF Navigation

Windows Phone 8.1 check if password set else load new page

I Have a very similar situation to this guys question in that I have a Login Page which is my MainPage.xaml file but I have another page called SetPassword.xaml that I want to load if a user has not set a password yet. Essentially this is the first time that the App loads after it has been installed.
I've spent hours on SO trying various different solutions (including the one I linked to) but I'm just not getting anywhere and it seems that many of the solutions are either for WP7 or WP8 and nothing similar has been solved for the new WP8.1.
This is the basic check, using Windows.Storage that I'm doing to see if a password has been set or not.
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
if (localSettings.Values["myPassword"] == null)
{
Debug.WriteLine("Password not set");
this.Frame.Navigate(typeof(SetPassword));
}
else
{
Debug.WriteLine("Password is set, continuing as normal");
}
If I add this to public MainPage() class I have no problem in the app returning "Password is not set" in the debug messages however the this.frame.Navigate(typeof(SetPassword)) navigation never loads the SetPassword view.
I have also tried this method in the OnNavigatedTo with exactly the same results.
In my App.xaml file I've also tried a number of different methods, again, with the same results. I can get the debug message but not the navigation I'm looking for. I looked at implementing a method on Application_Launching over here as well as implementing conditional navigation on RootFrame.Navigating+= RootFrameOnNavigating; over here but clearly I am missing something.
Hopefully you smarter people can help me get my navigation working based on a conditional value?
The solution was simple. To do the navigation I could have done it in either App or MainPage as per my question but the reason the navigation wasn't working was because I was trying to navigate to SetPassword.xaml which was a <ContentDialog> instead of a <Page>.
I feel embarrassed actually that I didn't even check that but hopefully if this happens to someone else they can check that they're actually trying to navigate to a Page and not any other type of element. How sadly foolish of me!
EDIT:
Here's what my OnLaunched in the App.xaml file looks like where I can now do my check and redirect to a different page based on the value being set.
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
rootFrame = new Frame();
rootFrame.CacheSize = 1;
Window.Current.Content = rootFrame;
// The following checks to see if the value of the password is set and if it is not it redirects to the save password page - else it loads the main page.
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
if (localSettings.Values["myPassword"] == null)
{
rootFrame.Navigate(typeof(SetPassword));
}
else
{
rootFrame.Navigate(typeof(MainPage));
}
}
if (rootFrame.Content == null)
{
if (rootFrame.ContentTransitions != null)
{
this.transitions = new TransitionCollection();
foreach (var c in rootFrame.ContentTransitions)
{
this.transitions.Add(c);
}
}
rootFrame.ContentTransitions = null;
rootFrame.Navigated += this.RootFrame_FirstNavigated;
if (!rootFrame.Navigate(typeof(MainPage), e.Arguments))
{
throw new Exception("Failed to create initial page");
}
}
Window.Current.Activate();
}

WP8: Fast app resume + secondary tile + MainPage = 2 instances

I'm having the same problem posed here:
http://social.msdn.microsoft.com/Forums/wpapps/en-us/af8615e7-8e90-4069-aa4d-3c4a84a6a3d0/windows-phone-8-fast-app-resume-with-deeplinks?forum=wpdevelop
I'm no C# or WP expert, so please bear with me.
I have secondary tiles which link to "/MainPage.xaml?id=XX".
I have fast app resume enabled. (ActivationPolicy="Resume" in the app manifest)
I only have one page in my app: MainPage.xaml.
Problem: When I resume the app using a secondary tile ("/MainPage.xaml?id=XX"), I get a brief view of the previous instance (that would have resumed) and then the MainPage initializes again, creating a new instance. In effect, the app is loading from scratch after giving me a peek of what was previously open.
That is obviously undesired behavior. I want to use the existing instance to perform my task.
Attempt 1:
Use e.Cancel = true; to cancel the navigation to the MainPage.xaml:
(using the App.xaml.cs code from the official Fast App Resume sample to identify how the app was launched)
...
else if (e.NavigationMode == NavigationMode.New && wasRelaunched)
{
// This block will run if the previous navigation was a relaunch
wasRelaunched = false;
if (e.Uri.ToString().Contains("="))
{
// This block will run if the launch Uri contains "=" (ex: "id=XX") which
// was specified when the secondary tile was created in MainPage.xaml.cs
sessionType = SessionType.DeepLink;
e.Cancel = true; // <======================== Here
// The app was relaunched via a Deep Link.
// The page stack will be cleared.
}
}
...
Problem: In doing so, my OnNavigatedTo event handlers never fire, so my query string is never parsed.
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
String navId;
if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
{
if (NavigationContext.QueryString.TryGetValue("id", out navId))
{
MessageBox.Show(navId.ToString()); // Not reached
}
}
...
Attempt 2:
Use e.Cancel = true; to cancel the navigation to the MainPage.xaml, AND pass the Uri to a method in MainPage:
// App.xaml.cs
...
else if (e.NavigationMode == NavigationMode.New && wasRelaunched)
{
// This block will run if the previous navigation was a relaunch
wasRelaunched = false;
if (e.Uri.ToString().Contains("="))
{
// This block will run if the launch Uri contains "=" (ex: "id=XX") which
// was specified when the secondary tile was created in MainPage.xaml.cs
sessionType = SessionType.DeepLink;
e.Cancel = true;
MainPage.GoToDeepLink(e.Uri); // <======================== Here
// The app was relaunched via a Deep Link.
// The page stack will be cleared.
}
}
...
// MainPage.xaml.cs
public static void GoToDeepLink(Uri uri) // <======================== Here
{
// Convert the uri into a list and navigate to it.
string path = uri.ToString();
string id = path.Substring(path.LastIndexOf('=') + 1);
MyList list = App.ViewModel.ListFromId(Convert.ToInt32(id));
pivotLists.SelectedItem = list;
}
Problem: I get an error that pivotLists is non-static and thus requires an object reference. I think that in order to get this to work I'd need to create a new instance of MainPage (MainPage newMainPage = new MainPage();) and call newMainPage.pivotLists.SelectedItem = list; -- BUT I don't know how to use newMainPage instead of the existing one/replace it... or if that's something I want/won't cause further problems/complications.
I don't know what the solution is to this problem, and I may be going in the completely wrong direction. Please keep all suggestions in simple terms with code examples if you can, I'm still learning.
Thanks for any help.
It seems that when you reopen your App from secondary tile, then it's reactivated and new instance of MainPage is created (even if there is one from previous run). If I understood you correctly, I've managed to do such a thing:
In app.xaml.cs:
I've added a variable which indicates if I should return to previous MainPage after Navigating from secondary tile - it needs to be static as I want to have access to it from MainPage
public static bool returnPage = false;
In RootFrame_Navigating I'm setting this variable to true in:
// ...
else if (e.NavigationMode == NavigationMode.New && wasRelaunched)
{
// This block will run if the previous navigation was a relaunch
wasRelaunched = false;
returnPage = true;
// ...
In ClearBackStackAfterReset - prevent from deleting the old Page, when returning:
// ...
if (e.NavigationMode != NavigationMode.New || returnPage)
return;
// ...
In MainPage.cs:
I've changed a little constructor, as I don't want to see a blink of a new Page:
public MainPage()
{
if (!App.returnPage)
InitializeComponent();
}
In MainPage I've also variable which is passed from secondary tile - it's also static, as I need only one instance of it:
private static string navId = "";
And the core of the trick - OnNavigatedTo:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (App.returnPage)
{
App.returnPage = false;
NavigationContext.QueryString.TryGetValue("id", out navId);
NavigationService.GoBack();
}
else if (e.NavigationMode != NavigationMode.Reset)
{
// normal navigation
}
}
It works like this:
when you launch normally your App, returnPage is false, everything goes normal
when you activate it from secondary tile few things happen:
1. first goes navigation to your previous page with NavigationMode.Reset - we are not interested in it, so I switched it off - nothing should happen
2. then program tries to create new instance of MainPage, but returnPage is true, and because of the if statement, InitializeComponent won't run. Just after this, in OnNavigatedTo, program saves passed querystring and Navigates Back to previous instance of MainPage - from previous run
3. at last we are navigating to right MainPage with NavigationMode.Back and we have our querystring saved in static variable.
You must be aware of two things: first - probably it can be little rebuild (I'm not sure if wasRelaunched is needed and so on) - you need to debug it and see of what you can get rid off. Second - you will probably need to test your App with Tombstone case.
Hope this helps.

Change App language at RunTime on-the-fly

I'm currently developing a metro app in which the user can change current language at runtime and all the custom controls that are loaded must update their text regarding to the new language. Problem is that when I change the language using the following code, the app language changes but it will only update text when I restart my app because the pages and controls that are already rendered are cached.
LocalizationManager.UICulture = new System.Globalization.CultureInfo((string)((ComboBoxItem)e.AddedItems[0]).Tag);
Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = ((ComboBoxItem)e.AddedItems[0]).Tag as String;
What should I do to force updating text of all custom controls at runtime without restarting my app?
Use this:
var NewLanguage = (string)((ComboBoxItem)e.AddedItems[0]).Tag;
Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = NewLanguage;
Windows.ApplicationModel.Resources.Core.ResourceContext.GetForViewIndependentUse().Reset();
//Windows.ApplicationModel.Resources.Core.ResourceContext.GetForCurrentView().Reset();
Windows.ApplicationModel.Resources.Core.ResourceManager.Current.DefaultContext.Reset();
and then reload your Page, using Navigate method:
if (Frame != null)
Frame.Navigate(typeof(MyPage));
In order to respond right away, you would need to reset the context of the resource manager.
For Windows 8.1:
var resourceContext = Windows.ApplicationModel.Resources.Core.ResourceContext.GetForCurrentView();
resourceContext.Reset();
You will still need to force your page to redraw itself and thus re-request the resources to get the changes to take place. For Windows 8, you can see https://timheuer.com/blog/archive/2013/03/26/howto-refresh-languages-winrt-xaml-windows-store.aspx
You can change the app's language at runtime with the help of this source code. I took help from this and manipulated my app's language settings page as follows:
In languageSettings.xaml.cs:
public partial class LanguageSettings : PhoneApplicationPage
{
public LanguageSettings()
{
InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (ChangeLanguageCombo.Items.Count == 0)
{ ChangeLanguageCombo.Items.Add(LocalizationManager.SupportedLanguages.En);
ChangeLanguageCombo.Items.Add(LocalizationManager.SupportedLanguages.Bn);
}
SelectChoice();
}
private void ButtonSaveLang_OnClick(object sender, RoutedEventArgs e)
{
//Store the Messagebox result in result variable
MessageBoxResult result = MessageBox.Show("App language will be changed. Do you want to continue?", "Apply Changes", MessageBoxButton.OKCancel);
//check if user clicked on ok
if (result == MessageBoxResult.OK)
{
var languageComboBox = ChangeLanguageCombo.SelectedItem;
LocalizationManager.ChangeAppLanguage(languageComboBox.ToString());
//Application.Current.Terminate(); I am commenting out because I don't neede to restart my app anymore.
}
else
{
SelectChoice();
}
}
private void SelectChoice()
{
//Select the saved language
string lang = LocalizationManager.GetCurrentAppLang();
if(lang == "bn-BD")
ChangeLanguageCombo.SelectedItem = ChangeLanguageCombo.Items[1];
else
{
ChangeLanguageCombo.SelectedItem = ChangeLanguageCombo.Items[0];
}
}
}
***Note: Before understanding what I did on LanguageSettings page's code behind, you must implement the codes from the link as stated earlier. And also it may be noted that I am working on windows phone 8

C#/XAML App Lifecycle - Jump login page after Terminating app

In my Windows Store App i've followed the Microsoft guidelines to resume the app after it is terminated(http://goo.gl/oZ7BG).
It all works but after the app is terminated i'd like to jump the login page(that is the first page in the app) and go directly to the Menu page of the app. It's absolutely like the Dropbox app. I know i have to work to the App.xaml.cs and this method:
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
bool appTerminated = false;
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
marketingHP.Common.SuspensionManager.RegisterFrame(rootFrame, "appFrame");
if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
await marketingHP.Common.SuspensionManager.RestoreAsync();
appTerminated = true;
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
if (appTerminated)
rootFrame.Navigate(typeof(HomePage), args.Arguments);
else if (!rootFrame.Navigate(typeof(LoginPage), args.Arguments))
{
throw new Exception("Failed to create initial page");
}
}
// Ensure the current window is active
Window.Current.Activate();
}
How can i understand that the app is terminated before?
Note that i've added the bool appTerminated but it works only for suspending...
Read up further on windows 8 app lifecycle here:
http://msdn.microsoft.com/en-us/library/windows/apps/hh464925.aspx
Particularly, notice the section regarding the PreviousExecutionState property and the table included there outlining the different terminated states. It seems like what you would want to do is check the value of PreviousExecutionState, and if that value reflects the condition in which you want to skip the login page (e.g. the user has already logged in), then you should navigate to your homepage, similar to what you've tried to do above.
If you are closing the app manually and windows is not terminating it due to resource constraints, or it was closed unexpectedly, then appTerminated won't be set to true.
Instead of:
if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
Include a check to see if the app was terminated by the user:
if (args.PreviousExecutionState == ApplicationExecutionState.Terminated ||
args.PreviousExecutionState == ApplicationExecutionState.ClosedByUser)
This will catch both when windows terminates the app due to resource constraints and when a user terminates the app manually. Then, on startup from termination, appTerminated should be set to true, and the app should navigate directly to HomePage.
edit:
In answer to your comment, what you could do is inside the App.xaml file, provide logic that changes the arguments passed in the Navigate call. You could do something like this:
string navArgs = "FromApp";
if (appTerminated)
{
navArgs = "FromTerminated";
}
Then, just pass this to HomePage when you call Navigate:
if (appTerminated)
{
rootFrame.Navigate(typeof(HomePage), navArgs);
}
Now, in your HomePage code behind file, define the OnNavigatedTo method. This takes a NavigatedEventArgs that you can then cast as some object (in this case as a String) and then check to see what was passed:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
string navArgs = e.Parameter as String;
switch (navArgs)
{
case "FromApp":
//Do something here
break;
case "FromTerminated":
//Do something different here
break;
default:
break;
}
}
Hope this helps!

Categories

Resources