Is back navigation on webview in a frame possible? - webview

In my Windows 10 UWP app I use the MainPage for a navigation hamburger menu. All other pages are loaded into a Frame on MainPage called MyFrame. With the following code, back navigation within this frame works perfectly:
private void MainPage_BackRequested(object sender, BackRequestedEventArgs e)
{
if (MyFrame.CanGoBack)
{
MyFrame.GoBack();
e.Handled = true;
}
}
However one of those pages contains a WebView. When this page is loaded, i want the webview instead of the frame to perform the GoBack Event.
Is it somehow possible to access the WebView's CanGoBack property from MainPage?
edit: maybe something like this works?
private void MainPage_BackRequested(object sender, BackRequestedEventArgs e)
{
if (MyFrame.Content == typeof(Web))
{
get state of the Webview "SearchURI" here and handle the GoBack event
}
else if (MyFrame.CanGoBack)
{
MyFrame.GoBack();
e.Handled = true;
}
}

WebView already has CanGoBack Property and GoBack Method for you to use. Using these methods, you can use BackButton and control your backkeyprocess.
Below is how I would do it.
In your Web.xaml.cs Declare a public variable
public WebView myWebView { get; private set; }
and in your page constructor, assign the actual WebView reference to this variable.
public Web()
{
this.InitializeComponent();
myWebView = this.webView;
}
Now in you MainPage.xaml.cs, The button where you use to navigate, Subscribe to BackRequested
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
And now your OnnackRequested will show something like below.
private void OnBackRequested(object sender, BackRequestedEventArgs e)
{
if (MyFrame.Content.GetType() == typeof(Web))
{
Web web = MyFrame.Content as Web;
if (web.myWebView.CanGoBack)
{
web.myWebView.GoBack();
}
else
{
if (MyFrame.CanGoBack)
{
e.Handled = true;
MyFrame.GoBack();
}
}
}
}
Unless the webview navigates back to your initial page, you will not be able to go back to the previous page that is loaded in your MyFrame
Good Luck.

Related

Weird behavior of back button for fragments within fragments

Hi I have a navigation drawer and am showing fragments based on selection from the navigation drawer. I have a tab layout along with view pager in majority of my fragments. And coming to the weird behavior, in one of the tabs I'm having to again switch fragments based on user behavior which is working as expected but when I press the back button the whole fragment is getting popped instead of the child fragments being popped first.
This is how I'm populating the tabbed fragment from mainActivity
fragment = new EquipmentFragment();
SupportFragmentManager.BeginTransaction()
.Replace(Resource.Id.content_frame, fragment)
.AddToBackStack(null).Commit();
This is how I've setup the tabLayout and viewPager
TabLayout tabLayout = view.FindViewById<TabLayout>(Resource.Id.tab_layout);
_viewpager = view.FindViewById<ViewPager>(Resource.Id.viewpager);
SetupViewPager(_viewpager);
tabLayout.SetupWithViewPager(_viewpager);
This is how I'm switching fragments within one of my tabs
FragmentTransaction transaction = FragmentManager.BeginTransaction();
transaction.AddToBackStack(null);
transaction.Add(Resource.Id.fragmentContainer, fragment);
transaction.Commit();
but pressing on back button results in the whole tabbedFragment being popped instead of the child items being popped first
You can override the "OnBackPressed" and the "OnOptionsItemSelected" and check, and here you need to handle the back navigation - determine whether to pop from ChildFragmentManager, FragmentManager, or finish the activity.
It could look something like this:
public override void OnBackPressed()
{
var didPopFromFragment = TryPopFromFragment();
if (didPopFromFragment == false)
{
Finish();
}
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.ItemId)
{
case Android.Resource.Id.Home:
var didPopFromFragment = TryPopFromFragment();
if (didPopFromFragment)
{
return true;
}
break;
}
return false;
}
private bool TryPopFromFragment()
{
var hasChildFragments = yourFragment.ChildFragmentManager.BackStackEntryCount > 0;
if (hasChildFragments)
{
yourFragment.ChildFragmentManager.PopBackStack();
}
return hasChildFragments;
}
... hope it helps.

