A stopwatch program is also a heart disease for me, because I always want to write such a thing, but I always want to go over the GUI, so it is quite awkward, it may be due to the issue of learning MFC at school, but when I want to write such a thing with Win Form today, it is so easy.
Therefore, it is not terrible to do it. You are afraid that you will not do it, because you will never know whether you can do it. Facts have proved that you can handle most of the things you hesitate to do.
Although a simple stopwatch function is successfully implemented, that is, start timing and stop. However, it raises a question about the win form and C # threads.
Next, let's talk about the class implementation of stopwatch.
Namespace Utils {public class Time {private int _ minute; private int _ second; private bool _ flag; // The Thread ID private Thread _ TimingThread = null; public Time () {this. _ minute = 0; this. _ second = 0; this. _ flag = true;} // <summary> // Start timing // </summary> public void Start () {if (_ TimingThread = null) {_ TimingThread = new Thread (new ThreadStart (AddSecond); _ TimingThread. start () ;}/// <summary> // Thread execution method /// </summary> private void AddSecond () {while (_ flag) {Thread. sleep (1000); if (this. _ second = 59) {this. _ minute ++; this. _ second = 0;} else {this. _ second ++ ;}}} /// <summary> /// format and display the timing result /// </summary> /// <returns> </returns> public string FormatTimeResult () {string minute = string. empty; string second = string. empty; if (this. _ minute <10) {minute = "0" + this. _ minute. toString ();} else {minute = this. _ minute. toString ();} if (this. _ second <10) {second = "0" + this. _ second. toString ();} else {second = this. _ second. toString ();} return minute + ":" + second;} // <summary> // Stop /// </summary> public void Stop () {this. _ flag = false;} /// <summary> // return to 0 operation // </summary> public void Zero () {this. _ minute = 0; this. _ second = 0 ;}}}
The implementation of stopwatch is still relatively simple. I feel like writing this, and it is also convenient for future expansion.
Let's talk about win form.
The form is like this. One label and two buttons
Public partial class Form1: Form {private Time mTime = null; private Thread mDisplayThread = null; public Form1 () {InitializeComponent (); mTime = new Time (); // instantiate stopwatch class} private void button_start_Click (object sender, EventArgs e) {mTime. start (); mDisplayThread = new Thread (new ThreadStart (DisplayCurrentTime); mDisplayThread. start (); button_start.Enabled = false;} public void DisplayCurrentTime () {while (true) {Thread. sleep (1000); label_Time.Text = mTime. formatTimeResult (); // update the Label Console in real time. writeLine ("{0}", mTime. formatTimeResult () ;}} private void button_stop_Click (object sender, EventArgs e) {mTime. stop (); button_start.Enabled = true ;}}
In this way, there is no problem in thinking. When you click the start timer button to create a thread, the thread is used to update the display timer on the label every second.
However, the following error is reported: Cross-thread operation not valid: Control 'label _ time' accessed from a thread other than the thread it was created on.
I checked it online. This error seems to be very common. The MSDN also gave me the cause of this error. It is said that when you try to update a win form from a separate thread, this error occurs.
Check that when you want to modify the Control attributes on win form, you can only call the Control creation thread and cannot call other threads. And the above
label_Time.Text = mTime.FormatTimeResult();
This code occurs in a newly created thread, so an error is reported.
The solution is to use delegate (delegate) and control. Invoke for joint implementation. See the implementation section below
Public partial class Form1: Form {private Time mTime = null; private Thread mDisplayThread = null; public delegate void UpdateLabel (); // declare a delegate public UpdateLabel updateLabel; // define a delegate public Form1 () {InitializeComponent (); mTime = new Time (); updateLabel = new UpdateLabel (UpdateTime ); // instantiate a delegate object} private void button_start_Click (object sender, EventArgs e) {mTime. start (); mDisplayThread = new Thread (new ThreadStart (DisplayTimeFunc); mDisplayThread. start (); button_start.Enabled = false;} // <summary> // thread execution method // </summary> public void DisplayTimeFunc () {while (true) {Thread. sleep (1000); this. invoke (this. updateLabel) ;}/// <summary> /// refresh the Label separately /// </summary> public void UpdateTime () {label_Time.Text = mTime. formatTimeResult ();} private void button_stop_Click (object sender, EventArgs e) {mTime. stop (); button_start.Enabled = true ;}}
In this Code, the mDisplayThread executes the DisplayTimeFunc method, while the DisplayTimeFunc method actually updates the label. The difference is that the Control is used. the Invoke method does not mean that the modification to the control property must be executed only in the thread of the control creation? It looks like the old one. That's because we don't know what Control. Invoke is. The MSDN explanation is: Execute the specified delegate on the thread that owns the basic window handle of this control. OK. Now, this. updateLabel is executed in the thread created in the window.
Looking back, the idea is also relatively simple, that is, first put the operation to change the control property in a method, then write a delegate, and then write a thread, it is okay to call this delegate in the thread's execution method.
However, this is not all done. There is also a small problem, that is, when I want to close this form after timing, I find that the error is reported again:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
The reason for this problem is that we have not wiped the PP after the toilet is finished. No operation in the code above is to terminate the mDisplayThread thread.
So we also need to add the following actions
private void Form1_FormClosing(object sender, FormClosingEventArgs e) { mDisplayThread.Abort(); }
In this way, the thread is terminated before the Form1 form is closed.
When doing this little thing, I learned some delegate and Control. Invoke and thread knowledge points. I will find a time to take a good look at this part and try to sum up something.