How to manage loading animation thread? - C# - c#

I am trying to do a loading form. So that I've put an animated gif to do the loading effect.
What happens is that when I use the loading.Visible = true property this is not visible until the click event ends.
Here is the code.
Private void btnLogin_Click (object sender, EventArgs e)
{
     loading.Visible = true;
     if (ConnectDataBase())
     {
         OpenForm2();
         This.Close();
     }
     else MessageBox.Show ("User or password, incorrect");
     loading.Visible = false;
}
The Data Base takes 3 to 6 seconds to respond in the ConnectDataBase() function, but the gif does not become visible until the event ends.
Someone could tell me, how could I do this?

You will need to run the database connection in the background. There are many ways of doing this but with the advent of async/await, this would be the easiest way:
private async void btnLogin_Click (object sender, EventArgs e)
{
loading.Visible = true;
if (await Task.Run(() => ConnectDataBase()))
{
OpenForm2();
This.Close();
}
else MessageBox.Show ("User or password, incorrect");
loading.Visible = false;
}
Note the following changes:
I made the method async void to signal to the compiler that we want to use async/await
I created a task to run the database connection:
await Task.Run(() => ConnectDataBase())
This will return a bool, the result of ConnectDataBase() and will
essentially do the same thing as the other answer here, just with
less manual clutter and handling of tasks.
Observe: With background execution like this it is possible for the user to click on the Login button again, while the first click is still executing (and trying to connect to the database). You should take steps to ensure this is not possible, like disabling the login button (and other fields, such as username, password, etc.) while this is executing, something like:
private async void btnLogin_Click (object sender, EventArgs e)
{
btnLogin.Enabled = eUsername.Enabled = ePassword.Enabled = false;
loading.Visible = true;
... rest of method
loading.Visible = false;
btnLogin.Enabled = eUsername.Enabled = ePassword.Enabled = true;
}
Note It would also be better, if possible, to rewrite the ConnectDataBase method to be async in nature as most .NET database connection services nowadays have methods to do this. Without knowing more about what this method does I can only make a general comment.
Here's an example of how to write a SQL Server connection attempt:
public static async Task<bool> ConnectDataBase()
{
var connection = new SqlConnection("connection string");
try
{
await connection.OpenAsync();
// store "connection" somewhere I assume?
return true;
}
catch (SqlException)
{
connection.Dispose();
return false;
}
}
With this change you would change your if-statement in your original method to this:
if (await ConnectDataBase())

In order for this to work you need to invoke ConnectDataBase() on a different thread. One way to do this is:
private void btnLogin_Click(object sender, EventArgs e)
{
loading.Visible = true;
Task.Run<bool>(() =>
{
return ConnectDataBase();
})
.ContinueWith(t =>
{
if (t.Result)
{
OpenForm2();
this.Close();
}
else MessageBox.Show("User or password, incorrect");
}, TaskScheduler.FromCurrentSynchronizationContext());
}

Related

C# WPF Update Label before and after processing - immediately