Xamarin.IOS go back from CNContactViewController.FromContact to Application

I have an application written with Xamarin for IOS and Android where I have a list of contacts and when I click on one of them I want to open the according contact in the addressbook of the phone. I have an interface for that implement the method on IOS that way:
private void ShowContactDialog(string addressbookId, bool isEdit = false)
{
var contact = new AddressBookService().GetCnContactById(addressbookId);
var view = CNContactViewController.FromContact(contact);
view.Editing = isEdit;
DisplayView(view);
}
private static void DisplayView(CNContactViewController view)
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
if (vc is UINavigationController navController)
{
vc = navController.ViewControllers.Last();
}
vc.PresentViewController(new UINavigationController(view), true, null);
}
That works so far as it opens the contact:
The issue now is, that there is just an edit button, but now Done, cancel or back button. Therefore I have to kill the whole application and start it again to come back.
Is there a way to add a extra button similar to the edit button to dismiss the dialog again?
EDIT: I adjusted the code as suggested in the comments by Kevin Li.
private void ShowContactDialog(string addressbookId, bool isEdit = false)
{
var contact = new AddressBookService().GetCnContactById(addressbookId);
var view = CNContactViewController.FromContact(contact);
view.Editing = isEdit;
GetCurrentViewController().NavigationController.PushViewController(view, true);
}
private static UIViewController GetCurrentViewController()
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
if (vc is UINavigationController navController)
{
vc = navController.ViewControllers.Last();
}
return vc;
}
Now it is shown within the Tabs:
Unfortunately is the back button not displayed as well.
I tried to make a new UINavigationController, but that didn't change anything.
EDIT2:
I adjusted the way the tabs are created another time. My MainViewController looks like that:
[MvxRootPresentation(WrapInNavigationController = true)]
public class MainViewController : MvxTabBarViewController<MainViewModel>
{
private bool constructed;
private bool firstTimePresented = true;
public MainViewController()
{
constructed = true;
// need this additional call to ViewDidLoad because UIkit creates the view before the C# hierarchy has been constructed
ViewDidLoad();
}
public override async void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (firstTimePresented)
{
// Initialize the tabs
firstTimePresented = false;
await ViewModel.GoToContactListCommand.ExecuteAsync();
await ViewModel.GoToBirthdayListCommand.ExecuteAsync();
await ViewModel.GoToProfileListCommand.ExecuteAsync();
await ViewModel.GoToSettingsCommand.ExecuteAsync();
}
}
public override void ViewDidLoad()
{
if (!constructed)
return;
base.ViewDidLoad();
Title = "Connect Update";
EdgesForExtendedLayout = UIRectEdge.None;
View.BackgroundColor = UIColor.White;
}
}
On each View who shall be a tab has to be this Attribute:
[MvxTabPresentation(TabIconName = "IconCake", TabName = "Birthdays")]
public partial class BirthdayView : MvxViewController<BirthdayViewModel>
The code for opening the contact is the same as above. Now it makes a new View without the tabs but with the back button, exactly as wished :)
The issue now is, that there is just an edit button, but now Done, cancel or back button.
It is because the value of isEdit at view.Editing = isEdit; is false. So, when you present the CNContactViewController, there's only an Edit button. When you click the Edit button, it will change to the editing mode and the Cancel and Done buttons will appear.
Or you can set the value of isEdit to true, then the Cancel and Done buttons will show when the CNContactViewController presents.
Therefore I have to kill the whole application and start it again to come back.
Is there a way to add a extra button similar to the edit button to dismiss the dialog again?
You don't have to kill and restart the app to dismiss the dialog. You can just implement the DidComplete in the delegate: CNContactViewControllerDelegate to dismiss the dialog when user finishes editing the contact and click the Done button or cancel it with clicking the Cancel button.
Here's the related code snippet:
var view = CNContactViewController.FromContact(contact);
view.Delegate = new MyCNConatactViewControllerDelegate();
public class MyCNConatactViewControllerDelegate : CNContactViewControllerDelegate
{
public override void DidComplete(CNContactViewController viewController, Contacts.CNContact contact)
{
viewController.NavigationController.DismissViewController(true, null);
}
}
Update:
To show the Back button in the CNContactViewController:
Make sure the UI hierarchy like this: Navigation->Viewcontroller(Page)->CNContactViewController. Then change the method from Present to Push to show the CNContactViewController, like this:
this.NavigationController.PushViewController(view, true);

