林蕴 發表於 2020-1-17 17:53:00

深入delphi编程理解之消息(一)WINDOWS原生窗口编写及消息处理过程

<div class="cnblogs_code">
<pre>  通过以sdk方式编制windows窗口程序,对理解windows消息驱动机制和delphi消息编程有很大的帮助。<br>   sdk编制windows窗口程序的步骤:<br>      1、对TWndClass对象进行赋值;<br>      2、向系统注册wndclass对象(RegisterClass);<br>      3、CreateWindow创建窗口,获得窗口句柄Hwnd;<br>      4、显示窗口(ShowWindow);<br><em id="__mceDel">      5、通过GetMessage函数不断获取系统消息,交给程序处理,程序过通回调函数(wndproc)处理系统消息。(消息处理部分)<br>程序代码如下:</em></pre>
<pre><em id="__mceDel">program<span> Project2;

//{$APPTYPE CONSOLE}

uses<span>
Windows,
Messages;

//==============================================================================
// 定义回调函数 Wndproc(),处理windows消息;
// 参数说明:
// HWND 可以理解为窗口句柄
// AMessage 可理解为消息的编号
// Wparam 消息的高位
// Lparam 消息的低位
//==============================================================================

function Wndproc(HWND: Thandle; AMessage: LongInt; wparam: wparam; lParam: lParam): Lresult; stdcall<span>;
begin
case AMessage of<span>
    WM_DESTROY:         //处理窗口退出消息
      begin<span>
      PostQuitMessage(0<span>);
      Result := 0<span>;
      end
else    {将未处理的消息交给系统处理}<span>
    Result :=<span> DefWindowProcW(HWND, AMessage, wparam, lParam);
end<span>;
end<span>;

procedure<span> WndMain;
var<span>
msg: TMsg;
Wndclass: TWndClass;
Hwnd: THandle;
begin
{给窗口类赋值}<span>
Wndclass.lpfnWndProc := @Wndproc; //窗口回调函数,处理windows消息
Wndclass.style := CS_HREDRAW or<span> CS_VREDRAW;
;            //窗口类型
Wndclass.cbClsExtra := 0<span>;
Wndclass.cbWndExtra := 0<span>;
Wndclass.hInstance :=<span> HInstance;
Wndclass.hIcon := LoadIcon(0<span>, IDI_APPLICATION);
Wndclass.hCursor := LoadCursor(0<span>, IDC_ARROW);
Wndclass.lpszClassName := 'MyWndClass'<span>;
Wndclass.lpszMenuName := nil<span>;
Wndclass.hbrBackground :=<span> GetStockObject(GRAY_BRUSH);
{向系统注册窗口类}<span>
RegisterClass(Wndclass);
{创建窗口}<span>
Hwnd := CreateWindow(Wndclass.lpszClassName, '用sdk编写的windows窗口', WS_OVERLAPPED, 100, 100, 300, 200, 0, 0, HInstance, 0<span>);

if Hwnd &lt;&gt; 0 then
begin
    {显示窗口}<span>
    ShowWindow(Hwnd, SW_SHOWNORMAL);
    UpdateWindow(Hwnd);
    {处理窗口消息}
    while GetMessage(msg, 0, 0, 0) do
    begin<span>
      TranslateMessage(msg);
      DispatchMessage(msg);
    end<span>;
end<span>;

end<span>;