I have already tried several online examples (Thread, Dispatcher, await/async) but none is working for me in my C#/WPF project.
I have the following button click method:
private void BtnInstall_Click(object sender, RoutedEventArgs e)
{
this.lblResponse.Content = "";
executeInstall(); //do some work
this.lblResponse.Content = "DONE";
}
The label gets updated afterwards to DONE, but when I click again on the button the label isnt getting emptied before the processing of executeInstall.
As I mentioned I already tried several different examples from other questions (Dispatcher.BeginInvoke, Thread, Task, await/async) but none of them has worked - the label change before is never done before the processing of executeInstall.
I am working in .NET framework 4.7.2.
Is there maybe a setting that debug mode only executes the program with one thread and thats maybe why none of the solutions works for me?
Use async for that.
private async void BtnInstall_Click(object sender, RoutedEventArgs e)
{
this.lblResponse.Content = "";
await Task.Run(()=> executeInstall());
this.lblResponse.Content = "DONE";
}
UPDATE: If you need to access the UI inside your executeIntall method you will need to invoke the Dispatcher. In this case you would need to delay the Task to give the label time to update before the install starts. Note that this will cause the UI to freeze during the entire install.
private async void BtnInstall_Click(object sender, RoutedEventArgs e)
{
lblResponse.Content = "starting...";
await Task.Delay(100).ContinueWith(_=>
{
App.Current.Dispatcher.Invoke(() =>
{
executeInstall();
lblResponse.Content = "DONE";
});
});
}
A better approach would be to only call the dispatcher when it's actually needed. This would keep the UI responsive during the entire process.
private async void BtnInstall_Click(object sender, RoutedEventArgs e)
{
lblResponse.Content = "starting...";
await Task.Run(()=> executeInstall());
lblResponse.Content = "DONE";
}
private void executeInstall()
{
Thread.Sleep(1000); //do time consuming operation
App.Current.Dispatcher.Invoke(() => lblResponse.Content = "Downloading Files...");
Thread.Sleep(1000); //do time consuming operation
App.Current.Dispatcher.Invoke(() => lblResponse.Content = "Unzipping Files...");
Thread.Sleep(1000); //do time consuming operation
App.Current.Dispatcher.Invoke(() => lblResponse.Content = "Updating Files...");
Thread.Sleep(1000); //do time consuming operation
}

c# append text withou waiting for onclick event to finish

I am doing an c# small app with a richtextbox and a button that will check if some files exists and display the result into the richtextbox.
private void RunButton_Click(object sender, EventArgs e)
{
myRichTextBox.AppendText("Starting...");
if (checkFileExists())
{
myRichTextBox.AppendText("File exists.");
}
else
{
myRichTextBox.AppendText("File NOT exists!");
}
}
The problem I am facing is that the text "Starting..." is appended into the richtextbox when the checkFileExists() has finished. Since this one can take some time I would like to display the "Starting" message at the beginning and the rest of text messages when checkFileExists has finished.
Can you help me with that?
thanks in advance!
You need to set the text on the main thread, then start the long running process (checking whether the file exists) on a separate thread. Once that's finished you can update the text with the result - but that would have to be marshalled back to the main (UI) thread.
Here's one way to do it.
private void button1_Click(object sender, EventArgs e)
{
button1.Text = "Starting...";
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => CheckIfFileExists(uiScheduler));
}
private async Task CheckIfFileExists(TaskScheduler uiScheduler)
{
await Task.Delay(2000);
var exists = true; // check if exists
await Task.Factory.StartNew(() => UpdateText(exists), CancellationToken.None, TaskCreationOptions.None, uiScheduler).ConfigureAwait(false);
}
private void UpdateText(bool exists)
{
button1.Text = $"Exists: {exists}.";
}

How to update some GUI control in a WinForm from another thread in C# using async

