WinForm的开发者们,想必对PostMessage和SendMessage两个API都非常熟悉了。下面给出PostMessage函数在C#中的两种声明形式:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
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()方法,使用该方法,不但有诸多限制,而且效率极为低下(因为需要额外分配内存和进行内存拷贝)。
今天我和大家分享一个“曲线救国”的方法和该方法的一些使用场景。
首先定义一个用于承载传递数据的类或结构,可以如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
/// 数据传递类
/// </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函数向控件发送任意类型数据的目的。当然,我们可以定义一个传递数据的辅助类,该辅助类看起来应该像是这样子的:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
/// 传递数据的辅助类
/// </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方法提取数据。
下面给出窗体如何使用该助手类的演示代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
/// 窗体基类
/// </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; |
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
/// 资源请求类
/// </summary>
public class AskDataTask
{
/// <summary>
/// 发起请求的控件句柄
/// </summary>
public IntPtr askSource;
/// <summary>
/// 模拟需要请求的资源ID
/// </summary>
public string dataName;
}
一个简单的后台线程处理界面请求的类看起来应该是这样子的:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
{
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();
}
}
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
{
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文件,可以在这里获取。
希望该技巧能够对大家有所帮助,刚刚和鈡秋洁分手,没想到更有魅力的郭琴婕又送上门来了,祝大家多拿过节费!呵呵。