没有系统地学过C#,所以一直以为C#只是拖拖拽拽,后来发现在程序实现过程中需要注意好多,标题说跨类的做法我不是很赞同,一种好的方法应该是把其他类的方法封装起来然后在winFrm里面调用,而不应该让其他的类涉及到任何和窗体程序相关函数。这个是面向对象程序设计中比较重要的一点。
但是有的时候,如果设计没做好,就可能真的会遇到要有点跨类调用的情况。
就像前几天写了一个基于TCP的C/S自由收发消息的多客户端小程序,如果把socket类处理从winFrm中分开,那么就会发现socket中阻塞的时候,如果要更新UI,就会比较麻烦,因为是跨类。
你不知道什么时候会子线程阻塞,所以我们要做的是对子线程中某时候状态的做事件侦听,然后委托更新UI。
下面我写了三种方法,可能不是最好的,但是相对比较合理的。
有问题欢迎批评指正,共同交流。
首先我定义一个OtherClass类,是一个想要访问UI的非窗体类。 里面有个函数CatchSocket是模拟捕获socket包的,捕获到后在UI上更新。捕获过程是一个线程。
class OtherClass
{
public void CatchSocket()
{
Random rd = new Random();
while(true) {
// 这里可以模拟捕捉客户Socket的,假定Socket内容用捕获的时间代替
int time = rd.Next(1000, 2000);
Thread.Sleep(time);
// 捕获到后我想显示在UI上
// ?????
}
}
}
主窗体的点击后执行button捕获:
public partial class Form1 : Form
{
// 其他想访问UI的类
OtherClass m_obj = new OtherClass();
public Form1()
{
InitializeComponent();
}
private void button_exc_Click(object sender, EventArgs e)
{
BackgroundWorker bgw = new BackgroundWorker();
// 线程执行的内容
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
}
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
m_obj.CatchSocket();
}
// invoke UpdateUI ?????
}
主窗体代码:略
class OtherClass
{
public void CatchSocket(Object WinFrm)
{
Random rd = new Random();
while(true) {
// 这里可以模拟捕捉客户Socket的,假定Socket内容用捕获的时间代替
int time = rd.Next(1000, 2000);
Thread.Sleep(time);
// CatchSocket执行时把窗体对象引用传入
// 然后调用一个WinFrm的public函数(用invoke)去更新
// 不推荐:因为WinFram中就有OtherClass的实例对象,不太好。
// 略…………
}
}
}
方法 2
利用BackGroundWorker的ReportsProgress来更新。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace 跨类跨线程访问UI
{
public partial class Form1 : Form
{
// 其他想访问UI的类
OtherClass m_obj = new OtherClass();
public Form1()
{
InitializeComponent();
}
private void button_exc_Click(object sender, EventArgs e)
{
BackgroundWorker bgw = new BackgroundWorker();
// 线程执行的内容
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
// 开启进度报告,如果用方法2,就这么处理。
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
bgw.RunWorkerAsync();
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
//把BackgroundWorker线程的引用传入线程中。
m_obj.CatchSocket(sender);
}
// 进度变化时,执行的操作,控件访问上线程安全
void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// e.ProgressPercentage线程执行百分比,这里把它作为ID来用
// Operator.printSocket枚举类型,你也可以直接用int型写if
// e.UserState 进度变化传回的对象
if (e.ProgressPercentage == (int)Operator.printSocket)
{
textBox_show.Text += "Socket :" + ((int)e.UserState).ToString() + "\r\n";
}
}
}
}
OtherClass类
namespace 跨类跨线程访问UI
{
// 这个只是为了我自己方便
public enum Operator
{
printSocket = 1
}
class OtherClass
{
public void CatchSocket(Object sender)
{
Random rd = new Random();
while (true)
{
// 这里可以模拟捕捉客户Socket的
// 假定Socket内容用捕获的时间代替
int time = rd.Next(1000, 4000);
Thread.Sleep(time);
// 用backgroudworker的ReportsProgress功能,这个功能原来是用来做线程进度报告的。
// 这里运用比较巧,要传入主线程backgroudworker
// Thread.CurrentThread的话没法得到backgroudworker
BackgroundWorker bgw = (BackgroundWorker)sender;
// 第一个参数为线程进度百分之?,1-100当作ID,可以设定不同ID来实现不同的Report.
// 第二个参数可以是一个对象,也可以是一个对象数据,e.UserState获得
bgw.ReportProgress((int)Operator.printSocket, time);
}
}
}
}
方法3
可以说是C#里一个比较正统的做法,用delegate和event组合操作。
namespace 跨类跨线程访问UI
{
class OtherClass
{
// 声明委托
public delegate void AcceptSocketHandler(int socket);
// 定义捕获到socket事件,类型AcceptSocketHandler
public event AcceptSocketHandler AcceptedEventHandler;
public void CatchSocket()
{
Random rd = new Random();
while(true) {
// 这里可以模拟捕捉客户Socket的,假定Socket内容用捕获的时间代替
int time = rd.Next(1000, 2000);
Thread.Sleep(time);
// 方法3:用delegate配合event事件处理。
accepted(time); // 捕获到socket后立即执行该函数,这个函数会触发AcceptedEventHandler
}
}
// accepted后执行的事件
void accepted(int socket)
{
if (AcceptedEventHandler != null)
{
AcceptedEventHandler(socket);
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace 跨类跨线程访问UI
{
public partial class Form1 : Form
{
// 其他想访问UI的类
OtherClass m_obj = new OtherClass();
public Form1()
{
InitializeComponent();
// 当AcceptedEventHandler触发时,执行PrintToTxb,这里还可+=多个,还可以-=。
m_obj.AcceptedEventHandler += new OtherClass.AcceptSocketHandler(PrintToTxb);
}
private void button_exc_Click(object sender, EventArgs e)
{
BackgroundWorker bgw = new BackgroundWorker();
// 线程执行的内容
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerAsync();
}
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
m_obj.CatchSocket();
}
// 方法3 对event事件的响应函数
void PrintToTxb(int socket)
{
// 委托更新,这里偷了两个懒,
// 一个是AcceptSocketHandler的委托类型刚好与更新UI的函数委托需要的参数相同
// 所以就直接使用了,其实应该在本类中也声明一个。
// lambda表达式可以把匿名函数写得更好,不习惯的还是单独写成委托方法比较好。
if (textBox_show.InvokeRequired)
{
textBox_show.Invoke(new OtherClass.AcceptSocketHandler(sk =>
{
textBox_show.Text += "方法3捕获到Socket :" + sk.ToString() + "\r\n";
}), socket);
}
}
}
}
另外下面放一个窗体类中跨线程更新UI的方法,这个网上很多,就不多说了。主要是留给自己看。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.Remoting.Messaging;
namespace ThreadSmp
{
///
/// 1、声明委托,delegate是关键字和public,private一样都是用来声明。
/// 2、定义ModifyLabelHanddler这种委托类,这种类封装了指向函数的"指针"!!
/// 这里只能定义了函数类型需要为void functionName(String arg)类型
/// 3、同理如果定义delegate String textHanddler(String arg1, int arg2);
/// 就是委托String functionName(String arg1, int arg2)这样的函数。
///
///
delegate void ModifyLabelHandler(String arg);
// 定义一个指向OtherClass.DataMaker的委托类(用BackGroundWorker就不用这个)
delegate String DataMakerHandler(int a, int b);
public partial class MainForm : Form
{
///
/// 组合自定义对象,涉及多线程调用,比如数据导入导出、串口通信、tcp通信等……
///
OtherClass m_classObj = null;
///
/// 委托对象
///
ModifyLabelHandler modifyLabelHanddler;
DataMakerHandler dataMakerHandler; // 用BackgroudWorker就不用这个。
public MainForm()
{
InitializeComponent();
m_classObj = new OtherClass(); // 实例化
modifyLabelHanddler = new ModifyLabelHandler(upDateLabelUI); //构造方法为声明时制定类型的函数名
dataMakerHandler = new DataMakerHandler(m_classObj.DataMaker); // 用BackgroudWorker就不用这个。
}
///
/// 创建线程来执行m_classObj中的一些操作,防止主界面卡死。
///
///
///
private void button_show_Click(object sender, EventArgs e)
{
label_show.Text = "处理中......";
Random r = new Random();
int a = r.Next(0, 999);
int b = r.Next(0, 999);
// 方法1:采用BackgroundWorker异步执行
/*BackgroundWorker bgw = new BackgroundWorker(); //也可以用Thread,不多记得用isBackGround运行。
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(completeRun);
bgw.RunWorkerAsync(new int[]{a, b}); // 异步执行,可以带任意类型,可以为空。
// 方法2:直接为组合对象的方法定义委托。
dataMakerHandler.BeginInvoke(a, b, new AsyncCallback(completeRun), null);
}
///
/// 后台线程的具体操作
///
///
///
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
//e.Argument这个参数可以强制转化为传入的类型
// 处理数据,并且希望在数据处理结束后更新UI。
String res = m_classObj.DataMaker(((int[])e.Argument)[0], ((int[])e.Argument)[1]);
// 利用委托更新UI;
if (label_show.InvokeRequired)
{
// 这里又设计同步请求和异步请求的问题。
// 如果是Invoke,为同步请求,只有当UI更新完后才能做别的事情。
// 如果是BeginInvoke,为异步请求,那么UI没更新完也可以做后面的事情。
label_show.Invoke(modifyLabelHanddler, res);
//IAsyncResult asyncResult = label_show.BeginInvoke(modifyLabelHanddler, res);
}
//返回值为委托函数的返回值,这里没有返回值。注意该方法如果得不到结果会阻塞线程
//label_show.EndInvoke(asyncResult);
Console.WriteLine("做别的事情。。。。。");
}
///
/// 更新UI
///
///
private void upDateLabelUI(String arg)
{
label_show.Text = arg;
}
// 法1用的 线程结束后执行的操作;
private void completeRun(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("处理结束。。。。");
}
// 法2用的
private void completeRun(IAsyncResult iResult)
{
DataMakerHandler handler = (DataMakerHandler)((AsyncResult)iResult).AsyncDelegate;
label_show.Invoke(modifyLabelHanddler, handler.EndInvoke(iResult));
Console.WriteLine("处理结束。。。。");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ThreadSmp
{
class OtherClass
{
public String DataMaker(int a, int b)
{
Thread.Sleep(2000);
String res = String.Format("我的处理结果是{0} + {1} = {2}", a, b, a + b);
return res;
}
}
}
参考博客
http://www.cnblogs.com/smileberry/p/3912918.html
http://blog.csdn.net/bdstjk/article/details/7004035