Cannot get MainPage in Xamarin.Forms to react/change - c#

I have tried to different methods to get my MainPage to change and it is not happening. Basically when my App starts it needs to do some Async tasks to initialize everything, and then get the real main page to display. I first tried an event handler in App.cs. I can confirm the event handler does fire off in my debugger. However the code to switch the main page is not successfully doing so. Nothing changes in my UI. This code is below:
private void UnitStatusModelChanged(object sender, EventArgs e)
{
UnitStatusModel = _unitStatusMonitor.UnitStatusModel;
UnitStatusViewModel = new UnitStatusViewModel(UnitStatusModel, InputResourceHandler);
MainPage = new MainTabbed(UnitStatusViewModel, InputResourceHandler);
Debug.WriteLine("Switched page to " + UnitStatusModel.Version.Name);
}
protected override void OnStart()
{
_unitStatusMonitor.UnitStatusChanged += new UnitStatusModelChangedEventHandler(UnitStatusModelChanged);
_commandQueue.StartQueue();
}
I thought maybe setting MainPage this way is not the way to go. So I tried making the MainPage a blank NavigationPage, and then pushing a model page on top once the app is ready. This also had no effect.
var newPage = new MainTabbed(
new UnitStatusViewModel(_unitStatusModel, inputResourceHandler),
inputResourceHandler
);
App.Current.MainPage.Navigation.PushModalAsync(newPage);
I can confirm that if I start the app with a MainTabbed page, the page does display, so it should not be a problem with MainTabbed. Again, I also verified the in the debugger these lines of code are being executed with no exceptions being thrown. Still, no change to MainPage. What gives?

Check your thread after your async completes, when you call PushModalAsync (or any UI thing). Make sure it's on the main thread.

Related

Detecting whether page is navigated to or off C#

In my WPF application i have multiple pages.
One page is called "startup" and this is the one shown most of the time.
An NFC-Reader is running in the background on a thread and everytime you scan a card, it executes code.
My navigation works by having a class called "InstanceContainer.cs" which basically just holds all the instances to my pages which i then navigate to using (for example) NavigationService.Navigate(InstanceContainer.startup());
However i want it to only execute said code, when "startup" is shown while a card is scanned. My idea would be to just set a public bool like public bool startupShown; inside of "startup" which is then checked for in the thread that runs the NFC-Scanning.
But how do i neatly update this bool? Of course it could be updated manually on every buttonclick that leads away and to "startup", but there has to be a better way than that.
I found this but didn't quite understand it nor could i get it to work.
Thanks in advance
You could use the Page's IsLoaded property to determine whether it's currently loaded and displayed in the Frame.
This property can however not be accessed directly from a background thread so you could either add your own field or property to the start page and handle the Loaded and Unloaded events to set it...:
public partial class StartupPage : Page
{
public bool startupShown;
public Page1()
{
InitializeComponent();
Loaded += (s, e) => startupShown = true;
Unloaded += (s, e) => startupShown = false;
}
}
...or use the dispatcher to access the built-in Loaded property from the background thread:
Page page = InstanceContainer.startup();
bool isloaded = page.Dispatcher.Invoke(() => page.IsLoaded);

Showing form from async method and waiting on a result causes an extra blank form to appear

Okay, here goes.
I am building a C# Winforms application on top of the CefSharp Winforms minimum example.
I have moved all of the code out of the main form's constructor and into a new method called Initialize(). So I instantiate and then call Initialize.
var shellForm = new ShellForm();
shellForm.Initialize();
Application.Run(shellForm);
Initialize() is declared async void, because somewhere in the middle of it a form is displayed, from which user input is required before continuing.
//set up the form based on the number of projects and their names
ProjectSelect projectSelectForm = new ProjectSelect();
projectSelectForm.SetUpForm(projPaths); //show/hide buttons based on length of list
projectSelectForm.TopMost = true;
projectSelectForm.Show();
await projectSelectForm.WhenEventTriggered();
The task-waiting stuff is implemented in this class:
public static class ProjectSelectEventWaiter
{
public static Task WhenEventTriggered(this ProjectSelect ps)
{
var tcs = new TaskCompletionSource<bool>();
ProjSelEvtHandler handler = null;
handler = (selected) =>
{
tcs.TrySetResult(true);
ps.ProjectSelected -= handler;
};
ps.ProjectSelected += handler;
return tcs.Task;
}
}
Everything works fine, except that when projectSelectForm is shown, another blank form appears above and to the left. When I click close on this form, the VS debugger stops. But if Initialize() is not declared async (and the await is removed), then this blank form doesn't appear.
blank form appearing at top left
I guess there are several no-no's in all of the above. I'm not a software engineer, I'm doing this project to demonstrate an important business case and there's no one else to do it.
The questions are: why might this form be appearing, and how could I try prevent it from appearing?
The problem is most likely that the wrong form becomes the mainForm for the application. It seems like you intend ShellForm to be the main form for the application, but you are displaying another form before this has been fully initialized. This is usually not a good idea.
You probably want to fully create your main form before displaying any other form to solve the problem. This might require rewriting to do a multi-step initialization, first load the shellform, then ask for project, and finally do the final initialization of the shellForm.
Another workaround could be to create a hidden form as the main-form. That way you could display your ProjectSelection-form before the shell-form.

How can I initialize the WebView2 in WPF?