How to open custom dialog box / popup using Xamarin.Forms?

I am newbie to Xamarin.Forms and stuck with a situation where I want to open up a popup box with my control details [e.g. View Employee Details] on click of parent page.
How can I open custom dialog box / popup using Xamarin.Forms?
Any example code will be appreciated?
Thanks in advance!
If you still want to have your popup's code in its own Page you can set up some custom renderers along the following logic.
1. A ModalPage & corresponding renderer
public class ModalPage : ContentPage { }
public class ModalPageRenderer : PageRenderer {
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
this.View.BackgroundColor = UIColor.Clear;
this.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
SetElementSize (new Size (View.Bounds.Width, View.Bounds.Height));
}
}
2. HostPage
public class ModalHostPage : ContentPage, IModalHost
{
#region IModalHost implementation
public Task DisplayPageModal(Page page)
{
var displayEvent = DisplayPageModalRequested;
Task completion = null;
if (displayEvent != null)
{
var eventArgs = new DisplayPageModalRequestedEventArgs(page);
displayEvent(this, eventArgs);
completion = eventArgs.DisplayingPageTask;
}
// If there is no task, just create a new completed one
return completion ?? Task.FromResult<object>(null);
}
#endregion
public event EventHandler<DisplayPageModalRequestedEventArgs> DisplayPageModalRequested;
public sealed class DisplayPageModalRequestedEventArgs : EventArgs
{
public Task DisplayingPageTask { get; set;}
public Page PageToDisplay { get; }
public DisplayPageModalRequestedEventArgs(Page modalPage)
{
PageToDisplay = modalPage;
}
}
}
3. HostPage renderer
public class ModalHostPageRenderer: PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if(e.OldElement as ModalHostPage != null)
{
var hostPage = (ModalHostPage)e.OldElement;
hostPage.DisplayPageModalRequested -= OnDisplayPageModalRequested;
}
if (e.NewElement as ModalHostPage != null)
{
var hostPage = (ModalHostPage)e.NewElement;
hostPage.DisplayPageModalRequested += OnDisplayPageModalRequested;
}
}
void OnDisplayPageModalRequested(object sender, ModalHostPage.DisplayPageModalRequestedEventArgs e)
{
e.PageToDisplay.Parent = this.Element;
var renderer = RendererFactory.GetRenderer (e.PageToDisplay);
e.DisplayingPageTask = this.PresentViewControllerAsync(renderer.ViewController, true);
}
}
Then it is as simple as calling
await ModalHost.DisplayPageModal(new PopUpPage());
from your host page or in this particular case from the ViewModel behind.
What Pete said about PushModalAsync / PopModalAsync still remains valid for this solution too (which in my opinion is not a disadvantage), but your popup would appear with transparent background.
The main advantage of this approach, in my opinion, is that you can have your popup XAML/code definition separate from the host page and reuse it on any other page where you wish to show that popup.
The general purpose of what you are trying to achieve can be accomplished by using the PushModalAsync and PopModalAsync methods of Xamarin.Forms Navigation object.
The chances are that this is good enough for what you are needing - However - this isn't truely modal. I will explain after a small code snippet:-
StackLayout objStackLayout = new StackLayout()
{
};
//
Button cmdButton_LaunchModalPage = new Button();
cmdButton_LaunchModalPage.Text = "Launch Modal Window";
objStackLayout.Children.Add(cmdButton_LaunchModalPage);
//
cmdButton_LaunchModalPage.Clicked += (async (o2, e2) =>
{
ContentPage objModalPage = new ContentPage();
objModalPage.Content = await CreatePageContent_Page2();
//
await Navigation.PushModalAsync(objModalPage);
//
// Code will get executed immediately here before the page is dismissed above.
});
//
return objStackLayout;
private async Task<StackLayout> CreatePageContent_Page2()
{
StackLayout objStackLayout = new StackLayout()
{
};
//
Button cmdButton_CloseModalPage = new Button();
cmdButton_CloseModalPage.Text = "Close";
objStackLayout.Children.Add(cmdButton_CloseModalPage);
//
cmdButton_CloseModalPage.Clicked += ((o2, e2) =>
{
this.Navigation.PopModalAsync();
});
//
return objStackLayout;
}
The problem with the above is that the
await Navigation.PushModalAsync(objModalPage);
will immediately return after the animation.
Although you can't interact with the previous page, as we are displaying a new NavigationPage with a Close button shown - the parent Navigation Page is still executing behind the scenes in parallel.
So if you had any timers or anything executing these still would get called unless you stopped those.
You could also use the TaskCompletionSource approach as outlined in the following post also How can I await modal form dismissal using Xamarin.Forms?.
Note - that although you can now await the 2nd page displaying and then when that page is dismissed allowing code execution to continue on the next line - this is still not truely a modal form. Again timers or anything executing still will get called on the parent page.
Update 1:-
To have the content appear over the top of existing content then simply include it on the current page, however make this section of content invisible until you need it.
If you use an outer container such like a Grid that supports multiple child controls in the same cell, then you will be able to achieve what you want.
You will also want to use something like a filled Box with transparency that will cover the entire page also, to control the visible, see through section, that surrounds your inner content section.
I followed above approach and found it impossible to run on iOS 7.
I found this library BTProgressHUD which you can modify and use.
I Use its methods by Dependency service.
Actual library for popups.
https://github.com/nicwise/BTProgressHUD
Following example uses BTProgressHUD library internally.
https://github.com/xximjasonxx/ScorePredictForms

