C# 所谓的跨类跨线程访问控件

没有系统地学过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 ?????
}

方法 1

主窗体代码:略

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的方法。


另外下面放一个窗体类中跨线程更新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


你可能感兴趣的:(Csharp)