I want to add an application bar to multiple pages of my app. So, I'm defining the application bar as an application resource so that it can be used by multiple pages. Now, the event handlers for these buttons are in the App class as mentioned here http://msdn.microsoft.com/en-us/library/hh394043%28v=VS.92%29.aspx.
But, these app bar buttons are basically shortcuts to important pages. So, clicking a button would just take you to the corresponding page. But, since I'm defining the event handlers in App.xaml.cs, it doesn't allow me to navigate. I understand the reason for this. But, I don't know how to solve the problem.
NavigationService.Navigate(new Uri("/Counting.xaml", UriKind.RelativeOrAbsolute));
says "An object reference is required for the non-static field, method or property System.Windows.Navigation.NavigationService.Navigate(System.Uri)"
Does it work if you get access to the frame?
(Application.Current.RootVisual as PhoneApplicationFrame).Navigate(new Uri("/Counting.xaml", UriKind.RelativeOrAbsolute));
Edit:
Each application has only one Frame. It's this frame that exposes the NavigationService. Therefore, the NavigationService is always accessible via the frame since there's always an instance of it in any Windows Phone app. Since you don't usually instantiate a new NavigationService, it's easy to think that it's a static method. However, it's actually a non-static class that gets instantiated automatically when your app is run. All you're doing in this case is getting the global instance, which is attached to the always-present Frame, and using that to navigate between pages. This means your class does not have to instantiate, or explicitly inherit, a NavigationService.
an other way to navigate to an other page from App.xaml.cs (using the app bar) is using the rootFrame var (at the end line):
private Frame rootFrame = null;
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
...
SettingsPane.GetForCurrentView().CommandsRequested += App_CommandRequested;
}
private void App_CommandRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)
{
SettingsCommand cmdSnir = new SettingsCommand("cmd_snir", "Snir's Page",
new Windows.UI.Popups.UICommandInvokedHandler(onSettingsCommand_Clicked));
args.Request.ApplicationCommands.Add(cmdSnir);
}
void onSettingsCommand_Clicked(Windows.UI.Popups.IUICommand command)
{
if (command.Id.ToString() == "cmd_snir")
rootFrame.Navigate(typeof(MainPage)); //, UriKind.RelativeOrAbsolute);
}
I found this approach a better one. The RootFrame object is already in the App.xaml.cs file, you just need to call it. Also putting this in a UI thread dispatcher is safer.
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
// change UI here
RootFrame.Navigate(new Uri("/MainPage.xaml", UriKind.Relative));
});
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
}
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 :
For example, I want a button to take me to a settings page, and then back to the main screen. Just as an example for me to understand the workflow. Any guidance?
I use the NavigationService to navigate to the new page, where the Uri has a path relative to the project's base directory.
private void OptionsMenuItem_Click(object sender, EventArgs e)
{
// Navigate to the new page
NavigationService.Navigate(new Uri("/Views/OptionsView.xaml", UriKind.Relative));
}
The back button on the phone will take the user back to the previous page automatically, or you could code you own return button using the NavigationService again.
Dr. Herbie's method works great.
Another option is to implement INavigate on your PhoneApplicationPage. Then use a HyperlinkButton. If you have a lot of buttons and don't want to write a bunch of click handlers, this can be more convenient.
Your implementation of INavigate.Navigate just uses the page's NavigationService like this:
public bool Navigate(Uri source)
{
NavigationService.Navigate(source);
return true;
}
You must take care of certification requirements! Look at this tutorial: http://www.yourwindowsphone7.com/tutorials/navigation-in-windows-phone-7-apps.html