I have a thread started and I want the user to be able to interrupt it by clicking a button on the form. I found the following code and it demonstrates what I want nicely.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
namespace ExThread {
public partial class MainForm : Form {
public int clock_seconds=0;
[STAThread]
public static void Main(string[] args) {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
public MainForm() {
InitializeComponent();
Thread thread_clock = new Thread(new ThreadStart(Thread_Clock));
thread_clock.IsBackground = true;
thread_clock.Start();
}
delegate void StringParameterDelegate (string value);
public void Update_Label_Seconds(string value) {
if (InvokeRequired) {
BeginInvoke(new StringParameterDelegate(Update_Label_Seconds), new object[]{value});
return;
}
label_seconds.Text= value + " seconds";
}
void Thread_Clock() {
while(true) {
clock_seconds +=1;
Update_Label_Seconds(clock_seconds.ToString());
Thread.Sleep(1000);
}
}
private void btnStop_Click(object sender, EventArgs e)
{
}
}
}
I have added the btnStop method. What code needs to be added to stop the thread_clock thread.
Thanks.
First, the thread needs to be able to recognize that it should end. Change
void Thread_Clock() {
while(true) {
to
bool endRequested = false;
void Thread_Clock() {
while(!endRequested) {
And then set endRequested to True in your button click handler.
private void btnStop_Click(object sender, EventArgs e)
{
endRequested = true;
}
Note that for this specific case, it is probably more appropriate to use a Timer
http://msdn.microsoft.com/en-us/library/system.windows.forms.timer.aspx
Simply start and stop the timer as desired. You would update the clock from the timer's Tick() event.
Related
This question already has answers here:
Thread.Sleep() in C#
(4 answers)
Closed 6 years ago.
Here's my code:
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;
using System.Threading;
namespace _8BB_2._0
{
public partial class Form1 : Form
{
public static class globalVars
{
public static bool spacerunning = false;
}
public Form1()
{
InitializeComponent();
globalVars.spacerunning = false;
}
private void button1_Click(object sender, EventArgs e)
{
if (!globalVars.spacerunning)
{
globalVars.spacerunning = true;
while (globalVars.spacerunning)
{
Thread.Sleep(1000);
SendKeys.Send(" ");
}
}
else if (globalVars.spacerunning)
{
globalVars.spacerunning = false;
}
}
}
}
When I click button1 it's starts hitting space every second like it should but when I try to click it again to shut it off the application freezes and it keeps pressing space. I've tried multiple other ways but can't seem to figure out how I can do two things at once since I get locked inside of the while loop.
Calling Thread.Sleep() will block the UI thread. Try to use async/await instead.
private async void button1_Click(object sender, EventArgs e)
{
globalVars.spacerunning = !globalVars.spacerunning;
while (globalVars.spacerunning)
{
await Task.Delay(1000);
SendKeys.Send(" ");
}
}
UPDATE:
You may use a Timer instead.
public class MainForm : Form
{
private Timer timer = new Timer() { Interval = 1000 };
public MainForm()
{
/* other initializations */
timer.Enabled = false;
timer.Tick += timer_Tick;
}
private void timer_Tick(object sender, EventArgs e)
{
SendKeys.Send(" ");
}
private void button1_Click(object sender, EventArgs e)
{
globalVars.spacerunning = !globalVars.spacerunning;
timer.Enabled = globalVars.spacerunning;
}
}
I have a gui form that displays a treeview. I display an alert form that uses a marquee progress bar while a backgroundworker builds the treeview. The issue I have is that the marquee does not display and the progresschanged routine does not fire until control returns to the gui, not while the dowork is running.
Can anyone please assist as to whether I have coded this incorrectly or have misunderstood how it should work? Thanks.
setup:
alert = new AlertForm();
alert.Message = string.Format("Building folder tree ({0}), please wait.", Global.RootDir);
// event handler for the Cancel button in AlertForm
alert.Cancelled += new EventHandler<EventArgs>(btnExit_Click);
alert.Show();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerAsync();
background worker code (debug steps onto .ReportProgress but not into backgroundWorker1_ProgressChanged) :
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
backgroundWorker1.ReportProgress(1);
Invoke(new MethodInvoker(() => CreateTreeView(treeView1)));
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
alert.Refresh();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
alert.Close();
if (treeView1.Nodes.Count > 0)
{
treeView1.ExpandAll();
treeView1.TopNode = treeView1.Nodes[0];
}
}
what is displayed:
worker running - no marquee in gui
worker complete - marquee displays in gui
========================================================================
Based on the comments below I have ended up with creating a new treeview object then cloning that into the gui object.
TreeView treeView = new TreeView();
CreateTreeView(treeView);
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
if (Global.Error)
{
_alert.Close();
string errorMessage = Global.ErrorMessage;
MessageBox.Show(errorMessage, #"Error occurred", MessageBoxButtons.OK);
Global.Error = false;
}
}
else
{
TreeView treeView = (TreeView)e.Result;
CopyTreeNodes(treeView, treeView1);
if (treeView1.Nodes.Count > 0)
{
treeView1.ExpandAll();
treeView1.TopNode = treeView1.Nodes[0];
}
_alert.Close();
treeView = null;
}
}
public void CopyTreeNodes(TreeView treeViewSource, TreeView treeViewTarget)
{
foreach (TreeNode node in treeViewSource.Nodes)
{
TreeNode treeNode = new TreeNode(node.Text, node.ImageIndex, node.SelectedImageIndex);
CopyNodeChildren(treeNode, node);
treeViewTarget.Nodes.Add((TreeNode)node.Clone());
}
}
public void CopyNodeChildren(TreeNode parent, TreeNode original)
{
foreach (TreeNode tn in original.Nodes)
{
TreeNode treeNode = new TreeNode(tn.Text, tn.ImageIndex, tn.SelectedImageIndex);
parent.Nodes.Add((TreeNode)tn.Clone());
CopyNodeChildren(treeNode, tn);
}
}
I am making assumptions regarding the version of c# you are using, but if you are using 4.5 or later then you don't need a BackgroundWorker if you can work async/await patterns into your code. The downside is that working async/await into an existing code base can sometimes be challenging.
Form1 kicks things off by showing the alert form. The alert form has a CancellationTokenSource that can be used to cancel the operation.
Then CallTreeView is run asynchronously. It will abort if you click the cancel button. The progress bar is simply set to marquee style, you don't need to increment it or do anything with it, it will simply pulse the entire time the alert form is open.
Form1:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
AlertForm af = new AlertForm();
af.Show();
//I assume CallTreeView is not implemented in your form's code behind,
//if it is you do not need to pass it as a parameter
await Task.Run(async () => await CallTreeView(treeView1, af.Cts.Token));
af.Close();
}
private async Task CallTreeView(TreeView tv, CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
if (token.IsCancellationRequested)
{
//clean up whatever you need to
return;
}
else
{
await Task.Delay(500); //just simulate doing something
//add nodes...
}
}
}
}
}
Alert Form:
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class AlertForm : Form
{
public CancellationTokenSource Cts { get; set; }
public AlertForm()
{
InitializeComponent();
Cts = new CancellationTokenSource();
}
private void Cancel_Click(object sender, EventArgs e)
{
Cts.Cancel();
}
}
}
I have a Windows Mobile 6.5 App under development. When the user opens the App a dialog box appears with the login form. User logs in and then after 30 seconds (small time in production) when the timer has run out without activity I show the login Dialog box again using events:
static private void _TimerTick(object state)
{
// the user has been inactive for 30 secs; log him out
MainForm.timer = null;
using (LoginForm LoginForm = new LoginForm())
{
if (LoginForm.ShowDialog() == DialogResult.OK)
{
MainForm.timer = new System.Threading.Timer(_TimerTick, null, 1000 * 30 * 1, Timeout.Infinite);
}
else
{
Application.Exit();
}
}
}
But once I press login and return with an ok button from the login form the original form does show. Although it is still in the task manager. I have tried:
.TopMost = true; but then I can't assess the windows button in the bar at the bottom of the app and no other apps can run as form in my app is always in front of it.
A simple solution as this is only for the login and one main form:
LoginForm.cs (with two textboxes and main menu with Login and Exit):
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace LoginFormTest
{
public partial class LoginForm : Form
{
public LoginForm()
{
InitializeComponent();
}
public void doShow(bool bShow)
{
if(bShow)
Invoke(new Action(() => this.Show()));
else
Invoke(new Action(() => this.Hide()));
}
public void doClose()
{
Invoke(new Action(() => this.Close()));
}
private void mnuLogin_Click(object sender, EventArgs e)
{
MainForm mainForm = new MainForm();
mainForm.Show();
System.Diagnostics.Debug.WriteLine("mainForm started");
}
}
}
Nothing special there.
The MainForm has code that will close the form if no activity:
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace LoginFormTest
{
public partial class MainForm : Form
{
int countDown = 3; //number of seconds for timeout
System.Threading.Timer timer;
object lockCounter = new object(); //to sync access to counter var
public MainForm()
{
InitializeComponent();
//start a independent timer after 1000ms and with a 1000ms interval
timer = new System.Threading.Timer(new TimerCallback(this.timerCallback), null, 1000, 1000);
}
private void mnuExit_Click(object sender, EventArgs e)
{
doClose();
}
private void mnuLogout_Click(object sender, EventArgs e)
{
doClose();
}
private void doClose()
{
System.Diagnostics.Debug.WriteLine("mainForm closing");
try
{
timer.Dispose(); //else timer thread will continue running!
Invoke(new Action(() => this.Close()));
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Exception in doClose(): " + ex.Message);
}
}
private void MainForm_MouseMove(object sender, MouseEventArgs e)
{
resetTimeout();
}
private void MainForm_KeyPress(object sender, KeyPressEventArgs e)
{
resetTimeout();
}
private void MainForm_Click(object sender, EventArgs e)
{
resetTimeout();
}
public void resetTimeout()
{
System.Diagnostics.Debug.WriteLine("resetTimeout()");
lock(lockCounter)
countDown = 3;
}
public void timerCallback(object stateInfo)
{
lock (lockCounter)
countDown--;
if (countDown == 0)
{
System.Diagnostics.Debug.WriteLine("timeout->doClose()");
doClose();
}
}
private void MainForm_Closed(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("mainForm CLOSED");
}
}
}
Specials:
a lock object to sync access to the counter var
a threading timer that runs independent of the message pump
a delegate to by called from the TimerCallback function
My form is going to run some code that might take a while to execute. I would like to display a "please wait" message while the operation is running on the background.
I'd like to have that message in a form, one that I can control its visibility, and also its text, from other forms.
I'd also like it to be set to start in the Program.cs file.
My code, so far:
namespace KAN
{
public partial class prosze_czekac : Form
{
public prosze_czekac()
{
InitializeComponent();
}
private delegate void OffVisible();
public void Wylacz()
{
if (this.InvokeRequired)
this.Invoke(new OffVisible(Wylacz));
else
this.Visible = false;
}
delegate void SetTextCallback(string text);
public void ZmienTekst(string text)
{
if (this.InvokeRequired)
{
//SetTextCallback d = new SetTextCallback(this.ZmienTekst);
Invoke(new SetTextCallback(this.ZmienTekst), text);
//Invoke(d, new object[] { text });
}
else
{
this.Visible = true;
this.Text = text;
this.lblKomunikat.Text = text;
this.Update();
}
}
}
}
I do not know how to run a form, how to create an instance and as editing text. All this in any form, any thread.
Is the above code is correct and how to use it to make it properly?
How am I so ready form "please wait" I would like to turn it on now in the initial class (Program.cs). Use it in any form design.
Sample code, do not know if correct:
namespace KAN
{
static class Program
{
public static prosze_czekac PleaseWait;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Thread thread = new Thread(new ThreadStart(PleaseWait.Show());
PleaseWait.ZmienTekst("Please wait... Running the program");
// long operation
PleaseWait.Wylacz();
Application.Run(new main());
}
}
}
namespace KAN
{
public partial class main: Form
{
public main()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// examples of long task in another form
for (int i = 0; i < 5; i++)
{
Program.PleaseWait.ZmienTekst((i + 1).ToString());
System.Threading.Thread.Sleep(1000);
}
Program.PleaseWait.Wylacz();
}
}
}
The first time I ask a question, please bear with me.
PS
"Wylacz" is "exit" (void) and is meant to "hide" so that every time you do not initiate the form.
"prosze_czekac" is "please wait".
Use the BackgroundWorker. The following code assumes, you have a button 'button1' in your form, which executes the worker, which starts the long running task on a different thread:
BackgroundWorker _worker;
// button click starts the execution of the lung running task on another thread
private void button1_Click(object sender, EventArgs e)
{
label1.Visible = true; // show the label "please wait"
_worker.RunWorkerAsync();
}
private void Form1_Load(object sender, EventArgs e)
{
// initialize worker
_worker = new BackgroundWorker();
_worker.DoWork += new DoWorkEventHandler(worker_DoWork);
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
}
// executes when long running task has finished
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// hide the label
label1.Visible = false;
}
// is called by 'RunWorkerAsync' and executes the long running task on a different thread
void worker_DoWork(object sender, DoWorkEventArgs e)
{
// long running task (just an example)
for (int i = 0; i < 1000000000; i++)
{
}
}
Hey I have this test back ground worker which seems to get stuck on the DoWork method or maybe the RunWorkerCompleted is not being fired can you guys see anything wrong here?
Maybe I am not implementing this properly :/
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;
using MailChimp;
using System.Threading;
using System.Runtime.InteropServices;
namespace Chimporter
{
public partial class Form1 : Form
{
//Worker thread flag set to false
static bool done = false;
//Console dll
[DllImport("Kernel32.dll")]
static extern Boolean AllocConsole();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
}
private void label1_Click(object sender, EventArgs e)
{
}
private void accountInformationToolStripMenuItem_Click(object sender, EventArgs e)
{
//loadWindow pleaseWait = new loadWindow();
//pleaseWait.Show();
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(bg_DoWork);
bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
bg.RunWorkerAsync();
while (!done)
{
//Console.WriteLine("Waiting in Main, tid " + Thread.CurrentThread.ManagedThreadId);
//Thread.Sleep(100);
}
//AccountInfo accInfo = new AccountInfo();
//accInfo.Show();
}
public void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!AllocConsole())
{
Console.WriteLine("Fnished! " + Thread.CurrentThread.ManagedThreadId);
}
done = true;
}
public void bg_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 3; i++)
{
if (!AllocConsole())
{
Console.WriteLine("Work Line: " + i + ", tid " + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(500);
}
}
//string key = "e42713458882f6c2c27b3d6d951174a2-us6";
//var mc = new MCApi(key, true);
//string user = mc.GetAccountDetails().Username.ToString();
return;
}
private void menuStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
//Exit application Form.Close()
Application.Exit();
}
}
}
Take your while (!done) out. It is locking up the main thread. The worker completed event gets raised on that thread, but since it is busy in a loop it will never get raised.
The whole point of the RunWorkerCompleted event is so that you get a notification on the main thread and you don't have to lock it up in a busy loop and make your gui unresponsive.