BackRequested is triggering more than once in UWP app - webview

I have an app in which i mainly have a webview. i am having a problem. i have made the back button to goto previous webpage of webview it works fine and when it has no previous pages it quits with a MessageBox(Popup). The problem is when i navigate another page and press back it recursively triggers back button event and shows the MessageBox
Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested += (s, e) =>
{
e.Handled = true;
if (Web_view.CanGoBack)
{
Web_view.GoBack();
e.Handled = true;
}
else
{
quit();
e.Handled = true;
}
};
The above is code of my main page
private async void quit()
{
MessageDialog msg = new MessageDialog("Do you really want to quit?", "Quit");
msg.Commands.Add(new UICommand("Yes") { Id = 0 });
msg.Commands.Add(new UICommand("No") { Id = 1 });
var ans = await msg.ShowAsync();
if(ans.Id.Equals(0))
{
//System.Diagnostics.Debug.WriteLine("Exit");
App.Current.Exit();
}
}
this is the code of quit function.
I am navigating to another page from this using code
private void about_Click(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(BlankPage1));
}
And the backRequested code of blanckPage1 is
SystemNavigationManager.GetForCurrentView().BackRequested += (s,e)=>
{
e.Handled = true;
// Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested -= BlankPage1_BackRequested;
//System.Diagnostics.Debug.WriteLine("BackRequested");
if (Frame.CanGoBack)
{
e.Handled = true;
Frame.GoBack();
}
else
{
e.Handled = true;
}
};
To make it more clear for example when i open the app the webview navigates to www.example.com then following the links there i will get to some other page(for example www.example.com/link/firstlink). then i will navigate my frame to blankpage1 and from there i will press back. then insted of coming back to previous page (www.example.com/link/firstlink) it comes to beginning page (www.example.com) and shows the quit popup how can i fix this?
Thank you for all your replay.

Your problem is that you are still keeping the event handler: In your code when navigating back from BlankPage1, both .BackRequested handlers are called. You would need to deregister from .BackRequested on MainPage when leaving it, for example like this:
MainPage:
protected override void OnNavigatedTo(NavigationEventArgs e) {
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
}
protected override void OnNavigatedFrom(NavigationEventArgs e) {
SystemNavigationManager.GetForCurrentView().BackRequested -= OnBackRequested;
}
private void OnBackRequested(object sender, BackRequestedEventArgs e) {
// Your code to navigate back
if (Web_view.CanGoBack)
{
Web_view.GoBack();
e.Handled = true;
}
else
{
quit();
e.Handled = true;
}
}
And the same on BlankPage1... Though it would be far easier to register to BackRequested in your App.xaml.cs where you would handle your (Window.Current.Content as Frame) for the whole app, something like this. To make it "nice" code also with an interface:
INavigationPage:
public interface INavigationPage {
// When overriding the method returns true or false if the Page handled back request
bool HandleBackRequested();
}
App.xaml.cs:
// ... Code before
protected override void OnLaunched(LaunchActivatedEventArgs e) {
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
}
private void OnBackRequested(object sender, BackRequestedEventArgs e) {
Frame frame = Window.Current.Content as Frame;
if (frame == null) return;
INavigationPage page = frame.Content as INavigationPage;
if (page == null) return;
// Ask if the page handles the back request
if (page.HandleBackRequested()) {
e.Handled = true;
// If not, go back in frame
} else if (frame.CanGoBack) {
e.Handled = true;
frame.GoBack();
}
}
// ... Code after
MainPage.xaml.cs:
... class MainPage : Page, INavigationPage {
// ... Code before
// Implement the interface handling the backRequest here if possible
public bool HandleBackRequested() {
if (Web_view.CanGoBack) {
Web_view.GoBack();
return true;
}
return false;
}
// ... Code after
}
Then the BlankPage does not require any code and no subscribing to .BackRequested.

Related

