C# 一个简单的秒表引发的窗体卡死问题

简介:

一个秒表程序也是我的一个心病,因为一直想写这样的一个东西,但是总往GUI那边想,所以就比较怵,可能是上学的时候学MFC搞出的后遗症吧,不过当我今天想好用Win Form(话说还是第一次写win form)写这么一个东西的时候,居然so easy。

所以说,做不了不可怕,怕的是你不去做,因为你不去做,你就永远不知道你能不能做它。事实证明,大部分你犹豫能不能做的事情,实际上你都能搞定。

虽然成功实现了一个秒表的简单功能,即开始计时和停止。但是却引发了一个关于win form和C#线程的问题。

下面一个一个来,先说一下秒表的类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
namespace  Utils
{
     public  class  Time
     {
         private  int  _minute;
         private  int  _second;
         private  bool  _flag; //线程标识
         private  Thread _TimingThread =  null ;
 
         public  Time()
         {
             this ._minute = 0;
             this ._second = 0;
             this ._flag =  true ;
         }
         /// <summary>
         /// 开始计时
         /// </summary>
         public  void  Start()
         {
             if  (_TimingThread ==  null  || ! this ._flag)
             {
                 this ._flag =  true ;
                 _TimingThread =  new  Thread( new  ThreadStart(AddSecond));
                 _TimingThread.Start();
             }
         }
         /// <summary>
         /// 线程执行方法
         /// </summary>
         private  void  AddSecond()
         {
             while (_flag)
             {
                 Thread.Sleep(1000);
                 if  ( this ._second == 59)
                 {
                     this ._minute++;
                     this ._second = 0;
                 }
                 else
                 {
                     this ._second++;
                 }
             }
         }
         /// <summary>
         /// 格式化显示计时结果
         /// </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>
         /// 停止
         /// </summary>
         public  void  Stop()
         {
             this ._flag =  false ;
         }
         /// <summary>
         /// 归0操作
         /// </summary>
         public  void  Zero()
         {
             this ._minute = 0;
             this ._second = 0;
         }
     }
}

秒表的实现还是比较简单的,感觉这样写,也方便以后做扩展。

下面说说win form方面

窗体就是这样,一个label,两个button

wKioL1OaoxXCE8UWAABYmeO2fas677.jpg

最开始,我写了这样一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
     public  partial  class  Form1 : Form
     {
         private  Time mTime =  null ;
         private  Thread mDisplayThread =  null ;
 
         public  Form1()
         {
             InitializeComponent();
             mTime =  new  Time(); //实例化秒表类
         }
 
         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();
                 Console.WriteLine( "{0}" , mTime.FormatTimeResult());
             }
         }
         
         private  void  button_stop_Click( object  sender, EventArgs e)
         {
             mTime.Stop();
             button_start.Enabled =  true ;
         }
     }

这样写感觉思路上没什么问题,当点击【开始计时】按钮的同时创建一个线程,而这个线程是用来每隔一秒去更新一下label上的显示计时时间。

然而,之后却报一个这样的错误:Cross-thread operation not valid: Control 'label_Time' accessed from a thread other than the thread it was created on.

网上查了一下,这个错误貌似很常见,MSDN上也给了一个出现此错误的原因,是这样说的,当您试图从单独的线程更新一个win form时,会出现这个错误。

查了一下,就是说win form上的控件属性想要进行修改的时候,只能在创建Control的线程里调用,不能在以外的线程被调用。而上面的

1
label_Time.Text = mTime.FormatTimeResult();

这段代码呢恰恰是发生在新创建的线程之中,所以就会报错了。

解决办法是用delegate(委托)加上control.Invoke去联合实现。下面看看实现部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public  partial  class  Form1 : Form
     {
         private  Time mTime =  null ;
         private  Thread mDisplayThread =  null ;
         public  delegate  void  UpdateLabel(); //声明一个委托
         public  UpdateLabel updateLabel; //定义一个委托
 
         public  Form1()
         {
             InitializeComponent();
             mTime =  new  Time();
             updateLabel =  new  UpdateLabel(UpdateTime);
         }
 
         private  void  button_start_Click( object  sender, EventArgs e)
         {
             mTime.Start();
             mDisplayThread =  new  Thread( new  ThreadStart(DisplayTimeFunc));
             mDisplayThread.Start();
             button_start.Enabled =  false ;
 
         }
         /// <summary>
         /// 线程执行方法
         /// </summary>
         public  void  DisplayTimeFunc()
         {
             while  ( true )
             {
                 Thread.Sleep(1000);
                 this .Invoke( this .updateLabel);
             }
         }
 
         /// <summary>
         /// 单独对Label进行刷新
         /// </summary>
         public  void  UpdateTime()
         {
             label_Time.Text = mTime.FormatTimeResult();
         }
 
         private  void  button_stop_Click( object  sender, EventArgs e)
         {
             mTime.Stop();
             button_start.Enabled =  true ;
         }
 
     }