begin<span>
WndMain;
{ TODO -oUser -cConsole Main : Insert code here }
end.</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></em></pre>
</div>
<p>一、运行界面如下:</p>
<p><img src="https://img2018.cnblogs.com/common/1920524/202001/1920524-20200116011340544-438233402.png"></p>
<p>二、程序函数说明:</p>
<p>  (一)、窗口类型 TWndClass 其实是一个结构, 是 tagWNDCLASSA 结构的重命名.</p>
<div class="cnblogs_code">
<pre>{tagWNDCLASSA 结构:}<span>
tagWNDCLASSA = packed record<span>
style: UINT;            {窗口风格, 见下表}<span>
lpfnWndProc: TFNWndProc;{窗口回调函数的指针, 后面要详细分析}<span>
cbClsExtra: Integer;      {为窗口类分配的额外空间, 一般为 0}<span>
cbWndExtra: Integer;      {为窗口实例分配的额外空间, 一般为 0}<span>
hInstance: HINST;         {窗口所在程序实例的句柄, 就是 HInstance}<span>
hIcon: HICON;             {指定窗口图标, 一般用 LoadIcon 加载; 不指定可设为 0}<span>
hCursor: HCURSOR;         {指定窗口光标, 一般用 LoadCursor 加载; 不指定可设为 0}<span>
hbrBackground: HBRUSH;    {指定窗口背景画刷, 这需要用 GetStockObject 函数检索; 也可以直接指定系统颜色}<span>
lpszMenuName: PAnsiChar;{菜单资源名称; 一般置为 nil, 表示窗口没有默认菜单}<span>
lpszClassName: PAnsiChar; {给该窗口类命名; CreateWindow 函数将使用这个名称}
end<span>;

//窗口风格参数 style 可选值:
CS_VREDRAW         = DWORD(1); {窗口高度变化时将被重绘}<span>
CS_HREDRAW         = DWORD(2); {窗口宽度变化时将被重绘}<span>
CS_KEYCVTWINDOW    = 4;      {}<span>
CS_DBLCLKS         = 8;      {不忽略鼠标双击的消息}<span>
CS_OWNDC         = $20;      {给用该类建立的每一个窗口分配独立的设备 DC}<span>
CS_CLASSDC         = $40;      {让属于该类的所有窗口共享一个设备 DC}<span>
CS_PARENTDC      = $80;      {允许窗口的子窗口继承一些共同特性}<span>
CS_NOKEYCVT      = $100;   {}<span>
CS_NOCLOSE         = $200;   {禁用系统菜单的 Close命令,同时窗口没有关闭按钮}<span>
CS_SAVEBITS      = $800;   {当窗口被覆盖时, 用位图缓存被覆盖区, 从而避免 WM_PAINT 消息, 一般用于菜单或对话框}<span>
CS_BYTEALIGNCLIENT = $1000;    {通过字节对齐, 增强客户区的绘制性能}<span>
CS_BYTEALIGNWINDOW = $2000;    {通过字节对齐, 增强窗口的绘制性能}<span>
CS_GLOBALCLASS   = $4000;    {全局窗口类, 一般用于 DLL; 没有此选项, 窗口类和窗口建立函数中指定的实例句柄须相同}

//关于窗口背景画刷:
{系统预定义了一些画刷, 需要用 GetStockObject 根据指定的常数检索;}
{但 GetStockObject 返回的句柄有可能是画刷、画笔、调色板或系统字体的句柄,}
{所以还需要把 GetStockObject 返回的句柄进行类型转换, 譬如:HBRUSH(GetStockObject(常数))}
//下面是 GetStockObject 函数参数的可选值:
WHITE_BRUSH         = 0<span>;
LTGRAY_BRUSH      = 1<span>;
GRAY_BRUSH          = 2<span>;
DKGRAY_BRUSH      = 3<span>;
BLACK_BRUSH         = 4<span>;
NULL_BRUSH          = 5<span>;
HOLLOW_BRUSH      =<span> NULL_BRUSH;
WHITE_PEN         = 6<span>;
BLACK_PEN         = 7<span>;
NULL_PEN            = 8<span>;
OEM_FIXED_FONT      = 10<span>;
ANSI_FIXED_FONT   = 11<span>;
ANSI_VAR_FONT       = 12<span>;
SYSTEM_FONT         = 13<span>;
DEVICE_DEFAULT_FONT = 14<span>;
DEFAULT_PALETTE   = 15<span>;
SYSTEM_FIXED_FONT   = $10<span>;
DEFAULT_GUI_FONT    = 17<span>;
DC_BRUSH            = 18<span>;
DC_PEN            = 19<span>;
STOCK_LAST          = 19<span>;

