在主winForm中有个进度条控件,点击某个button后,调用一个cs文件里的某个函数,这个函数用来遍历一个文件的,希望将函数遍历文件的进度反映到进度条上。
这里因为遍历文件的函数在另外一个类中,不在winForm类中,而遍历函数要对winForm中的progressbar进行操作,这就要用到委托了。
为什么要用到委托呢?
因为winform是主线程创建的,你用另外一个线程来调用它就可能会出现两个线程同时访问同一个资源的问题,这个时候很容易出现错误,比如A线程改变窗口的颜色为红色,B线程取窗口的颜色,如果这两个线程正好碰到一起,可能A略先于B,那么就是B取出来的红色,如果A略后于B,那么B取出来的就是窗口原来的颜色,而谁先谁后,这和CPU的繁忙度、时间片的轮转是相关的,是一种随机的情况,那么B取出来的颜色就不可靠了,因此为了避免这种状况采用委托,B线程向A线程发出委托,由A线程来完成取色工作,那么可以保证取色工作的稳定性,结果也可靠
Invoke,BeginInvoke,InvokeRequired
如果我们需要在另外一个线程里面对UI进行操作,我们需要一个操作UI界面的函数,还需要一个该函数的委托delegate,当然,我们可以自定义委托。
.net中有很多其他类型的委托,可以直接使用,不需要而外声明。例如:MethodInvoker和EventHandler,这两种类型委托的函数外观是固定的。
MethodInvoker是void Function()类型的委托,无参数。
EventHandler是void Function(object,EventArgs)类型的委托,这里参数类型和数量都是固定的。
这两种委托可以很方便的调用,但是缺乏灵活性。
请注意BeginInvoke前面的对象是this,也就是主线程。
再介绍Control.InvokeRequired,Control是所有控件的基类,对于这个属性MSDN的描述是:
获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke方法,因为调用方位于创建控件所在的线程以外的线程中。
该属性可用于确定是否必须调用Invoke 方法,当不知道什么线程拥有控件时这很有用。
也就是说通过判断InvokeRequired可以知道是否需要用委托来调用当前控件的一些方法。
下面来进行实例演练
首先在主Form中加入下面代码:
/// <summary>
/// Form1 类
/// </summary>
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//用子线程工作
new System.Threading.Thread(new System.Threading.ThreadStart(StartParse)).Start();
}
//开始parse file
public void StartParse()
{
ParseFile pf = new ParseFile();
pf.onParseProgress += new ParseFile.ParseProgress(parse_onParseProgress);
pf.Parse(txtMerchant.Text.Trim(), txtSave.Text.Trim()); //two textboxes
}
//同步更新UI
private void parse_onParseProgress(int nTotal, int nCurrent)
{
if (this.InvokeRequired)
{
//ret = this.BeginInvoke(new ParseFile.ParseProgress(parse_onParseProgress), new object[] { nTotal, nCurrent });
this.Invoke(new ParseFile.ParseProgress(parse_onParseProgress), new object[] { nTotal, nCurrent });
}
else
{
this.progressBar1.Maximum = nTotal;
this.progressBar1.Value = nCurrent;
lblProgress.Text = Convert.ToInt32(nCurrent / nTotal * 100).ToString() + "% Processing";
}
}
在遍历文件的类ParseFile中,我们加入委托和事件,用来反映现在的进度
class ParseFile
{
public delegate void ParseProgress(int nTotal, int nCurrent);
public event ParseProgress onParseProgress;
public bool Parse(string strFileName, string strOutputFile)
{
//get total lines of a file
int nTotal = 1;
using (StreamReader reader1 = new StreamReader(strFileName))
{
string strText = reader1.ReadToEnd();
MatchCollection mc = Regex.Matches(strText, "rn");
if (mc != null)
nTotal = mc.Count;
}
//process each line
using (StreamReader reader = new StreamReader(strFileName))
{
int i=0;
while (reader.Peek() > 0)
{
string strLine = reader.ReadLine();
ParseOneLine(strLine);
i++;
if (onParseProgress != null)
onParseProgress(nTotal, i);
}
}
}
这样就完成了在winform窗口不断更新进度条功能。
http://visionsky.blog.51cto.com/733317/368837
多线程基本知识点
新建一个线程(无参数,无返回值)
Thread th = new Thread(new ThreadStart(PrintName));
public void PrintName() // 函数
{
//函数体
}
这里一定注意ThreadStart中的函数是没有返回值和参数的
那么有参数时,就该如下:
Thread th = new Thread(new ParameterizedThreadStart(PrintName));
public void PrintName(string name) // 函数
{
//函数体
}
如果遇到又需要返回值,又需要参数的时候,就可以考虑用异步:
但是需要先申明个委托
public delegate string MethodCaller(string name);//定义个代理
MethodCaller mc = new MethodCaller(GetName);
string name = "my name";//输入参数
IAsyncResult result = mc.BeginInvoke(name,null, null);
string myname = mc.EndInvoke(result);//用于接收返回值
public string GetName(string name) // 函数
{
return name;
}