欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-03 08:18:59
通过以sdk方式编制windows窗口程序,对理解windows消息驱动机制和delphi消息编程有很大的帮助。 sdk编制windows窗口程序的步骤: 1、对TWndClass对象进行赋值; 2、向系统注册wndclass对象(RegisterClass); 3、CreateWindow创建窗口 ......
  通过以sdk方式编制windows窗口程序,对理解windows消息驱动机制和delphi消息编程有很大的帮助。
sdk编制windows窗口程序的步骤:
1、对twndclass对象进行赋值;
2、向系统注册wndclass对象(registerclass);
3、createwindow创建窗口,获得窗口句柄hwnd;
4、显示窗口(showwindow);
5、通过getmessage函数不断获取系统消息,交给程序处理,程序过通回调函数(wndproc)处理系统消息。(消息处理部分)
程序代码如下:
program project2;

//{$apptype console}

uses
  windows,
  messages;

//==============================================================================
// 定义回调函数 wndproc(),处理windows消息;
// 参数说明:
// hwnd 可以理解为窗口句柄
// amessage 可理解为消息的编号
// wparam 消息的高位
// lparam 消息的低位
//==============================================================================

function wndproc(hwnd: thandle; amessage: longint; wparam: wparam; lparam: lparam): lresult; stdcall;
begin
  case amessage of
    wm_destroy:         //处理窗口退出消息
      begin
        postquitmessage(0);
        result := 0;
      end
  else    {将未处理的消息交给系统处理}
    result := defwindowprocw(hwnd, amessage, wparam, lparam);
  end;
end;

procedure wndmain;
var
  msg: tmsg;
  wndclass: twndclass;
  hwnd: thandle;
begin
  {给窗口类赋值}
  wndclass.lpfnwndproc := @wndproc; //窗口回调函数,处理windows消息
  wndclass.style := cs_hredraw or cs_vredraw;
  ;              //窗口类型
  wndclass.cbclsextra := 0;
  wndclass.cbwndextra := 0;
  wndclass.hinstance := hinstance;
  wndclass.hicon := loadicon(0, idi_application);
  wndclass.hcursor := loadcursor(0, idc_arrow);
  wndclass.lpszclassname := 'mywndclass';
  wndclass.lpszmenuname := nil;
  wndclass.hbrbackground := getstockobject(gray_brush);
  {向系统注册窗口类}
  registerclass(wndclass);
  {创建窗口}
  hwnd := createwindow(wndclass.lpszclassname, '用sdk编写的windows窗口', ws_overlapped, 100, 100, 300, 200, 0, 0, hinstance, 0);

  if hwnd <> 0 then
  begin
    {显示窗口}
    showwindow(hwnd, sw_shownormal);
    updatewindow(hwnd);
    {处理窗口消息}
    while getmessage(msg, 0, 0, 0) do
    begin
      translatemessage(msg);
      dispatchmessage(msg);
    end;
  end;

end;

begin
  wndmain;
  { todo -ouser -cconsole main : insert code here }
end.

一、运行界面如下:

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

二、程序函数说明:

  (一)、窗口类型 twndclass 其实是一个结构, 是 tagwndclassa 结构的重命名.

{tagwndclassa 结构:}
tagwndclassa = packed record
  style: uint;              {窗口风格, 见下表}
  lpfnwndproc: tfnwndproc;  {窗口回调函数的指针, 后面要详细分析}
  cbclsextra: integer;      {为窗口类分配的额外空间, 一般为 0}
  cbwndextra: integer;      {为窗口实例分配的额外空间, 一般为 0}
  hinstance: hinst;         {窗口所在程序实例的句柄, 就是 hinstance}
  hicon: hicon;             {指定窗口图标, 一般用 loadicon 加载; 不指定可设为 0}
  hcursor: hcursor;         {指定窗口光标, 一般用 loadcursor 加载; 不指定可设为 0}
  hbrbackground: hbrush;    {指定窗口背景画刷, 这需要用 getstockobject 函数检索; 也可以直接指定系统颜色}
  lpszmenuname: pansichar;  {菜单资源名称; 一般置为 nil, 表示窗口没有默认菜单}
  lpszclassname: pansichar; {给该窗口类命名; createwindow 函数将使用这个名称}