{另外背景画刷还可以使用 Windows 定义系统颜色常量, 譬如: HBRUSH(COLOR_WINDOW + 1) }<span>
COLOR_SCROLLBAR               = 0<span>;
COLOR_BACKGROUND            = 1<span>;
COLOR_ACTIVECAPTION         = 2<span>;
COLOR_INACTIVECAPTION         = 3<span>;
COLOR_MENU                  = 4<span>;
COLOR_WINDOW                  = 5<span>;
COLOR_WINDOWFRAME             = 6<span>;
COLOR_MENUTEXT                = 7<span>;
COLOR_WINDOWTEXT            = 8<span>;
COLOR_CAPTIONTEXT             = 9<span>;
COLOR_ACTIVEBORDER            = 10<span>;
COLOR_INACTIVEBORDER          = 11<span>;
COLOR_APPWORKSPACE            = 12<span>;
COLOR_HIGHLIGHT               = 13<span>;
COLOR_HIGHLIGHTTEXT         = 14<span>;
COLOR_BTNFACE               = 15<span>;
COLOR_BTNSHADOW               = $10<span>;
COLOR_GRAYTEXT                = 17<span>;
COLOR_BTNTEXT               = 18<span>;
COLOR_INACTIVECAPTIONTEXT   = 19<span>;
COLOR_BTNHIGHLIGHT            = 20<span>;
COLOR_3DDKSHADOW            = 21<span>;
COLOR_3DLIGHT               = 22<span>;
COLOR_INFOTEXT                = 23<span>;
COLOR_INFOBK                  = 24<span>;
COLOR_HOTLIGHT                = 26<span>;
COLOR_GRADIENTACTIVECAPTION   = 27<span>;
COLOR_GRADIENTINACTIVECAPTION = 28<span>;
COLOR_MENUHILIGHT             = 29<span>;
COLOR_MENUBAR               = 30<span>;
COLOR_ENDCOLORS               =<span> COLOR_MENUBAR;
COLOR_DESKTOP               =<span> COLOR_BACKGROUND;
COLOR_3DFACE                  =<span> COLOR_BTNFACE;
COLOR_3DSHADOW                =<span> COLOR_BTNSHADOW;
COLOR_3DHIGHLIGHT             =<span> COLOR_BTNHIGHLIGHT;
COLOR_3DHILIGHT               =<span> COLOR_BTNHIGHLIGHT;
COLOR_BTNHILIGHT            = COLOR_BTNHIGHLIGHT;</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<pre>(二)、认识 CreateWindow 函数</pre>
<hr>
<pre></pre>
<div class="cnblogs_code">
<pre><span>CreateWindow(
lpClassName: PChar;   {窗口类的名字}<span>
lpWindowName: PChar;    {窗口标题}<span>
dwStyle: DWORD;         {窗口样式, 参加下表}<span>
X,Y: Integer;         {位置; 默认的X,Y可以指定为: Integer(CW_USEDEFAULT)}<span>
nWidth,nHeight: Integer;{大小; 默认的宽度、高度可以指定为: Integer(CW_USEDEFAULT)}<span>}
hWndParent: HWND;       {父窗口句柄}<span>
hMenu: HMENU;         {主菜单句柄}<span>
hInstance: HINST;       {模块实例句柄, 也就是当前 exe 的句柄}<span>
lpParam: Pointer      {附加参数, 创建多文档界面时才用到, 一般设为 nil}<span>
): HWND;                  {返回所创建的窗口的句柄}