Navigation Stack not cleared after calling RemovePage in iOS - Xamarin.Forms

I am currently developing app using Xamarin.Forms. I am facing some serious issue in iOS. My root page is TabbedPage and I want to make my tabs visible for entire applications. Hence I am setting MainPage as below
App.cs
MainPage = new NavigationPage(new MyTabbedPage());
MyTabbedPage.cs
Children.Add(new NavigationPage(new FirstTabbedPage()));
Children.Add(new NavigationPage(new SecondTabbedPage()));
Children.Add(new NavigationPage(new ThirdTabbedPage()));
Both FirstTabbedPage & SecondTabbedPage shows DataGrid using DevExpress.Mobile. On tap of any row from the Grid I am Navigating to another ContentPage say MyContentPage within the Root Tab.
SecondTabbePage.cs
private async void Grid_RowTap(object sender, RowTapEventArgs e)
{
//Some code logic, to get data from server
await Navigation.PushAsync(new MyContentPage());
}
For Example I am navigating to MyContentPage from SecondTabbedPage. From ContentPage I am Navigating to FirstTabbedPage. Now if I click SecondTabbedPage, MyContentPage will be shown, but I don't want this behaviour hence I am removing the page from NavigationStack in OnDisappearing method of MyContentPage as below:
MyContentPage.cs
protected override void OnDisappearing()
{
base.OnDisappearing();
//Clear Navigation Stack, clicking on tab page should always
//go to corresponding page
try
{
var existingPages = Navigation.NavigationStack.ToList();
foreach (var page in existingPages)
{
//existingPages count should be greater than 1, so that this will never be root page. Otherwise removing root page will throw exception
if (string.Compare(page.GetType().Name, "MyContentPage", StringComparison.OrdinalIgnoreCase) == 0 && existingPages.Count > 1)
{
Navigation.RemovePage(page);
}
}
//Just to check whether page was removed or not, but still was able to see MyContentPage even after removing it from Navigation Stack
var existingPages = Navigation.NavigationStack.ToList();
}
catch(Exception ex)
{
}
}
Now the issues are:
Even after calling RemovePage(MyContentPage) I am able to see that page in Debug mode.
Due to this behaviour await Navigation.PushAsync(new MyContentPage()); is not navigating to MyContentPage second time even-though it executes code without any exception.
The same code is working fine in Android as Android life cycle is
different which I was able to see in Debug mode
Few Things I Tried Are:
Removed NavigationPage from MainPage (TabbedPage) as I read that TabbedPage inside NavigationPage is not good design for iOS.
Removed NavigationPage on Child Items but tabbed icons were not shown after navigating to MyContentPage
Tried removing MyContentPage from NavigationStack in OnAppearing event of FirstTabbedPage.
As you said, due to the Android life cycle is different from iOS, I would recommend you to achieve the requirement by using custom renderer in iOS.
You should create a custom renderer of your MyTabbedPage, and then in ViewControllerSelected event, remove your ContentPage from NavigationStack.
[assembly: ExportRenderer(typeof(MainPage), typeof(myTabbarRenderer))]
namespace TabbedPageWithNavigationPage.iOS
{
class myTabbarRenderer : TabbedRenderer
{
private MainPage _page;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
_page = (MainPage)e.NewElement;
}
else
{
_page = (MainPage)e.OldElement;
}
try
{
var tabbarController = (UITabBarController)this.ViewController;
if (null != tabbarController)
{
tabbarController.ViewControllerSelected += OnTabbarControllerItemSelected;
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
private async void OnTabbarControllerItemSelected(object sender, UITabBarSelectionEventArgs eventArgs)
{
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
{
await _page.CurrentPage.Navigation.PopToRootAsync();
}
}
}
}
And for Android, add Device.RuntimePlatform == Device.Android to make sure the code only works on Android platform:
protected override void OnDisappearing()
{
base.OnDisappearing();
//Clear Navigation Stack, clicking on tab page should always
//go to corresponding page
if (Device.RuntimePlatform == Device.Android)
{
try
{
var existingPages = Navigation.NavigationStack.ToList();
foreach (var page in existingPages)
{
//existingPages count should be greater than 1, so that this will never be root page. Otherwise removing root page will throw exception
if (string.Compare(page.GetType().Name, "UpcomingAppointmentsPage", StringComparison.OrdinalIgnoreCase) == 0 && existingPages.Count > 1)
{
Navigation.RemovePage(page);
}
}
}
catch (Exception ex)
{
}
}
}
I write a demo here and you can check it. Let me know if it works.
I tried #Jackhua solution. It works perfect as well. But I used following way to fix the above problem as suggested in Xamarin Forum.
Reference: https://forums.xamarin.com/discussion/comment/375638#Comment_375638
MyTabbedPage.cs
Page page1 = null;
Page page2 = null;
Page page3 = null;
public MainPage()
{
InitializeComponent();
page1 = new NavigationPage(new FirstTabbedPage());
page1.Title = "Page1";
page2 = new NavigationPage(new SecondTabbedPage());
page2.Title = "Page2";
page3 = new NavigationPage(new ThirdTabbedPage());
page3.Title = "Page3";
Children.Add(page1);
Children.Add(page2);
Children.Add(page3);
}
protected override void OnCurrentPageChanged()
{
base.OnCurrentPageChanged();
int index = Children.IndexOf(CurrentPage);
if (index == 0)
{
if (Children.Count > 1)
{
page2 = Children[1] as NavigationPage;
page2.Navigation.PopToRootAsync();
page3 = Children[2] as NavigationPage;
page3.Navigation.PopToRootAsync();
}
}
else if (index == 1)
{
if (Children.Count > 1)
{
page1 = Children[0] as NavigationPage;
page1.Navigation.PopToRootAsync();
page3 = Children[2] as NavigationPage;
page3.Navigation.PopToRootAsync();
}
}
else if (index == 2)
{
if (Children.Count > 1)
{
page1 = Children[0] as NavigationPage;
page1.Navigation.PopToRootAsync();
page2 = Children[1] as NavigationPage;
page2.Navigation.PopToRootAsync();
}
}
}
Above solution doesn't require Custom Renderer and works for both Android & iOS.
Also code block under MyContentPage.cs is not required. i.e
remove iterating of existingPages under OnDisappearing method

Detecting when a template was loaded in wpf

I am working with an attached behavior for logging user actions on a ScrollBar.
my code:
class ScrollBarLogBehavior : Behavior<ScrollBar>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
...
var track = (Track)AssociatedObject.Template.FindName("PART_Track", AssociatedObject);
// ** HERE is the problem: track is null ! **
...
}
How can I detect that the template has loaded and I can find the Track?
(when I call AssociatedObject.Template.LoadContent() the result containt the requested Track, so it i a matter of timing and not a matter of wrong template or naming)
Override the method OnApplyTemplate
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var textBox = Template.FindName("PART_Textbox", this) as TextBox;
}
I did not find any good way to detect when the template was loaded. However, I did find a way to find the Track:
in OnAttached() - register to Scroll event fo the ScrollBar (this can only happen after the entire template is loaded, of course):
protected override void OnAttached()
{
base.OnAttached();
_scrollHandler = new ScrollEventHandler(AssociatedObject_Scroll);
AssociatedObject.AddHandler(ScrollBar.ScrollEvent, _scrollHandler, true);
}
When handling the Scroll event, remove registration and find the Thumb:
void AssociatedObject_Scroll(object sender, ScrollEventArgs e)
{
var track = (Track)AssociatedObject.Template.FindName("PART_Track", Associated
if (track == null)
return;
AssociatedObject.RemoveHandler(ScrollBar.ScrollEvent, _scrollHandler);
// do my work with Track
...
}
If I understand correctly, you wish to create an attached behavior that will reference a template part after the ScrollBar has been loaded.
The following should work:
internal static class ScrollBarLogBehavior
{
public static readonly DependencyProperty LogUserActionProperty = DependencyProperty.RegisterAttached(
"LogUserAction",
typeof(bool),
typeof(ScrollBarLogBehavior),
new UIPropertyMetadata(default(bool), LogUserActionChanged));
public static bool GetLogUserAction(DependencyObject obj)
{
return (bool)obj.GetValue(LogUserActionProperty);
}
public static void SetLogUserAction(DependencyObject obj, bool value)
{
obj.SetValue(LogUserActionProperty, value);
}
public static void LogUserActionChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
if (s is ScrollBar scrollBar)
{
scrollBar.Loaded += OnScrollBarLoaded;
}
}
private static void OnScrollBarLoaded(object sender, RoutedEventArgs e)
{
if (sender is ScrollBar scrollBar)
{
if (scrollBar.Template != null)
{
// I'm not sure, but the `name` in the following method call might be case sensitive.
if (scrollBar.Template.FindName("PART_Track", scrollBar) is Track track)
{
// do work with `track` here
}
}
}
}
}
where you would "attach" the behavior in your XAML with:
<ScrollBar guiControls:ScrollBarLogBehavior.LogUserAction="True">
<!-- more here -->
</ScrollBar>
BE ADVISED: this implementation completely ignores the bool value that is being set for LogUserAction

