How suppress multiple button clicks? - c#

I would like to know the best way to handle an http Request on Xamarin.Forms.
For now I was handling the request this way:
First I have a button on my forms like this:
btn_1.Clicked += (sender, e) => {
Confirm(name, password);
};
My Confirm() function checks the entrees and throws the event of the request. Also it do the logic after the request event is completed. For example:
private async void Confirm(string name, string password) {
UserController user_controller = new UserController();
if (name != null && password != null) {
User user = new User(name, password);
bool ok = user_controller.Login(user);
if(ok){
Navigation.InsertPageBefore(new NextPage(), this);
await Navigation.PopAsync();
} else {
//Show error code...
}
}
}
My UserController has two functions for each http request. The first one does the request. The second one calls the first one and handles the answer.
1º:
private async Task<HttpResponseMessage> user_login(User user){
try {
Uri uri = new Uri("http://localhost/user/login");
string user_json = JsonConvert.SerializeObject(user);
StringContent content = new StringContent(user_json, Encoding.UTF8, "application/json");
return await Utilities.client.PostAsync(uri, content).ConfigureAwait(false);
} catch {
return null;
}
}
2º:
public bool Login(User user) {
http_response = user_login(user).GetAwaiter().GetResult();
//If it doesn't reach the server...
if (http_response != null) {
//Depending of the status of the response
switch (http_response.StatusCode) {
case (System.Net.HttpStatusCode)200:
try {
string content = http_response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
Response response = JsonConvert.DeserializeObject<Response>(content);
return (bool) response.aux;
} catch {
}
break;
case (System.Net.HttpStatusCode)401:
...
break;
default:
...
break;
}
} else {
App.Current.MainPage.DisplayAlert("Error", "No server connection", "OK");
}
return false;
}
This completes my protocol for each request. My problem is:
1. I'm not sure if this is the correct way to do it
2. When I click several times the btn_1 it throws many times the request
How could I do to avoid this? I try to put a lock on my button but it doesn't work. I'm having many troubles with the asynchronous requests. I don't know which is the best way to handle the request to throw only one request at the time.
EDIT:
I have created this button extension:
public partial class LockableButton: Button {
public event EventHandler ThrowEvent;
public bool ShowLoading { get; set; }
public LockableSimpleButton() {
ShowLoading=false;
InitializeComponent();
this.Clicked += async (object sender,EventArgs e) => {
if (!Utilities.Unlocked) { return; }
Utilities.Unlocked=false;
try {
if (ShowLoading) {
await Navigation.PushModalAsync(new LoadingPopUp());
ThrowEvent(sender,e);
await Navigation.PopModalAsync();
} else {
ThrowEvent(sender,e);
}
} finally {
await Task.Delay(1000);
Utilities.Unlocked=true;
}
};
}
}
And now my buttons are like this:
btn_1.ThrowEvent += async (sender, e) => {
Navigation.InsertPageBefore(new Page(),this);
await Navigation.PopAsync(false);
};
How it is even posible that the error still persisting?
When I click several times the button it throws an error because it is trying to PopAsyc to many time the same page... It is the delay to short?

When I click several times the btn_1 it throws many times the request
This problem has nothing to do with handling an Async HTTP Request.
Here are two classic coding techniques for discarding extra button presses.
They are variations on having a flag set, and discarding any clicks that happen while that flag is set.
Common pseudo-code:
static bool _busy;
...click handler... {
if (_busy) return;
_busy = true;
// Special code as needed.
... handle the click ...
// This code must always be executed.
// If it isn't, the button action will never occur again.
_busy = false;
}
When you finish processing the first click, start a one-time timer. Until that timer fires, discard any additional clicks.
Pseudo-code:
...click handler... {
if (_busy) return;
_busy = true;
try {
... handle the click ...
} finally {
var timer = new Timer(TimerTick, 250 milliseconds, one-time);
timer.Start();
}
}
void TimerTick() {
// This code must always be executed.
// If it isn't, the button action will never occur again.
_busy = false;
//maybe timer.Stop();
}
When you start processing the first click, set a flag. Clear that flag when you are done processing. Discard any clicks that happen while that flag is set.
Pseudo-code:
// Must be `async` method, so events continue while processing.
// Otherwise, the second button press might simply be on hold,
// until after this finishes, so doesn't get suppressed.
...click handler... {
if (_busy) return;
_busy = true;
try {
... handle the click ...
} finally {
// This code must always be executed.
// If it isn't, the button action will never occur again.
_busy = false;
}
}

Related

Webview2 Navigation