Wait for PresentModalViewController to finish (monotouch)

I would like to wait for a controller being displayed with PresentModalViewController() to finish its job before resuming execution (like modal dialogs in WinForms). How can this be done with monotouch?
I know there is a similar question on SO but the answer is for Objective-C and, frankly, I don't get it.
Many thanks.
EDIT
Here's the first setup I tried and which didn't seem to work:
Create a new Project (Single View Application);
Add two controllers (iPhone View Controller): FirstController and SecondController; the first controller overrides DismissModalViewControllerAnimated and fires a OnFirstFinished event just after being dismissed;
In the main controller:
...
public partial class TestModalViewController : UIViewController
{
private UIButton button;
private FirstController first;
private SecondController second;
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
button = UIButton.FromType(UIButtonType.RoundedRect);
button.Frame = new RectangleF(0, 0, 100, 50);
button.SetTitle("Test", UIControlState.Normal);
button.TouchUpInside += PresentFirstController;
View.Add(button);
PresentFirstController(null, null);
}
void PresentFirstController (object sender, EventArgs e)
{
bool firstFinished = false;
first = new FirstController();
first.OnFirstFinished += delegate(object s, EventArgs args) {
firstFinished = true;
};
this.PresentModalViewController(first, true);
do
{
NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow (0.5));
} while (!firstFinished);
second = new SecondController();
this.PresentModalViewController(second, true);
}
// ...
}
In FirstController:
...
public override void DismissModalViewControllerAnimated (bool animated)
{
base.DismissModalViewControllerAnimated (animated);
if(null != OnFirstFinished)
{
OnFirstFinished(this, null);
}
}
In this setup the execution blocks (black loading screen) and the first controller isn't loaded.
If the call to PresentFirstController() is removed from ViewDidLoad(), the main controller loads fine and when clicking the "Test" button the first controller is loaded. However, after the first controller is dismissed, the second controller is NOT loaded - iOS doesn't seem to like presenting a modal controller right after dismissing another one. This can be solved by adding a small delay (but how small is still safe?) like below:
public override void DismissModalViewControllerAnimated (bool animated)
{
base.DismissModalViewControllerAnimated (animated);
NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow(0.2));
if(null != OnFirstFinished)
{
OnFirstFinished(this, null);
}
}
In general, the code pattern that you want to use on iOS is to chain these actions. For example, your view controller would likely have a login/password field, and a button to do the login.
What you would do is connect an action to the Login button that contacts the server, validates the user and if the credentials are OK, dismiss the dialog view controller and at that point resume execution.
That said, you could present the view controller and run the UI main loop manually and wait for some event to trigger before resuming execution.
do {
NSRunLoop.Current.RunUntil (NSDate.FromTimeIntervalSinceNow (0.5));
} while (!done);
You can save yourself some pain and structure your code with the UIKit patterns instead of trying to fight them. You will end up saving time.