end;

//窗口风格参数 style 可选值:
cs_vredraw         = dword(1); {窗口高度变化时将被重绘}
cs_hredraw         = dword(2); {窗口宽度变化时将被重绘}
cs_keycvtwindow    = 4;        {}
cs_dblclks         = 8;        {不忽略鼠标双击的消息}
cs_owndc           = $20;      {给用该类建立的每一个窗口分配独立的设备 dc}
cs_classdc         = $40;      {让属于该类的所有窗口共享一个设备 dc}
cs_parentdc        = $80;      {允许窗口的子窗口继承一些共同特性}
cs_nokeycvt        = $100;     {}
cs_noclose         = $200;     {禁用系统菜单的 close命令,同时窗口没有关闭按钮}
cs_savebits        = $800;     {当窗口被覆盖时, 用位图缓存被覆盖区, 从而避免 wm_paint 消息, 一般用于菜单或对话框}
cs_bytealignclient = $1000;    {通过字节对齐, 增强客户区的绘制性能}
cs_bytealignwindow = $2000;    {通过字节对齐, 增强窗口的绘制性能}
cs_globalclass     = $4000;    {全局窗口类, 一般用于 dll; 没有此选项, 窗口类和窗口建立函数中指定的实例句柄须相同}

//关于窗口背景画刷:
{系统预定义了一些画刷, 需要用 getstockobject 根据指定的常数检索;}
{但 getstockobject 返回的句柄有可能是画刷、画笔、调色板或系统字体的句柄,}
{所以还需要把 getstockobject 返回的句柄进行类型转换, 譬如:  hbrush(getstockobject(常数))}
//下面是 getstockobject 函数参数的可选值:
white_brush         = 0;
ltgray_brush        = 1;
gray_brush          = 2;
dkgray_brush        = 3;
black_brush         = 4;
null_brush          = 5;
hollow_brush        = null_brush;
white_pen           = 6;
black_pen           = 7;
null_pen            = 8;
oem_fixed_font      = 10;
ansi_fixed_font     = 11;
ansi_var_font       = 12;
system_font         = 13;
device_default_font = 14;
default_palette     = 15;
system_fixed_font   = $10;
default_gui_font    = 17;
dc_brush            = 18;
dc_pen              = 19;
stock_last          = 19;

{另外背景画刷还可以使用 windows 定义系统颜色常量, 譬如: hbrush(color_window + 1) }
color_scrollbar               = 0;
color_background              = 1;
color_activecaption           = 2;
color_inactivecaption         = 3;
color_menu                    = 4;
color_window                  = 5;
color_windowframe             = 6;
color_menutext                = 7;
color_windowtext              = 8;
color_captiontext             = 9;
color_activeborder            = 10;
color_inactiveborder          = 11;
color_appworkspace            = 12;
color_highlight               = 13;
color_highlighttext           = 14;
color_btnface                 = 15;
color_btnshadow               = $10;
color_graytext                = 17;
color_btntext                 = 18;
color_inactivecaptiontext     = 19;
color_btnhighlight            = 20;
color_3ddkshadow              = 21;
color_3dlight                 = 22;
color_infotext                = 23;
color_infobk                  = 24;
color_hotlight                = 26;
color_gradientactivecaption   = 27;
color_gradientinactivecaption = 28;
color_menuhilight             = 29;
color_menubar                 = 30;
color_endcolors               = color_menubar;
color_desktop                 = color_background;
color_3dface                  = color_btnface;
color_3dshadow                = color_btnshadow;
color_3dhighlight             = color_btnhighlight;
color_3dhilight               = color_btnhighlight;
color_btnhilight              = color_btnhighlight;
(二)、认识 createwindow 函数