I'm trying to update the text of some GUI controls from a second thread but Visual Studio shows an exception:
An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code
Additional information:
Invalid operation through sub-processes: access to 'impPROF_combo' took control from a other thread than that in which he created .
My code is:
private async void checkBox1_CheckedChanged(object sender, EventArgs e)
{
sciLoadingCircle1.Visible = true;
sciLoadingCircle1.Active = true;
await Task.Run(() => refreshComboColumnsNames());
sciLoadingCircle1.Visible = false;
sciLoadingCircle1.Active = false;
}
private void refreshComboColumnsNames()
{
object[] columnsNames = this.generateComboColumnsNames();
int impPROF_combo_selected = impPROF_combo.SelectedIndex; //the exceptions throws from here
impPROF_combo.Items.Clear();
impPROF_combo.Items.AddRange(columnsNames);
impPROF_combo.SelectedIndex = impPROF_combo_selected;
}
How I can do this the right way?
Thank you.
UI components should only be accessed from their UI thread. In this case, you're putting too much code in the Task.Run:
private async void checkBox1_CheckedChanged(object sender, EventArgs e)
{
sciLoadingCircle1.Visible = true;
sciLoadingCircle1.Active = true;
await refreshComboColumnsNamesAsync();
sciLoadingCircle1.Visible = false;
sciLoadingCircle1.Active = false;
}
private async Task refreshComboColumnsNamesAsync()
{
object[] columnsNames = await Task.Run(() => this.generateComboColumnsNames());
int impPROF_combo_selected = impPROF_combo.SelectedIndex;
impPROF_combo.Items.Clear();
impPROF_combo.Items.AddRange(columnsNames);
impPROF_combo.SelectedIndex = impPROF_combo_selected;
}
In order to update GUI (or use) elements from another thread you should use Control.BeginInvoke() https://msdn.microsoft.com/ru-ru/library/system.windows.forms.control.begininvoke(v=vs.110).aspx
And here is how to use it.
How to use BeginInvoke C#
Usual reason why you want to execute some code in a thread other than GUI thread is that the operation takes to much time to finish and blocks GUI thread.
I can see in refreshComboColumnsNames method the only line of code, which can potentially take time - generateComboColumnsNames. Other statements are GUI update operations, which have to be executed in GUI thread. As I understand, generateComboColumnsNames does some heavy calculations or executes db query or sends http request or does any other long operations. Result of this operation needs to be displayed in UI. Correct me, if I am wrong.
I would suggest to make generateComboColumnsNames async operating, which is executed in separate thread. This operation must not update UI - just return data.
Result will look like next:
private async void checkBox1_CheckedChanged(object sender, EventArgs e)
{
sciLoadingCircle1.Visible = true;
sciLoadingCircle1.Active = true;
await refreshComboColumnsNames();
sciLoadingCircle1.Visible = false;
sciLoadingCircle1.Active = false;
}
private async Task refreshComboColumnsNames()
{
object[] columnsNames = await this.generateComboColumnsNames();
int impPROF_combo_selected = impPROF_combo.SelectedIndex;
impPROF_combo.Items.Clear();
impPROF_combo.Items.AddRange(columnsNames);
impPROF_combo.SelectedIndex = impPROF_combo_selected;
}

Timer issue in C# with threads

