ITEEDU

Win32汇编教程九

综合篇(一)复杂形状的窗口

概述

在前面八篇的 Win32asm 教程中,已经初步讲述了消息框、对话框、菜单、资源、GDI 等内容,基本上已经设计到了 Windows 界面的大部分内容,在继续新的 Windows 其他部分的内容如多线程、文件操作、内存操作之前,我先综合前面的内容并加上一些新内容,写上一篇综合篇。
本篇的例子程序是一个复杂形状的窗口,窗口的形状是根据位图自动计算得到的,这也就是在我编写的小闹钟中使用的技术(大家可以到我的软件发布中下载一个看看),由于以前在网上看到的有关特殊形状窗口的例子最多就是画一个圆形,或者几个方块和椭圆结合的形状,没有一篇文章指出如何画出如“唐老鸭”这样一个造型的窗口。本文使用的算法可以自动根据位图的形状计算窗口形状。
在源程序中,很多代码都是前面教程提到的,主要有以下部分:

  • 首先建立一个标准的窗口。(参考窗口一节)
  • 设置窗口为特殊形状。(见下面的程序分析)
  • 在窗口的 WM_PAINT 消息中更新窗口的图片。(参考图形界面一节)
  • 由于窗口没有标题栏,所以在右击窗口时弹出一个菜单。(参考菜单一节)
  • 菜单中有个“关于本程序”项,里面有超联结文本。(参考窗口子类化一节)
  • Windows 里有专门的 API 来实现特殊形状的窗口,步骤是首先建立区域(Region),Region 可以合并,这样一来就可以用几个简单的区域合并出一个复杂的区域,建立、合并区域和设置窗口的 API 主要有以下几条:

  • CreateRectRgn(Left,Top,Right,Bottom) - 建立矩型区域
  • CreateEllipticRgn(Left,Top,Right,Bottom) - 建立椭圆区域
  • CreatePolygonRgn(lpPoints,NumberOfPoints,Mode) - 建立多边形区域,这些API返回区域句柄
  • CombineRgn(hDest,hSource1,hSource2,CombineMode) - 合并区域
  • SetWindowRgn(hWnd,hRgn,bRedraw) - 根据区域设置窗口形状
  • 本程序的方法是扫描位图的点,按行设置区域,然后合并到总的区域中。

    源程序 - 汇编源文件

    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;	是否包括调试代码
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
           DEBUG  =         0
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;	Programmed by 罗云彬, bigluo@telekbird.com.cn
    ;	Website: http://asm.yeah.net
    ;	LuoYunBin's Win32 ASM page (罗云彬的编程乐园)
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;	版本信息
    ;	特殊形状窗口的演示程序 Ver 1.0
    ;	可以根据位图自动设置窗口的形状。
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    
    .386
    .model        flat, stdcall
    	option casemap :none   ; case sensitive
    
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;	Include 数据
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
         include  windows.inc
         include  user32.inc
         include  kernel32.inc
         include  comctl32.inc
         include  comdlg32.inc
         include  shell32.inc
         include  gdi32.inc
    
      includelib  user32.lib
      includelib  kernel32.lib
      includelib  comctl32.lib
      includelib  comdlg32.lib
      includelib  shell32.lib
      includelib  gdi32.lib
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;	Equ 数据
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;**************	Equ 数据 **********************************
        IDI_MAIN  equ       1           ;icon
      IDC_HANDLE  equ       2           ;Cursor
    ;**************	Equ 数据 **********************************
       DLG_ABOUT  equ       1200        ;dialog - about
     ID_ABOUT_OK  equ       1201
        ID_EMAIL  equ       1202
     ID_HOMEPAGE  equ       1203
    ;**************	Equ 数据 **********************************
        IDM_MAIN  equ       2000
       IDM_ABOUT  equ       2001
        IDM_EXIT  equ       2002
    ;**************	Equ 数据 **********************************
           IDB_0  equ       3000        ;bitmap
    
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;	数据段
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    .data?
       hInstance  dd        ?
        hWinMain  dd        ?
           hIcon  dd        ?
         hCursor  dd        ?
           hMenu  dd        ?
    
        hBmpBack  dd        ?           ;background bitmap
         hDcBack  dd        ?
    
    ;**************	数据段 ************************************
    .data
    
     szClassName  db        'ShapeWindow',0
    
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;	代码段
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    
    .code
    
                  if        DEBUG
         include  Debug.asm
                  endif
    ;********************************************************************
    ;	设置窗口形状为BMP图形形状
    ;	参数:窗口句柄,BMP图形句柄
    ;	输入BMP图形要求:0,0处颜色为背景色
    ;********************************************************************
             _SetWindowShape  proc      hWnd:DWORD,hBitMap:DWORD
                  local     @hDC:DWORD,@hBmpDC:DWORD
                  local     @stPs:PAINTSTRUCT
                  local     @stRect:RECT
                  local     @stBmp:BITMAP
                  local     @dwX:DWORD,@dwY:DWORD,@dwStartX:DWORD
                  local     @hRgn:DWORD,@hRgnTemp:DWORD
                  local     @rgbBack:DWORD
    
                  invoke    GetObject,hBitMap,sizeof BITMAP,addr @stBmp
                  invoke    GetWindowRect,hWnd,addr @stRect
                  invoke    ShowWindow,hWnd,SW_HIDE
                  invoke    MoveWindow,hWnd,@stRect.left,@stRect.top,\
    			@stBmp.bmWidth,@stBmp.bmHeight,FALSE
    
                  invoke    GetDC,hWnd
                  mov       @hDC,eax
                  invoke    CreateCompatibleDC,@hDC
                  mov       @hBmpDC,eax
                  invoke    SelectObject,@hBmpDC,hBitMap
    ;*************** 计算窗口形状 ***************************************
                  invoke    GetPixel,@hBmpDC,0,0
                  mov       @rgbBack,eax
                  invoke    CreateRectRgn,0,0,0,0
                  mov       @hRgn,eax
    
                  mov       @dwY,0
    .while        TRUE
                  mov       @dwX,0
                  mov       @dwStartX,-1
    .while        TRUE
                  invoke    GetPixel,@hBmpDC,@dwX,@dwY
    .if           @dwStartX == -1
    .if           eax != @rgbBack
                  mov       eax,@dwX
                  mov       @dwStartX,eax
    .endif
    .else
    .if           eax == @rgbBack
                  mov       ecx,@dwY
                  inc       ecx
                  invoke    CreateRectRgn,@dwStartX,@dwY,@dwX,ecx
                  invoke    CombineRgn,@hRgn,@hRgn,eax,RGN_OR
                  mov       @dwStartX,-1
    .else
                  mov       eax,@dwX
    .if           eax == @stBmp.bmWidth
                  inc       eax
                  mov       ecx,@dwY
                  inc       ecx
                  invoke    CreateRectRgn,@dwStartX,@dwY,eax,ecx
                  invoke    CombineRgn,@hRgn,@hRgn,eax,RGN_OR
                  mov       @dwStartX,-1
    .endif
    .endif
    .endif
                  inc       @dwX
                  mov       eax,@dwX
    .break        .if eax > @stBmp.bmWidth
    .endw
                  inc       @dwY
                  mov       eax,@dwY
    .break        .if eax > @stBmp.bmHeight
    .endw
    
                  invoke    SetWindowRgn,hWnd,@hRgn,TRUE
    ;********************************************************************
                  invoke    BitBlt,@hDC,0,0,@stBmp.bmWidth,@stBmp.bmHeight,\
    			@hBmpDC,0,0,SRCCOPY
                  invoke    DeleteDC,@hBmpDC
                  invoke    ReleaseDC,hWnd,@hDC
                  invoke    InvalidateRect,hWnd,NULL,-1
    
                  ret
    
             _SetWindowShape  endp
    ;********************************************************************
    ;	将窗口移动到屏幕中间
    ;	参数:窗口句柄
    ;********************************************************************
               _CenterWindow  proc      hWnd:DWORD
                  local     @stRectDeskTop:RECT,@stRectWin:RECT
                  local     @dwWidth:DWORD,@dwHeight:DWORD
    
                  invoke    GetWindowRect,hWnd,addr @stRectWin
                  invoke    GetDesktopWindow
                  mov       ebx,eax
                  invoke    GetWindowRect,ebx,addr @stRectDeskTop
    
                  mov       eax,@stRectWin.bottom
                  sub       eax,@stRectWin.top
                  mov       @dwHeight,eax
                  mov       eax,@stRectWin.right
                  sub       eax,@stRectWin.left
                  mov       @dwWidth,eax
    
                  mov       ebx,@stRectDeskTop.bottom
                  sub       ebx,@dwHeight
                  shr       ebx,1
                  mov       ecx,@stRectDeskTop.right
                  sub       ecx,@dwWidth
                  shr       ecx,1
    
                  invoke    MoveWindow,hWnd,ecx,ebx,@dwWidth,@dwHeight,FALSE
                  ret
    
               _CenterWindow  endp
    ;********************************************************************
         include  About.asm
    
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;	程序开始
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
          start:
                  call      _WinMain
                  invoke    ExitProcess,NULL
    
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ;	主窗口程序
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        _WinMain  proc
                  local     @stWcMain:WNDCLASSEX
                  local     @stMsg:MSG
    
                  invoke    InitCommonControls
                  invoke    GetModuleHandle,NULL
                  mov       hInstance,eax
                  invoke    LoadIcon,hInstance,IDI_MAIN
                  mov       hIcon,eax
                  invoke    LoadMenu,hInstance,IDM_MAIN
                  invoke    GetSubMenu,eax,0        ;PopUp 菜单要用到子菜单
                  mov       hMenu,eax
    ;*************** 注册窗口类 *****************************************
                  invoke    LoadCursor,0,IDC_ARROW
                  mov       @stWcMain.hCursor,eax
                  mov       @stWcMain.cbSize,sizeof WNDCLASSEX
                  mov       @stWcMain.hIconSm,0
                  mov       @stWcMain.style,CS_HREDRAW or CS_VREDRAW
                  mov       @stWcMain.lpfnWndProc,offset WndMainProc
                  mov       @stWcMain.cbClsExtra,0
                  mov       @stWcMain.cbWndExtra,0
                  mov       eax,hInstance
                  mov       @stWcMain.hInstance,eax
                  mov       @stWcMain.hIcon,0
                  mov       @stWcMain.hbrBackground,COLOR_WINDOW + 1
                  mov       @stWcMain.lpszClassName,offset szClassName
                  mov       @stWcMain.lpszMenuName,0
                  invoke    RegisterClassEx,addr @stWcMain
    ;***************** 建立输出窗口	*****************************************
    ;	属性:没有标题栏,不显示在任务栏
    ;********************************************************************
                  invoke    CreateWindowEx,WS_EX_TOOLWINDOW,\
    			offset szClassName,NULL,\
        WS_POPUP  or        WS_SYSMENU,\
    			0,0,1,1,\
    			NULL,NULL,hInstance,NULL
    
                  invoke    ShowWindow,hWinMain,SW_SHOWNORMAL
                  invoke    UpdateWindow,hWinMain
    ;*************** 消息循环 *******************************************
    .while        TRUE
                  invoke    GetMessage,addr @stMsg,NULL,0,0
    .break        .if eax	== 0
                  invoke    TranslateMessage,addr @stMsg
                  invoke    DispatchMessage,addr @stMsg
    .endw
                  ret
    
        _WinMain  endp
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
     WndMainProc  proc      uses ebx edi esi, \
    		hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
                  local     @stPos:POINT
                  local     @stPs:PAINTSTRUCT,@hDC:DWORD
    
                  mov       eax,uMsg
    .if           eax ==	WM_CREATE
                  mov       eax,hWnd
                  mov       hWinMain,eax
                  call      _Init
    ;********************************************************************
    .elseif       eax == WM_PAINT
                  invoke    BeginPaint,hWnd,addr @stPs
                  mov       @hDC,eax
    
                  mov       eax,@stPs.rcPaint.right
                  sub       eax,@stPs.rcPaint.left
                  mov       ecx,@stPs.rcPaint.bottom
                  sub       ecx,@stPs.rcPaint.top
    
                  invoke    BitBlt,@hDC,@stPs.rcPaint.left,@stPs.rcPaint.top,eax,ecx,\
    				hDcBack,@stPs.rcPaint.left,@stPs.rcPaint.top,SRCCOPY
    
                  invoke    EndPaint,hWnd,addr @stPs
    ;********************************************************************
    ;	由于没有菜单,下面代码用于按下右键时弹出POPUP菜单
    ;********************************************************************
    .elseif       eax == WM_RBUTTONDOWN
    .if           wParam == MK_RBUTTON
                  invoke    GetCursorPos,addr @stPos
                  invoke    TrackPopupMenu,hMenu,TPM_LEFTALIGN,@stPos.x,@stPos.y,NULL,hWnd,NULL
    .endif
    ;********************************************************************
    ;	由于没有标题栏,下面代码用于按下左键时移动窗口
    ;********************************************************************
    .elseif       eax == WM_LBUTTONDOWN
                  invoke    UpdateWindow,hWnd       ;即时刷新
                  invoke    ReleaseCapture
                  invoke    SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0
    ;********************************************************************
    .elseif       eax ==	WM_COMMAND
    .if           lParam == 0
                  mov       eax,wParam
    .if           ax == IDM_EXIT
                  call      _Quit
    .elseif       ax == IDM_ABOUT
                  invoke    DialogBoxParam,hInstance,DLG_ABOUT,hWnd,offset AboutDialogProc,DLG_ABOUT
    .endif
    .endif
    ;********************************************************************
    .elseif       eax ==	WM_CLOSE
                  call      _Quit
    ;********************************************************************
    .else
                  invoke    DefWindowProc,hWnd,uMsg,wParam,lParam
                  ret
    .endif
    ;********************************************************************
    ;	注意:WndProc 处理 Windows 消息后,必须在 Eax 中返回 0
    ;	但是由 DefWindowProc 处理后的返回值不能改变,否则窗口
    ;	将无法显示!
    ;********************************************************************
                  xor       eax,eax
                  ret
    
     WndMainProc  endp
    
    
    ;********************************************************************
           _Init  proc
                  local     @hDC
    
                  invoke    SendMessage,hWinMain,WM_SETTEXT,0,offset szClassName
                  invoke    SendMessage,hWinMain,WM_SETICON,ICON_SMALL,hIcon
    
                  invoke    LoadBitmap,hInstance,IDB_0          ;装入背景图片
                  mov       hBmpBack,eax
                  invoke    _SetWindowShape,hWinMain,hBmpBack   ;设置窗口形状为背景图片
                  invoke    GetDC,hWinMain
                  mov       @hDC,eax
                  invoke    CreateCompatibleDC,@hDC ;建立背景及数字 DC
                  mov       hDcBack,eax
                  invoke    ReleaseDC,hWinMain,@hDC
                  invoke    SelectObject,hDcBack,hBmpBack
                  invoke    _CenterWindow,hWinMain
    
                  ret
    
           _Init  endp
    ;********************************************************************
           _Quit  proc
                  local     @stWindow:RECT
    
                  invoke    DestroyMenu,hMenu
                  invoke    DeleteDC,hDcBack
                  invoke    DeleteObject,hBmpBack
                  invoke    DestroyWindow,hWinMain
                  invoke    PostQuitMessage,NULL
    
                  ret
    
           _Quit  endp
    ;********************************************************************
                  end       start
    

    程序的分析和要点

    创建窗口的时候,窗口风格为 WS_POPUP,所以创建的窗口没有标题栏,这样的窗口适合于设置成特殊形状的窗口

    		invoke	CreateWindowEx,WS_EX_TOOLWINDOW,\
    			offset szClassName,NULL,\
    			WS_POPUP or WS_SYSMENU,\
    			0,0,1,1,\
    			NULL,NULL,hInstance,NULL

    但是当窗口没有标题栏后,我们就无法用拖动标题栏的办法来移动窗口,如果让窗口一动不动呆在屏幕中间显然是不行的,这里有一个替代办法,我们可以响应按下鼠标左键的消息,在 WM_LBUTTONDOWN 消息中想窗口发送 WM_NCLBUTTONDOWN (非客户区鼠标按下消息) 位置在 HTCAPTION 来模拟鼠标按在标题栏中来实现移动的功能。

    		.elseif eax == WM_LBUTTONDOWN
    			invoke	UpdateWindow,hWnd		;即时刷新
    			invoke	ReleaseCapture
    			invoke	SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0