createwindow(
  lpclassname: pchar;     {窗口类的名字}
  lpwindowname: pchar;    {窗口标题}
  dwstyle: dword;         {窗口样式, 参加下表}
  x,y: integer;           {位置; 默认的x,y可以指定为: integer(cw_usedefault)}
  nwidth,nheight: integer;{大小; 默认的宽度、高度可以指定为: integer(cw_usedefault)}}
  hwndparent: hwnd;       {父窗口句柄}
  hmenu: hmenu;           {主菜单句柄}
  hinstance: hinst;       {模块实例句柄, 也就是当前 exe 的句柄}
  lpparam: pointer        {附加参数, 创建多文档界面时才用到, 一般设为 nil}
): hwnd;                  {返回所创建的窗口的句柄}

//dwstyle 窗口样式参数可选值:
ws_overlapped       = 0;                {重叠式窗口, 应带标题栏和边框}
ws_popup            = dword($80000000); {弹出式窗口, 不能与 ws_child 一起使用}
ws_child            = $40000000;        {子窗口, 不能与 ws_popup 一起使用}
ws_minimize         = $20000000;        {最小化窗口}
ws_visible          = $10000000;        {初始时可见}
ws_disabled         = $8000000;         {禁止输入}
ws_clipsiblings     = $4000000;         {裁剪子窗口, 也就是子窗口重绘不影响重叠的其他子窗口, 应与 ws_child 一起使用}
ws_clipchildren     = $2000000;         {在父窗口中绘图时绕开子窗口区域, 创建父窗口是使用}
ws_maximize         = $1000000;         {最大化窗口}
ws_caption          = $c00000;          {有标题栏}
ws_border           = $800000;          {有细线边框}
ws_dlgframe         = $400000;          {对话框窗口}
ws_vscroll          = $200000;          {有垂直滚动条}
ws_hscroll          = $100000;          {有水平滚动条}
ws_sysmenu          = $80000;           {带系统标题栏, 须同时指定 ws_caption}
ws_thickframe       = $40000;           {带宽边框, 宽边框用于改变窗口大小}
ws_group            = $20000;           {能用方向键转移焦点}
ws_tabstop          = $10000;           {能用 tab 转移焦点}
ws_minimizebox      = $20000;           {有最小化按钮}
ws_maximizebox      = $10000;           {有最大化按钮}
ws_tiled            = ws_overlapped;
ws_iconic           = ws_minimize;
ws_sizebox          = ws_thickframe;
ws_overlappedwindow = (ws_overlapped or ws_caption or ws_sysmenu or ws_thickframe or ws_minimizebox or ws_maximizebox);
ws_tiledwindow      = ws_overlappedwindow;
ws_popupwindow      = (ws_popup or ws_border or ws_sysmenu);
ws_childwindow      = (ws_child);

