Xamarin best habits on switching between pages? - c#

I'm new with Xamarin.Forms, and I'd like to know how to switch to a "previously loaded" Page.
Let say my App run, and it loads (as First Page) my MainPage. There I tweak some button/toggle (changing status), and than I click on a button that will load another Page (using Navigation model):
await Navigation.PushAsync(new MyNewPage());
Now here I want to return to the previous MainPage
await Navigation.PushAsync(new MainPage());
But at this point, all content/button status are vanished (basically it creates a new MainPage).
Since its a LIFO, I aspect to have it on memory/stack. How can I recall the previous loaded MainPage? Or is it dangerous since GC could clean it at any point?
Notice that in this specific case this would works (since I just back one time):
await Navigation.PopAsync();
But I'd like to use a selective approch (calling what I need where I am, learning the whole concept).

One thing to note is that the Navigation object you use to Push and Pop has a NavigationStack property you can use to get previously pushed pages - assuming you didn't pop them.
To the best of my knowledge, there aren't any methods to directly navigate to an existing Page in the stack so you may have to get somewhat creative in your approach.
If MainPage is always at the bottom of your NavigationStack, you can use
await Navigation.PopToRootAsync()
to go back to the Page at the bottom of the stack.
If 'MainPage' can be elsewhere in the stack, you may have to maintain your own list of Pages separate from the NavigationStack.
Something like the following may work if you don't care about keeping the Pages between MainPage and your current page:
Note that you will want to create a separate List of Pages to remove as calling RemovePage() on the NavigationStack in the foreach loop will cause an exception sometimes, something to the effect of "Enumeration can't complete, collection has changed":
List<Page> pagesToRemove = new List<Page>();
foreach( Page page in Navigation.NavigationStack )
{
if( page.GetType() != typeof(MainPage) && page != Navigation.NavigationStack.Last() )
{
pagesToRemove.Add(page);
}
}
// pagesToRemove now contains all Pages between your current page and MainPage
foreach( Page pageToRemove in pagesToRemove )
{
Navigation.RemovePage(pageToRemove);
}
// the NavigationStack only contains MainPage (now the root) and your current page
// Pop to MainPage
await Navigation.PopAsync();
Someday it would be nice to have a NavigateTo(Page) or the like but for now this approach seems to work.

Related

How to access PreviousPage property in NavigatedToEventArgs in MAUI

I am trying to determine which was the previous page for current page in .NET MAUI application.
For example, there are 3 pages Page1, Page2, Page3
When going from Page1 to Page2, in Page2, I would like to access PreviousPage property which gives me "Page1" as value.
When going from Page3 to Page2, in Page2, PreviousPage property gives me "Page3" as value.
^^
However, I can only see "PreviousPage" property member in Debug mode when VS hits breakpoint. I cannot access it in code. Intellisense does not show this too.
How can I access and use this "PreviousPage" in my code? Is there any other way?
See screenshot.
I am using:
Microsoft Visual Studio Community 2022 (64-bit) - Preview
Version 17.5.0 Preview 1.0
Thank you.
https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/navigation?view=net-maui-7.0
Navigation events
The Shell class defines the Navigating event, which is fired when navigation is about to be performed, either due to programmatic navigation or user interaction. The ShellNavigatingEventArgs object that accompanies the Navigating event provides the following properties...
Technically you can implement your own custom logic, to see if something has been loaded, what was source, the navigation paths, etc...
Your requirement can be achieved with minimum amount of code.
However, I do not think that navigation stack is a healthy way to check if something is displayed on your page or not.
You could get all the page in Navigation Stack, simply used:
var stack = Application.Current.MainPage.Navigation.NavigationStack;
int count = Application.Current.MainPage.Navigation.NavigationStack.Count;
The current page is the last one, names (count - 1) index (as it is zero-based index) in the stack, so the previous page is (count - 2) index.
Hope it works for you.
Like #h-a-h mentioned, custom logic was the way.
I implemented sort of workaround to make it work. I used navigation parameters.
My viewmodel constructor has attribute like this:
[QueryProperty("IsBack", "IsBack")]
public class Page2ViewModel
{
//called from code-behind on OnNavigatedTo event using associated Command.Execute
async Task LoadData()
{
if(IsBack == false)
{
await _repos.GetListAsync();
}
}
public bool IsBack {get;set;}
}
When going from Page1 to Page2, I do:
await Shell.Current.GoToAsync($"{nameof(View.Page2)}?IsBack={false}");
When going back to Page2 from Page3, I do:
await Shell.Current.GoToAsync($"..?IsBack={true}");
At least this way I know "when" Page2 is visited so as to prevent loading of data again. Though this does not let me know if I am coming back to Page2 from Page3 or Page'n', it solves my current requirement.
One can always use query parameters to provide the parent page.
e.g., from Page5 to Page2, we can do:
await Shell.Current.GoToAsync($"..?IsBack={true}&ParentPageId={nameof(View.Page5)}");
provided Page2's ViewModel has this QueryParameter attribute.
[QueryProperty("ParentPageId", "ParentPageId")]
Not very elegant but workable.
Thank you all for your help.

