Xamarin Forms Exrin Framework pushing a container as a Modal? - c#

I've been playing with the Exrin Xamarin Forms framework which I'm liking.
I've come to a blocker where I want to push a container as a Modal. Anyone know any tips on how to do this? I can't find any examples, or a best approach on how to push a container as Modal.
Looking at the Exrin code, might have to change BaseStack.cs, create a NavigationModal ResultType, lifestyle so it doesn't affect already in memory containers, etc...?
Any other ideas?

The idea behind Exrin and its Stacks, is that a Modal wouldn't be needed. The only reason for Modals, is for a page that you can't just press back from and sits on top of the existing page.
The recommended approach here, is to create a new Stack and just navigate to that. Then go back to the previous stack once finished. It behaves the same way as a Modal, but you don't need to actually use a Modal.
Update
If you must have a Modal, while other ways to support transitions come into play, you can modify your NavigationProxy. Replace the 2 functions below.
public async Task PopAsync()
{
if (_page.Navigation.ModalStack.Count > 0)
{
var page = _page.Navigation.ModalStack[0];
_page_Popped(null, new NavigationEventArgs(page));
await _page.Navigation.PopModalAsync();
}
else
await _page.PopAsync();
}
public async Task PushAsync(object page)
{
var xamarinPage = page as Page;
if (xamarinPage == null)
throw new Exception("PushAsync can not push a non Xamarin Page");
if (page is ExrinSample.View.AboutView)
await _page.Navigation.PushModalAsync(xamarinPage);
else
await _page.PushAsync(xamarinPage);
}
Of course this is manually referencing a specific page in your proxy. If you wanted to clean that up, you could add some methods in your Stack to list pages that are modal, and use that list to push to modal if needed, rather than directly putting the page names inside the proxy.

Related

How to execute script using webview2 to expand an html element using c#

I'm not quite sure how to ask this question. So I'm attaching two images
How can I expand the Product Details tab so that the html rendered in the webview2 control looks like the second picture using code behind?
You can inject script into a page using CoreWebView2.ExecuteScriptAsync that will find that element and perform a click on it. However, this will be a fragile solution since if the layout of the page or names of elements on the page change your script may no longer work.
I would recommend opening the desired page in the Edge browser, opening DevTools, Console, and trying out different script to click on the correct element. For example document.getElementById("product-section-overview").children[0].children[0].click() might work but depends on the ID of that element not changing, and the structure and order of its children.
Once you have the script you want you can inject it into the document via
await myWebView2.CoreWebView2.ExecuteScriptAsync("document.getElementById("product-section-overview").children[0].children[0].click()");
If you are interested in a library for use with WebView2 that provides a DOM API and automation functionality then you can try WebView2.DevTools.Dom. It's free for anyone to use.
// Add using WebView2.DevTools.Dom; to access the CreateDevToolsContextAsync extension method
// Only a single WebView2DevToolsContext should exist at any given time, when you are finished them make sure you dispose via DisposeAsync. The WebView2DevToolsContext can be reused if required
await using var devToolsContext = await webView.CoreWebView2.CreateDevToolsContextAsync();
// Get element by Id
// https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
var element = await devToolsContext.QuerySelectorAsync<HtmlElement>("#myElementId");
// Click The element
// This will simulate a mouse move then click
await element.ClickAsync();
More details and examples in the Readme

How to call the previous page function in MAUI?

My problem:
My parent page (AdminPanel.xaml) contains a function: updateAllMaterialList()
It's the page previous one on the navigation stack.
Now on the AddUser.xaml page, I try to navigate back by calling updateAllMaterialList() like this:
private async void GoToBack(object sender, EventArgs e)
{
await Navigation.PopAsync();
NavigationPage navPage = (NavigationPage)App.Current.MainPage;
Page page = navPage.Navigation.NavigationStack[navPage.Navigation.NavigationStack.Count - 1];
((AdminPanel)page).updateAllMaterialList();
}
But it doesn't work
First I want to point out, that there is good reason not keep your business logic in your pages.
I recommend that you check CommunityToolkit.MVVM/MAUI.
And then add some service, to do this work for you.
Or use EvenToCommandBehavior to bind your page events.(Appearing, Loading, etc..)
Or use Messenger to send/receive data between decoupled segments.
The way you are attempting to handle your logic and navigation will bring a lot of problems. One page navigation should have nothing to do with another page business logic.
As Jason said, you can Generate OnAppearing method override in AdminPanel.xaml.cs, and add updateAllMaterialList() function to OnAppearing. So, whenever the AddUser.xaml page navigates to the AdminPanel.xaml page, it will call updateAllMaterialList() automatically. OnAppearing means that when the specified page appears, it will be called.
Yes, as they said, you can use the OnAppearing and everything works great. But I also found this way:
var navPage = App.Current.MainPage;
if (navPage.Navigation != null)
{
navPage = navPage.Navigation.NavigationStack[navPage.Navigation.NavigationStack.Count - 2];
(navPage as AdminPanel).UpdateAllMaterialList();
}

