On my Xamarin.Forms app I using a three different types of Buttons. These are their on click functionalities:
Navigation Buttons:
I use them to navigate to an other page if I will need to came back later to the previous page.
btn_navigate.Clicked += (sender,e) => {
Navigation.PushAsync(new Page());
};
Error: If the new page takes a few seconds to load, doing a quick multi click on these buttons open several pages.
Non returnable navigation Buttons:
I use them to navigate to an other page when I want to avoid the user came back to the previous page. I destroy the current page after I insert before my new page.
btn_non_returnable_navigate.Clicked += (sender,e) => {
Navigation.InsertPageBefore(new Page(), this);
Navigation.PopAsync();
};
Error: If the new page takes a few seconds to load, doing a quick multi click on these buttons throws an exception: Before must be in the pushed stack of the current context. This is because the first click create the new page and destroy the current one, the second click cant not destroy to the current page because it is already destroyed so it throws the exception.
HTTP request Buttons:
I use them to send a HTTP request to the server. Usually after the HTTP request was completed it navigate to an other page. Those are obviously the more important ones.
btn_http_request.Clicked += (sender,e) => {
Uri uri = new Uri("http://192.168.0.1:8080/request");
HttpClient client = new HttpClient();
HttpResponseMessage http_response = client.GetAsync(uri);
....
Navigation.InsertPageBefore(new Page(), this);
Navigation.PopAsync();
};
Error: If the request takes a few seconds to get the answer, doing a quick multi click on these buttons throws several HTTP request. It should not happen.
All of these buttons are combined on the same page multiple times. So if the user try to click multiple buttons repeatedly he will tear down the app.
These issues are caused because the asynchronously of the button actions. But I think it will be solve by using a locker for these buttons so they could only clicked once a time.
Have Buttons some property like this? If it is not, how could I create a Buttons extension that fix this problems? I would like to have as simple solution as it possible. I need to control this issue in a lots of Buttons and if it is a complicated solutions it may do a tricky code problem.
On WPF this code works as I need but on Xamarin there is not RoutedEventArgs:
C# Button extension
public partial class ButtonEx : Button{
public bool Active;
public ButtonEx(){
InitializeComponent();
Active = true;
}
private void Extension_Click(object sender, RoutedEventArgs e){
if (Active) {
Active = false;
} else {
e.Handled = true;
}
}
}
XAML Button extension
<Button x:Class="test.ButtonEx"
..
Click="Extension_Click">
</Button>
C# on the code
...
ButtonEx buttonEx = new ButtonEx()
//Click function of the button
buttonEx.Click += (sender, e) =>{
//This code only happen if buttonEx.active = true
...
(sender as Button).Active = true;
}
...
When you click the extended button it throws two events. The fist one, the Extension_Click and the second one the Click function of the button. The second event only happen when the buttonEx.active = true. The first even allows or blocks the second.
It is there an alternative like this to Xamarin.Formns?
Thank you
The best practice for this including on WPF is to make the button disabled while it performs some action and shouldn't/can't perform additional actions. That is by the book. Technically someone can make a new sort of button that would perform this automatically. I would guess that the fact that no one made it by now is that this is not worth the effort / brings at least as much troubles as it resolves. Unfortunately I would guess that if you need something like that you will have to make it yourself, but it is definitely possible.
Related
Scenario
I am creating a simple embedded "browser" for my app, which opens in a Popup, when the user clicks on a button with a hyperlink designed to open "in-app".
The popup is opened, and the (simply-named) WebView is navigated to the URL specified in the hyperlink.
There are the typical Back, Forward and Refresh/Stop buttons that are enabled/disabled accordingly.
Current Situation
I have wired up the necessary events for NavigationStarted, NavigationCompleted and others for Falied, Loaded etc. etc.
These are performed along with some "naughty" ViewModel wiring when the UserControl is loaded TL;DR - there is no way I can find to keep to MVVM practice with WebViews, what a PITA!:
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (this.DataContext is IWebViewUserControlViewModel)
{
this.WebView.ContentLoading += OnWebViewContentLoading;
this.WebView.DOMContentLoaded += OnWebViewDomContentLoaded;
this.WebView.NavigationStarting += OnWebViewNavigationStarting;
this.WebView.NavigationCompleted += OnWebViewNavigationCompleted;
this.WebView.UnviewableContentIdentified += OnWebViewUnviewableContentIdentified;
this.WebView.NavigationFailed += OnWebViewNavigationFailed;
this.viewModel = DataContext as IWebViewUserControlViewModel;
NavigateToUrl(this.viewModel?.Url);
}
}
This is so that I navigate to the URL when the UserControl is loaded, and can evaluate the button states as the user navigates around using the events above.
The NavigateToUrl() method just contains a try/catch block to counteract any errors forming the Uri etc.:
private void NavigateToUrl(string url)
{
try
{
var uri = new Uri(url);
this.WebView.Navigate(uri);
}
catch (Exception ex)
{
this.WebView.NavigateToString($"An error occurred: {ex.Message}")
}
}
Back/Forward UX
In particular the Back and Forward buttons are disabled/enabled when navigation to a page is started/completed respectively.
I evaluate their IsEnabled states like so:
btnBackButton.IsEnabled = this.WebView.CanGoBack;
btnForwardButton.IsEnabled = this.WebView.CanGoForward;
This works fine throughout the entire time that the user is browsing.
The Issue
Should the user close the popup, and re-open it via the same or a different link, the correct URL is navigated to - all good.
The issue is, that their previous browsing session was never cleared from the MyWebView, and thus the btnBackButton (not the forward, as this the latest navigation in the history stack) is now enabled again - allowing them to traverse their previously visited pages.
I don't want this behaviour.
I would like it to appear that their session is a "new", fresh one - without the Back button enabled - as if it had just been opened.
What I have already tried...
I am unable to manually set the MyWebView.CanGoBack/MyWebView.CanGoForward properties to false when the popup is (re)opened.
They are get-only properties, so this is not possible.
I have tried re-initializing the WebView control when the containing UserControl is Loaded (in the same OnLoaded delegate as above):
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (this.DataContext is IWebViewUserControlViewModel)
{
// Re-initialize the WebView
this.WebView = new WebView();
// Detect when the new control has loaded, and then wire up events/navigate as normal
this.WebView.Loaded += (sender, e) =>
{
this.WebView.ContentLoading += OnWebViewContentLoading;
this.WebView.DOMContentLoaded += OnWebViewDomContentLoaded;
this.WebView.NavigationStarting += OnWebViewNavigationStarting;
this.WebView.NavigationCompleted += OnWebViewNavigationCompleted;
this.WebView.UnviewableContentIdentified += OnWebViewUnviewableContentIdentified;
this.WebView.NavigationFailed += OnWebViewNavigationFailed;
this.viewModel = DataContext as IWebViewUserControlViewModel;
NavigateToUrl(this.viewModel?.Url);
}
}
}
In the hope that this might work - but the Loaded delegate of the WebView is never fired.
In the UI, the WebView just doesn't appear.
Help!?
Is there any way for me to clear the navigation history for the WebView control, so it appears that the browsing session is a "new" one?
Your help is appreciated, as always. Many thanks.
If your app is Windows 8.1, actually there is no programmatic way to clear the webview cache according to what #Matt said in this link(part 7).
And if it is a UWP app, please refer to this doc.
In my page.xaml, i have hooked the Back hardware button as this:
Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;
and implement the method:
private void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e)
{
// Handle the Virtual Hardware Button: Back,
// When user taps it, I need to get the previous page name.
System.Diagnostics.Debug.WriteLine("CurrentSourcePageType = " + Frame.CurrentSourcePageType.FullName);
System.Diagnostics.Debug.WriteLine("Back button is pressed...");
}
But here, the Frame.CurrentSourcePageType.FullName is already the back navigated page name, how can i get the previous page name?
Maybe I need to describe my question better here:
Suppose I have 2 pages A and B,
Through page A I navigate to page B, in the page B I have done something, then I only want to use back button to trigger some customize action (I don't want to add button in my Page), but this action needs to get the page B's name first.
First of all, HardwareButtons.BackPressed is an app-wide event, so it's not a good idea to subscribe to it in Page, unless you do it very carefully and remember to unsubscribe when not needed any more. It's worth also to mention that you should pay special attention if anything has been susbscribed to this event before your Page - for example in app's constructor or anywhere else. (some typical places are shows here in Igrali's answer).
To do what you want you can for example either build an app-wide event that will be responsible for an action, or use the code as shown below (following Rob Caplan's answer). In both cases the name (type) of your previous page you can get from Frame.BackStack.
RelayCommand myGoBackCommand;
public BasicPage1()
{
this.InitializeComponent();
this.navigationHelper = new NavigationHelper(this);
this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
this.navigationHelper.SaveState += this.NavigationHelper_SaveState;
myGoBackCommand = new RelayCommand(() => GoBackAction());
this.navigationHelper.GoBackCommand = myGoBackCommand;
}
private void GoBackAction()
{
//print previous page name before going back
Debug.WriteLine(Frame.BackStack.Last().SourcePageType);
if (navigationHelper.CanGoBack())
navigationHelper.GoBack();
}
Note: To make the above code work, delete all other subscriptions to HardwareButtons.BackPressed and make your pages BasicPages (use navigation helper and so on). Of course you can have other subscriptions, but you need to handle them carefully, in most cases these events are fired with order they were subscribed.
Remark - in case you need also handle the case when user navigates back from other Page and you want the name of that page - then recognize type of navigation in OnNavigatedTo (forward/back navigation) and then read suitable Frame's stack - BackStack or ForwardStack.
The code above is using BasicPage template - if you don't have mentioned classes, then add to your project a new BasicPage and VS should ask you if you want to add the common files (NavigationHelper ans so on).
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.
I am currently working on an app for WP7 for my university, and need a temporary solution to a problem. Now this solution is, that I will be loading a webpage using the web browser control for WP7. For example: http://m.iastate.edu/laundry/
Now as you see on the webpage, there are certain elements I want to hide, for example the back button. For now, what I have done to handle the back button is something like this:
private void webBrowser1_Navigating(object sender, NavigatingEventArgs e)
{
// Handle loading animations
// Handle what happens when the "back" button is pressed
Uri home = new Uri("http://m.iastate.edu/");
// The the current loading address is home
// Cancel the navigation, and go back to the
// apps home page.
if (e.Uri.Equals(home))
{
e.Cancel = true;
NavigationService.Navigate(new Uri("/MainPage.xaml", UriKind.Relative));
}
}
Now that works beautifully, except for the part that there is a back button on the hardware.
So my second option is to completely hide the back button ONLY on that page, and not its children. So not on http://m.iastate.edu/laundry/l/0
I am still debating on just parsing the data and displaying it in my own style, but I'm not sure if that's completely needed seeing how the data needs constant internet service and is already in a well-put format. Plus, I feel like that would be a waste of resources? Throw in your opinions on that too :)
Thanks!
You should inject a script in the page with InvokeScript.
Here is the kind of Javascript code you need to remove the back button:
// get the first child element of the header
var backButton = document.getElementsByTagName("header")[0].firstChild;
// check if it looks like a back button
if(backButton && backButton.innerText == "Back") {
// it looks like a back button, remove it
document.getElementsByTagName("header")[0].removeChild[backButton];
}
Call this script with InvokeScript:
webBrowser1.InvokeScript("eval", "(function() { "+ script +"}()");
Warning: IsScriptEnabled must be set to true on the web control
If the removal of the back button depends of the page, just test the navigating URI in C# and inject the script if neeeded.
I'm having a problem screenscraping some data from this website using the MSHTML COM component. I have a WebBrowser control on my WPF form.
The code where I retrieve the HMTL elements is in the WebBrowser LoadCompleted events. After I set the values of the data to the HTMLInputElement and call the click method on the HTMLInputButtonElement, it is refusing to submit the the request and display the next page.
I analyse the HTML for the onclick attribute on the button, it is actually calling a JavaScript function and it processes my request. Which makes me not sure if calling the JavaScript function is causing the problem? But funny enough when I take my code out of the LoadCompleted method and put it inside a button click event it actually takes me to the next page where as the LoadCompleted method didn't do. Doing that sort of thing defeats the point of trying to screenscrape the page automatically.
On another thought: when I had the code inside the LoadCompleted method, I'm thinking the HTMLInputButtonElement is not fully rendered on to the page which result in click event not firing, despite the fact when I looked at the object in run time it is actually held the submit button element there and the state is saying I completed which baffles me even more.
Here is the code I used inside the LoadCompleted method and the click method on the button:
private void browser_LoadCompleted(object sender, NavigationEventArgs e)
{
HTMLDocument dom = (HTMLDocument)browser.Document;
IHTMLElementCollection elementCollection = dom.getElementsByName("PCL_NO_FROM.PARCEL_RANGE.XTRACKING.1-1-1.");
HTMLInputElement inputBox = null;
if (elementCollection.length > 0)
{
foreach (HTMLInputElement element in elementCollection)
{
if (element.name.Equals("PCL_NO_FROM.PARCEL_RANGE.XTRACKING.1-1-1."))
{
inputBox = element;
}
}
}
inputBox.value = "Test";
elementCollection = dom.getElementsByName("SUBMIT.DUM_CONTROLS.XTRACKING.1-1.");
HTMLInputButtonElement submitButton = null;
if (elementCollection.length > 0)
{
foreach (HTMLInputButtonElement element in elementCollection)
{
if (element.name.Equals("SUBMIT.DUM_CONTROLS.XTRACKING.1-1."))
{
submitButton = element;
}
}
}
submitButton.click();
}
FYI: This is the URL of the web page I'm trying to access using MSHTML,
http://track.dhl.co.uk/tracking/wrd/run/wt_xtrack_pw.entrypoint.
There are many possibilities:
You may try to put your code at
other events, such as on Navigation
Completed, or on Download Completed.
You may need to explicitly evaluate the OnClick event after the click() function.
Using the MS WebBrowser control is
easier than using the MSHTML COM.
To make life easier, you may just use a webscraping library such as the IRobotSoft ActiveX control to automate your entire process.
Delay in OnBeforeNavigate can cause click actions to fail.
We have noticed that with some submit actions OnBeforeNavigate is called twice, especially where onClick is used. The first call is before the onClick action is performed, the second is after it is complete.
Turn off your BHO, put a breakpoint on onClick, step over the submit action return jsSubmit() and then wait a bit and you should be able to cause the same issue without your automation.
Any delay >150ms on the second call to OnBeforeNavigate causes some failure in page load/navigation to the result.
Edit:
Having tried our own automation of this DHL page we don't currently have an issue with the timing described above.