PuppeteerSharp - Access a background page, that opens in a new tab, as a regular page

I'm having issues with a program that generates reddit apps with refresh tokens from a discord command.
I've managed to get to a point, where I can generate the application, get all the relevant information, head over to https://not-an-aardvark.github.io/reddit-oauth-helper/ and from there generate the token, which opens a reddit confirmation page in a new window.
I've tried accessing it in various ways and have gone through multiple different methods, until I landed on using Target.PageAsync() to get the page.
For some reason, Puppeteer only sees the page as an iFrame and only gives this link when getting the Url property - https://www.redditmedia.com/gtm/jail?cb=8CqR7FcToPI - which doesn't lead to anywhere but seems to be related to the very first iFrame from what I've gathered in the HTML.
I've ran out of ideas on how to access the page to press quite literally one button and would appreciate any ideas or solutions on how to solve this or how to generate the refresh token without the use of an external website.
Another two hours later and I managed to figure out a solution.
Since PuppeteerSharp was unwilling to recognize the page, I just subscribed to Browser.TargetCreated at the correct moment with a handler that, after immediately unsubscribing, will log the most recent target (in this case, a javascript calling window.open()) and take the sender as the Browser, will then try to get the pages into an array and with a bit of code to ensure that it doesn't break itself, I finally managed a solution, I feel kinda dumb after three days and 12+ hours of work.
For anyone who might run into a similar situation, here's the snippet of code that made it finally work:
// Bla bla bla code to crawl or do whatever on the main page.
// Immediately subscribe to the target created event with the event handler
// that will handle the background page once it has
// been triggered by a button, link, etc.
browser.TargetCreated += TargetCreatedEventHandler;
}
static async void TargetCreatedEventHandler(object sender, TargetChangedArgs e)
{
// Unsubscribe from the event to
// make sure there are no duplicate unnecessary calls that might break the code.
browser.TargetCreated -= TargetCreatedEventHandler;
// Since I know the sender is the Browser object,
// I cast it it to another Browser used inside the event handler.
Browser eventBrowser = (Browser) sender;
// Get all the pages from the event browser
// and assume the first page is background one (for now)
Page[] pages = await eventBrowser.PagesAsync();
Page page = pages[0];
int counter = 0;
// Iterate through the pages, check if they're the page you were just on,
// use an int to help you keep track of of indexes.
// If it isn't the page you were on, assign the Page object the page
// with the current counter index from pages.
// (basically make sure it doesn't break itself with the wrong order).
foreach (var item in pages)
{
if (item.Url != "Main Page URL HERE")
{
page = pages[counter];
break;
}
counter++;
}
// Do whatever you need to do on your background page here
}

UWP Navigate throws AccessViolationException for xaml-less pages