Currently in Webview2 browser if navigated to a particular URL as written below
browser.Source = URL;
This triggers NavigatingStarting event asynchronously.
How can I trigger a synchronous call for each source being set to trigger the event?
Problem: I am keeping a bool variable to check for the if navigation triggered inside my application and resetting it at the end for the navigatingstarting event. since it is an asynchronous call it is resetting after the first call without the next call being inside my application.
void SetBrowserUrl(Uri value)
{
m_bInsideNavigateCall = true;
priorsWebBrowser.Source = value;
}
void priorsWebBrowser_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
{
if(m_bInsideNavigateCall)
{
e.Cancel = false;
m_bInsideNavigateCall = false; // Reset for next inside call
}
else
{
e.Cancel = true;
}
}
Here the issue is if call SetBrowserUrl twice. Navigate starting cancels the second call made because it is not synchronous
I have created a list of strings.
List<String> insideNavigatingURLS; //Class level variable
Just before calling web-browser to navigate, I add the URL to list.
internalURLs.Add(uri.AbsolutePath.ToLower());
webBrowser.Source = uri;
In the NavigationStarting Event added a check to see if list contains the navigation url if it doesn't then will cancel the request.
void webBrowser_Navigating(object sender, CoreWebView2NavigationStartingEventArgs e)
{
if (!internalUrls.Contains(e.Uri))
{
e.Cancel = true;
}
else
{
internalUrls.Remove(e.Uri);
e.Cancel = false;
}
}
So when back or forward navigation is triggered, the list doesn't contain a URL and the navigation request is cancelled

Using Manual Reset Event till last property changes

I have call back that comes as Property Changed Notifications from Firmware. Now in my code I want to wait till I hit the last property changed. I read the following post Manual Reset Event regarding ManualResetEvent but it says Manual Reset Events are used in case of multi threading. I am new to ManualResetEvents. This is my following code can I use the manual reset event in my case? if so how? If not whats the best way to wait there? Please help.
//This is some button click action of RelayCommand
private void StartCurrentRun(bool obj)
{
this.worker = new BackgroundWorker();
this.worker.WorkerReportsProgress = true;
this.worker.WorkerSupportsCancellation = true;
OriginalTime = SelectedVolumeEstimatedTime();
StartTimer();
WhenCancelledBlurVolumesGrid = false;
//this.worker.DoWork += this.DoWork;
//this.worker.ProgressChanged += this.ProgressChanged;
//this.worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
//this.worker.RunWorkerAsync();
IsLiveProgress = true;
CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} has been started", SelectedAssay, SelectedVolume), LogInformationType.Info);
var instance = ConnectToInstrument.InstrumentConnectionInstance;
instance.InitalizeRun(PopulateRespectiveVolumes());
PropertyCallBackChangedInstance.PropertyChanged += PropertyCallBackChangedInstance_PropertyChanged;
//Here I want to perform some action after I get a Processed state after the final property change event occurs.
//Can I do a manual reset event here.
}
private void PropertyCallBackChangedInstance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
try
{
if (e.PropertyName == "InstrumentStatusChanged")
{
var value = sender as InstrumentCallBackProperties;
if (value.InstrumentStatusChanged == CurrentInstrumentStatus.Busy)
{
CurrentStatus = Application.Current.TryFindResource("Wait").ToString();
}
}
if (e.PropertyName == "RunStepStatusName")
{
var value = sender as InstrumentCallBackProperties;
CurrentStatus = EnumExtensions.GetDescription(value.RunStepStatusName);
NewProgressValue += 20;
UpdateProgress = true;
}
else if (e.PropertyName == "CurrentCartridgeStatusChanged")
{
var value = sender as InstrumentCallBackProperties;
if (value.CurrentCartridgeStatusChanged == CurrentCartridgeStatus.Processed)
{
PropertyCallBackChangedInstance.PropertyChanged -= PropertyCallBackChangedInstance_PropertyChanged;
EstimatedTimeRemaining = "00:00:00";
stopWatch.Stop();
timer.Stop();
IsLiveProgress = false;
CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} has been completed", SelectedAssay, SelectedVolume), LogInformationType.Info);
if (IsRunSuccessfullyComplete != null && !WhenCancelledBlurVolumesGrid) //This indicates that Success will only open when the run is complete
{
IsRunSuccessfullyComplete();
}
WhenCancelledBlurVolumesGrid = true;
if (ClearSelections != null)
{
ClearSelections();
}
}
}
}
catch (Exception ex)
{
CreateEventLogs.WriteToEventLog(string.Format("Run with Assay:{0} Volume{1} failed", SelectedAssay, SelectedVolume), LogInformationType.Error);
}
}
It doesn't look to me like you want ManualResetEvent, which is used to signal multiple listeners. If your results were being produced by multiple tasks then you would use await WhenAll(...) however in your case the results are being informed by a property change not by a task completion. I would suggest that you simply record each property notification when it occurs, and check to see whether they are all complete. An easy way to do this is to use an enum with the [Flags] attribute: [Flags]public enum Completions { InstrumentStatusChanged, CurrentCartridgeStatusChanged, RunStepStatusName }. Just set the appropriate flag for each property callback and when you have set all the flags then you are done. If the property callbacks can occur in parallel or on different threads then you will need some form of locking such as a SemaphoreSlim(1,1).