I am developing a skype-like application, I have an external DLL that do most of the work and fires events handled in my class ip2ip, one of this events is incoming_call fired when there is an incoming call as the name suggest. I'm trying to manage missed calls.
Now this is the relevant part of the code in this class:
private void ics_IncomingCall(object sender, string authenticationData, int socketHandle, string callbackid, string callbackipaddress, int callbackvideoport, int callbackaudiotcpport, int callbackaudiudpport)
{
if (Calling)
{
ics.RejectCall("The contact have another call", (IntPtr)socketHandle);
Message = "An incoming call from [" + callbackipaddress + "] has rejected.";
}
else
{
AcceptIncomingCall = null;
UserCaller = FindUserName(callbackipaddress);
IncomingCall = true;
//waiting for the call to be accepted from outside of this class
while (AcceptIncomingCall.HasValue == false) Thread.Sleep(100);
if(AcceptIncomingCall.Value == true)
{
//call back to have a 1 on one video conference
icc.Parent.BeginInvoke(new MethodInvoker(delegate
{
//accept the incoming call
ics.AcceptCall("n/a", socketHandle);
icc.Call(callbackipaddress, callbackvideoport, 0, 0,
"n/a", callbackid,
ics.GetLocalIp()[0].ToString(), 0, 0, 0, "");
Calling = true;
}));
}
else
{
ics.RejectCall("Call not accepted", (IntPtr)socketHandle);
Log = "Incoming call not accepted";
Calling = false;
}
AcceptIncomingCall = null;
IncomingCall = false;
}
}
IncomingCall is a property generating a PropertyChangedEvent, wich is captured in my main class where I have this code:
private void ip2ip_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e != null && string.IsNullOrEmpty(e.PropertyName) == false)
{
..............
if (e.PropertyName.Equals("IncomingCall") && ip2ip.IncomingCall == true)
{
Invoke(new MethodInvoker(delegate
{
pnlCalling.Visible = true;
aTimer.Start();
}));
}
................
}
}
public Form1()
{
.......
aTimer = new System.Windows.Forms.Timer();
aTimer.Interval = 10000;
aTimer.Tick += aTimer_Tick;
}
void aTimer_Tick(object sender, EventArgs e)
{
aTimer.Stop();
btnNo.PerformClick();
}
private void btnNo_Click(object sender, EventArgs e)
{
aTimer.Stop();
ip2ip.AcceptIncomingCall = false;
}
private void btnOk_Click(object sender, EventArgs e)
{
aTimer.Stop();
ip2ip.AcceptIncomingCall = true;
}
I need the timer to manage the missed call, when there is an incoming call a panel appears, with buttons to accept/reject the call. If the user waits too much the call is considered rejected (missed).
In this way it doesn't work, probably I'm doing something wrong with the timer, as without any timer everything works. I also tried the timer of the class System.Timers with same results. Any Idea?
EDIT
This is my expectation, there is an incoming call so the event ics_IncomingCall is fired, IncomingCall=true cause the execution to go to the main class (we are still in same thread, I see it debugging step by step in VS) where is invoked in the GUI thread the panel to be visible and started the timer, now we have one thread where a while loop block the execution until in the other thread user do something (accept/reject).
The problem exist when the user accept the call, the code after the while loop is always executed, the caller has no problem at all and receive the stream, but in the receiver (who receive the stream as I verified in wireshark) the DLL (who is responsible to show the incoming video) fails to do its job for some reason unknown to me but caused by the timer.
It is unfortunate your question does not include a good, minimal, complete code example that reliably reproduces the problem. Having such a code example would make it much more practical for someone to provide a useful answer.
That said, as explained by commenter varocarbas, your fundamental problem appears to be that you have blocked the UI thread (with the while loop), while at the same time hoping for the UI thread to handle other activity (such as the timer's tick event). In fact, you are also preventing the button click from having an effect. The button Click event handlers can't execute either, while the UI thread is blocked.
One possible way to fix this would be to use a TaskCompletionSource<T> to provide the ics_IncomingCall() with a waitable object, which the buttons and timer can use to signal. For example:
// Change from "bool?" to this:
private TaskCompletionSource<bool> AcceptIncomingCall;
public void HandleCall(bool accept)
{
AcceptIncomingCall.SetResult(accept);
}
private async Task ics_IncomingCall(object sender, string authenticationData, int socketHandle, string callbackid, string callbackipaddress, int callbackvideoport, int callbackaudiotcpport, int callbackaudiudpport)
{
if (Calling)
{
ics.RejectCall("The contact have another call", (IntPtr)socketHandle);
Message = "An incoming call from [" + callbackipaddress + "] has rejected.";
}
else
{
AcceptIncomingCall = new TaskCompletionSource<bool>();
UserCaller = FindUserName(callbackipaddress);
IncomingCall = true;
//waiting for the call to be accepted from outside of this class
if (await AcceptIncomingCall.Task)
{
//call back to have a 1 on one video conference
icc.Parent.BeginInvoke(new MethodInvoker(delegate
{
//accept the incoming call
ics.AcceptCall("n/a", socketHandle);
icc.Call(callbackipaddress, callbackvideoport, 0, 0,
"n/a", callbackid,
ics.GetLocalIp()[0].ToString(), 0, 0, 0, "");
Calling = true;
}));
}
else
{
ics.RejectCall("Call not accepted", (IntPtr)socketHandle);
Log = "Incoming call not accepted";
Calling = false;
}
AcceptIncomingCall.Dispose();
IncomingCall = false;
}
}
and:
void aTimer_Tick(object sender, EventArgs e)
{
aTimer.Stop();
btnNo.PerformClick();
}
private void btnNo_Click(object sender, EventArgs e)
{
aTimer.Stop();
genericServerClient.HandleCall(false);
}
private void btnOk_Click(object sender, EventArgs e)
{
aTimer.Stop();
genericServerClient.HandleCall(false);
}
This causes the ics_IncomingCall() method to return when it reaches the await statement, allowing its thread to continue executing. The button Click event handlers will call back to the public method that encapsulates your field (public fields are very dangerous and should be avoided in almost all situations), setting the result value for the TaskCompletionSource object that is being awaited.
Once the result value has been set, this will cause the framework to resume executing your ics_IncomingCall() method where it left off, but now with the value returned from the button Click event handlers. I.e. true if the user clicked the btnOk and false if they clicked btnNo or the timer interval elapsed.
Note that this changes the signature of your ics_IncomingCall() method, which will force a change to the caller. The best way to handle that will be to change the caller as well, to be async and to use await ics_IncomingCall(...). That will of course force a change in its caller, and its caller's caller, and so on. But you need to release the UI thread, and this is the best way to do it. Hopefully you don't have a lot of callers to change, but even if you do, this is the way to go.
If the above does not seem to address your problem, please provide a good MCVE. Note that a good MCVE is both complete and minimal. You will want to remove from the example any code that is not strictly required to reproduce the problem. At the same time, make sure someone can copy and paste the code into an empty project and have it run with at most very minimal effort, and preferably none at all.

Awaiting Asynchronous function inside FormClosing Event

I'm having a problem where I cannot await an asynchronous function inside of the FormClosing event which will determine whether the form close should continue. I have created a simple example that prompts you to save unsaved changes if you close without saving (much like with notepad or microsoft word). The problem I ran into is that when I await the asynchronous Save function, it proceeds to close the form before the save function has completed, then it comes back to the closing function when it is done and tries to continue. My only solution is to cancel the closing event before calling SaveAsync, then if the save is successful it will call the form.Close() function. I'm hoping there is a cleaner way of handling this situation.
To replicate the scenario, create a form with a text box (txtValue), a checkbox (cbFail), and a button (btnSave). Here is the code for the form.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TestZ
{
public partial class Form1 : Form
{
string cleanValue = "";
public Form1()
{
InitializeComponent();
}
public bool HasChanges()
{
return (txtValue.Text != cleanValue);
}
public void ResetChangeState()
{
cleanValue = txtValue.Text;
}
private async void btnSave_Click(object sender, EventArgs e)
{
//Save without immediate concern of the result
await SaveAsync();
}
private async Task<bool> SaveAsync()
{
this.Cursor = Cursors.WaitCursor;
btnSave.Enabled = false;
txtValue.Enabled = false;
cbFail.Enabled = false;
Task<bool> work = Task<bool>.Factory.StartNew(() =>
{
//Work to do on a background thread
System.Threading.Thread.Sleep(3000); //Pretend to work hard.
if (cbFail.Checked)
{
MessageBox.Show("Save Failed.");
return false;
}
else
{
//The value is saved into the database, mark current form state as "clean"
MessageBox.Show("Save Succeeded.");
ResetChangeState();
return true;
}
});
bool retval = await work;
btnSave.Enabled = true;
txtValue.Enabled = true;
cbFail.Enabled = true;
this.Cursor = Cursors.Default;
return retval;
}
private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (HasChanges())
{
DialogResult result = MessageBox.Show("There are unsaved changes. Do you want to save before closing?", "Unsaved Changes", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
if (result == System.Windows.Forms.DialogResult.Yes)
{
//This is how I want to handle it - But it closes the form while it should be waiting for the Save() to complete.
//bool SaveSuccessful = await Save();
//if (!SaveSuccessful)
//{
// e.Cancel = true;
//}
//This is how I have to handle it:
e.Cancel = true;
bool SaveSuccessful = await SaveAsync();
if (SaveSuccessful)
{
this.Close();
}
}
else if (result == System.Windows.Forms.DialogResult.Cancel)
{
e.Cancel = true;
}
//If they hit "No", just close the form.
}
}
}
}
Edit 05/23/2013
Its understandable that people would ask me why I would be trying to
do this. The data classes in our libraries will often have Save,
Load, New, Delete functions that are designed to be run asynchronously
(See SaveAsync as an example). I do not actually care that much about
running the function asynchronously in the FormClosing Event specifically. But if
the user wants to save before closing the form, I need it to wait and
see if the save succeds or not. If the save fails, then I want it to
cancel the form closing event. I'm just looking for the cleanest way to
handle this.
The best answer, in my opinion, is to cancel the Form from closing. Always. Cancel it, display your dialog however you want, and once the user is done with the dialog, programatically close the Form.
Here's what I do:
async void Window_Closing(object sender, CancelEventArgs args)
{
var w = (Window)sender;
var h = (ObjectViewModelHost)w.Content;
var v = h.ViewModel;
if (v != null &&
v.IsDirty)
{
args.Cancel = true;
w.IsEnabled = false;
// caller returns and window stays open
await Task.Yield();
var c = await interaction.ConfirmAsync(
"Close",
"You have unsaved changes in this window. If you exit they will be discarded.",
w);
if (c)
w.Close();
// doesn't matter if it's closed
w.IsEnabled = true;
}
}
It is important to note the call to await Task.Yield(). It would not be necessary if the async method being called always executed asynchronously. However, if the method has any synchronous paths (ie. null-check and return, etc...) the Window_Closing event will never finish execution and the call to w.Close() will throw an exception.
Dialogs handle messages while still keeping the current method on the stack.
You could show a "Saving..." Dialog in your FormClosing handler, and run the actual saving-operation in a new task, which programmatically closes the dialog once it's done.
Keep in mind that SaveAsync is running in a non-UI Thread, and needs to marshal any access UI elements via Control.Invoke (see call to decoy.Hide below). Best would probably be to extract any data from controls beforehand, and only use variables in the task.
protected override void OnFormClosing(FormClosingEventArgs e)
{
Form decoy = new Form()
{
ControlBox = false,
StartPosition = FormStartPosition.CenterParent,
Size = new Size(300, 100),
Text = Text, // current window caption
};
Label label = new Label()
{
Text = "Saving...",
TextAlign = ContentAlignment.MiddleCenter,
Dock = DockStyle.Fill,
};
decoy.Controls.Add(label);
var t = Task.Run(async () =>
{
try
{
// keep form open if saving fails
e.Cancel = !await SaveAsync();
}
finally
{
decoy.Invoke(new MethodInvoker(decoy.Hide));
}
});
decoy.ShowDialog(this);
t.Wait(); //TODO: handle Exceptions
}
You can't keep your form from closing with async/await. And you can get strange results.
What I would do is creating a Thread and setting its IsBackground property to false (which is false by default) to keep the process alive while form is closing.
protected override void OnClosing(CancelEventArgs e)
{
e.Cancel = false;
new Thread(() => {
Thread.Sleep(5000); //replace this line to save some data.....
MessageBox.Show("EXITED");
}).Start();
base.OnClosing(e);
}
I had a similar issue when I tried to handle all of the close event async. I believe it is because there is nothing to block the main thread from moving forward with the actual FormClosingEvents. Just put some inline code after the await and it solves the problem. In my case I save the current state no matter the response (while waiting for the response). You could easily have the task return a current state ready to be saved appropriately once the user responds.
This worked for me: Spin off task, ask exit confirmation, await task, some inline code.
Task myNewTask = SaveMyCurrentStateTask(); //This takes a little while so I want it async in the background
DialogResult exitResponse = MessageBox.Show("Are you sure you want to Exit MYAPPNAME? ", "Exit Application?", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
await myNewTask;
if (exitResponse == DialogResult.Yes)
{
e.Cancel = false;
}
else
{
e.Cancel = true;
}
I needed to abort closing the form if an exeption was raised during the execution of an async method.
I'm actually using a Task.Run with .Wait()
private void Example_FormClosing(object sender, FormClosingEventArgs e)
{
try
{
Task.Run(async () => await CreateAsync(listDomains)).Wait();
}
catch (Exception ex)
{
MessageBox.Show($"{ex.Message}", "Attention", MessageBoxButtons.OK, MessageBoxIcon.Error);
e.Cancel = true;
}
}
Why does asynchronous behavior have to be involved? It sounds like something that has to happen in a linear fashion.. I find the simplest solution is usually the right one.
Alternatively to my code below, you could have the main thread sleep for a second or two, and have the async thread set a flag in the main thread.
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (HasChanges())
{
DialogResult result = MessageBox.Show("There are unsaved changes. Do you want to save before closing?", "Unsaved Changes", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
e.Cancel = true;
if(!Save())
{
MessageBox.Show("Your work could not be saved. Check your input/config and try again");
e.Cancel = true;
}
}
else if (result == DialogResult.Cancel)
{
e.Cancel = true;
} } }

Categories

Resources