I've a form that runs a query from a DataGridView on a button click. The below code works how I expect it to;
Opens a form that says "Please Wait while query runs"
Runs query
Closes form when loaded and then fill the DataGridView with the
data.
I have added a picturebox with simple gif - purely for the UI and the user to see it is working. But the gif doesn't actually spin - just shows as a picture. Testing it, if I run it on a form on its own it is fine, I can only hazard a guess that the query running is stopping it from working how it should.
PleaseWaitForm pleaseWait = new PleaseWaitForm();
try
{
pleaseWait.Show();
Application.DoEvents();
this.singleCenTableAdapter.Fill(this.singleCenData.SingleCenTable, ((System.DateTime)(System.Convert.ChangeType(txtBookFrom.Text, typeof(System.DateTime)))), ((System.DateTime)(System.Convert.ChangeType(txtBookTo.Text, typeof(System.DateTime)))));
int RowC = singleCenTableDataGridView.RowCount;
pleaseWait.Close();
if (RowC == 0)
{
MessageBox.Show(GlobVar.NoResults, "", MessageBoxButtons.OK, MessageBoxIcon.Hand);
}
pleaseWait.Close();
}
catch (System.Exception exc)
{
GlobVar.vid.Open();
var LogName = GlobVar.LoginName.ExecuteScalar();
GlobVar.vid.Close();
MessageBox.Show
(
"An error has occured " + LogNam + ". Please try again. \n\n" +
exc.Message, "An error has occured", MessageBoxButtons.OK, MessageBoxIcon.Warning
);
}
finally
{
pleaseWait.Close();
}
The "Please Wait" form is just a label & picture so there is nothing but the Initalize in there at the minute ;
public PleaseWaitForm()
{
InitializeComponent();
}
Does anybody have any ideas on how to tackle this to make it work correct? Or anything particular that I am doing wrong? I know for the most part I might get a bit of stick for using Application.DoEvents() anyway, but any help is appreciated!
I read in your comment that you use .NET 4.0 and therefore can not use await and async with Task<>.
Before .NET 4.5, you can use Thread or BackgroundWorker to achieve this,
refer to the following example of BackgroundWorker:
Background Worker loading screen in Winforms
Your current code is running in synchronously, you are expecting it to work in asynchronously.
You need async and await to keep your UI responsive.
First make the method this code is in async as follows:
private async void Method()
{
...
}
Next place the following code in a new method:
this.singleCenTableAdapter.Fill(this.singleCenData.SingleCenTable, ((System.DateTime)(System.Convert.ChangeType(txtBookFrom.Text, typeof(System.DateTime)))), ((System.DateTime)(System.Convert.ChangeType(txtBookTo.Text, typeof(System.DateTime)))));
int RowC = singleCenTableDataGridView.RowCount;
For example:
private async Task<int> FillAndGetRowCount( ... ) // add parameters if needed
{
this.singleCenTableAdapter.Fill(this.singleCenData.SingleCenTable, ((System.DateTime)(System.Convert.ChangeType(txtBookFrom.Text, typeof(System.DateTime)))), ((System.DateTime)(System.Convert.ChangeType(txtBookTo.Text, typeof(System.DateTime)))));
return singleCenTableDataGridView.RowCount;
}
Finally, change your try block to:
try
{
pleaseWait.Show();
Application.DoEvents();
int RowC = await FillAndGetRowCount( ... );
pleaseWait.Close();
if (RowC == 0)
{
MessageBox.Show(GlobVar.NoResults, "", MessageBoxButtons.OK, MessageBoxIcon.Hand);
}
}
Related
I've got a WinForms project that scans a given network and returns valid IP addresses. Once all the addresses are found, I create a user control for each and place it on the form. My functions to ping ip addresses use async and Task which I thought would "wait" to execute before doing something else, but it doesn't. My form shows up blank, then within 5 seconds, all the user controls appear on the form.
Declarations:
private List<string> networkComputers = new List<string>();
Here's the Form_Load event:
private async void MainForm_Load(object sender, EventArgs e)
{
//Load network computers.
await LoadNetworkComputers();
LoadWidgets();
}
The LoadNetworkComputers function is here:
private async Task LoadNetworkComputers()
{
try
{
if (SplashScreenManager.Default == null)
{
SplashScreenManager.ShowForm(this, typeof(LoadingForm), false, true, false);
SplashScreenManager.Default.SetWaitFormCaption("Finding computers");
}
else
Utilities.SetSplashFormText(SplashForm.SplashScreenCommand.SetLabel, "Scanning network for computers. This may take several minutes...");
networkComputers = await GetNetworkComputers();
}
catch (Exception e)
{
MessageBox.Show(e.Message + Environment.NewLine + e.InnerException);
}
finally
{
//Close "loading" window.
SplashScreenManager.CloseForm(false);
}
}
And the last 2 functions:
private async Task<List<string>> GetNetworkComputers()
{
networkComputers.Clear();
List<string> ipAddresses = new List<string>();
List<string> computersFound = new List<string>();
for (int i = StartIPRange; i <= EndIPRange; i++)
ipAddresses.Add(IPBase + i.ToString());
List<PingReply> replies = await PingAsync(ipAddresses);
foreach(var reply in replies)
{
if (reply.Status == IPStatus.Success)
computersFound.Add(reply.Address.ToString());
}
return computersFound;
}
private async Task<List<PingReply>> PingAsync(List<string> theListOfIPs)
{
var tasks = theListOfIPs.Select(ip => new Ping().SendPingAsync(ip, 2000));
var results = await Task.WhenAll(tasks);
return results.ToList();
}
I'm really stuck on why the form is being displayed before the code in the MainForm_Load event finishes.
EDIT
I forgot to mention that in the LoadNetworkComputers it loads a splash form which lets the user know that the app is running. It's when the form shows up behind that, that I'm trying to avoid. Here's a screenshot (sensitive info has been blacked out):
The reason one would use async-await is to enable callers of functions to continue executing code whenever your function has to wait for something.
The nice thing is that this will keep your UI responsive, even if the awaitable function is not finished. For instance if you would have a button that would LoadNetworkComputers and LoadWidgets you would be glad that during this relatively long action your window would still be repainted.
Since you've defined your Mainform_Loadas async, you've expressed that you want your UI to continue without waiting for the result of LoadNetWorkComputers.
In this interview with Eric Lippert (search in the middle for async-await) async-await is compared with a a cook making dinner. Whenever the cook finds that he has to wait for the bread to toast, he starts looking around to see if he can do something else, and starts doing it. After a while when the bread is toasted he continues preparing the toasted bread.
By keeping the form-load async, your form is able to show itself, and even show an indication that the network computers are being loaded.
An even nicer method would be to create a simple startup-dialog that informs the operator that the program is busy loading network computers. The async form-load of this startup-dialog could do the action and close the form when finished.
public class MyStartupForm
{
public List<string> LoadedNetworkComputers {get; private set;}
private async OnFormLoad()
{
// start doing the things async.
// keep the UI responsive so it can inform the operator
var taskLoadComputers = LoadNetworkComputers();
var taskLoadWidgets = LoadWidgets();
// while loading the Computers and Widgets: inform the operator
// what the program is doing:
this.InformOperator();
// Now I have nothing to do, so let's await for both tasks to complete
await Task.WhenAll(new Task[] {taskLoadComputers, taskLoadWidgets});
// remember the result of loading the network computers:
this.LoadedNetworkComputers = taskLoadComputers.Result;
// Close myself; my creator will continue:
this.Close();
}
}
And your main form:
private void MainForm_Load(object sender, EventArgs e)
{
// show the startup form to load the network computers and the widgets
// while loading the operator is informed
// the form closes itself when done
using (var form = new MyStartupForm())
{
form.ShowDialog(this);
// fetch the loadedNetworkComputers from the form
var loadedNetworkComputers = form.LoadedNetworkComputers;
this.Process(loadedNetworkComputers);
}
}
Now while loading, instead of your mainform the StartupForm is shown while the items are loaded.. The operator is informed why the main form is not showing yet. As soon as loading is finished, the StartupForm closes itself and loading of the main form continues
My form shows up blank, then within 5 seconds, all the user controls appear on the form.
This is by design. When the UI framework asks your app to display a form, it must do so immediately.
To resolve this, you'll need to decide what you want your app to look like while the async work is going on, initialize to that state on startup, and then update the UI when the async work completes. Spinners and loading pages are a common choice.
I'm creating a simple desktop application using Gtk#, When the user clicks a button I want to show a "loading indicator" MessageDialog and make some processing in background, when the process is finished close the dialog and update some controls from the UI.
I'm very new to Gtk# and Mono, so my code looks like this:
protected void OnBtnClicked(object sender, EventArgs e)
{
try
{
Task.Factory.StartNew(() =>
{
var dlg = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.None, "Processing...");
dlg.Run();
//Some sync calls to remote services
//...
//The process finished so close the Dialog
dlg.Destroy();
//Here: Update the UI with remote service's response
//txtResult.Buffer.Text = result.Message;
});
}
catch (Exception ex)
{
var dlg = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Close, ex.Message);
dlg.Title = "Error";
dlg.Run();
dlg.Destroy();
}
}
This code is showing the MessageDialog but it never closes.
Mono version: 4.4.2
IDE: Xamarin Studio Community Edition 6.0.2
Gtk# version: 2.12.38
After reading a guide for responsive Mono applications and asking to Miguel de Icaza through Twitter I've found the way of doing this.
Things to take into account:
1) Never create or try to modify UI elements from another thread.
2) If you need to modify UI controls from another thread use the Application.Invoke() method inside that thread.
3) The Run() method from MessageDialog class waits for user interaction to get closed i.e Click the close button or something that calls a Close/Destroy event. The use of that method is wrong in this scenario because I will close the MessageDialog from my code, so the right method to show the dialog is Show().
With that in my mind, my final code looks like this:
protected void OnBtnClicked(object sender, EventArgs e)
{
try
{
var mdCalculate = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.None, "Processing...");
mdCalculate.Title = "Calculate";
mdCalculate.Show();
Task.Factory.StartNew(() =>
{
//Some sync calls to remote services
//...
//returns the data I will show in the UI, lets say it's a string
return someData;
}).ContinueWith((prevTask) =>
{
Application.Invoke((send, evnt) =>
{
txtResult.Buffer.Text = prevTask.Result; //this is the string I returned before (someData)
mdCalculate.Hide();
mdCalculate.Destroy();
});
});
}
catch (Exception ex)
{
var dlg = new MessageDialog(this, DialogFlags.Modal, MessageType.Error, ButtonsType.Close, ex.Message);
dlg.Title = "Error";
dlg.Run();
dlg.Destroy();
}
}
Demo:
I have messagebox in one .cs file and i want to clear textbox which is in another .cs file on ok button click.
I have used
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
_upload._txtbxNotes.Text = "";
}));
for solving threading issue .But it doesnt help me .it still shows the same error.
Any help appreciated.
Update:
Hello,I also updated my code but it does not change textbox value .it remain as it is .I have one more thread on upload screen for progresssbar.
Here is my code:FileUpload.cs
if (String.Equals(objFileUploadResponse.responseCode, 102))
{
// MessageBox.Show("File Uploaded Successfully");
ipbup.ReportProgress(qpvsChunk);
DialogResult dialogResult = System.Windows.Forms.MessageBox.Show("File Uploaded", "", MessageBoxButtons.OK);
if (dialogResult == DialogResult.OK)
{
_upload.SetNotes(" ");
}
}
else
{
System.Windows.MessageBox.Show("File Uploaded Failed");
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Upload.cs:
public void SetNotes(string note)
{
_txtbxNotes.Dispatcher.Invoke(() =>
{
_txtbxNotes.Text = note;
});
}
This is probably because you are running on another thread. When you call the System.Windows.Threading.Dispatcher.CurrentDispatcher on another thread, it will create a new Dispatcher. But you don't want a new dispatcher, you want to use the Dispatcher who 'owns' the control/window.
To solve this, your controls/window has a Dispatcher property. (it referes to the Dispatcher it was created on)
You can try:
// use the Dispatcher from the _upload control.
_upload._txtbxNotes.Dispatcher.Invoke(() =>
{
_upload._txtbxNotes.Text = "";
}));
A 'better' aproach could be: Creating a method within your _upload class called (for example) _upload.SetNotes(string note); This way you keep logic separated. Today you want a TextBox tomorrow you might want a Label. This way only your upload window/control/class is inflicted.
// for example: (pseudo)
_upload.SetNotes("");
class UploadWindow
{
// ......
public void SetNotes(string note)
{
_txtbxNotes.Dispatcher.Invoke(() =>
{
_txtbxNotes.Text = note;
}));
}
}
Every time I had problem with my SQL Server connection (ex. someone switch of server) my application will show an exception message in a messagebox. I don't know when will the connection available except I keep trying to open the connection / execute a query.
So I create a wait form that will appear if connection is unavailable, keep trying to open the connection, and close itself when connection is available again.
To hide the freeze from user, I use a background worker.
This is the background worker code
private void StartLoader(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 10; i++)
{
if (this.par.sqSuccess) //if no error, means connection is available, stop looping
{
break;
}
else
{
i -= 1;
}
System.Threading.Thread.Sleep(5000); //report progress every 5 second
}
This is the background worker's progress changed event
this.cnn = new SqlConnection(this.par.Constr);
try
{
this.cnn.Open(); //SqlConnection
this.par.sqSuccess = true; //if no error, then I change this variable
}
catch (Exception ex)
{
this.par.Exception = ex.Message;
}
finally
{
if (this.cnn != null) { this.cnn.Dispose(); }
}
if (this.par.sqSuccess) { this.Close(); }
After everything is complete, I tried to stop SQL Server service from services.msc, then I try to connect.
The wait form will appear and keep doing its job.
A few second after I try to connect, I start the service again and the wait form did close, success.
This is the problem, when I wait a little bit longer before I start the service again, the wait form still closed, but it takes a while.
After I check everything, it seems like the cnn.open() queue up and the longer I stop the service, the longer it takes for the wait form to close.
I searched google and try to add Connect Timeout=3; behind my connection string, as I'm sure my thread.sleep(5000) won't make them queue up, but still not working.
Someone told me to use cnn.OpenAsync();
After reading the documentation about OpenAsync, this is what I do.
static async Task<ConnectionState> Method(SqlConnection cnn)
{
await cnn.OpenAsync();
return cnn.State;
}
And this
private void SQLClientLoader_Load(object sender, EventArgs e)
{
do
{
this.cnn = new SqlConnection(this.par.Constr);
try
{
ConnectionState cst = Method(cnn).Result;
if (cst == ConnectionState.Open)
{
this.par.sqSuccess = true;
}
else
{
}
}
catch (Exception ex)
{
this.par.sqSuccess = false;
this.par.Exception = ex.Message;
}
finally
{
}
} while ((bool)this.par.sqSuccess != true);
}
The code above freezes my application every time the form load code executed.
I need simple instruction of how to wait for the cnn.Open process to finish or to cancel it if it takes too long.
Thank you in advance
You can set the ConnectionTimeout property for your SqlConnection in your code or in your ConnectionString. No need to use Async IMHO..
cnn.ConnectionTimeout = 5000
So this will create about a 10-second delay if connection does not work (connectiontimeout + Sleep(5000).
I have this code:
public void Blah(IWin32Window _this)
{
for (int i = 0; i < item_quantity; i++)
{
try { File.Delete(item[0, i]); }
catch (Exception ex)
{
if (MessageBox.Show(_this, String.Format("Error while accessing {0}\n{1}"
, item[0, i], ex.Message), "Error", MessageBoxButtons.RetryCancel
, MessageBoxIcon.Error) == DialogResult.Retry)
{ i--; }
}
}
}
...and this code in the main UI thread:
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
AnotherClass.Blah(this);
}
When I execute this code, I get the unsafe cross-thread exception. What's the safe way to do this operation?
What's the safe way to do this operation?
There is no real safe way to do this. The message box pops out of nowhere, without any direct connection to a command that the user gave. One failure mode is that the user continues working with your UI, clicking the mouse or pressing the space bar. And your message box pops up a millisecond before he clicked the mouse or pressed a key. He'll never see the message.
So something was supposed to be done, it didn't get done and the user is completely unaware of it. Not a good thing. You'll need to doctor your UI so this condition can never occur. Clearly that will require that you do error reporting a different way than by using a temporary message box. Many possible alternatives of course, could be as simple as a Label that reports state. StatusStrip is good for this.
The actual exception is a bogus one. It is triggered by the built-in diagnostics that checks that code uses UI in a thread-safe way. The underlying winapi call is GetParent(), one of the very few user32 Windows functions that can safely be called, and used, from a worker thread. The only legitimate reason I know where using Control.CheckForIllegalCrossThreadCalls to work around the problem is okay. But fix the real problem instead.
I'm not condoning the design, but you can pass in the Form to Blah() and then Invoke() against the referenced form:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (!backgroundWorker.IsBusy)
{
button1.Enabled = false;
backgroundWorker.RunWorkerAsync();
}
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
SomeClass AnotherClass = new SomeClass();
AnotherClass.Blah(this);
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Enabled = true;
MessageBox.Show("Done!");
}
}
public class SomeClass
{
public void Blah(Form frm)
{
int item_quantity = 5;
for (int i = 0; i < item_quantity; i++)
{
try
{
//File.Delete(item[0, i]);
Console.WriteLine("i = " + i.ToString());
throw new Exception("duh");
}
catch (Exception ex)
{
frm.Invoke(new Action(() =>
{
DialogResult result = MessageBox.Show(frm, String.Format("Error while accessing {0}\n{1}", "something", ex.Message), "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
if (result == DialogResult.Retry)
{
i--;
}
}));
}
}
}
}
You are trying to do UI work on a background thread, hence the cross-thread exception. RunWorkerCompletedEventArgs has a property called Error that will hold any exception that gets thrown by the RunWorkerAsync delegate. Set up a handler for RunWorkerCompleted on your BackgroundWorker and check if the Error property has a value. If it does, prompt the MessageBox in the handler because you will be on the UI thread at that point. Call the BackgroundWorker's RunWorkerAsync method again on the DialogResult.Retry scenario.
(You will probably have to tweak your BackgroundWorker and AnotherClass.Blah to take in the value of i to prime your loop condition for that second call to your BackgroundWorker. The DoWorkEventArgs has a property called Argument that you can use to pass in that value.)
You need to execute UI code like this when calling it from another thread:
// must use invoke because the timer event is running on a separate thread
this.Invoke(new Action(() =>
{
MessageBox.Show("Message");
}));