在进行Windows Mobile编程的时候,经常需要将Form上控件的Location随输入法面板的高度改变而调整,以免被面板遮盖。
但是.Net CF的InputPanel控件只给我们提供了EnabledChanged(即输入法打开/关闭)事件,这就意味着在Enabled属性没有发生变化的情况下进行输入法切换,我们便无法及时获取新的面板高度以做出相应处理。
有过Win32 sdk编程经验的朋友都知道,用户对应用程序产生的动作是首先以消息的形式传递给操作系统,操作系统根据消息中的窗口句柄(hWnd)回调该窗口的的消息响应函数而得到应用程序的反馈的。既然.Net CF没有对此消息进行封装,那么我们只好自己手动处理消息了。
在Windows环境下我们可以通过重写System.Windows.Forms.Form类的WndProc()函数来实现对该窗体消息的控制。但CE环境下,System.Windows.Forms.Form类没有提供给我们此方法,我们可以利用API函数从消息队列中取出消息经过处理后再将该消息的处理权返还给系统。
OK,有了思路就开始行动啦。首先,我们把.Net CF自带的Application扔掉,建立自己的消息循环,代码如下:
我们再定义一个接口,规定消息接收者对象必须实现消息处理函数WndProc。
到此就实现了消息的捕获,我们只要在让Form实现IMessageListener接口,就可以利用WndProc函数对消息进行处理了。
下面是一个TextBox控件随InputPanel高度调整而自动调整位置的示例:
但是.Net CF的InputPanel控件只给我们提供了EnabledChanged(即输入法打开/关闭)事件,这就意味着在Enabled属性没有发生变化的情况下进行输入法切换,我们便无法及时获取新的面板高度以做出相应处理。
有过Win32 sdk编程经验的朋友都知道,用户对应用程序产生的动作是首先以消息的形式传递给操作系统,操作系统根据消息中的窗口句柄(hWnd)回调该窗口的的消息响应函数而得到应用程序的反馈的。既然.Net CF没有对此消息进行封装,那么我们只好自己手动处理消息了。
在Windows环境下我们可以通过重写System.Windows.Forms.Form类的WndProc()函数来实现对该窗体消息的控制。但CE环境下,System.Windows.Forms.Form类没有提供给我们此方法,我们可以利用API函数从消息队列中取出消息经过处理后再将该消息的处理权返还给系统。
OK,有了思路就开始行动啦。首先,我们把.Net CF自带的Application扔掉,建立自己的消息循环,代码如下:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.WindowsCE.Forms;
/// < summary>
/// Application扩展类。
/// LastUpdate: 2007-12-25 NSnaiL
/// Merry Christmas! :)
/// < /summary>
public class ApplicationEx
{
#region Native
///////////////////////导出非托管代码///////////////////////
/// < summary>
/// 向系统表明有个线程有终止请求,通常用来响应WM_DESTROY消息。
/// < /summary>
/// < param name="nExitCode">退出代码,此值被用作消息WM_QUIT的wParam参数。< /param>
[DllImport("coredll.dll", SetLastError = true)]
internal static extern void PostQuitMessage(int nExitCode);
/// < summary>
/// 将虚键消息转换为字符消息;
/// 字符消息被寄送到调用线程的消息队列里,
/// 当下一次线程调用函数GetMessage或PeekMessage时被读出。
/// < /summary>
/// < param name="lpMsg">指向含有消息的MSG结构的指针。< /param>
/// < returns>
/// 如果消息被转换(即,字符消息被寄送到调用线程的消息队列里),返回非零值。
/// 如果消息是WM_kEYDOWN,WM_KEYUP WM_SYSKEYDOWN或WM_SYSKEYUP,返回非零值,不考虑转换。
/// 如果消息没被转换(即,字符消息没被寄送到调用线程的消息队列里),返回值是零。
/// < /returns>
[DllImport("coredll.dll", SetLastError = true)]
internal static extern bool TranslateMessage(out MessageExtern lpMsg);
/// < summary>
/// 调度一个消息给窗口程序;
/// 通常调度从GetMessage取得的消息。
/// < /summary>
/// < param name="lpMsg">指向含有消息的MSG结构的指针。< /param>
/// < returns>返回值是窗口程序返回的值。尽管返回值的含义依赖于被调度的消息,但返回值通常被忽略。< /returns>
[DllImport("coredll.dll", SetLastError = true)]
internal static extern bool DispatchMessage(ref MessageExtern lpMsg);
/// < summary>
/// 从调用线程的消息队列里取得一个消息并将其放于指定的结构。
/// < /summary>
/// < param name="lpMsg">指向MSG结构的指针,该结构从线程的消息队列里接收消息信息。< /param>
/// < param name="hWnd">取得其消息的窗口的句柄。< /param>
/// < param name="wMsgFilterMin">指定被检索的最小消息值的整数。< /param>
/// < param name="wMsgFilterMax">指定被检索的最大消息值的整数。< /param>
/// < returns>
/// 如果函数取得WM_QUIT之外的其他消息,返回非零值。
/// 如果函数取得WM_QUIT消息,返回值是零。
/// 如果出现了错误,返回-1。
/// < /returns>
[DllImport("coredll.dll", EntryPoint = "GetMessageW", SetLastError = true)]
internal static extern bool GetMessage(out MessageExtern lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
/// < summary>
/// 消息结构体。
/// < /summary>
[StructLayout(LayoutKind.Sequential)]
internal struct MessageExtern
{
public IntPtr hwnd; //窗口句柄。
public int message; //消息ID。
//附加参数。
public IntPtr wParam;
public IntPtr lParam;
public int time; //产生时间。
//鼠标位置。
public int pointX;
public int pointY;
}
#endregion
#region Fields
/// < summary>用于接收消息。< /summary>
private static MessageExtern Msg;
/// < summary>主窗体。< /summary>
private static Form FrmMain;
/// < summary>指示是否返还处理权。< /summary>
private static bool IsSysProc;
/// < summary>接收消息的对象集合(实现IMessageListener接口)。< /summary>
private static List< IMessageListener> MessageListeners;
/// < summary>空的对象,用于线程锁。< /summary>
private static object SyncObject;
#endregion
#region Methods
static ApplicationEx()
{
MessageListeners = new List< IMessageListener>();
Msg = new MessageExtern();
SyncObject = new object();
}
/// < summary>
/// 增加一个消息接收对象。
/// < /summary>
/// < param name="value">< /param>
public static void AddMessageListener(IMessageListener value)
{
MessageListeners.Add(value);
}
/// < summary>
/// 移除一个消息接收对象。
/// < /summary>
/// < param name="value">< /param>
public static void RemoveMessageListener(IMessageListener value)
{
MessageListeners.Remove(value);
}
/// < summary>
/// 在当前线程上开始运行标准应用程序消息循环,并使指定窗体可见。
/// < /summary>
/// < param name="frmMain">一个 System.Windows.Forms.Form,它代表要使之可见的窗体。< /param>
public static void Run(Form frmMain)
{
ApplicationEx.FrmMain = frmMain;
ApplicationEx.FrmMain.Closed += new EventHandler(OnFrmMainClosed);
MessageLoop();
}
/// < summary>
/// 结束应用程序。
/// < /summary>
public static void Exit()
{
if (FrmMain != null)
FrmMain.Dispose();
GC.GetTotalMemory(true);
}
/// < summary>
/// 消息循环。
/// < /summary>
private static void MessageLoop()
{
FrmMain.Visible = true;
for (; MessageHook(); )
{
}
Exit();
}
/// < summary>
/// 消息获取及处理。
/// < /summary>
/// < returns>< /returns>
private static bool MessageHook()
{
if (GetMessage(out Msg, IntPtr.Zero, 0, 0))
{
IsSysProc = true;
IMessageListener[] listeners = MessageListeners.ToArray();
lock (SyncObject)
{
for (int i = 0; i < listeners.Length; i++)
{
Message m = Message.Create(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
IsSysProc = IsSysProc ? !listeners[i].WndProc(ref m) : false;
}
if (IsSysProc)
{
TranslateMessage(out Msg);
DispatchMessage(ref Msg);
}
return true;
}
}
return false;
}
/// 主窗体关闭事件。
private static void OnFrmMainClosed(object sender, EventArgs e)
{
PostQuitMessage(0);
}
#endregion
}
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.WindowsCE.Forms;
/// < summary>
/// Application扩展类。
/// LastUpdate: 2007-12-25 NSnaiL
/// Merry Christmas! :)
/// < /summary>
public class ApplicationEx
{
#region Native
///////////////////////导出非托管代码///////////////////////
/// < summary>
/// 向系统表明有个线程有终止请求,通常用来响应WM_DESTROY消息。
/// < /summary>
/// < param name="nExitCode">退出代码,此值被用作消息WM_QUIT的wParam参数。< /param>
[DllImport("coredll.dll", SetLastError = true)]
internal static extern void PostQuitMessage(int nExitCode);
/// < summary>
/// 将虚键消息转换为字符消息;
/// 字符消息被寄送到调用线程的消息队列里,
/// 当下一次线程调用函数GetMessage或PeekMessage时被读出。
/// < /summary>
/// < param name="lpMsg">指向含有消息的MSG结构的指针。< /param>
/// < returns>
/// 如果消息被转换(即,字符消息被寄送到调用线程的消息队列里),返回非零值。
/// 如果消息是WM_kEYDOWN,WM_KEYUP WM_SYSKEYDOWN或WM_SYSKEYUP,返回非零值,不考虑转换。
/// 如果消息没被转换(即,字符消息没被寄送到调用线程的消息队列里),返回值是零。
/// < /returns>
[DllImport("coredll.dll", SetLastError = true)]
internal static extern bool TranslateMessage(out MessageExtern lpMsg);
/// < summary>
/// 调度一个消息给窗口程序;
/// 通常调度从GetMessage取得的消息。
/// < /summary>
/// < param name="lpMsg">指向含有消息的MSG结构的指针。< /param>
/// < returns>返回值是窗口程序返回的值。尽管返回值的含义依赖于被调度的消息,但返回值通常被忽略。< /returns>
[DllImport("coredll.dll", SetLastError = true)]
internal static extern bool DispatchMessage(ref MessageExtern lpMsg);
/// < summary>
/// 从调用线程的消息队列里取得一个消息并将其放于指定的结构。
/// < /summary>
/// < param name="lpMsg">指向MSG结构的指针,该结构从线程的消息队列里接收消息信息。< /param>
/// < param name="hWnd">取得其消息的窗口的句柄。< /param>
/// < param name="wMsgFilterMin">指定被检索的最小消息值的整数。< /param>
/// < param name="wMsgFilterMax">指定被检索的最大消息值的整数。< /param>
/// < returns>
/// 如果函数取得WM_QUIT之外的其他消息,返回非零值。
/// 如果函数取得WM_QUIT消息,返回值是零。
/// 如果出现了错误,返回-1。
/// < /returns>
[DllImport("coredll.dll", EntryPoint = "GetMessageW", SetLastError = true)]
internal static extern bool GetMessage(out MessageExtern lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
/// < summary>
/// 消息结构体。
/// < /summary>
[StructLayout(LayoutKind.Sequential)]
internal struct MessageExtern
{
public IntPtr hwnd; //窗口句柄。
public int message; //消息ID。
//附加参数。
public IntPtr wParam;
public IntPtr lParam;
public int time; //产生时间。
//鼠标位置。
public int pointX;
public int pointY;
}
#endregion
#region Fields
/// < summary>用于接收消息。< /summary>
private static MessageExtern Msg;
/// < summary>主窗体。< /summary>
private static Form FrmMain;
/// < summary>指示是否返还处理权。< /summary>
private static bool IsSysProc;
/// < summary>接收消息的对象集合(实现IMessageListener接口)。< /summary>
private static List< IMessageListener> MessageListeners;
/// < summary>空的对象,用于线程锁。< /summary>
private static object SyncObject;
#endregion
#region Methods
static ApplicationEx()
{
MessageListeners = new List< IMessageListener>();
Msg = new MessageExtern();
SyncObject = new object();
}
/// < summary>
/// 增加一个消息接收对象。
/// < /summary>
/// < param name="value">< /param>
public static void AddMessageListener(IMessageListener value)
{
MessageListeners.Add(value);
}
/// < summary>
/// 移除一个消息接收对象。
/// < /summary>
/// < param name="value">< /param>
public static void RemoveMessageListener(IMessageListener value)
{
MessageListeners.Remove(value);
}
/// < summary>
/// 在当前线程上开始运行标准应用程序消息循环,并使指定窗体可见。
/// < /summary>
/// < param name="frmMain">一个 System.Windows.Forms.Form,它代表要使之可见的窗体。< /param>
public static void Run(Form frmMain)
{
ApplicationEx.FrmMain = frmMain;
ApplicationEx.FrmMain.Closed += new EventHandler(OnFrmMainClosed);
MessageLoop();
}
/// < summary>
/// 结束应用程序。
/// < /summary>
public static void Exit()
{
if (FrmMain != null)
FrmMain.Dispose();
GC.GetTotalMemory(true);
}
/// < summary>
/// 消息循环。
/// < /summary>
private static void MessageLoop()
{
FrmMain.Visible = true;
for (; MessageHook(); )
{
}
Exit();
}
/// < summary>
/// 消息获取及处理。
/// < /summary>
/// < returns>< /returns>
private static bool MessageHook()
{
if (GetMessage(out Msg, IntPtr.Zero, 0, 0))
{
IsSysProc = true;
IMessageListener[] listeners = MessageListeners.ToArray();
lock (SyncObject)
{
for (int i = 0; i < listeners.Length; i++)
{
Message m = Message.Create(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
IsSysProc = IsSysProc ? !listeners[i].WndProc(ref m) : false;
}
if (IsSysProc)
{
TranslateMessage(out Msg);
DispatchMessage(ref Msg);
}
return true;
}
}
return false;
}
/// 主窗体关闭事件。
private static void OnFrmMainClosed(object sender, EventArgs e)
{
PostQuitMessage(0);
}
#endregion
}
我们再定义一个接口,规定消息接收者对象必须实现消息处理函数WndProc。
using Microsoft.WindowsCE.Forms;
/// < summary>
/// 消息接收者接口。
/// LastUpdate: 2007-12-25 NSnaiL
/// Merry Christmas! :)
public interface IMessageListener
{
/// < summary>
/// 消息处理过程。
/// < /summary>
/// < param name="m">消息结构体。< /param>
/// < returns>返回false以将消息处理权返回系统。< /returns>
bool WndProc(ref Message m);
}
/// < summary>
/// 消息接收者接口。
/// LastUpdate: 2007-12-25 NSnaiL
/// Merry Christmas! :)
public interface IMessageListener
{
/// < summary>
/// 消息处理过程。
/// < /summary>
/// < param name="m">消息结构体。< /param>
/// < returns>返回false以将消息处理权返回系统。< /returns>
bool WndProc(ref Message m);
}
到此就实现了消息的捕获,我们只要在让Form实现IMessageListener接口,就可以利用WndProc函数对消息进行处理了。
下面是一个TextBox控件随InputPanel高度调整而自动调整位置的示例:
using System;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
/// < summary>
/// 测试程序。
/// LastUpdate: 2007-12-25 NSnaiL
/// Merry Christmas! :)
/// < /summary>
public class FrmTest : Form, IMessageListener
{
/// < summary>
/// 必需的设计器变量。
/// < /summary>
private System.ComponentModel.IContainer components = null;
private TextBox textBox1;
private Microsoft.WindowsCE.Forms.InputPanel inputPanel1;
private System.Windows.Forms.MainMenu mainMenu1;
/// < summary>
/// 清理所有正在使用的资源。
/// < /summary>
/// < param name="disposing">如果应释放托管资源,为 true;否则为 false。< /param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows 窗体设计器生成的代码
/// < summary>
/// 设计器支持所需的方法 - 不要
/// 使用代码编辑器修改此方法的内容。
/// < /summary>
private void InitializeComponent()
{
this.mainMenu1 = new System.Windows.Forms.MainMenu();
this.textBox1 = new System.Windows.Forms.TextBox();
this.inputPanel1 = new Microsoft.WindowsCE.Forms.InputPanel();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(0, 206);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(240, 62);
this.textBox1.TabIndex = 0;
this.textBox1.Text = "textBox1";
//
// inputPanel1
//
this.inputPanel1.EnabledChanged += new System.EventHandler(this.inputPanel1_EnabledChanged);
//
// FrmTest
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoScroll = true;
this.ClientSize = new System.Drawing.Size(240, 268);
this.Controls.Add(this.textBox1);
this.Menu = this.mainMenu1;
this.MinimizeBox = false;
this.Name = "FrmTest";
this.Text = "FrmTest";
this.Closing += new System.ComponentModel.CancelEventHandler(this.FrmTest_Closing);
this.ResumeLayout(false);
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////////////
//从这里开始 -------- 入口函数
static void Main()
{
//使用Application扩展类替代Application类。
ApplicationEx.Run(new FrmTest());
}
//原始位置(以textbox1为例)
private Point InitLocation = new Point(0, 206);
//记录面板的高度,当此值发生改变应调整textbox1的Location
private int SIPHeight = 0;
public FrmTest()
{
InitializeComponent();
//将此窗体加入消息接受者集合。
ApplicationEx.AddMessageListener(this);
}
#region IMessageListener 成员
bool IMessageListener.WndProc(ref Microsoft.WindowsCE.Forms.Message m)
{
if (inputPanel1.Enabled && inputPanel1.Bounds.Height != SIPHeight)
{
textBox1.Location = new Point(InitLocation.X,
InitLocation.Y - inputPanel1.Bounds.Height);
SIPHeight = inputPanel1.Bounds.Height;
}
return false;
}
#endregion
private void FrmTest_Closing(object sender, CancelEventArgs e)
{
//将此窗体从消息接受者集合移除。
ApplicationEx.AddMessageListener(this);
}
private void inputPanel1_EnabledChanged(object sender, EventArgs e)
{
textBox1.Location = new Point(InitLocation.X,
inputPanel1.Enabled ? InitLocation.Y - inputPanel1.Bounds.Height :
InitLocation.Y);
}
}
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
/// < summary>
/// 测试程序。
/// LastUpdate: 2007-12-25 NSnaiL
/// Merry Christmas! :)
/// < /summary>
public class FrmTest : Form, IMessageListener
{
/// < summary>
/// 必需的设计器变量。
/// < /summary>
private System.ComponentModel.IContainer components = null;
private TextBox textBox1;
private Microsoft.WindowsCE.Forms.InputPanel inputPanel1;
private System.Windows.Forms.MainMenu mainMenu1;
/// < summary>
/// 清理所有正在使用的资源。
/// < /summary>
/// < param name="disposing">如果应释放托管资源,为 true;否则为 false。< /param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows 窗体设计器生成的代码
/// < summary>
/// 设计器支持所需的方法 - 不要
/// 使用代码编辑器修改此方法的内容。
/// < /summary>
private void InitializeComponent()
{
this.mainMenu1 = new System.Windows.Forms.MainMenu();
this.textBox1 = new System.Windows.Forms.TextBox();
this.inputPanel1 = new Microsoft.WindowsCE.Forms.InputPanel();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(0, 206);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(240, 62);
this.textBox1.TabIndex = 0;
this.textBox1.Text = "textBox1";
//
// inputPanel1
//
this.inputPanel1.EnabledChanged += new System.EventHandler(this.inputPanel1_EnabledChanged);
//
// FrmTest
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoScroll = true;
this.ClientSize = new System.Drawing.Size(240, 268);
this.Controls.Add(this.textBox1);
this.Menu = this.mainMenu1;
this.MinimizeBox = false;
this.Name = "FrmTest";
this.Text = "FrmTest";
this.Closing += new System.ComponentModel.CancelEventHandler(this.FrmTest_Closing);
this.ResumeLayout(false);
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////////////
//从这里开始 -------- 入口函数
static void Main()
{
//使用Application扩展类替代Application类。
ApplicationEx.Run(new FrmTest());
}
//原始位置(以textbox1为例)
private Point InitLocation = new Point(0, 206);
//记录面板的高度,当此值发生改变应调整textbox1的Location
private int SIPHeight = 0;
public FrmTest()
{
InitializeComponent();
//将此窗体加入消息接受者集合。
ApplicationEx.AddMessageListener(this);
}
#region IMessageListener 成员
bool IMessageListener.WndProc(ref Microsoft.WindowsCE.Forms.Message m)
{
if (inputPanel1.Enabled && inputPanel1.Bounds.Height != SIPHeight)
{
textBox1.Location = new Point(InitLocation.X,
InitLocation.Y - inputPanel1.Bounds.Height);
SIPHeight = inputPanel1.Bounds.Height;
}
return false;
}
#endregion
private void FrmTest_Closing(object sender, CancelEventArgs e)
{
//将此窗体从消息接受者集合移除。
ApplicationEx.AddMessageListener(this);
}
private void inputPanel1_EnabledChanged(object sender, EventArgs e)
{
textBox1.Location = new Point(InitLocation.X,
inputPanel1.Enabled ? InitLocation.Y - inputPanel1.Bounds.Height :
InitLocation.Y);
}
}