Monotouch Importing Contact - Return Type Issue

I'm trying to have this method return a person if they are selected. My problem is that I get an error saying that the delegate cannot return a type other than void which is annoying because I don't want this method to return unless one of those two actions have happened. Any suggestions?
public static ABPerson ImportContact ()
{
// Create placeholder for contact
ABPeoplePickerNavigationController _contactController = new ABPeoplePickerNavigationController ();
AppDelegate.navigation.PresentViewController (_contactController, true, null);
_contactController.Cancelled += delegate {
AppDelegate.navigation.DismissViewController (true, null);
return;
};
_contactController.SelectPerson += delegate(object sender, ABPeoplePickerSelectPersonEventArgs e) {
_importedContact = e.Person.GetEmails().FirstOrDefault;
AppDelegate.navigation.DismissViewController (true, delegate {
return e.Person;
});
};
}
You're trying to return function result in inner delegate method. It's not possible to implement. Try to use public event instead. I. e.:
public event Action<ABPerson> OnPersonSelect;
public void ImportContact ()
{
// Create placeholder for contact
ABPeoplePickerNavigationController _contactController = new ABPeoplePickerNavigationController ();
NavigationController.PresentViewController (_contactController, true, null);
_contactController.Cancelled += delegate {
NavigationController.DismissViewController (true, null);
return;
};
_contactController.SelectPerson += delegate(object sender, ABPeoplePickerSelectPersonEventArgs e) {
var _importedContact = e.Person;
NavigationController.DismissViewController (true, delegate {
if (OnPersonSelect != null)
{
OnPersonSelect(_importedContact);
}
});
};
}
Note that I mark this method as non-static to use it in my own test sample.
This is what I did to solve it:
private static void ImportContact ()
{
if (_contactController == null)
_contactController = new ABPeoplePickerNavigationController ();
AppDelegate.navigation.PresentViewController (_contactController, true, null);
_contactController.Cancelled += delegate {
if (!AppDelegate.navigation.PresentedViewController.IsBeingPresented)
AppDelegate.navigation.DismissViewController (true, null);
return;
};
_contactController.SelectPerson += delegate(object sender, ABPeoplePickerSelectPersonEventArgs e) {
if (!AppDelegate.navigation.PresentedViewController.IsBeingPresented) {
AppDelegate.navigation.DismissViewController (true, delegate {
MapImportedContact (e.Person);
});
}
return;
};
}
After I map the fields from that, I called a method
PopulateClientFields (Client mappedClient)
That method sets all of the EntryElements to the value passed in and then reloaded the view with:
clientsView.ReloadData ();
Worked like a charm. Sorry I didn't actually post this sooner.