Trigger QuerySubmitted once

I have a AutoSuggestBox with QuerySubmitted property, so when I hit enter button , it will search for products and will show error message when no data found , my problem is it will show twice or multiple times when i hit enter multiple times too.
here is my code:
try {
if (!ViewModel.IsBusy) {
ViewModel.IsBusy = true;
await this.ViewModel.FindAsync(args.QueryText);
}
}
catch (Exception e) {
}
finally {
ViewModel.IsBusy = false;
}
Its because the second call to your function is making the bool false and hence the 3rd call will go into if condition and will do a FindAsync()
Instead you can do this :
try {
if (!ViewModel.IsBusy) {
ViewModel.IsBusy = true;
await this.ViewModel.FindAsync(args.QueryText);
ViewModel.IsBusy = false;
}
}
catch (Exception e) {
ViewModel.IsBusy = false;
}
Or you can really use Task Cancellation for better design and you will get the benefit of sending the latest args.QueryText to the FindAsync if there are changes in querytext between multiple Enter key hit. Of course, you need to cancel the earlier Task if you encounter that there is a new call.

On ASP.NET formview when are the ModeChanged and ModeChanging events raised?

This popped up, when I was trying to find why the OnModeChanging handler wasn't being called when I called the ChangeMode event of my formview.
On the formview's ChangeMode method MSDN page , it is stated that it:
switches the FormView control to the specified data-entry mode
but also that:
the ModeChanged and ModeChanging events are not raised when this method is called
And in the ModeChanged and ModeChanging events pages, it says that they occur:
when the FormView control switches between edit, insert, and read-only mode
after/before the mode changes, respectively.
Can you explain it to me: when are the ModeChanged/ing events raised?
And, is there a way to force these events to be raised?
I think I know why now. I've found an answer in other forum, and though I didn't find the code of FormView, I've found a DetailsView implementation and I think in this case it might be similar.
Basically what I've understood of it, is that the ModeChanged/ing events are raised when command buttons are clicked (Cancel, Edit, Insert, New and Update), i.e. when one doesn't have direct control over these events, and when we use the ChangeMode method, we know that the mode has changed (or will be changed) and it would make no sense of raising an event..
DetailsView ChangeMode:
public void ChangeMode(DetailsViewMode newMode) {
Mode = newMode;
}
DetailsView command handlers:
private void HandleCancel() {
bool isBoundToDataSourceControl = IsBoundUsingDataSourceID;
DetailsViewModeEventArgs e = new DetailsViewModeEventArgs(DefaultMode, true);
OnModeChanging(e);
if (e.Cancel) {
return;
}
if (isBoundToDataSourceControl) {
Mode = e.NewMode;
OnModeChanged(EventArgs.Empty);
}
RequiresDataBinding = true;
}
private void HandleEdit() {
if (PageIndex < 0) {
return;
}
DetailsViewModeEventArgs e = new DetailsViewModeEventArgs(DetailsViewMode.Edit, false);
OnModeChanging(e);
if (e.Cancel) {
return;
}
if (IsBoundUsingDataSourceID) {
Mode = e.NewMode;
OnModeChanged(EventArgs.Empty);
}
RequiresDataBinding = true;
}
private bool HandleInsertCallback(int affectedRows, Exception ex) {
DetailsViewInsertedEventArgs dea = new DetailsViewInsertedEventArgs(affectedRows, ex);
dea.SetValues(_insertValues);
OnItemInserted(dea);
_insertValues = null;
if (ex != null && !dea.ExceptionHandled) {
if (PageIsValidAfterModelException()) {
return false;
}
dea.KeepInInsertMode = true;
}
if (!dea.KeepInInsertMode) {
DetailsViewModeEventArgs eMode = new DetailsViewModeEventArgs(DefaultMode, false);
OnModeChanging(eMode);
if (!eMode.Cancel) {
Mode = eMode.NewMode;
OnModeChanged(EventArgs.Empty);
RequiresDataBinding = true;
}
}
return true;
}
private void HandleNew() {
DetailsViewModeEventArgs e = new DetailsViewModeEventArgs(DetailsViewMode.Insert, false);
OnModeChanging(e);
if (e.Cancel) {
return;
}
if (IsBoundUsingDataSourceID) {
Mode = e.NewMode;
OnModeChanged(EventArgs.Empty);
}
RequiresDataBinding = true;
}
private bool HandleUpdateCallback(int affectedRows, Exception ex) {
DetailsViewUpdatedEventArgs dea = new DetailsViewUpdatedEventArgs(affectedRows, ex);
dea.SetOldValues(_updateOldValues);
dea.SetNewValues(_updateNewValues);
dea.SetKeys(_updateKeys);
OnItemUpdated(dea);
_updateKeys = null;
_updateOldValues = null;
_updateNewValues = null;
if (ex != null && !dea.ExceptionHandled) {
if (PageIsValidAfterModelException()) {
return false;
}
dea.KeepInEditMode = true;
}
if (!dea.KeepInEditMode) {
DetailsViewModeEventArgs eMode = new DetailsViewModeEventArgs(DefaultMode, false);
OnModeChanging(eMode);
if (!eMode.Cancel) {
Mode = eMode.NewMode;
OnModeChanged(EventArgs.Empty);
RequiresDataBinding = true;
}
}
return true;
}
With ChangeMode you are choosing that the control switch to one of it's modes.
When it starts to performing this task, the ModeChanging event is raised (to indicate that it's in progress) (optionally do something here).
Once that task is completed, it raises the ModeChanged event (to indicate that it's done) (optionally do something here).
[Updated]
I see your point. How could you consume the events if they don't get raised.
I'm going to guess at it, that they don't get raised initially because of nothing to do, just perform the changing of the mode.
In either case I guess, it's more of a state change than the raising of events.
[Updated]
I think what we are both saying is that if no one has subscribed to the event (i.e., no one is listening for it), there's no point in raising it.

Back Button/ Cancel Button during In App Purchase

I am having trouble detecting the Exception that is thrown by the in-app-purchase store during Unit Test (Beta app) for Windows Phone 8 when I press the Cancel or Back button on the phone. The app simply exits.
There are no errors when I use the MockIAP. Cancel or Back Button returns an empty receipt variable during the await receipt = Store... It is handled correctly in MockIAP. But apparently Unit Test and the real app Store handleds Cancel or Back events differently. The app simply exits, which I believe because it is throwing an unhandled error.
My app is a Phonegap 2.3 and the purchase part is handled by the plugin. Unlike the MockIAP, I can't see (i.e. attach break points) what is happening on the wrapper side when Cancel or Back button is pressed during purchase. I have tried showing MessageBox.Show for every step of the purchase. The MessageBox.Show code is working when I press confirm purchase but not when I press Cancel or Back Button. I have made it synchronous already with EventWaitHandle.
In addition, I have set e.Handled = true for the unhandled Exception event to try to stop it from exit the app with no luck.
From online, my purchase code is boilerplate, so I dont' understand why other people hasn't come across this problem before, and why there are no solutions online. Does anyone have any idea how to fix this?
Purchase.cs (Plugin):
private static string receipt;
private async void purchaseProduct()
{
bool canBuy = false;
try
{
li = await Store.CurrentApp.LoadListingInformationAsync();
if (li.ProductListings.ContainsKey(package_id))
{
canBuy = true;
EventWaitHandle Wait = new AutoResetEvent(false);
Deployment.Current.Dispatcher.BeginInvoke(async () =>
{
// Here is the problem.. Don't know what is passed back to receipt when Cancel or Back is pressed, which is causing the app to close during Unit Test but not MockIAP
receipt = await Store.CurrentApp.RequestProductPurchaseAsync(package_id, true);
receipt = receipt.ToString();
Wait.Set();
});
Wait.WaitOne();
}
}
catch(Exception e)
{
var eMsg = e.Message.ToString();
errorMsg("Catch Exception: ", eMsg);
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
}
finally
{
errorMsg("Receipt with await: ", receipt);
if (canBuy && receipt!= "")
{
errorMsg("Hitting the parsing", "");
parseXML(receipt);
prepData();
httpPostData();
Store.CurrentApp.ReportProductFulfillment(package_id);
}
else
{
errorMsg("Else Finally", "");
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
}
}
}
private static void errorMsg(String caption, String msg)
{
EventWaitHandle Wait = new AutoResetEvent(false);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(caption + msg);
Wait.Set();
});
Wait.WaitOne();
}
App.cs
private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
EventWaitHandle Wait = new AutoResetEvent(false);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
MessageBox.Show("Unhandled Exception: " + ex.Message);
Wait.Set();
});
Wait.WaitOne();
// Stop from exiting..
e.Handled = true;
if (System.Diagnostics.Debugger.IsAttached)
{
// An unhandled exception has occurred; break into the debugger
//System.Diagnostics.Debugger.Break();
}
}
to fix this enclose try/catch around RequestProductPurchaseAsync method call even though you had a try/catch for entire method...
try
{
receipt = await CurrentApp.RequestProductPurchaseAsync("MyItem", false);
}
catch (Exception){}
.... other code

Categories

Resources