Is it just me, or is it impossible to navigate to a page that does not have a .xaml head?
I am constructing a page entirely in code, and I want to navigate to it. I do not want a xaml page because this is a class library and also it is constructed based on data received. I know all about using .xaml to create the page with templates, binding, etc., but I want to avoid that.
When I call Frame.Navigate(typeof(CodePage)), I get nice AccessViolationException.
My page is simple, and so is the navigation. This is the code with a clean, new project
Navigation (button click):
Frame rootFrame = Window.Current.Content as Frame;
rootFrame.Navigate(typeof(CodePage));
Page:
public class CodePage : Page
{
public CodePage()
{
Content = new TextBlock
{
Text = "It works!",
};
}
I know about this issue: Navigate to a Page of another Class Library but, this is because ALL the pages are in the library, I just have 1 specific page in my library. Also, I have other pages in the "launcher" app.
Navigation only works with pages who have xaml part as well because when the page does InitializeComponent in its constructor it sets up the page for Navigation routes and NavigationCache etc.

ASP.NET UpdatePanel: Make browser "back" button return to *last* version of the page

I have page A and page B. You can do the following things in page A:
Do stuff on page A (e.g., choose an item in a list box), which causes an UpdatePanel in page A to be redrawn with additional information.
Move on to page B. This is done with a Button and Response.Redirect.
Now the problem is as follows:
The user does stuff on page A. Page A is now different from its initial state.
The user moves to page B.
The user hits the back button of the browser.
What happens: Page A in its initial state is shown.
What I would like to happen: Page A in its final state is shown (i.e., with the "correct" item selected in the list box).
I know about ScriptManager.AddHistoryPoint! As far as I can see, it does not solve my problem:
I could call AddHistoryPoint every time something is done on page A. This is bad, because it litters the browser history with lots of entries. (A new entry every time a different list box item is selected.) But that's exactly what I want to avoid by using an UpdatePanel! Of course, if there were a ReplaceLastHistoryPoint method, that would be perfect, but I did not find one...
I tried to call AddHistoryPoint right before Response.Redirect, to save only the last state of page A, but, alas, that doesn't work (no history point is saved). This is not surprising, considering how Response.Redirect works.
Is there some solution I have missed? I'm using .NET 3.5SP1, in case it matters.
This is a bit of an old question, but I'll go ahead and provide the mechanism I use for this. The basic idea is that instead of allowing AddHistoryPoint to manage your name-value pairs, just allow it to manage a key to your NameValueCollection that you keep somewhere else, like in your Session cache. Then as subsequent Ajax requests come in, you never make another call to AddHistoryPoint; instead, you just replace your NameValueCollection with the state of the current request.
The only other bit is to keep track of whether you're on your first Ajax call and need to make that first call to AddHistoryPoint or not.
My code looks something like this:
protected void Page_LoadComplete(object sender, EventArgs e) {
if (!ScriptManager.GetCurrent(this).IsNavigating && (IsCallback || IsInAsyncPostback())) {
var state=new NameValueCollection();
//OnCallbackHistory(state); // this gets state for all interested parties
if (state.Count != 0) {
string key=ViewState["HistoryStateKey"] as string; // empty on first AJAX call
if (string.IsNullOrEmpty(key) || ScriptManager.GetCurrent(this).EnableHistory) {
key=CallbackHistoryKeyRoot+Interlocked.Increment(ref callbackHashKey).ToString();
ViewState["HistoryStateKey"]=key;
ScriptManager.GetCurrent(this).AddHistoryPoint("", key);
}
Session[key]=state;
}
}
}
Instead of calling AddHistoryPoint on the server you could call addHistoryPoint on the client using the Sys.Application class, http://msdn.microsoft.com/en-us/library/vstudio/cc488025(v=vs.90).aspx.
So you'd add a client side click listener to the button which would addHistoryPoint on the client before the button does the post back and redirect.

Reload page in metro app C#

I'm developing metro app using Windows 8 RTM and C#(VS 2012 RTM), I'm stuck with page reload,
Can any one explains me how to reload page with out navigating to same page again.
Brief: I'm developing metro app with multilingual support. When user selects the language I'm overriding primary language by below code
Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = "de";
and reload the page by using this code
this.Frame.Navigate(this.GetType());
Language changed to "de",But when i press "Back" on page its navigating same page instead of navigating to previous page.Did i miss something, Can someone please explains me how to do this. Thanks in advance
This will refresh your page:
var _Frame = Window.Current.Content as Frame;
_Frame.Navigate(_Frame.Content.GetType());
_Frame.GoBack(); // remove from BackStack
Handling OnNavigatingFrom() you can save your page's data and state.
Handling OnNavigatingTo() you can load your page's data and state.
As a caveat, my sample does not account for page parameters, you may need to.
Also, another caveat, my sample reloads your page twice. But the GoBack() is necessary to remove the new entry from the BackStack. Unlike WP, Frame does not have Refresh(). Also, the BackStack does not have Remove().
UPDATE
I no longer use the above approach. I use this:
public bool Reload() { return Reload(null); }
private bool Reload(object param)
{
Type type = this.Frame.CurrentSourcePageType;
if (this.Frame.BackStack.Any())
{
type = this.Frame.BackStack.Last().SourcePageType;
param = this.Frame.BackStack.Last().Parameter;
}
try { return this.Frame.Navigate(type, param); }
finally { this.Frame.BackStack.Remove(this.Frame.BackStack.Last()); }
}
I’m not sure I fully understand what you are trying to do, so this may be wrong.
By calling that line of code when you are refreshing the page, you are creating a brand new object of the current type and navigating to it, so this does not save changes the user makes while they are on the current page.
Are you using any type of design pattern? For things like this I use MVVM (using the MVVM light library) which implements a really cool navigation service which will keep stuff like this in check.

Categories

Resources