How to close streaming player screen when sp.realize() method executing

I have read the knowledgebase article "Streaming media - Start to finish" It is working fine. When I click the open video, the player screen is open. When I click the back button before the player is realized, it does not come to back to the right screen.
when sp.realize(); method executing user can't come to back screen.
after loading player. it close.
How to go back a screen if sp.realize() method is still executing?
new Thread(new Runnable()
{
public void run()
{
try
{
if(sp==null)
{
sp = new StreamingPlayer(url, contentType);
sp.setBufferCapacity(bufferCapacity);
sp.setInitialBuffer(initBuffer);
sp.setRestartThreshold(restartThreshold);
sp.setBufferLeakSize(bufferLeakSize);
sp.setConnectionTimeout(connectionTimeout);
sp.setLogLevel(logLevel);
sp.enableLogging(eventLogEnabled, sdLogEnabled);
sp.addStreamingPlayerListener(playerScreen);
sp.realize();
volC = (VolumeControl)sp.getControl("VolumeControl");
if(contentType.toLowerCase().indexOf("video")!=-1)
{
vidC = (VideoControl)sp.getControl("VideoControl");
videoField = (Field)vidC.initDisplayMode(VideoControl.USE_GUI_PRIMITIVE, "net.rim.device.api.ui.Field");
vidC.setDisplaySize(Display.getWidth(), Display.getHeight()-timeSeeker.getHeight()-byteSeeker.getHeight());
UiApplication.getUiApplication().invokeLater(new Runnable()
{
public void run()
{
replace(getField(0), videoField);
}
});
vidC.setVisible(true);
}
if(contentType.toLowerCase().indexOf("audio")!=-1)
{
audioIcon = true;
if(!(getField(0)==albumArt))
{
UiApplication.getUiApplication().invokeLater(new Runnable(){
public void run()
{
replace(videoField, (Field)albumArt);
}
});
}
}
sp.start();
}
else
{
sp.stop();
sp.close();
sp = null;
run();
}
} catch(Throwable t)
{
//log(t.toString());
}
}
}).start();
I'm not sure exactly what you mean by this. However, have you tried running the audio stuff in a separate thread? That should reduce the likelihood of it interfering with anything else.