//另外还有一些扩展样式:
ws_ex_dlgmodalframe    = 1;      {指定双边界窗口; 藉此指定 ws_caption 创建标题栏}
ws_ex_noparentnotify   = 4;      {在窗口创建或取消时不向父窗口发送 wm_parentnotify 消息}
ws_ex_topmost          = 8;      {在所有非最顶层窗口的上面}
ws_ex_acceptfiles      = $10;    {可接受拖放文件}
ws_ex_transparent      = $20;    {透明样式, 在同属窗口已重画时该窗口才可重画}
ws_ex_mdichild         = $40;    {创建一个 mdi 子窗口}
ws_ex_toolwindow       = $80;    {工具窗口}
ws_ex_windowedge       = $100;   {带立体的边框}
ws_ex_clientedge       = $200;   {带阴影的边界}
ws_ex_contexthelp      = $400;   {标题包含一个问号标志, 不能与 ws_maximizebox 和 ws_minimizebox 同时使用}
ws_ex_right            = $1000;  {窗口具有右对齐属性}
ws_ex_left             = 0;      {窗口具有左对齐属性, ws_ex_left 是缺省设置}
ws_ex_rtlreading       = $2000;  {窗口文本从右到左}
ws_ex_ltrreading       = 0;      {窗口文本从左到右, ws_ex_ltrreading 是缺省设置}
ws_ex_leftscrollbar    = $4000;  {垂直滚动条在左边界, 只用于特殊语言环境}
ws_ex_rightscrollbar   = 0;      {垂直滚动条在右边界, ws_ex_rightscrollbar 是缺省设置}
ws_ex_controlparent    = $10000; {允许用户使用 tab 键在窗口的子窗口间搜索}
ws_ex_staticedge       = $20000; {窗口不可用时创建一个三维边界}
ws_ex_appwindow        = $40000; {当窗口可见时, 将一个顶层窗口放置到任务条上}
ws_ex_overlappedwindow = (ws_ex_windowedge or ws_ex_clientedge); {立体边框并带阴影}
ws_ex_palettewindow    = (ws_ex_windowedge or ws_ex_toolwindow or ws_ex_topmost); {立体边框、工具条窗口样式、在顶层}
ws_ex_layered          = $00080000; {分层或透明窗口, 该样式可使用混合特效}
ws_ex_noinheritlayout  = $00100000; {子窗口不继承父窗口的布局}
ws_ex_layoutrtl        = $00400000; {从右到左的布局}
ws_ex_composited       = $02000000; {用双缓冲从下到上绘制窗口的所有子孙}
ws_ex_noactivate       = $08000000; {处于顶层但不激活}
分析:  
  首先要用 createwindow 创建窗口, 才能用 showwindow 显示窗口; 因为 showwindow 需要 createwindow 返回的句柄.
  在 createwindow 的参数中, 位置与大小与窗口标题无须多说;
  父窗口与菜单, 暂时都不需要, 先可置为 0;
  程序实例的句柄, delphi 已经为我们准备好了: hinstance; (参见原来的说明)
  窗口样式在前面的例子中我们使用了: ws_overlappedwindow, 它代表几种特点的组合, 表示了常规窗口.
  createwindow 还有一个重要参数(第一个参数 lpclassname): 窗口类的名字.
  windows 要求我们要登记并注册一个窗口类以后, 才可以用 createwindow 建立窗口!

(三)认识消息机制函数
  一个程序会有一个或多个线程, 系统有一个线程队列(就是个链表)管理所有这些线程, 并为每个线程建立一个消息队列.  
  当消息产生时(譬如点击了窗口), 系统会把该消息放到窗口所在的消息队列, 等待窗口处理.
  窗口应该时刻待命, 准备从所在的线程队列中取出消息并处理! 
  从消息队列中取出消息, 一般用 getmessage 函数; 要随时取出消息, 需要用个循环, 譬如:


while(getmessage(msg, 0, 0, 0)) do
begin
  ...
end;

  取出消息怎么处理? 用 dispatchmessage 函数将消息交给(前面提到的)窗口回调函数, 一般是这样:


while(getmessage(msg, 0, 0, 0)) do
begin
  translatemessage(msg); {有些键盘消息需要用 translatemessage 函数并发出系统需要的更具体的键盘消息}
  dispatchmessage(msg);
end;

  如果 getmessage 函数不返回 0 ; 这个消息循环就是死循环, 什么时候 getmessage 可以返回 0 呢?
  只有当 getmessage 收到 wm_quit 消息时才返回 0. 
  我们可在需要的时候, 在回调函数中通过 postquitmessage 函数向线程的消息队列中发送一条 wm_quit 消息, 以关闭线程.
  postquitmessage 函数的参数是给应用程序的退出码, postquitmessage(0) 中的 0 就是我们指定的退出码, 它将作为 wm_quit 消息的 wparam 参数. 譬如:

function wndproc(wnd: hwnd; msg: uint; wparam: integer; lparam: integer): lresult; stdcall;
begin
  result := 0;
  if msg = wm_destroy then
    postquitmessage(0)
  else
    result := defwindowproc(wnd, msg, wparam, lparam);
end;