I make a connection in C# with a login and a password and I would like
put a button to cancel the connection to the database if it gets too much
long.
I would like to know how to do it and put it in a thread if possible.
Here is the code:
private void btncon_Click(object sender, EventArgs e)
{
string strLogin = tblogin.Text.Trim();
string pass = tbpwd.Text;
bool success = false;
if (String.IsNullOrWhiteSpace(strLogin) || String.IsNullOrWhiteSpace(pass))
{
MessageBox.Show("Veuillez remplir tous les champs SVP ");
}
else if(String.IsNullOrEmpty(strLogin) || String.IsNullOrEmpty(pass)){
MessageBox.Show("Veuillez remplir tous les champs SVP ");
}
else
{
model.Connexion cm = new model.Connexion();
pass = Snippets.SHA1Util.SHA1HashStringForUTF8String(pass).ToString();
string[] user = cm.login(strLogin, pass);
if(user[0] != null)
{
Int32.TryParse(user[0], out iduser);
Int32.TryParse(user[1], out idGrp);
Int32.TryParse(user[2], out idbtq);
nom = user[3];
if (idGrp != 3)
success = true;
else
{
MessageBox.Show("Accès Non autorisé , Veuillez contacter l'administrateur");
success = false;
}
}
else
{
MessageBox.Show("Email ou mot de passe incorrect.");
success = false;
}
}
if (success)
{
main Principale = new main();
Principale.Show();
Hide();`
}
}
Problem is that when user clicks the Cancel button, there is no way to cancel the cm.login() method gracefully while it's executing. You could use Thread.Abort() to terminate login forcefully, but it's unsafe and strongly discouraged, because making it right would require execute code in another AppDomain and would make the code very complicated.
Fortunatelly, you can still implement Cancel button, if the following conditions are true:
It is safe to call cm.login() on another thread
Letting cm.login() finish in the background (on another thread) after user clicks on the Cancel button does not have undesired effects.
This code also assumes that it is an Winform app (but solution for WPF app would be very similar). It also assumes that main form has a button btnCancel that is hidden (Visible=false) and it's Click event handler is set to btnCancel_Click method.
TaskCompletionSource<Object> CancelLoginTcs = new TaskCompletionSource<Object>();
// Make button click handler method async
private async void btncon_Click(object sender, EventArgs e)
{
try
{
// Make Cancel button visible, so that user can click on it
btnCancel.Visible = true;
// Prepare everything needed to start login
//var strLogin = ...;
//var pass = ...;
//model.Connexion cm = new model.Connexion();
// ...
// Start login on another thread
var loginTask = Task<string[]>.Run(() => cm.login(strLogin, pass));
// Create task that is used to wake-up main thread, when user clicks
// on the Cancel button before login finishes.
CancelLoginTcs = new TaskCompletionSource<Object>();
// Wait for login task or cancel task to complete, whichever finishes first
await Task.WhenAny(loginTask, CancelLoginTcs.Task);
if (CancelLoginTcs.Task.IsCanceled)
{
// User clicked on the Cancel button.
// Login method will be running in the background, until it
// finishes. This assumes that it is safe to do so.
// Here you should do neccessary clean-up, inform user, etc.
// ...
}
else
{
// Login finished and user did NOT click on the Cancel button.
try
{
// Simply read result of login. If an Exception occured during login,
// it will be rethrow now, so you should handle it appropriatelly
var user = loginTask.Result;
// Here program continues in a usual way
// ...
}
catch(Exception E)
{
// Handle login exception
// ...
}
}
}
finally
{
// Hide Cancel button again
btnCancel.Visible = false;
CancelLoginTcs = null;
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
// Set cancel task to cancelled state.
// This will wake-up main thread and let it continue
CancelLoginTcs.SetCanceled();
}
Related
I have a WPF application that fetches data from a database and displays information in the form a table. There is a refresh button that can be clicked and it updates the table. When the user clicks on the refresh button repeatedly the application hangs for a couple seconds.
I tried experimenting with async and await but it is still locking up.
Before:
public void Refresh_Handler(object sender, System.Windows.RoutedEventArgs e)
{
this.Refresh(); //asynchronous method that has several nested awaits
...
}
After:
var task = Task.Run(() =>
{
this.Refresh();
});
async void runTask()
{
this.cts = new CancellationTokenSource();
try
{
await task();
}
catch (OperationCanceledException ex)
{
Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {ex.Message}");
}
finally
{
this.cts = null;
}
}
if (this.cts == null)
{
runTask();
}
else
{
this.cts.Cancel();
runTask();
}
How can I cancel previous/pending refresh clicks and make the UI responsive? Currently it is stacking the requests and locking the application when the refresh button is pressed repeatedly. I would like it to kick off a brand new request on the final button click so that the app doesn't hang.
Can anyone help me understand why my call to dialogservice executes after the CanNavigateAway function has returned its value? (My goal is to warn the user they are about to navigate away from a view without saving their changes. If they click OK, the navigation is allowed. I'm using MVVM Light.
When I step through the code, it does reach the dialog service, but then proceeds to the end of CanNavigateAway before creating the dialog. The CanNavigateAway method is called by OnNavigatingFrom.
public bool CanNavigateAway()
{
if (!changesSaved && Model.IsModified && !continueNavigation)
{
dialogService.ShowMessage("Are you sure you want to continue?",
"Confirmation",
buttonConfirmText: "Continue", buttonCancelText: "Discard",
afterHideCallback: (confirmed) =>
{
if (confirmed)
{
// User has pressed the "confirm" button.
// ...
continueNavigation = true;
}
else
{
// User has pressed the "cancel" button
// (or has discared the dialog box).
// ...
continueNavigation = false;
}
});
return continueNavigation;
}
}
Here is the OnNavigatingFrom method from the MVVM Light Bindable Page class:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
if (!navigableViewModel.CanNavigateAway())
{
e.Cancel = true;
}
}
}
I tried this a different way to get the dialog service out of the mix, but showConfirmationDialogAsync still does not seem to execute in time:
public bool CanNavigateAway()
{
continueNavigation = false;
if (!changesSaved && Model.IsModified && !continueNavigation)
{
showConfirmationDialogAsync();
return continueNavigation;
}
private async void showConfirmationDialogAsync()
{
continueNavigation = false;
ContentDialog noSaveConfirmation = new ContentDialog
{
Title = "Warning",
Content = "You have unsaved changes. Are you sure you want to leave this page without saving?",
PrimaryButtonText = "Leave without saving",
SecondaryButtonText = "Stay and finish"
};
ContentDialogResult result = await noSaveConfirmation.ShowAsync();
if (result == ContentDialogResult.Primary)
{
continueNavigation = true;
}
else if (result == ContentDialogResult.Secondary)
{
continueNavigation = false;
}
}
None of the solutions will work if you require a response from the user. The problem is that when the code is inside the navigation event handler, it is running on the UI thread and the user prompt runs asynchronously, so that the UI is free to present the dialog to the user. This however means that the event handler finishes before the user has a chance to respond.
However, you can use a workaround solution. Add a flag bool field like forceNavigation. Then inside the OnNavigatingFrom display the dialog to the user and set Cancel to true right away and display the user the confirmation dialog. If the user says yes, then set forceNavigaiton to true and trigger the navigation manually again. Now it will skip the confirmation part and navigate right away.
protected async override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
//if navigation is forced, skip all logic
if ( !forceNavigation )
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
e.Cancel = true;
//display the dialog to the user, if he says yes, set
//forceNavigation = true; and repeat the navigation (e.g. GoBack, ... )
}
}
}
In my login window, when I click the login button, the configuration and login processes will be executed and those methods are in another class file. So far what I've achieved is when I clicked the login button, the loading animation will be displayed on top of the login window and those processes will be executed as well. There are some login error checking in the configuration class file, so when login error caught, a message box with relevant info will be prompted out and stop the login process, the problem is the message box will not be prompted out since I put those config and login process in the BackgroundWorker.DoWork event.
Here's the codes for Login.xaml.cs:
private void LoginBtn_Click(object sender, RoutedEventArgs e)
{
Loading.Visibility = Visibility.Visible; //The loading animation
Loading.Visibility = Visibility.Visible;
Cursor = Cursors.Wait;
bw.DoWork += new DoWorkEventHandler(LoginInfoVerification);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(RunWorkerCompleted);
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
}
private void LoginInfoVerification(object sender, DoWorkEventArgs e) {
var loginInfoVerification = config.ServerConnection(loginInfo.userName,
loginInfo.galPassword, loginInfo.place,
loginInfo.host, loginInfo.port,
loginInfo.application);
}
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (GlobalVariables.loginSuccess == true)
{
//Pass these variables to main window
var mainWindow = new MainWindow(loginInfo.userName, loginInfo.place, loginInfo.host, loginInfo.port,
loginInfo.application);
mainWindow.Show();
this.Close();
}
else
Loading.Visibility = Visibility.Collapsed;
Cursor = Cursors.Arrow;
}
For Configuration.cs:
public Configuration ConfigServerConnection(string loginUserName, string loginPassword, string loginPlace, string loginHost, int loginPort, string loginApplication)
{
//Server connection
//Login info verification
//If login error caught, prompt message box, different errors, different message
}
Is there any better suggestion in handling UI update and process update at the same time for my case? Please help.
To display the message box you need to switch back to the UI thread with Dispatcher.Invoke, see this.
Application.Current.Dispatcher.Invoke(() => /* show appropriate message box */);
Alternatively if you are using .NET 4.5 or higher you can make your life a lot easier with async-await by marking LoginBtn_Click with the async keyword and then awaiting the log in process.
If there is an asynchronous version of ServerConnection that returns a task you can just await that otherwise you can use Task.Run() to execute ServerConnection on a thread pool thread.
Await will kick off the log in operation asynchronously and once complete will resume the rest of the method on the GUI thread so you can manipulate GUI components without using Dispatcher.Invoke.
private async void LoginBtn_Click(object sender, RoutedEventArgs e)
{
Loading.Visibility = Visibility.Visible; //The loading animation
Loading.Visibility = Visibility.Visible;
Cursor = Cursors.Wait;
LoginVerification loginInfoVerification = null;
await Task.Run(() =>
{
loginInfoVerification = config.ServerConnection(loginInfo.userName,
loginInfo.galPassword,
loginInfo.place,
loginInfo.host,
loginInfo.port,
loginInfo.application);
});
.. rest of code, check login success, show message box..
}
I have a code which reads information from a file and displays them to the user.... now i want to STOP my code after displaying the information to the the user and WAIT for the buttonpress which starts my event, because the button clears the textbox and returns some information the the user / admin ...
But i don't have any idea how to break my code from running and wait for the button to get pressed...
Thx a lot
StringBuilder strbuildsection = new StringBuilder();
StringBuilder strbuildbetreff = new StringBuilder();
StringBuilder strbuildinhalt = new StringBuilder();
StringBuilder strbuilduser = new StringBuilder(System.Environment.UserName);
StringBuilder strbuildusercheck = new StringBuilder();
foreach (string Ccat in this.ini.IniGetCategories())
{
string readval = ini.IniReadValue(Ccat, "Read");
string usercheckvar = (this.ini.IniReadValue(Ccat, "SpecifyUser"));
string user = System.Environment.UserName;
if (readval == "0")
{
if (usercheckvar == user || usercheckvar.Equals("All"))
{
strbuildsection.Append(Ccat + Environment.NewLine);
foreach (string cat in this.ini.IniGetKeys(Ccat))
{
strbuildinhalt.Clear();
strbuildusercheck.Clear();
strbuildbetreff.Clear();
strbuildbetreff.Append(this.ini.IniReadValue(Ccat, "Betreff") + Environment.NewLine);
strbuildinhalt.Append(this.ini.IniReadValue(Ccat, "Inhalt") + Environment.NewLine);
}
textBox1.AppendText(strbuildsection.ToString() + strbuildbetreff.ToString() + strbuildinhalt.ToString() + strbuildusercheck.ToString() + Environment.NewLine);
strbuildsection.Clear();
// HERE I want to stop my process and wait until the User
// press the button and start my event
// but i don't know how to do this
// After this the loop continue and so on
}
private void BT_Bestaetigung_Click(object sender, EventArgs e)
{
CODE
}
So i want to start my ´Event´ if the button get pressed and if not, the code should wait for this
As it seems you placed the code in the form which you showed to the user and this blocks you from stopping to wait for the user to respond because you are still inside your loop.
The solution is to use a separate modal dialog:
Create a separate Form which you construct inside your loop and show it to the user when necessary - wait for the form to be closed - work on the results and repeat until done.
Inside this new form you place your controls and buttons that the user needs to interact with and fill them before you show it to him.
frmConfirmationDialog myConfirmationDialog = new frmConfirmationDialog()
//Fill in information to show to the user
myConfirmationDialog.textBox1.AppendText(strbuildsection.ToString() + strbuildbetreff.ToString() + strbuildinhalt.ToString() + strbuildusercheck.ToString() + Environment.NewLine);
//Open Form modally (this will stop your loop until the dialog is closed)
DialogResult myResult = myConfirmationDialog.ShowDialog();
if (myResult == System.Windows.Forms.DialogResult.OK)
{
//Do Stuff here
}
else //catch further type of results here (you could also work with a switch statement
{
//Do Stuff here
}
BTW to get a DialogResult when closing the form set the DialogResult Property of the Confirm or Cancel Buttons to the values you like. This will cause the modal form to be automatically closed with the DialogResult of the button. If you need to catch stuff before closing the form you can either implement an EventHandler for FormClosing or handle the Click event of the buttons.
You have to think it this way: you have a continous operation and you are waiting for the user interaction.
In winforms application, where you can not perform continous operation in UI thread (because your window required constantly receive messages from os, i.e. user input or repaint requrests and have to react to them, or you will have laggy or even frozen UI), typical solution is:
split operation into sub-operations (which are short and doesn't cause UI lags);
put operation into a thread.
So, basicaly, go this way (there is a pattern to avoid using switch with delegating steps, but for simplicity we show classical way):
_timer = new Timer();
_timer.Tick += timer_Tick;
...
// in timer tick
switch(_operation)
{
case operation1:
_operation = nextoperation; // _operation++
...
break;
case operation2:
// do nothing, wait for button press
if(_buttonPressed)
_operation = nextoperation;
break;
case operation3:
// continue after button press
..
...
}
or this way
_thread = new Thread(...)
thread.Start();
...
// in thread
// start operation
...
// wait for button press
while(_buttonPressed)
Thread.Sleep(0);
// continue operation
...
in both cases
// in button click event
_buttonPressed = true;
Random rnd = new Random();
bool stop = false;
Thread thread = null;
btnShow.Click += (source, e) =>
{
stop = true;
MessageBox.Show("Show something");
};
btnClose.Click += (source, e) =>
{
stop = false;
};
if (thread == null)
{
thread = new Thread(() =>
{
while (true) // your working loop
{
while (stop)
{}
// action of your loop
Console.WriteLine(rnd.Next());
}
});
thread.Start();
}
Individually, all code works perfectly. The snippet for saving the file, the snippet for picking a directory to save it to and also the message dialog works great.
But when I tie it all together, I get an access denied. I am not using the DocumentsLibrary capability since it is not required of me to do so in this case, however, enabling this capability after running into issues confirmed that it is not the issue.
Scenario:
User wants to create a new document after entering text in the text box. A MessageDialog appears, asking them if they want to save changes to the existing file first - the user clicks Yes (save file).
Now, here is where you handle the event that was raised by the MessageDialog.
Inside the IUICommand command event handler, you test for which button was clicked, and act accordingly.
I did this with a switch statement:
switch(command.Label) {
case "Yes":
SaveFile(); // extension method containing save file code that works on its own
break;
case "No":
ClearDocument();
break;
default:
break;
}
Now, each case works great except for the Yes button. When you click yes, an e tension method is called which has code that saves to a file
It is when you click yes button that you get the ACCESS DENIED exception. Details of the exception didn't reveal anything.
I think that it has something to do with how I am using the MesaageDialog. But after searching for hours I have yet to find a sample on how to save a file with the FileSavePicker when a MesaageDialog button is pressed.
Any ideas in how this should be done?
Update w/ Code
When the user clicks the New document button on the AppBar, this method fires:
async private void New_Click(object sender, RoutedEventArgs e)
{
if (NoteHasChanged)
{
// Prompt to save changed before closing the file and creating a new one.
if (!HasEverBeenSaved)
{
MessageDialog dialog = new MessageDialog("Do you want to save this file before creating a new one?",
"Confirmation");
dialog.Commands.Add(new UICommand("Yes", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.Commands.Add(new UICommand("No", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.DefaultCommandIndex = 0;
dialog.CancelCommandIndex = 2;
// Show it.
await dialog.ShowAsync();
}
else { }
}
else
{
// Discard changes and create a new file.
RESET();
}
}
And the FileSavePicker stuff:
private void CommandInvokedHandler(IUICommand command)
{
// Display message showing the label of the command that was invoked
switch (command.Label)
{
case "Yes":
MainPage rootPage = this;
if (rootPage.EnsureUnsnapped())
{
// Yes was chosen. Save the file.
SaveNewFileAs();
}
break;
case "No":
RESET(); // Done.
break;
default:
// Not sure what to do, here.
break;
}
}
async public void SaveNewFileAs()
{
try
{
FileSavePicker saver = new FileSavePicker();
saver.SuggestedStartLocation = PickerLocationId.Desktop;
saver.CommitButtonText = "Save";
saver.DefaultFileExtension = ".txt";
saver.FileTypeChoices.Add("Plain Text", new List<String>() { ".txt" });
saver.SuggestedFileName = noteTitle.Text;
StorageFile file = await saver.PickSaveFileAsync();
thisFile = file;
if (file != null)
{
CachedFileManager.DeferUpdates(thisFile);
await FileIO.WriteTextAsync(thisFile, theNote.Text);
FileUpdateStatus fus = await CachedFileManager.CompleteUpdatesAsync(thisFile);
//if (fus == FileUpdateStatus.Complete)
// value = true;
//else
// value = false;
}
else
{
// Operation cancelled.
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.InnerException);
}
}
Any progress on this issue? I currently have the same problem. I have also found that the same problem occurs if a second MessageDialog is shown in the IUICommand event.
My solution is to cancel the first operation (that shows the first message dialog). Here some code I’m using (it’s accessible in a global object):
private IAsyncInfo mActiveDialogOperation = null;
private object mOperationMutex = new object();
private void ClearActiveOperation(IAsyncInfo operation)
{
lock (mOperationMutex)
{
if (mActiveDialogOperation == operation)
mActiveDialogOperation = null;
}
}
private void SetActiveOperation(IAsyncInfo operation)
{
lock (mOperationMutex)
{
if (mActiveDialogOperation != null)
{
mActiveDialogOperation.Cancel();
}
mActiveDialogOperation = operation;
}
}
public void StopActiveOperations()
{
SetActiveOperation(null);
}
public async void ShowDialog(MessageDialog dialog)
{
StopActiveOperations();
try
{
IAsyncOperation<IUICommand> newOperation = dialog.ShowAsync();
SetActiveOperation(newOperation);
await newOperation;
ClearActiveOperation(newOperation);
}
catch (System.Threading.Tasks.TaskCanceledException e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
So every time I want to show a MessageDialog I call ShowDialog. This will cancel the current dialog if any (then a TaskCanceledException occurs).
In the case when I will use a FileSavePicker, I call StopActiveOperations before PickSaveFileAsync is called.
This works but I can’t say I like it. It feels like I’m doing something wrong.
OK, now I have figured it out :-). The documentation says explicit that you shouldn’t show new popups/file pickers in the UICommand:
http://msdn.microsoft.com/en-US/library/windows/apps/windows.ui.popups.messagedialog.showasync
This is an example of a bad way to do it:
private async void Button_Click(object sender, RoutedEventArgs e)
{
MessageDialog dialog = new MessageDialog("Press ok to show new dialog (the application will crash).");
dialog.Commands.Add(new UICommand("OK", new UICommandInvokedHandler(OnDialogOkTest1)));
dialog.Commands.Add(new UICommand("Cancel"));
await dialog.ShowAsync();
}
private async void OnDialogOkTest1(IUICommand command)
{
MessageDialog secondDialog = new MessageDialog("This is the second dialog");
secondDialog.Commands.Add(new UICommand("OK"));
await secondDialog.ShowAsync();
}
This is the correct way to do it:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
MessageDialog dialog = new MessageDialog("Press ok to show new dialog");
UICommand okCommand = new UICommand("OK");
UICommand cancelCommand = new UICommand("Cancel");
dialog.Commands.Add(okCommand);
dialog.Commands.Add(cancelCommand);
IUICommand response = await dialog.ShowAsync();
if( response == okCommand )
{
MessageDialog secondDialog = new MessageDialog("This is the second dialog");
secondDialog.Commands.Add(new UICommand("OK"));
await secondDialog.ShowAsync();
}
}
Quite simple actually, I should have get this earlier...