I have a page X in a Windows Store App (Windows 8.1) that gets shown after the splash screen. I want to evaluate something while / after the page loads, before the user can interact with the page.
Depending on the evaluation I want to do one of the two:
Just show the page X to the user and only navigate to page Y when the user clicks a button
Skip the current page and navigate to page Y automatically
1 works fine.
2 doesn't. I tried with calling this.Frame.Navigate(typeof(Y)) in the constructor, which didn't work because this.Frame was still null. Then I tried calling it in LoadState and OnNavigatedTo, but while in both cases this.Frame is not null, the Navigate() method returns false, which means the navigation fails. I tried to step into the method in Debugging, but it didn't work (?).
One hint is, that when hitting a breakpoint in the Navigate() code line when it gets called automatically, the screen still shows the splash screen, so it seems the UI elements have not been loaded yet. So the final question is: How can I do the evaulation and automatic navigation with all elements being loaded (or just so that it works)?
I don't see why you would call Frame.Navigate(...) in OnNavigatedFrom, but I can suggest two options that work for me:
Navigate to the second page in OnNavigatedTo: Although this doesn't work out of the box, it does when instructing the dispatcher to do the navigation:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
this.Frame.Navigate(typeof(SecondPage));
});
}
}
An even easier solution is to do the navigation within the first page's Loaded event:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.Loaded += (sender, args) =>
{
this.Frame.Navigate(typeof(SecondPage));
};
}
}
Of course, in both cases you can add whatever calculation you like within the lambda expression before calling Frame.Navigate(...)
EDIT: As a third alternative, maybe you should think about moving the calculation and decision part to OnLaunched in App.xaml.cs, and load either the first or the second page as root page, instead of opening the first page and immediately navigating on to the second?
Related
I'd like to pre-load the ShellPage in a WinUI 3 (v1.1.5) Desktop application. That is, during Activation (called by
await App.GetService<IActivationService>().ActivateAsync(args);
in the OnLaunched handler of the App class), I'd like to make sure ShellPage is loaded before any of the navigation pages are displayed. I've changed the service configuration to include
services.AddSingleton<ShellPage>();
services.AddSingleton<ShellViewModel>();
in the constructor for the App class which should mean only one of each of ShellPage and ShellViewModel will be instantiated for the app run but the question is when are they fully provisioned?
The normal progression is that the Activation step first assigns ShellPage to MainWindow.Content, then navigates to MainPage (these are the names for the default project). Because MainPage is actually loaded into a Frame on ShellPage, it seems layout for MainPage happens before ShellPage layout is completed.
Any idea how I do this on initial startup? This is only an issue when the first Page is presented. After that, ShellPage is reused.
By default, TemplateStudio's navigation re-instantiates pages for every navigation. It doesn't use the ServicesProvider, so registering your pages as singleton won't help.
If you want to keep your page instances, you need to set NavigationCacheMode="Required" on your pages. This way, your pages will be cached even after you navigate away.
Still, your pages won't be instantiated until you navigate to them once at least. In order to instantiate all of your pages at the very beginning, you need to navigate through them at least once.
You can get all the NavigationViewItems with a method like this.
private static IEnumerable<NavigationViewItem> GetNavigationViewItems(IEnumerable<object> items)
{
foreach (var item in items.OfType<NavigationViewItem>())
{
yield return item;
foreach (var grandChild in GetNavigationViewItems(item.MenuItems.OfType<NavigationViewItem>()))
{
yield return grandChild;
}
}
}
And use it like this in the NavigationViewService's Initialize method.
[MemberNotNull(nameof(_navigationView))]
public void Initialize(NavigationView navigationView)
{
_navigationView = navigationView;
_navigationView.BackRequested += OnBackRequested;
_navigationView.ItemInvoked += OnItemInvoked;
IEnumerable<NavigationViewItem> menuItems =
GetNavigationViewItems(_navigationView.MenuItems);
foreach (var item in menuItems)
{
if (item.GetValue(NavigationHelper.NavigateToProperty) is string pageKey)
{
_navigationService.NavigateTo(pageKey);
}
}
}
A little clarification first, and then the answer I found to the issue.
Andrew's answer (above) is great for instantiating all of the Pages in the NavigationView at startup but the very first page loaded still would not have access to a fully loaded ShellPage in its constructor (and thus, a fully populated element tree). Andrew is right that the NavigationViewItems (Pages) don't persist by default, but the ShellPage does as it's part of the UI. Specifically, it is the content of the MainWindow and defines a Frame into which NavigationViewItems are loaded. Regardless of which Page is displayed, it's the same instance of the ShellPage people see.
The issue arises because of the order in which Activation (specifically, the DefaultActivationHandler) is done at App startup. When the App starts, it calls
await App.GetService<IActivationService>().ActivateAsync(args);
which does
// Set the MainWindow Content.
if (App.MainWindow.Content == null)
{
_shell = App.GetService<ShellPage>();
App.MainWindow.Content = _shell ?? new Frame();
}
and navigates to the first Page (loads the first Page into the NavigationView.Frame by calling DefaultActivationHandler) before finishing the loading of ShellPage. Thus, ShellPage is not fully loaded (ShellPage.IsLoaded == false) when MainPage is loaded.
To fully instantiate ShellPage before any of the NavigationViewItem Pages are loaded, simply change the loading sequence. First, defer the navigation to the first page (whichever you choose) by editing HandleInternalAsync in DefaultActivationHandler.cs to
protected async override Task HandleInternalAsync(LaunchActivatedEventArgs args)
{
//_navigationService.NavigateTo(typeof(MainViewModel).FullName!, args.Arguments);
await Task.CompletedTask;
}
Move the navigation to the OnLoaded handler in ShellPage.xaml.cs:
private void OnLoaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
TitleBarHelper.UpdateTitleBar(RequestedTheme);
KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.Left, VirtualKeyModifiers.Menu));
KeyboardAccelerators.Add(BuildKeyboardAccelerator(VirtualKey.GoBack));
App.GetService<INavigationService>().NavigateTo(typeof(MainViewModel).FullName!);
}
All Pages now receive a loaded ShellPage when navigated to, regardless of order.
I'm building an XAML app for Win 8 metro and ran into a "problem".
I have my mainpage.xaml with a button and in the mainpage.xaml.cs i have my constructor with initializecomponent(). when i click the button i call this function method:
private void GoToOtherPage()
{
this.Frame.Navigate(typeof(MySecondPage));
}
and works just fine.
However, in the contructor i also have a condition, and if true just carry on, but if it's false i want to run the GoToOtherPage() as well.
the constructor then looks somtehing like this
Public Mainpage()
{
InitializeComponent();
if(....)
{
//do some stuff
}
else
{
GoToOtherPage();
}
}
Since the initializecomponent() not is ready when this happens, i get the error Object reference not set to an instance of an object. which i (think) have found is refferring to this.Frame.
How should i do this the correct way? Put something like "WaitForThisFormToBeReady()" before the .Navigate or am i just on the complete wrong track here?
I think this.Frame becomes non-null after the page has been navigated to, so you could override OnNavigatedTo to handle it. Otherwise you can grab the Frame through (Frame)Window.Current.Content, a property on your App class or a NavigationService implementation - depending on how far you went with design patternizing your app.
I have an application for Windows 8 with a page (Frame) for displaying a list of items and a page for downloading & displaying the items details. I am also using MVVM Light for sending notifications.
Application use goes something like this:
Open Main Page
Navigate to List Page
Frame.Navigate(typeof(MyPage));
Choose Item
//Complete logic
Frame.GoBack();
Back on Main Page, I start downloading the file in the view model, I send ONE NotificationMessage saying BeginDownloadFile and after it is downloaded ONE NotificationMessage saying EndDownloadFile.
The first time I do steps 2,3, & 4 my NotificationReceived method is hit once, the second twice and so forth.
private async void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == Notifications.BeginDownloadFile)
{
FileDownloadPopup.IsOpen = true;
}
else if (msg.Notification == Notifications.EndDownloadFile)
{
FileDownloadPopup.IsOpen = false;
}
}
Additional information: I only have one FileDownloadPopup, yet each time, an additional popup is shown each time the NotificationMessageReceived method is called.
My only conclusion is that between navigating forwards and backwards in my app, there are multiple MainPages being created and never closed. This results in many NotificationsMessageReceived methods just waiting for a notification to come their way so they can show their popup.
I have two questions:
1. Does this sound like normal behaviour for a Windows 8 app?
2. How can I close all instances of the MainPage or return to the previous instance without creating a new instance?
Please let me know if I have missed something important out before marking my question down.
This sounds normal to me. The default navigation behaviour in Windows 8 is to create a new page instance each time you navigate to a new page, regardless of whether this is forward or back navigation.
Try setting the NavigatinCacheMode on MainPage to Required. See the MSDN documentation for details of how page caching works.
It sounds like you are registering eventhandlers in the page and then not removing them. Each time you navigate to the page again the handler is being added again in addition to the one you previously added. Try to add your event handler in OnNavigatedTo, and make sure you unregister it in OnNavigatedFrom.
protected override void OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
MyEvent.OnDownloadRequest += MyLocalDOwnloadHandler; // add the handler
}
protected override void OnNavigatedFrom(Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
MyEvent.OnDownloadRequest -= MyLocalDOwnloadHandler; // remove the handler
}
I am working on a Windows Phone application, here is the scenario that I have problem:
So I have three pages, lets call it page 1, 2, 3.
In page 1, I have a button called start downloading. Click the button and use NavigateService.Navigate(page2Uri) and navigate to page2.
Page 2 makes query and downloads images from internet, so in its OnNavigateTo handler, I check the page back stack, if it is navigated from page 1, I will do the download. In the app bar of this page, I have a button that can navigate to page3.
Page 3 is a list of options that will perform some behavior on the image that is downloaded in page2. Once I choose an option, I want to go back to page 2 and perform some behavior on the loaded image.Here the question comes: if I use NavigateService.Navigate(page2Uri) to navigate from page3 to page2, it will call the Page2 constructor and OnNavigateTo handler again, which will cause it to lose every instance variable it already got.
But if I use NavigatService.GoBack it will go back to page2, then realizes that the backstack top entry is page1 (since page1 -> page2 -> page3). So it will re-download everything again.
I dont want anything to be downloaded again when navigate back form page3 to page2. So wondering if anyone has good idea about this.
Thank you.
You can use the query parameters and NavigationEventArgs to help.
First, you can use the NavigationEventArgs to determine if the user is going forward or background by checking the NavigationMode.
Second, you can tell page 2 to download by using the query parameters.
From page1:
private void MoveToPage2FromPage1()
{
NavigationService.Navigate(new Uri("/Page2.xaml?shouldDownload=true", UriKind.Relative));
}
and page2:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.NavigationMode == NavigationMode.Back) return;
string shouldDownload = ""; //May not be needed if you'll only ever go to page 2 from page 1 to download...
if (NavigationContext.QueryString.TryGetValue("shouldDownload", out shouldDownload))
{
Convert.ToBoolean(shouldDownload);
}
}
There are several ways to pass data to another page:
You can use query parameters as Shawn suggested.
You can use global data stored somewhere like in app.cs
You can use a static class to hold the data.
You can use a shared viewModel to hold the parameters. (or static properties in the viewmodel)
It all depends on the particular case. I think Shawns suggestion of using query paramaters is probably the most 'correct' MVVM way, but the other methods have their place.
You need to implement the following function and the navigation service.
These code will definitely solve your problem
for two or more parameters, use this code
String download="true";
String file="image";
NavigationService.Navigate(new Uri("/Page3.xaml?download="+download+"&file="+file+"", UriKind.Relative));
OnNavigatedTo, add the following code on to your Page2
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
String download=NavigationContext.QueryString["download"];
String file=NavigationContext.QueryString["file"];
}
For the above OnNavigatedTo function outputs true and image. You can use MessageBox.Show(); to output
As you can see, I want to navigate to "ScoreInputDialog.xaml" page, where the user can type in a name. After this I am trying to save the name to a list, but it is always empty because navigation to page "ScoreInputDialog.xaml" is being done at last. How can I navigate to the desired page and get my value before continuing with rest of the code?
NavigationService.Navigate(new Uri("/ScoreInputDialog.xaml", UriKind.Relative)); // Sets tempPlayerName through a textbox.
if (phoneAppService.State.ContainsKey("tmpPlayerName"))
{
object pName;
if (phoneAppService.State.TryGetValue("tmpPlayerName", out pName))
{
tempPlayerName = (string)pName;
}
}
highScorePlayerList.Add(tempPlayerName);
You should do nothing directly after the Navigate call. Instead override the OnNavigatedTo method of the page you are coming from, to get notified when the user comes back:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
This method will be called when the user exits the "ScoreInputDialog.xaml", probably by pressing the back button or because you call NavigationService.GoBack(). This exits the "ScoreInputDialog.xaml" page and goes to the previous page, where the OnNavigatedTo will be called. This is the time to check for the value.
Illustration of the navigation flow:
"OriginPage" ---[Navigate]---> "ScoreInputDialog" ---[GoBack() or Back-button]---> "OriginPage" (*)
Where the (*) is there the OnNavigatedTo will be called. The implementation could look like this:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
if (phoneAppService.State.ContainsKey("tmpPlayerName"))
{
object pName;
if (phoneAppService.State.TryGetValue("tmpPlayerName", out pName))
{
tempPlayerName = (string)pName;
}
highScorePlayerList.Add(tempPlayerName);
}
}
Remember to clear the temp player name before calling Navigate:
phoneAppService.State.Remove("tmpPlayerName");
NavigationService.Navigate(new Uri("/ScoreInputDialog.xaml", UriKind.Relative));
Note: OnNavigatedTo will also be called when the user sees the page the first time or navigates back from other pages than "ScoreInputDialog.xaml". But then the "tmpPlayerName" value will not be set.
Navigate isn't being performed last, it is just happening asynchronously. You have to wait for the navigation to complete.
http://msdn.microsoft.com/en-us/library/system.windows.navigation.navigationservice.navigated.aspx
Read the following page : http://msdn.microsoft.com/en-us/library/ms615507.aspx
At the bottom after the Methods and Properties definitions in the "Remark" part it explains how the NavigationService Class works and this nice little graphic explains a lot :