博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
小技巧分享:使用PostMessage向窗体传递任何数据
阅读量:5044 次
发布时间:2019-06-12

本文共 6267 字,大约阅读时间需要 20 分钟。

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

 

ExpandedBlockStart.gif
代码
 [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()方法,使用该方法,不但有诸多限制,而且效率极为低下(因为需要额外分配内存和进行内存拷贝)。

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

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

ExpandedBlockStart.gif
代码
    
///
 
<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函数向控件发送任意类型数据的目的。当然,我们可以定义一个传递数据的辅助类,该辅助类看起来应该像是这样子的:

ExpandedBlockStart.gif
代码
  
///
 
<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方法提取数据。

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

ExpandedBlockStart.gif
代码
  
///
 
<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消息发给窗体,窗体收到消息后提取数据并按照你需要的方式显示,线程安全的问题以及效率的问题解决了。
下面给出场景一的演示代码,首先定义一个资源请求的承载类或结构,该类用于承载长时间操作的任务,可以简单如下:
ExpandedBlockStart.gif
代码
 
///
 
<summary>
    
///
 资源请求类
    
///
 
</summary>
    
public
 
class
 AskDataTask
    {
        
///
 
<summary>
        
///
 发起请求的控件句柄
        
///
 
</summary>
       
public
 IntPtr askSource;
        
///
 
<summary>
        
///
 模拟需要请求的资源ID
        
///
 
</summary>
       
public
 
string
 dataName;
    }

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

ExpandedBlockStart.gif
代码
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 
==
 
null
continue
;
                
//
模拟长时间的操作
                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,那么就可以这样向服务器请求资源:
 
 

 

ExpandedBlockStart.gif
代码
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文件,可以在获取。

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

转载于:https://www.cnblogs.com/kangyifei/archive/2010/09/29/1838846.html

你可能感兴趣的文章