Hi I have read other tutorials but could not figure it out. I am running a task and when task is completed I want to hide the current form and load another form but It hangs and nothing is displayed. This is my code please guide me -
public Loading()
{
InitializeComponent();
Shown += Loading_Shown;
}
private void Loading_Shown(object sender, EventArgs e)
{
label2.Text = "Step 1...";
Task.Run(() =>
{
if (Directory.Exists(contentPath))
{
filePresent = false;
}
if (filesPresent == false)
{
BeginInvoke(
(MethodInvoker)delegate
{
label2.Text = "Downloading Files...";
}
);
Directory.CreateDirectory(contentPath);
Home form = new Home();
form.Visible = true;
}
else
{
Home form = new Home();
form.Visible = true;
}
});
}
The other form loads half and screen hangs. Please guide me how to continue with this. Thanks
You don't create the second form "when [the] task is completed", but inside that task. So you create the second form on a different thread than the first one. This is a bad idea.
One solution is to make Loading_Shown an async method and await the task. Then, when the task really has completed and control flow returned to the original ui thread, you can create the second form:
private async void Loading_Shown(object sender, EventArgs e)
{
label2.Text = "Step 1...";
await Task.Run(() =>
{
// different thread
filePresent = Directory.Exists(contentPath);
if (!filePresent) Directory.CreateDirectory(contentPath);
});
// back on UI thread
if (!filesPresent)
{
label2.Text = "Downloading Files..."; });
Home form = new Home();
form.Visible = true;
}
else{
Home form = new Home();
form.Visible = true;
}
}
Related
I'm having troubles rendering my custom non blocking 'MessageBox' like form. So I have made my Alert form:
public partial class Alert : Form
{
public Alert(string title, string text)
{
InitializeComponent();
this.Text = title;
labelText.Text = text;
}
private void buttonOk_Click(object sender, System.EventArgs e)
{
Close();
}
}
and looks like this in designer:
But when I try to open this Alert form within my main form like this:
var alert = new Alert("Test", "Hello, this is test.");
alert.Show();
Then my Alert form renders like this:
Where is my button and label?
//EDIT
alert.ShowDialog();
renders the form as intended
The method I'm using this form in:
private void buttonTestConnection_Click(object sender, EventArgs e)
{
var f = new Alert("Test", "Hello, this is test");
f.Show();
Thread.Sleep(2000);
var result = TestSqlConnection(); // This may that a while
if (result)
{
f.Close();
MessageBox.Show("Test", "Test successful", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
f.Close();
MessageBox.Show("Test", "Connection failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
The UI thread renders your UI. You are blocking the UI thread by calling Thread.Sleep. Thread.Sleep has it's place in programming, but not in UI programming.
My suggestion would be to move to asynchronous programming. For example:
private async void buttonTestConnection_Click(object sender, EventArgs e)
{
var f = new Alert("Test", "Hello, this is test");
f.Show();
await Task.Delay(2000);
var result = await Task.Run(() => TestSqlConnection());
if (result)
{
f.Close();
MessageBox.Show("Test", "Test successful",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
f.Close();
MessageBox.Show("Test", "Connection failed",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Notice the async keyword added to your method signature and calls to await.
I won't go in to how this works as there are many, many tutorials online. Here is a great place to start. The guy is a resident genius around here when it comes to this pattern.
Typically SQL connections can be converted to asynchronous, so that Task.Run should be temporary until you figure out the pattern.
Another quick example:
private async void buttonTestConnection_Click(object sender, EventArgs e)
{
buttonTestConnection.Enabled = false;
using (var f = new Alert("Test", "Hello, this is test"))
{
f.Show();
bool result = await Task.Run(() => {
Thread.Sleep(2000);
return TestSqlConnection();
});
MessageBox.Show("Test", "Test " + (result ? "successful" : "failed"),
MessageBoxButtons.OK,
(result ? MessageBoxIcon.Information : MessageBoxIcon.Error));
}
buttonTestConnection.Enabled = true;
}
I have a small program that starts as an appbar (a window which docks to the desktop and can be hidden (on the top, right, bottom, or left side of the desktop when not in use). The program permits a user to drag a file (or files) from any given location on to the appbar and convert it to PDF (by converting each file to PDF and then merging the resulting PDF's into a single PDF file).
The conversion process runs on a seperate thread using a backgroundworker. Once the backgroundworker acquires the list of files, I have a modal dialog which pops up, having loaded a listview with the relevant files, and allows the user to reorder them prior to the final merge process.
I am having cross-thread issues with the modal dialog. I have searched high and low for a solution, but am stumped. The problems arises as a result of the use of the keyword this.
I have tried the following code in the midst of the backgroundworker:
using (var md = new MergeDlg())
{
md.Files = (string[])files;
if (md.ShowDialog(this) == DialogResult.OK)
files = md.Files;
}
If I remove the keyword this I get no error, but the dialog behaves as if it is started on the main thread and the backgroundworkerthread continues as if there is no modal dialog - I understand that is because the dialog is started on the main UI thread.
I have also tried moving the dialog creation out of the background worker thread and calling it in the thread
the code to create the modal dialog is as follows:
private string[] ShowMergeDlg(string[] files)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new Action(() =>
{
MergeDlg md = new MergeDlg();
md.Files = (string[])files;
if (md.ShowDialog(this) == DialogResult.OK)
files = md.Files;
}
));
}
else
{
MergeDlg md = new MergeDlg();
md.Files = (string[])files;
if (md.ShowDialog(this) == DialogResult.OK)
files = md.Files;
}
return files;
}
On the backgroundworker thread, the function is called:
files = ShowMergeDlg(files);
Again that code obviously starts the dialog on the main UI thread with the same result.
My question is then:
How do I show a modal dialog on a backgroundworker thread, pausing execution of the thread until such times as the modal dialog has been closed?
You better switch to async/await and Tasks. Here a very limited sample
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click( object sender, EventArgs e )
{
button1.Enabled = false;
label1.Text = "acquire files ...";
ICollection<string> acquiredFiles = await AcquireFileAsync();
label1.Text = "select files ...";
ICollection<string> selectedFiles = SelectFilesDialog( acquiredFiles );
label1.Text = "process files ...";
await ProcessFilesAsync( selectedFiles );
label1.Text = "finished.";
button1.Enabled = true;
}
private async Task ProcessFilesAsync( ICollection<string> selectedFiles )
{
foreach (var item in selectedFiles)
{
await Task.Delay( 250 ).ConfigureAwait( false );
}
}
private ICollection<string> SelectFilesDialog( ICollection<string> acquiredFiles )
{
var dialog = new Form2();
dialog.ShowDialog();
return acquiredFiles;
}
private async Task<ICollection<string>> AcquireFileAsync()
{
await Task.Delay( 2500 ).ConfigureAwait( false );
return Enumerable.Range( 1, 20 ).Select( e => e.ToString() ).ToList();
}
}
If you call ShowMergeDlg from the background worker, InvokeRequired will be true, then the dialog creates on the UI thread. So just remove it, let the dialog create on the background thread.
private string[] ShowMergeDlg(string[] files)
{
/*
if (this.InvokeRequired)
{
this.BeginInvoke(new Action(() =>
{
MergeDlg md = new MergeDlg();
md.Files = (string[])files;
if (md.ShowDialog(this) == DialogResult.OK)
files = md.Files;
}
));
}
else
*/
{
MergeDlg md = new MergeDlg();
md.Files = (string[])files;
if (md.ShowDialog(this) == DialogResult.OK)
files = md.Files;
}
return files;
}
My test code
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 5; ++i)
{
this.Invoke(new Action(() => textBox1.AppendText("1")));
Thread.Sleep(500);
}
var f2 = new Form2();
if(f2.ShowDialog(this) == DialogResult.OK)
this.Invoke(new Action(() => textBox1.AppendText("2")));
else
this.Invoke(new Action(() => textBox1.AppendText("3")));
for (int i = 0; i < 100; ++i)
{
this.Invoke(new Action(() => textBox1.AppendText("1")));
Thread.Sleep(500);
}
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
}
No work await Task.Run():
private async void button2_Click(object sender, EventArgs e)
{
await Task.Run(() => {
monitor_r(label1);
});
}
protected async Task monitor_r(Label L1)
{
MessageBox.Show(L1.Name);
L1.ForeColor = Color.Blue;
L1.Text = "test";
}
These commands
MessageBox.Show(L1.Name);
and
L1.ForeColor = Color.Blue;
works fine but
L1.Text = "test";
does not work.
Can you help, why do not change a Label Text?
Try Control.Invoke: we should run Winform UI in the main thread only
protected async Task monitor_r(Label L1)
{
Action action = () =>
{
MessageBox.Show(L1.Name);
L1.ForeColor = Color.Blue;
L1.Text = "test";
};
if (L1.InvokeRequired)
L1.Invoke(action); // When in different thread
else
action(); // When in the main thread
}
If you're on debug mode, take a look at the output window. It should shows exception message something like this:
System.InvalidOperationException' in System.Windows.Forms.dll.
That because label1 accessed from a thread other than the thread it was created on. And it will causing invalid cross-thread operation.
You can solve this by using Control.Invoke as Dmitry Bychenko already mentioned. Here is simple extension to make thread-safe calls to Winforms Control.
public static void TryInvoke(this Control control, Action<Control> action)
{
if (control.InvokeRequired) control.Invoke(new Action(() => action(control)));
else action(control);
}
Sample usage
label1.TryInvoke(x => x.Text = "test");
label1.TryInvoke(x => x.ForeColor = Color.Blue);
Or
this.TryInvoke(x =>
{
label1.Text = "test";
label1.ForeColor = Color.Blue;
});
Secondly, since you don't await anything at monitor_r, i'd recommend to use void instead of async Task.
Even if you're await something at monitor_r you don't need
await Task.Run(() => {
monitor_r(label1);
});
..because monitor_r itself is a task. So just call await monitor_r(label1);
If you wish to have a separate thread, you can try this using BackgroundWorker. You can implement the ReportProgress if you have a loop.
private void button1_Click(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(label1.Name);
label1.ForeColor = Color.Blue;
label1.Text = "test";
}
I have a program that runs and starts 2 long running tasks. One of the tasks is a web scraper in which I have to use the WebBrowser ActiveX control so that I can get the rendered page. In order to use the control I have to start a thread so that I can set the apartment state for the message loop. When I do this, the proogram works fine, or at least for the first page that is fetched. Subsequent pages/calls, the webbrowser times out and it's state seems to remain at "uninitialized". In tracing my code, I never see the "HandleDestroyed" fire for the WebClient.
What do I need to do to Properly Destroy the WebBrowser control and or my own class in order for it to be reused again.
public static string AXFetch(string url, string ua)
{
TestBrowser TB = new TestBrowser();
Thread th = new Thread(() => TB.MakeLiRequest(url,ua));
th.SetApartmentState(ApartmentState.STA);
th.Start();
th.Join(new TimeSpan(0, 0, 90)); //90 second timeout
SiteData = TB.DocumentText;
TB = null;
return SiteData;
}
class TestBrowser
{
public string DocumentText = "";
private bool DocCompleted = false;
public TestBrowser()
{
}
private void reset_fetch_status()
{
this.DocCompleted = false;
this.DocumentText = "";
}
public void MakeLiRequest(string url, string UA)
{
reset_fetch_status();
using (WebBrowser wb = new WebBrowser())
{
wb.Visible = false;
wb.AllowNavigation = true;
wb.ScriptErrorsSuppressed = true;
wb.DocumentCompleted += this.wb_DocumentCompleted;
wb.Navigate(url, "_self", null, "User-Agent: " + UA + "\r\n");
WaitForPage();
wb.Url = null;
wb.DocumentCompleted -= this.wb_DocumentCompleted;
}
}
private void HandleDestroyed(Object sender, EventArgs e)
{
//This never seems to fire, I don't knwo why
Logging.DoLog("You are in the Control.HandleDestroyed event.");
}
private bool WaitForPage()
{
int timer = 0;
while (this.DocCompleted == false)
{
Application.DoEvents();
System.Threading.Thread.Sleep(100);
++timer;
if (timer == (PageTimeOut * 10))
{
Console.WriteLine("WebBrowser Timeout has been reached");
Application.Exit();
return false;
}
}
return true;
}
private void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser wb = (WebBrowser)sender;
if (wb.ReadyState == WebBrowserReadyState.Complete)
{
this.DocumentText = wb.DocumentText;
this.DocCompleted = true;
}
}
}
On handle destroyed will only be called by the parent form.
If you were to try to access from the webbrowser control you would get this error:
Error 1 Cannot access protected member
'System.Windows.Forms.Control.OnHandleDestroyed(System.EventArgs)' via a
qualifier of type 'System.Windows.Forms.WebBrowser'; the qualifier must be of type 'stackoverflowpost47036339.Form1' (or derived from it)
Also you are not hooking it up. But since you haven't given your web browser any parent form, It can't be called. This is how you would hook it up to the parent form.
form1.HandleDestroyed += Form1_HandleDestroyed;
}
void Form1_HandleDestroyed(object sender, EventArgs e)
{
}
Context: I am playing music through a media element, and using a slider to display the point in the song that it is at. That updating is done in a backgroundworker, for obvious reasons.
private void bgPlay_DoWork(object sender,DoWorkEventArgs e)
{
while (isMediaPlaying)
{
this.Dispatcher.Invoke((Action)(() =>
{
timelineSlider.Value = mediaElement1.Position.TotalMilliseconds;
}));
}
}
private void Library_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
try
{
mediaElement1.Stop();
isMediaPlaying = false;
mediaElement1.Source = new Uri(songData[Library.SelectedIndex].Location);
mediaElement1.Volume = (double)volumeSlider.Value;
mediaElement1.Play();
isMediaPlaying = true;
bgPlay.RunWorkerAsync();
}
catch(Exception ex) {
F.MessageBox.Show(ex.ToString());
}
}
When I play a song, then double click on a different one, the background worker is still looping and throws an exception because it reaches bgPlay.RunWorkerAsync(); before the previous instance has finished. I tried to use the isMediaPlaying bool to tell the backgroundworker when to exit the loop, but the main thread reaches bgPlay.RunWorkerAsync(); before it finishes.
You are suffering of a common mistake when one is barely starting to program with threading, a race condition
I'd advise rewriting the code like this:
private static String threadingLock = "";
private void bgPlay_DoWork(object sender,DoWorkEventArgs e)
{
while (true)
{
lock(threadingLock) {
if(!isMediaPlaying)
break;
}
this.Dispatcher.Invoke((Action)(() =>
{
timelineSlider.Value = mediaElement1.Position.TotalMilliseconds;
}));
}
}
private void Library_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
try
{
lock(threadingLock) {
isMediaPlaying = false;
}
mediaElement1.Stop();
mediaElement1.Source = new Uri(songData[Library.SelectedIndex].Location);
mediaElement1.Volume = (double)volumeSlider.Value;
mediaElement1.Play();
isMediaPlaying = true;
bgPlay.RunWorkerAsync();
}
catch(Exception ex) {
F.MessageBox.Show(ex.ToString());
}
}
As a friendly tip, add a Thread.sleep(200) before invoking the update on the slider. It will reduce cpu usage without affecting the functionality of your application.