//dwStyle 窗口样式参数可选值:
WS_OVERLAPPED       = 0;                {重叠式窗口, 应带标题栏和边框}<span>
WS_POPUP            = DWORD($80000000); {弹出式窗口, 不能与 WS_CHILD 一起使用}<span>
WS_CHILD            = $40000000;      {子窗口, 不能与 WS_POPUP 一起使用}<span>
WS_MINIMIZE         = $20000000;      {最小化窗口}<span>
WS_VISIBLE          = $10000000;      {初始时可见}<span>
WS_DISABLED         = $8000000;         {禁止输入}<span>
WS_CLIPSIBLINGS   = $4000000;         {裁剪子窗口, 也就是子窗口重绘不影响重叠的其他子窗口, 应与 WS_CHILD 一起使用}<span>
WS_CLIPCHILDREN   = $2000000;         {在父窗口中绘图时绕开子窗口区域, 创建父窗口是使用}<span>
WS_MAXIMIZE         = $1000000;         {最大化窗口}<span>
WS_CAPTION          = $C00000;          {有标题栏}<span>
WS_BORDER         = $800000;          {有细线边框}<span>
WS_DLGFRAME         = $400000;          {对话框窗口}<span>
WS_VSCROLL          = $200000;          {有垂直滚动条}<span>
WS_HSCROLL          = $100000;          {有水平滚动条}<span>
WS_SYSMENU          = $80000;         {带系统标题栏, 须同时指定 WS_CAPTION}<span>
WS_THICKFRAME       = $40000;         {带宽边框, 宽边框用于改变窗口大小}<span>
WS_GROUP            = $20000;         {能用方向键转移焦点}<span>
WS_TABSTOP          = $10000;         {能用 TAB 转移焦点}<span>
WS_MINIMIZEBOX      = $20000;         {有最小化按钮}<span>
WS_MAXIMIZEBOX      = $10000;         {有最大化按钮}<span>
WS_TILED            =<span> WS_OVERLAPPED;
WS_ICONIC         =<span> WS_MINIMIZE;
WS_SIZEBOX          =<span> WS_THICKFRAME;
WS_OVERLAPPEDWINDOW = (WS_OVERLAPPED or WS_CAPTION or WS_SYSMENU or WS_THICKFRAME or WS_MINIMIZEBOX or<span> WS_MAXIMIZEBOX);
WS_TILEDWINDOW      =<span> WS_OVERLAPPEDWINDOW;
WS_POPUPWINDOW      = (WS_POPUP or WS_BORDER or<span> WS_SYSMENU);
WS_CHILDWINDOW      =<span> (WS_CHILD);