BlackBerry java detecting screen foreground event

In my BlackBerry application, I have a home screen. The user can then navigate to a settings screen. When the user goes back to the home screen, is there no method that is called on the home screen indicating that the screen has come to the foreground?
I have tried onFocus() with no avail.
Thanks!
Unfortunately, hooking on the onExposed is not enough. I found that in Blackberry dialogs are also screens and even context menus are screens too. They are pushed on top of your screen so you receive onExposed callback when they are dismissed.
Though it's OK in many cases, in other cases it poses a problem - e.g. if I must refresh the screen's content only when the user returns to it, but not after menus/dialogs, then how do I do that? My case is, unfortunately, one of those.
I found no documented way of detecting "covered"/"uncovered" events. Here is my approach. onCovered/onUncovered callbacks are called when the current screen is covered/uncovered by another screen of the app, but not by dialogs/menus/virtual keyboard:
public class MyAppScreen extends MainScreen {
private boolean isCovered;
protected void onExposed() {
Log.d("onExposed");
super.onExposed();
if (isCovered) {
onUncovered();
isCovered = false;
}
}
protected void onObscured() {
Log.d("onObscured");
super.onObscured();
final Screen above = getScreenAbove();
if (above != null) {
if (isMyAppScreen(above)) {
isCovered = true;
onCovered();
}
}
}
private boolean isMyAppScreen(final Screen above) {
return (above instanceof MyAppScreen);
}
protected void onUncovered() {
Log.d("onUncovered");
}
protected void onCovered() {
Log.d("onCovered");
}
protected void onUiEngineAttached(final boolean attached) {
if (attached) {
Log.d("UI Engine ATTACHED");
} else {
Log.d("UI Engine DETACHED");
}
super.onUiEngineAttached(attached);
}
protected void onFocusNotify(final boolean focus) {
if(focus){
Log.d("focus GAINED");
} else {
Log.d("focus LOST");
}
super.onFocusNotify(focus);
}
}
And a test. Try various combinations and see what events you receive in the log.
public class TestLifecycle extends MyAppScreen implements FieldChangeListener {
private final ABNTextEdit txt1;
private final ButtonField btn1;
private final ButtonField btn2;
public TestLifecycle() {
final Manager manager = getMainManager();
txt1 = new ABNTextEdit();
manager.add(txt1);
btn1 = new ButtonField("Dialog", ButtonField.CONSUME_CLICK);
btn1.setChangeListener(this);
manager.add(btn1);
btn2 = new ButtonField("Screen", ButtonField.CONSUME_CLICK);
btn2.setChangeListener(this);
manager.add(btn2);
}
public void fieldChanged(final Field field, final int context) {
if (field == btn1) {
Dialog.alert("Example alert");
} else if (field == btn2) {
UiApplication.getUiApplication().pushScreen(new TestLifecycle());
}
}
}
Update:
This method has a limitation: if a new screen is pushed when a dialog or the soft keyboard has focus your current screen will not receive onCovered/onUncovered notification.
Example A: if you have an input field of fixed size and you push a new screen when the user completes it, your current screen will not receive the notification if the user types very quickly. This happens because in the moment between you call push(newScreen) and it is actually pushed the user clicks on a letter on soft KB and it grabs the focus. So only onObscured is called, but not onCovered.
Solution: explicitly hide the soft keyboard before the push(newScreen).
Example B: if you have a customized dialog which pushes new screen and then dismisses itself, your current screen will not receive the notification. This happens because your customized dialog is not recognized as a screen, so only onObscured is called, but not onCovered.
Solution: dismiss the dialog in the first place returning a result value, and let your screen push the new screen based on that value. -OR- override isMyAppScreen() to return true also for your customized dialog.
You should be able to use protected void onExposed() to detect when it is displayed again.

Resources