I want to show splash screen until i am done with downloading xml files from server and after completion show next screen

I am trying to download xml files from server when my application starts. So i want to show splash screen until am done with downloading and then show next screen. below is my code:
Here, i want to show My splash screen when getTopNotDoc() method is under execution. and after completion of that method show next screen.
//get _topics and notification document<br>
_getDoc = new ServerConnectivity(this);
public class ServerConnectivity {
private Document _questionDoc;
private Document _topics;
private Document _notifications;
public ServerConnectivity(ApplicationSession appSession){
//getTopNotDoc();
_this = this;
_appSession = appSession;
new Thread(new Runnable(){
public void run(){
getTopNotDoc();
}
}).start();
}
}
private void getTopNotDoc(){
InputStream inputStream = null ;
try{
// Build a document based on the XML file.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
inputStream = getClass().getResourceAsStream("topics.xml");
_topics = builder.parse( inputStream );
inputStream = getClass().getResourceAsStream("notification.xml");
_notifications = builder.parse( inputStream );
if(_topics == null || _notifications == null){
Dialog.alert("Unable to connect to internet");
}
}
catch ( Exception e ){
System.out.println( e.toString() );
}
finally{
if(inputStream != null){
try {
inputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
Usually when I do this, I create a loading screen, then I just extend the Thread class.
So I would create a loading screen like this:
public class LoadingScreen extends MainScreen {
public LoadingScreen() {
super();
this.setTitle("loading...");
// add a spinning animated gif or whatever here
final Screen me = this;
new Thread(new Runnable(){
public void run(){
// do something that takes a long time
try { Thread.sleep(1000);} catch (Exception e) {}
}
}){
public void run() {
super.run();
synchronized (UiApplication.getEventLock()) {
UiApplication.getUiApplication().popScreen(me);
}
}
}.start();
}
}
Then I push this screen, it will perform the long task, and then pop itself when its done.
(you may or may not want to disable the back button and menus on this screen)
I made the Runnable as an anonymous inner class just to compact the code, but you probably have this code already in a class somewhere else, so you would pass it in instead.
To add some flexibility and keep your classes loosely coupled together, you could make some modifications to your ServerConnectivity class so your calls could go something like the following:
// push your splash screen on to the stack
//
final SplashScreen splashScreen = new SplashScreen();
UiApplication.getUiApplication().pushScreen(splashScreen);
_getDoc = new ServerConnectivity(this, new ServerConnectivityListener() {
public void onCompleted(ServerConnectivity sender) {
// display next screen
//
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
splashScreen.close();
UiApplication.getUiApplication().pushScreen(new NextScreen());
}
});
}
public void onError(ServerConnectivity sender) {
splashScreen.close();
// display error message, retry, etc...
}
});
For this to work, you need an interface with the following definition:
public interface ServerConnectivityListener {
void onCompleted(ServerConnectivity sender);
void onError(ServerConnectivity sender);
}
So, your ServerConnectivity class maintains a reference to some object that implements the interface called ServerConnectivityListener This allows you to maintain loose coupling between the subject class and any observers that need to listen for events.
Within ServerConnectivity, you would make calls to the listener's methods something like this:
// begin excerpt from above...
//
if(_topics == null || _notifications == null) {
_listener.onError(this);
} else {
_listener.onCompleted(this);
}
catch ( Exception e ){
System.out.println( e.toString() );
_listener.onError(this);
//
// end excerpt from above...
Here is code for splash screen in java........after and call that view.........
http://www.randelshofer.ch/oop/javasplash/javasplash.html
import java.awt.*;
import java.awt.event.*;
public class SplashTest extends Frame implements ActionListener {
static void renderSplashFrame(Graphics2D g, int frame) {
final String[] comps = {"foo", "bar", "baz"};
g.setComposite(AlphaComposite.Clear);
g.fillRect(130,250,280,40);
g.setPaintMode();
g.setColor(Color.BLACK);
g.drawString("Loading "+comps[(frame/5)%3]+"...", 130, 260);
g.fillRect(130,270,(frame*10)%280,20);
}
public SplashTest() {
super("SplashScreen demo");
setSize(500, 300);
setLayout(new BorderLayout());
Menu m1 = new Menu("File");
MenuItem mi1 = new MenuItem("Exit");
m1.add(mi1);
mi1.addActionListener(this);
MenuBar mb = new MenuBar();
setMenuBar(mb);
mb.add(m1);
final SplashScreen splash = SplashScreen.getSplashScreen();
if (splash == null) {
System.out.println("SplashScreen.getSplashScreen() returned null");
return;
}
Graphics2D g = (Graphics2D)splash.createGraphics();
if (g == null) {
System.out.println("g is null");
return;
}
for(int i=0; i<100; i++) {
renderSplashFrame(g, i);
splash.update();
try {
Thread.sleep(200);
}
catch(InterruptedException e) {
}
}
splash.close();
setVisible(true);
toFront();
}
public void actionPerformed(ActionEvent ae) {
System.exit(0);
}
public static void main (String args[]) {
SplashTest test = new SplashTest();
}
}
Since,it is a thread based one,We cannot do it the normal way.So Check the following link
http://supportforums.blackberry.com/t5/Java-Development/What-is-the-Event-Thread/ta-p/446865
and Check whether parsing is done,Until that have the same screen,Check the condition of whehter it is downloaded or not ,and then push the screen

Resources