//另外还有一些扩展样式:
WS_EX_DLGMODALFRAME    = 1;      {指定双边界窗口; 藉此指定 WS_CAPTION 创建标题栏}<span>
WS_EX_NOPARENTNOTIFY   = 4;      {在窗口创建或取消时不向父窗口发送 WM_PARENTNOTIFY 消息}<span>
WS_EX_TOPMOST          = 8;      {在所有非最顶层窗口的上面}<span>
WS_EX_ACCEPTFILES      = $10;    {可接受拖放文件}<span>
WS_EX_TRANSPARENT      = $20;    {透明样式, 在同属窗口已重画时该窗口才可重画}<span>
WS_EX_MDICHILD         = $40;    {创建一个 MDI 子窗口}<span>
WS_EX_TOOLWINDOW       = $80;    {工具窗口}<span>
WS_EX_WINDOWEDGE       = $100;   {带立体的边框}<span>
WS_EX_CLIENTEDGE       = $200;   {带阴影的边界}<span>
WS_EX_CONTEXTHELP      = $400;   {标题包含一个问号标志, 不能与 WS_MAXIMIZEBOX 和 WS_MINIMIZEBOX 同时使用}<span>
WS_EX_RIGHT            = $1000;{窗口具有右对齐属性}<span>
WS_EX_LEFT             = 0;      {窗口具有左对齐属性, WS_EX_LEFT 是缺省设置}<span>
WS_EX_RTLREADING       = $2000;{窗口文本从右到左}<span>
WS_EX_LTRREADING       = 0;      {窗口文本从左到右, WS_EX_LTRREADING 是缺省设置}<span>
WS_EX_LEFTSCROLLBAR    = $4000;{垂直滚动条在左边界, 只用于特殊语言环境}<span>
WS_EX_RIGHTSCROLLBAR   = 0;      {垂直滚动条在右边界, WS_EX_RIGHTSCROLLBAR 是缺省设置}<span>
WS_EX_CONTROLPARENT    = $10000; {允许用户使用 Tab 键在窗口的子窗口间搜索}<span>
WS_EX_STATICEDGE       = $20000; {窗口不可用时创建一个三维边界}<span>
WS_EX_APPWINDOW      = $40000; {当窗口可见时, 将一个顶层窗口放置到任务条上}<span>
WS_EX_OVERLAPPEDWINDOW = (WS_EX_WINDOWEDGE or WS_EX_CLIENTEDGE); {立体边框并带阴影}<span>
WS_EX_PALETTEWINDOW    = (WS_EX_WINDOWEDGE or WS_EX_TOOLWINDOW or WS_EX_TOPMOST); {立体边框、工具条窗口样式、在顶层}<span>
WS_EX_LAYERED          = $00080000; {分层或透明窗口, 该样式可使用混合特效}<span>
WS_EX_NOINHERITLAYOUT= $00100000; {子窗口不继承父窗口的布局}<span>
WS_EX_LAYOUTRTL      = $00400000; {从右到左的布局}<span>
WS_EX_COMPOSITED       = $02000000; {用双缓冲从下到上绘制窗口的所有子孙}<span>
WS_EX_NOACTIVATE       = $08000000; {处于顶层但不激活}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<pre>分析:  </pre>
<pre>  首先要用 CreateWindow 创建窗口, 才能用 ShowWindow 显示窗口; 因为 ShowWindow 需要 CreateWindow 返回的句柄.<br>  在 CreateWindow 的参数中, 位置与大小与窗口标题无须多说;<br>  父窗口与菜单, 暂时都不需要, 先可置为 0;<br>  程序实例的句柄, Delphi 已经为我们准备好了: HInstance; (参见原来的说明)<br>  窗口样式在前面的例子中我们使用了: WS_OVERLAPPEDWINDOW, 它代表几种特点的组合, 表示了常规窗口.<br>  CreateWindow 还有一个重要参数(第一个参数 lpClassName): 窗口类的名字.<br>  Windows 要求我们要登记并注册一个窗口类以后, 才可以用 CreateWindow 建立窗口!<br><br>(三)认识消息机制函数<br>  一个程序会有一个或多个线程, 系统有一个线程队列(就是个链表)管理所有这些线程, 并为每个线程建立一个消息队列.  <br>  当消息产生时(譬如点击了窗口), 系统会把该消息放到窗口所在的消息队列, 等待窗口处理.<br>  窗口应该时刻待命, 准备从所在的线程队列中取出消息并处理!&nbsp;<br>  从消息队列中取出消息, 一般用 GetMessage 函数; 要随时取出消息, 需要用个循环, 譬如:</pre>
<hr>
<pre></pre>
<div class="cnblogs_code">
<pre>while(GetMessage(Msg, 0, 0, 0)) do
begin<span>
...
end;</span></pre>
</div>
<hr>
<pre>  取出消息怎么处理? 用 DispatchMessage 函数将消息交给(前面提到的)窗口回调函数, 一般是这样:</pre>
<hr>
<pre></pre>
<div class="cnblogs_code">
<pre>while(GetMessage(Msg, 0, 0, 0)) do
begin<span>
TranslateMessage(Msg); {有些键盘消息需要用 TranslateMessage 函数并发出系统需要的更具体的键盘消息}<span>
DispatchMessage(Msg);
end;</span></span></pre>
</div>
<hr>
<pre>  如果 GetMessage 函数不返回 0 ; 这个消息循环就是死循环, 什么时候 GetMessage 可以返回 0 呢?<br>  只有当 GetMessage 收到 WM_QUIT 消息时才返回 0.&nbsp;<br>  我们可在需要的时候, 在回调函数中通过 PostQuitMessage 函数向线程的消息队列中发送一条 WM_QUIT 消息, 以关闭线程.<br>  PostQuitMessage 函数的参数是给应用程序的退出码, PostQuitMessage(0) 中的 0 就是我们指定的退出码, 它将作为 WM_QUIT 消息的 wParam 参数. 譬如:</pre>
<hr>
<div class="cnblogs_code">
<pre>function WndProc(wnd: HWND; msg: UINT; wParam: Integer; lParam: Integer): LResult; stdcall<span>;
begin<span>
Result := 0<span>;
if msg = WM_DESTROY then<span>
    PostQuitMessage(0<span>)
else<span>
    Result :=<span> DefWindowProc(wnd, msg, wParam, lParam);
end;</span></span></span></span></span></span></span></pre>
</div><br><br>
来源:https://www.cnblogs.com/LifeStartPoint/p/12206975.html
頁: [1]
查看完整版本: 深入delphi编程理解之消息(一)WINDOWS原生窗口编写及消息处理过程