How to handle large number of "dialogs" without navigating away from the root component?

Having an existing WinForms application that I'm migrating to .NET 6 with C# Blazor WebAssembly.
In this WinForms application there is one main window and let's say approximately 50 individual modal dialog box windows (which in turn sometimes show additional modal dialog box windows).
Steps in WinForms
In WinForms to show a dialog it was pretty straightforward:
User clicks on a main menu item "Edit this thing".
A C# click handler is called.
In this click handler, I create an instance of my dialog box class.
The instance is being shown.
When the user closes the dialog box, the instance of the class goes out of scope and is later automatically cleaned up by the GC.
Steps in Blazor
Currently I cannot wrap my head around on how to adapt this concept to a WASM Blazor world.
In my planning I have one single "root" component ("Page") that does not change throughout the whole time that the user interacts with the application. This equals the "main window" concept in WinForms.
For each formerly WinForms dialog box window I plan to create one Blazor component (each with a DxPopup component that shows the form controls as a dialog box).
But I'm not sure on where and how to place these components.
Questions
Should I really place 50+ components right in the root component and show/hide them as needed?
Should I have one single DynamicComponent component that I tell by code which actually component to render?
Any other options?
My fear is to get horrible performance when polluting the component tree with so much components in advance when all I really actually need is one component at a time.
As I mentioned in the comments, Blazored.Modal is a very powerful library for showing dialogs just-in-time by injecting an IModalService in your components, created by Chris Sainty. Example usage:
#inject IModalService Modal
<button #onclick="ShowFormBtnClicked">Show form</button>
#code {
void ShowFormBtnClicked()
{
// FormDialog is a different component (FormDialog.razor).
Modal.Show<FormDialog>("Form dialog title");
}
}
You can also pass parameters to the dialogs and receive results from them (e.g. confirm dialog).
Documentation
This way each dialog is a separate component that is loaded only where and when it's needed. With this library you can avoid having to put all modals inside your components like that:
<Modal #bind-Visible="_modal1Visible">...</Modal>
<Modal #bind-Visible="_modal2Visible">...</Modal>
<Modal #bind-Visible="_modal3Visible">...</Modal>
...
...
#code {
private bool _modal1Visible;
private bool _modal2Visible;
private bool _modal3Visible;
...
}
I believe good practice is to render only when it's needed.
When it comes to component based frameworks, you need to leverage off your components as much as possible. Dynamic components are amazing for this, and can really improve development speed.
I recommend not using dialogs/modals, bar special occasions. I see it being used a lot, and quite frankly, I believe it to be bad for user experience and performance. Only render what is needed at that time, especially in WASM since the client is doing all the UI computing.
In your instance I recommend starting with the basics as you work on this application, if done correctly, this foundation will improve your development speed 10 fold.
Need to put in a textbox? Create a component, and doing so you will learn the ins and outs of Blazor a lot quicker. You will also realise the dos and don'ts.
Ok now you have a textbox, next up is a form item to contain the textbox. Create a component for the form item.
Now you can wrap your textbox in a form item. So later, if you need to change anything, be it logic or CSS, you only need to update a single component.
Grow your component library from there, and with that your understanding of Blazor.
Now to the subject at hand:
It is good to hide what isn't used. Wrap code blocks in if statements, and render them when needed. There's also no reason not to navigate to a new page. If you have a lot going on in a single page component, I recommend splitting what you have into smaller components. It's easier to work through, and a lot easier to maintain.
At the end of the day you can have:
#if (_showForm1) {
<MyFormComponent Heading="Form 1">
<MyFormItem Label="Value 1">
<MyInputText #bind-Value="#form1.Value1" />
</MyFormItem>
</MyFormComponent>
}
Important note: Not every blazor component library is good. I ran across MatBlazor and I was astonished at the bad implementations within this library. It is an overcomplicated mess. It is open source on GitHub, so you can have a look.
Keep whatever you do as simple as possible. Don't make a component a jack of all trades. Your components must be very specific, and focused around performance. A generic component is not a jack of all trades component if it has a specific focus. Example, an input component for numeric values can be generic to support int, long, decimal etc. But an input component that can work with string, DateTime and int, is a jack of all trades. This should be obvious...but I've seen some things.
This is a follow-up on my journey. I took a longer look at the recommended Blazor.Modal but initially didn't like their own dialog HTML markup.
So I started asking a similar question in the DevExpress support center on how to do the same as Blazored.Modal with DxPopup. And I did get some rather decent replies, basically the support guy providing me with a full solution on how to do something similar to what Blazored.Modal does.
Later, I figured out that Blazored.Modal lets you completely replace their UI with a custom one (example file 1 and file 2).
So I am now going this way: Use Blazored.Modal and provide my own UI through DxPopup.
Example
File "UweKeimPopupTest.razor":
<DxPopup Visible="true"
ShowHeader="true"
ShowFooter="true"
HeaderText="Edit element">
<BodyTemplate>
<div class="p-3" style="height: 200px">
<p>#Message</p>
</div>
</BodyTemplate>
<FooterContentTemplate>
<DxButton RenderStyle="ButtonRenderStyle.Primary" Text="Store" Click="#Close" />
<DxButton RenderStyle="ButtonRenderStyle.Secondary" Text="Cancel" Click="#Cancel" />
</FooterContentTemplate>
</DxPopup>
#code
{
[CascadingParameter] BlazoredModalInstance BlazoredModal { get; set; } = default!;
[Parameter] public string? Message { get; set; }
async Task Close() => await BlazoredModal.CloseAsync(ModalResult.Ok(true));
async Task Cancel() => await BlazoredModal.CancelAsync();
}
File "index.razor":
<DxButton Text="Blazored.Modal with DxPopup" Click="#ShowModalCustomLayout" />
#code
{
[CascadingParameter] public IModalService Modal2 { get; set; } = default!;
void ShowModalCustomLayout()
{
var options = new ModalOptions { UseCustomLayout = true };
var parameters = new ModalParameters {
{ nameof(UweKeimPopupTest.Message), "Hello custom modal." } };
Modal2.Show<UweKeimPopupTest>("Custom Layout", parameters, options);
}
}
For me, this combines the advantages of having well tested libraries Blazored.Modal and DevExpress Blazor as well as the core functionality to use ad hoc popup window creating and still being able to use the DevExpress popup implementation DxPopup.
See also this related question of mine in the Blazored.Modal issue tracker.