这段代码里mDisplayThread线程执行了DisplayTimeFunc方法,而DisplayTimeFunc方法里实际就是在更新label,不同的是使用了Control.Invoke方法,上面不是说对控件属性的更改要在创建控件的线程里才执行吗?现在看起来好像还是老样子。那是因为我们不了解Control.Invoke是什么东东。MSDN上的解释是:在拥有此控件的基础窗口句柄的线程上执行指定的委托。OK,明白了,this.updateLabel这个委托最后还是在窗口创建的线程中执行的。

回头想想,其实思路也比较简单,就是先将更改控件属性的操作放在一个方法里,然后写个委托,再写个线程,在线程的执行方法中调用这个委托就OK啦。

不过到这还不算全完,还有一个小问题,就是当我计时之后,想要关闭这个窗体的时候,发现又开始报错了:

Invoke or BeginInvoke cannot be called on a control until the window handle has been created.

研究了一下发现了出现此问题的原因,就是我们“上完厕所没有擦PP”,上面的代码中没有一个操作是对 mDisplayThread 这个线程做了终止的动作。

所以我们还需要添加以下动作

1
2
3
4
         private  void  Form1_FormClosing( object  sender, FormClosingEventArgs e)
         {
             mDisplayThread.Abort();
         }

这样就完整了,在关闭Form1窗体之前,先把线程终止。

做这个小东西的时候居然连带着让我了解了一些委托和Control.Invoke以及线程的知识点。我会找个时间好好把这部分看看的,争取能总结点什么出来。










本文转自 我不会抽烟 51CTO博客,原文链接:http://blog.51cto.com/zhouhongyu1989/1425919,如需转载请自行联系原作者

目录
相关文章
|
7月前
|
Java 数据库 C#
C#winforms实现windows窗体人脸识别
C#winforms实现windows窗体人脸识别
|
关系型数据库 MySQL C#
C# winform 一个窗体需要调用自定义用户控件的控件名称
给用户控件ucQRCode增加属性: //二维码图片 private PictureBox _pictureBoxFSHLQrCode; public PictureBox PictureBoxFSHLQrCode {   get { return _pictureBoxFSHLQrCode; }   set { this.pictureBoxFSHLQrCode = value; } } 在Form1窗体直接调用即可: ucQRCode uQRCode=new ucQRCode(); ucQRCode.PictureBoxFSHLQrCode.属性= 要复制或传给用户控件上的控件的值
73 0
|
2月前
|
API C# Windows
【C#】在winform中如何实现嵌入第三方软件窗体
【C#】在winform中如何实现嵌入第三方软件窗体
133 0
|
5月前
|
开发框架 数据可视化 C#
|
7月前
|
C#
C#如何实现窗体最小化到托盘
C#如何实现窗体最小化到托盘
103 0
|
7月前
|
JavaScript Linux C#
【傻瓜级JS-DLL-WINCC-PLC交互】1.C#用windows窗体控件创建.net控件
【傻瓜级JS-DLL-WINCC-PLC交互】1.C#用windows窗体控件创建.net控件
150 0
|
7月前
|
C# Windows
C#安装“Windows 窗体应用(.NET Framework)”
C#安装“Windows 窗体应用(.NET Framework)”
218 0
|
C# 数据安全/隐私保护
C# 窗体之间参数互相传递的两种方法与使用
C# 窗体之间参数互相传递的两种方法与使用
|
C# Kotlin
C#is、as关键字及获取当前活动窗体的实例
这篇日志记录一下C#中is关键字及as关键字的用法。 Is :判断检查对象是否与给定类型兼容 As :将对象转换为指定类型(强转),就跟(int )这样的用法是一样的。 获取当前窗体的活动子窗体。
60 0
|
C# 数据安全/隐私保护
ApeForms | C# WinForm窗体控件平滑减速运动
在桌面软件开发中,有时会需要控制窗体或控件移动以实现某些界面效果,比如幻灯片换页、侧面的展开栏等。 通常情况下我们会使用Timer以每隔一段时间修改一下坐标位置的方式来实现目标对象的位移效果,但通过这个方式实现的动效存在几个问题: 匀速运动效果生硬; 运动过程中不便灵活改变运动状态(如侧栏展开一半时令其收起); 动效多时需要创建多个Timer对象,不易管理且占用资源; ApeForms中为控件和窗体提供了平滑运动的扩展方法,很好的解决了这些问题。不仅是坐标的平滑运动,还有控件\窗体尺寸的平滑变化、透明度的平滑变化。允许在变化的中途随时更改目标坐标\尺寸\透明度,且使用共享的Timer
11404 1