zoukankan      html  css  js  c++  java
  • 小技巧分享:使用PostMessage向窗体传递任何数据

    WinForm的开发者们,想必对PostMessage和SendMessage两个API都非常熟悉了。下面给出PostMessage函数在C#中的两种声明形式:

    代码
     [DllImport("user32.dll ", CharSet = CharSet.Unicode)]
     
    static extern bool PostMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
     [DllImport(
    "user32.dll ", CharSet = CharSet.Unicode)]
     
    static extern bool PostMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);

    wParam参数和lParam参数是一个32位(在32位的系统中)的整型。因为C++支持指针,wParam和lParam可以是指向任意类型对象的指针(指针本质上就是整型),而C#不支持指针,所以wParam参数和lParam参数只能为int型或IntPtr型。这就限制了我们向控件传递任意的数据类型。

    当然IntPtr有类似指针的作用,但我们又如何使自定义类型的对象获得IntPtr呢?当然会想到用System.Runtime.InteropServices.Marshal.StructureToPtr()方法,使用该方法,不但有诸多限制,而且效率极为低下(因为需要额外分配内存和进行内存拷贝)。

    今天我和大家分享一个“曲线救国”的方法和该方法的一些使用场景。

    首先定义一个用于承载传递数据的类或结构,可以如下:

    代码
        /// <summary>
        
    /// 数据传递类
        
    /// </summary>
      public  class DataTransfer
        {
            
    /// <summary>
            
    /// 传递的字符串
            
    /// </summary>
          public  string transferMessage;
            
    /// <summary>
            
    /// 传递的其他任意类型的数据
            
    /// </summary>
          public  object obj;
        }

    您可以自定义DataTransfer类来承载你需要传递的数据,里面的transferMessage和obj字段就是你要传递的实际数据。

    定义一个类型为Dictionary<int, DataTransfer>的集合对象datas,当你需要给某控件传递数据时,先生成一个DataTransfer对象value,把value加入到datas集合中,则会获得从datas中提取该value的数据键key(关键是该key是整型的),调用PostMessage把key赋值给wParam参数或lParam参数发送消息到窗体,窗体收到消息后用key来datas中提取数据,再把datas中该键值对删除。这样貌似就达到了利用 PostMessage函数向控件发送任意类型数据的目的。当然,我们可以定义一个传递数据的辅助类,该辅助类看起来应该像是这样子的:

    代码
      /// <summary>
        
    ///  传递数据的辅助类
        
    /// </summary>
        class TransferDataHelper
        {
            
    /// <summary>
            
    /// 自定义消息号
            
    /// </summary>
            public const int User_Message = 0x401;
            
    /// <summary>
            
    /// 数据集合
            
    /// </summary>
            static Dictionary<int, DataTransfer> datas;

            
    /// <summary>
            
    /// 线程同步对象
            
    /// </summary>
            static object objLock;

            
    /// <summary>
            
    /// 数据键,用于标记数据
            
    /// </summary>
            static   int dataKey=0;

            [DllImport(
    "user32.dll ", CharSet = CharSet.Unicode)]
            
    private static extern bool PostMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);

            
            
    static TransferDataHelper()
            {
                datas 
    = new Dictionary<int, DataTransfer>();
                objLock 
    = new object();
            }
            
    /// <summary>
            
    /// 传递数据
            
    /// </summary>
            
    /// <param name="value"></param>
            
    /// <param name="formHandle"></param>
            public static void TansferData(DataTransfer value,IntPtr formHandle)
            {
                
    lock (objLock)
                {
                    dataKey
    ++;
                    datas.Add(dataKey, value);
                    PostMessage(formHandle, User_Message, dataKey, 
    0);
                }
            }

            
    /// <summary>
            
    ///  提取数据
            
    /// </summary>
            
    /// <param name="pickDataKey"></param>
            
    /// <returns></returns>
            public static DataTransfer GetData(int pickDataKey)
            {
                  
    lock(objLock)
                  {
                      
                      DataTransfer value 
    = datas[pickDataKey];
                      datas.Remove(pickDataKey);
                      
    return value;
                  }
            }

        }

    使用该助手类,使用TansferData方法向控件发送数据,控件收到消息后使用GetData方法提取数据。

    下面给出窗体如何使用该助手类的演示代码:

    代码
      /// <summary>
        
    /// 窗体基类
        
    /// </summary>
        public partial class BaseForm : Form
        {
            
    public BaseForm()
            {
                InitializeComponent();
            }

            
    protected virtual void ReceiveData(DataTransfer value)
            {
     
            }


            
    protected override void WndProc(ref Message m)
            {
                
    // 若是自定义消息号,则利用WParam提取数据内容,并调用ReceiveData处理数据
                if (m.Msg == TransferDataHelper.User_Message)
                {
                    
    this.ReceiveData(TransferDataHelper.GetData(m.WParam.ToInt32()));
                    
    return;
                }
                
    base.WndProc(ref m);
            }
        }

    原理介绍完了,巨简单吧,呵呵!技巧虽小,但用处却多,下面就给出两个应用场景。

    场景一:

    这个场景最常见,窗体上一个交互动作,需要去向后台线程提交一项长时间操作的任务,例如海量的数据查询、复杂的数据计算、长时间的IO操作……,总之,为了不让界面假死,该任务必须由后台线程去做。后台线程完成后,把任务结果返回给窗体来显示。这时我们该如何把返回的数据给窗体并在窗体上正确的显示呢?

    我们知道, 在frameWork2.0中,跨线程调用控件,是不被容许的,大部分同学采用的是这两种方法来解决该问题:

    //方法一:不进行跨线程安全检查

     System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;
     
    //方法二:使用委托
    Invoke(delegate{……});
     
    方法一的好处和坏处,我说不出个所以然来,但这个办法肯定不妙,比尔大叔都不建议这么做。方法二的缺点我是明白的,一是效率的问题,会导致数据在线程间的封送;是如果这个Invoke方法需要频繁的调用(每秒50次以上,我在做射频读卡器软件时遇到过),就会出问题。
     
    这时PostMessage就发挥作用了,把后台线程的操作结果通过Windows消息发给窗体,窗体收到消息后提取数据并按照你需要的方式显示,线程安全的问题以及效率的问题解决了。
    下面给出场景一的演示代码,首先定义一个资源请求的承载类或结构,该类用于承载长时间操作的任务,可以简单如下:
    代码
     /// <summary>
        
    /// 资源请求类
        
    /// </summary>
        public class AskDataTask
        {
            
    /// <summary>
            
    /// 发起请求的控件句柄
            
    /// </summary>
           public IntPtr askSource;
            
    /// <summary>
            
    /// 模拟需要请求的资源ID
            
    /// </summary>
           public string dataName;
        }

    一个简单的后台线程处理界面请求的类看起来应该是这样子的:

    代码
    class Server
        {
            
    private static Server instance;

            
    /// <summary>
            
    /// 唯一实例
            
    /// </summary>
            public static Server Instance
            {
                
    get {
                    
    if (instance == null)
                        instance = new Server();
                    
    return instance;
                }
                
            }
            
    /// <summary>
            
    /// 线程通知对象
            
    /// </summary>
            AutoResetEvent autoReset;
            
    /// <summary>
            
    /// 工作线程
            
    /// </summary>
            Thread threadWork;

            
    /// <summary>
            
    /// 工作任务队列
            
    /// </summary>
            Queue<AskDataTask> tasks;

            
    public Server()
            {
                
    this.autoReset = new AutoResetEvent(false);
                
    this.tasks = new Queue<AskDataTask>();
                
    this.threadWork = new Thread(Work);
                
    this.threadWork.Start();
            }

            
    private void Work()
            {
                
    while (true)
                {
                    AskDataTask task=null;
                    
    if (this.tasks.Count == 0)
                            autoReset.WaitOne();
                    
    else
                       task = tasks.Dequeue();
                    
                    
    if (task == nullcontinue;
                    
    //模拟长时间的操作
                    Thread.Sleep(1000);
                    
    //生成一个数据传递对象,回发给窗体。
                    DataTransfer data = new DataTransfer();
                    data.transferMessage = "来自服务器的数据";
                    
    //模拟获得的数据
                    data.obj = DateTime.Now.Ticks;
                    TransferDataHelper.TansferData(data, task.askSource);
                }
            }

            
    /// <summary>
            
    /// 增加数据请求任务
            
    /// </summary>
            
    /// <param name="task"></param>
            public void AddTask(AskDataTask task)
            {
                
    lock (tasks)
                {
                    tasks.Enqueue(task);
                    autoReset.Set();
                }
            }
            
        }
    如果我们有某一个窗体继承自上面的BaseForm,那么就可以这样向服务器请求资源:
     
     
    代码
    public partial class TestFrom : BaseForm
        {
            
    public TestFrom()
            {
                InitializeComponent();
            }

              
    /// <summary>
              
    /// 模拟长时间的资源请求操作
              
    /// </summary>
              
    /// <param name="sender"></param>
              
    /// <param name="e"></param>
            private void btn_askData_Click(object sender, EventArgs e)
            {
                AskDataTask task 
    = new AskDataTask();
                task.askSource 
    = this.Handle;
                task.dataName 
    = "需要获得XXXX资源";
                Server.Instance.AddTask(task);
            }

            
    protected override void ReceiveData(DataTransfer value)
            {
                
    base.ReceiveData(value);
                MessageBox.Show(value.transferMessage 
    + ":  " + value.obj.ToString());
            }

            
    /// <summary>
            
    /// 模拟传递数据给其他窗体
            
    /// </summary>
            
    /// <param name="sender"></param>
            
    /// <param name="e"></param>
            private void btn_SendData_Click(object sender, EventArgs e)
            {
                TestFrom otherForm 
    = new TestFrom();
                otherForm.Text 
    = "其他窗体";
                otherForm.StartPosition 
    = FormStartPosition.CenterScreen;
                otherForm.Show();


                DataTransfer message 
    = new DataTransfer();
                message.transferMessage 
    = "来自TestForm的问候";
                message.obj 
    = "Hello!";
                TransferDataHelper.TansferData(message, 
    this.Handle);
            }
        }

    场景二:

    任意窗体和窗体间传递数据,这个也是一个很常见的场景,上面代码中也实现了该场景。

    如果需要Demo文件,可以在这里获取。

    希望该技巧能够对大家有所帮助,刚刚和鈡秋洁分手,没想到更有魅力的郭琴婕又送上门来了,祝大家多拿过节费!呵呵。

  • 相关阅读:
    虚拟机Centos安装docker小记
    Python selenium入门
    selenium Error
    DveOps路线指南
    DevOps
    Go语言常量和变量
    安装Go语言及环境的搭建
    Win10 搭建IIS服务
    linux 上搭建sftp服务
    linux小命令
  • 原文地址:https://www.cnblogs.com/kangyifei/p/1838846.html
Copyright © 2011-2022 走看看