I'm trying to use WebView2 in a WPF application. I read in the docs and in other posts as well, that I should call the EnsureCoreWebView2Async() or set the Source property in order to initialize the WebView. If I set the Source, it loads the web page correctly, but I can't do this way, because I have the html content in memory (and it's not allowed to write to disk).
So I tried to call the initialization method:
var webView = new WebView2();
webView.NavigationCompleted += Navigation_Completed;
webView.Initialized += new EventHandler((object sender, EventArgs e) => {
webView.NavigateToString(myHtml);
});
await webView.EnsureCoreWebView2Async(null);
Running of this code is blocked by the EnsureCoreWebView2Async() method. I can even wait for one minute, but nothing happens, it's just stuck in initialization. No exception, no error message.
I run this code on UI thread, but the same thing happened when I called this method on another thread.
Does anyone experienced this behavior? Any ideas?
To show the WebView2 manually, you must add it to the Controls collection of the form.
When you drop a WebView2 on the form, the designer does this automatically.
Simply call:
Controls.Add(webView)
await webView.EnsureCoreWebView2Async(null);
Now you should be able to display your html.
Update:
You can still drop the WebView2 on your form, if you prefer, just don't set the Source property.
Then you can use the designer to assign event handlers and in Initialized event you call:
webView.NavigateToString(myHtml);
just as you do now.
If you need to go to a specific URI, I had to do the following calls.
As previously noted, do not set the Source attribute in your XAML.
Note that wv is the Name of my WevView2. Important part is that you still need to wait for the CoreWebView2 to be loaded. Now you can put any URI string in place of "https://www.microsoft.com"
private async void wv_Initialized(object sender, EventArgs e)
{
await wv.EnsureCoreWebView2Async(null);
wv.CoreWebView2.Navigate("https://www.microsoft.com");
}
A proper place to initialize webview2 is on WPF OnContentRendered()
Example:
protected override async void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
var webView2Environment = await CoreWebView2Environment.CreateAsync();
await webView2.EnsureCoreWebView2Async(webView2Environment);
}

Check if ContentPage is currently shown

Is there a way to tell if a ContentPage is currently shown?
I need code inside an event handler in the ContentPage to check whether the page is currently shown and act accordingly.
In addition to GBreen12's answer, you could also do it this way...
bool isShowingMyPage = Application.Current.MainPage is MyPage || (Application.Current.MainPage is NavigationPage navPage && navPage.CurrentPage is MyPage); //If you are using a MasterDetailPage or something else, then you would have to handle this differently
//I think the below will work for checking if any modals are showing over your page but you should definitely test it in different scenarios to make sure. I am not sure if the modal is immediately removed from ModalStack when it gets popped or not
isShowingMyPage = Application.Current.MainPage?.Navigation?.ModalStack?.LastOrDefault() == null;
You can override OnAppearing which is called anytime the page is about to be shown:
Page lifecycle events in xamarin.forms
You can listen to the NavigationPage's Pushed and Popped events, like so:
((Xamarin.Forms.NavigationPage)MyProject.App.Current.MainPage).Pushed += (s, e) =>
{
if (e.Page is ContentPage)
{
// Do what you gotta do
}
// or, for a specific page:
if (e.Page is MyProject.Views.MyCustomPage)
{
// Do what you gotta do
}
};
Of course, this will only be called when the page is pushed onto the navigation stack; If you need it to be called each time the page appears on the screen, then go with what GBreen12 or hvaughan3 said.

Override BackKeyPress in a classlibrary, is that possible on WP8?

I pass a PhoneApplicationPage instance to a classlibrary, and popup an usercontrol in this classlibrary, when I press back button, the whole application exit. Yesterday I sovled the problem in an application, but I cannot use the method in this classlibrary case.
I tried to subscribe to the event(BackKeyPress), but VS2012 says "parent_BackKeyPress" "System.EventHandler" override and delegate cannot match. I checked, they match.
PhoneApplicationPage mContext=...;
mContext.BackKeyPress += new EventHandler(parent_BackKeyPress);
void parent_BackKeyPress(CancelEventArgs e)
{
ppChangePIN.IsOpen = false;
Application.Current.RootVisual.Visibility = Visibility.Visible;
}
anything incorrect here? plus, can I use navigationservice in classlibrary? I did this before to navigate to a page created in the classlibrary like below, well it ends up crashing. Some say can't use pages in classlibrary, instead we should use Popup(usercontrol).
mContext.NavigationService.Navigate(new Uri("/ChangePINPage.xaml", UriKind.Relative));
I have successfully done just that:
// or some other method of accessing the current page
// - but via Application, to which you have access also in class library
var currentPage = (PhoneApplicationPage)((PhoneApplicationFrame)Application.Current.RootVisual).Content;
currentPage.BackKeyPress += (sender, args) =>
{
// Display dialog or something, and when you decide not to perform back navigation:
args.Cancel = true;
};
Of course you have to make sure that this code is executed if and only if the CurrentPage is the main page.
I also use Pages in class library. You can use NavigationService in class library: you can get it for example from current page obtained as above (currentPage.NavigationService). Or you could use the Navigate method of PhoneApplicationFrame:
((PhoneApplicationFrame)Application.Current.RootVisual)
.Navigate(
new Uri(
"/ClassLibraryName;component/SamplePage.xaml",
UriKind.Relative));
As the short Uris like "/SamplePage.xaml" will work in Application Project, to navigate to page in class library you have to give full location: "/ClassLibraryName;component/SamplePage.xaml".
But note, that if the application chooses to display message box to stop from exiting, it will not pass certification, as (from Technical certification requirements for Windows Phone):
5.2.4.2 – Back button: first screen
Pressing the Back button from the first screen of an app must close the app.

Categories