MVVMCross-Android dynamic fragment inside fragment and manage navigation stack per fragment

I am currently working on MVVMCross Xamarin Android project. So far I have achieved normal navigation and bottom bar navigation. I want to open dynamic fragments inside each bottom bar tabs. e.g I have bottom bars menus Recent, Favorite and NearBy. Clicking on each tab it create fragments. Inside each fragment I want to provide facility to dynamically create fragments on click.
I want to achieve here is, it should keep stack of navigation tab wise. Let say I created 5,3,4 fragments respectively for Recent, Favorite and Nearby and currently I am on favorite tab and clicking on back should first navigate back to all 3 tabs. Like wise it should follow navigation for other tabs.
Exact same feature and functionality available in this github link
[https://github.com/ncapdevi/FragNav][1]. This is one is for Android but I need advice how can I achieve same functionality with Xamarin, MVVMCross and C#.
Any help greatly appreciated.
First your link is dead,and then i think you need to understand the back stack of fragments and show hidden features to implement that.FragmentTransaction
general train of thought, you have RecentFragment, FavoriteFragment and NearByFragment three root fragments,
when you click the relative tab ,you can use show and hide method of fragment like
this :
FragmentTransaction fTransaction = FragmentManager.BeginTransaction();
hideAllFragment(fTransaction);
//judge which tab is clicked
switch (tab.Id)
{
case Recent:
if (recentFragment== null)
{
recentFragment= new RecentFragment ();
fTransaction.Add(Resource.Id.ly_content, recentFragment);
}
else{fTransaction.Show(recentFragment);}break;
case Favorite:
if (favoriteFragment== null)
{
favoriteFragment= new FavoriteFragment();
fTransaction.Add(Resource.Id.ly_content, favoriteFragment);
}
else{fTransaction.Show(favoriteFragment);}
break;
case NearBy:
if (nearByFragment== null)
{
nearByFragment= new NearByFragment();
fTransaction.Add(Resource.Id.ly_content, nearByFragment);
}else{fTransaction.Show(nearByFragment);}break;
}
fTransaction.Commit();
and then in each root fragment to implement the back stack use addToBackStack :
FragmentManager fragmentManager = FragmentManager;
FragmentTransaction fragmentTransaction = fragmentManager.BeginTransaction();
fragmentTransaction.Replace(containerViewId,fragment);
fragmentTransaction.AddToBackStack(null);
fragmentTransaction.Commit();
at last you could try to encapsulating a controller

Xamarin Forms Navigation and dealing with a Login Page

I'm trying to create an App with a Login Page as the first page.
Once the user logs in, the pages that come after will be in a standard page stack organisation so I can easily use the build in Navigation object and wrap everything in Navigation pages.
e.g.
Login Page -> MainAppPage |-> Category1Page -> Cat1SubPage
|-> Category2Page -> Cat2SubPage
My understanding is that I should wrap MainAppPage with new NavigationPage(), and then I'll have access to the Navigation object allowing me to do things like this:
await this.Navigation.PushAsync(new Category1Page());
And the various platforms will give me automatic back button support to go back to the previous page.
But I don't want a user to navigate from LoginPage -> MainAppPage in this manner, because I don't want the backbutton to take them back to Login, without them explicitly hitting the logout button.
So how should I handle that first Page Transition from LoginPage -> MainApp Page.
Is there an alternative way to have 2 Primary pages and swap between them? Or is there a way to intercept the back button requests on MainAppPage and discard them?
Not finding an awful lot of info in the documentation regarding this, but it seems like a fairly standard requirement so possibly PEBKAC
I just posted a quick sample on Github for this scenario. The idea is that you want to initially navigate to your NavigationPage, then if necessary (meaning the user isn't already logged in), push the LoginPage modally. Then, on successful Login, simply pop the LoginPage from the stack. You can check out the sample here, https://github.com/jamesqquick/Xamarin-Forms-Login-Navigation/blob/master/ReadMe.MD
I can think of at least two solutions.
One way is to create a MainAppPage first and within that page show Login Page as Modal.
Other would be to create a platform specific page, load Login Page and only upon successful login navigate to MainPage using platform specific navigation (LoginPage should be NoHistory or something like that to avoid going back) not Forms navigation (i.e. in Android both pages should be separate activities). This involves a bit more work since you have to deal with the three platforms but it shouldn't be much overhead.
That said there is a better navigation supposedly coming with, hopefully, 1.3.0.
As Miha Markic said, a Modal window is a good option. One other thing you can also consider, especially if you want the login page to have the same Navigation Bar as your other pages, would be the same thing that I already posted about in the question URL below.
Basically, you would keep a reference to your NavigationPage in the App class (lets call it AppNavPage), then, when showing your login page, you put your login page within a separate NavigationPage and do a PushAsync() with your new NavigationPage and login page.
Once the user logs in successfully, you just replace the current MainPage with your old NavigationPage using this code:
Application.Current.MainPage = App.AppNavPage
Check out the link below for better code examples.
https://stackoverflow.com/a/32382852/3850012
This is what I have working on Android:
protected override void OnCreate (Bundle bundle){
base.OnCreate (bundle);
string start = "new";
Bundle extras = Intent.Extras;
if (extras != null) {
start = extras.GetString ("start");
}
if(start == "new"){
SetPage (App.GetLoginPage (OnLoginCompleted));
} else if (start == "login") {
SetPage (App.GetMainPage (OnSignOutCompleted));
}
}
void OnLoginCompleted(){
// ...
var refresh = new Intent (this, typeof(MainActivity));
refresh.PutExtra ("start", "login");
StartActivity (refresh);
Finish ();
}
void OnSignOutCompleted(){/* mirrors OnLoginCompleted */ }
This is effectively an activity with a configurable landing page. In order to change to it we restart with a different setting. It's a tiny bit slower than navigating on my phone but only just noticeable.
I think the best way would be to Remove the LoginPage from the stack once you verify login, then it's not available any longer.
async void OnLoginButtonClicked (object sender, EventArgs e)
{
...
var isValid = AreCredentialsCorrect (user);
if (isValid) {
App.IsUserLoggedIn = true;
Navigation.InsertPageBefore (new MainPage (), this);
await Navigation.PopAsync ();
} else {
// Login failed
}
}
Provided that the user's credentials are correct, the MainPage
instance is inserted into the navigation stack before the current
page. The PopAsync method then removes the current page from the
navigation stack, with the MainPage instance becoming the active page.
See full description here

Categories