首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 組合語言

Windows 通用控件深度解析:從理論到 MASM 實戰範例

     編輯:組合語言

前言:Windows 95 帶來的 UI 革命

Windows 95 相較於 Windows 3.x 最大的突破之一,在於引入了一套豐富的「通用控件」。這些控件如工具列、狀態列、樹狀檢視、列表檢視、進度條、分頁控件等,早在 Windows 95 正式釋出前已在系統內部使用。過去程式設計師必須自行撰寫複雜代碼才能實現這些介面元素,如今 Microsoft 將它們封裝在 comctl32.dll 中,任何應用程式皆可輕鬆調用,極大地降低了開發門檻。

核心控件一覽與類別名稱

通用控件種類繁多,以下列出常用控件及其對應的窗口類別名稱(以 VC++ 資源編輯器為準,精確可靠):

類別名稱控件名稱
ToolbarWindow32工具列
tooltips_class32提示文字
msctls_statusbar32狀態列
SysTreeView32樹狀檢視
SysListView32列表檢視
SysAnimate32動畫控件
SysHeader32表頭控件
msctls_hotkey32熱鍵控件
msctls_progress32進度條
RICHEDIT豐富文字編輯
msctls_updown32微調控件
SysTabControl32分頁控件

注意:Property Sheet、Property Page、Image List、Drag List 等控件擁有專屬建立函式,不依賴標準類別名稱。

動態載入機制:InitCommonControls 的奧秘

為避免一次性將所有控件載入記憶體造成浪費,Windows 採用動態載入策略。除 RTF 編輯控件(位於 richedxx.dll)需手動呼叫 LoadLibrary/FreeLibrary 外,其餘控件皆在 comctl32.dll 中。

呼叫 InitCommonControls 函式的實際作用極其巧妙:該函式本體僅有一條 ret 指令,真正目的是讓 PE Loader 在載入應用程式時,於 PE 標頭的「匯入表」中註冊 comctl32.dll。如此,系統便會自動載入該 DLL,並在其進入點註冊所有通用控件類別,後續即可像建立普通子視窗一樣建立控件。

控件建立方式:CreateWindowEx 與封裝函式

幾乎所有通用控件均可透過 CreateWindowEx 或 CreateWindow 建立,只需傳入正確的類別名稱。為了方便開發,系統提供了封裝函式:

  • CreateToolbarEx - 建立工具列
  • CreateStatusWindow - 建立狀態列
  • CreatePropertySheetPage / PropertySheet - 屬性頁
  • ImageList_Create - 影像清單

訊息通訊機制:WM_NOTIFY 取代 WM_COMMAND

通用控件與父視窗的通訊不再使用傳統的 WM_COMMAND,改以 WM_NOTIFY 訊息傳遞複雜的通知資訊(透過 NMHDR 結構)。父視窗則可發送專屬訊息(如 PBM_SETRANGE、SB_SETTEXT)控制控件行為。各控件擁有專屬訊息集合,詳情請參閱 Win32 API 文件。

實戰範例:MASM 打造進度條與狀態列示範程式

以下完整 MASM 程式碼展示如何建立主視窗、初始化通用控件、建立進度條與狀態列,並利用計時器驅動進度條遞增,完成後彈出訊息框並重置介面。

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.const
IDC_PROGRESS equ 1
IDC_STATUS   equ 2
IDC_TIMER    equ 3

.data
ClassName      db "CommonControlWinClass",0
AppName        db "Common Control Demo",0
ProgressClass  db "msctls_progress32",0
Message        db "Finished!",0
TimerID        dd 0

.data?
hInstance      HINSTANCE ?
hwndProgress   dd ?
hwndStatus     dd ?
CurrentStep    dd ?

.code
start:
    invoke GetModuleHandle, NULL
    mov hInstance, eax
    invoke WinMain, hInstance, NULL, NULL, SW_SHOWDEFAULT
    invoke ExitProcess, eax
    invoke InitCommonControls   ; 故意置於 ExitProcess 之後,驗證僅為匯入表註冊

WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov wc.cbSize, SIZEOF WNDCLASSEX
    mov wc.style, CS_HREDRAW or CS_VREDRAW
    mov wc.lpfnWndProc, OFFSET WndProc
    mov wc.cbClsExtra, NULL
    mov wc.cbWndExtra, NULL
    push hInst
    pop wc.hInstance
    mov wc.hbrBackground, COLOR_APPWORKSPACE
    mov wc.lpszMenuName, NULL
    mov wc.lpszClassName, OFFSET ClassName
    invoke LoadIcon, NULL, IDI_APPLICATION
    mov wc.hIcon, eax
    mov wc.hIconSm, eax
    invoke LoadCursor, NULL, IDC_ARROW
    mov wc.hCursor, eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx, WS_EX_CLIENTEDGE, ADDR ClassName, ADDR AppName, \
        WS_OVERLAPPED or WS_CAPTION or WS_SYSMENU or WS_MINIMIZEBOX or WS_MAXIMIZEBOX or WS_VISIBLE, \
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, \
        NULL, NULL, hInst, NULL
    mov hwnd, eax
    .while TRUE
        invoke GetMessage, ADDR msg, NULL, 0, 0
        .BREAK .IF (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
    .endw
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .if uMsg == WM_CREATE
        ; 建立進度條
        invoke CreateWindowEx, NULL, ADDR ProgressClass, NULL, \
            WS_CHILD or WS_VISIBLE, 100, 200, 300, 20, \
            hWnd, IDC_PROGRESS, hInstance, NULL
        mov hwndProgress, eax
        mov eax, 1000
        mov CurrentStep, eax
        shl eax, 16
        invoke SendMessage, hwndProgress, PBM_SETRANGE, 0, eax  ; 設定範圍 0-1000
        invoke SendMessage, hwndProgress, PBM_SETSTEP, 10, 0   ; 步進 10

        ; 建立狀態列
        invoke CreateStatusWindow, WS_CHILD or WS_VISIBLE, NULL, hWnd, IDC_STATUS
        mov hwndStatus, eax

        ; 建立計時器,每 100ms 觸發
        invoke SetTimer, hWnd, IDC_TIMER, 100, NULL
        mov TimerID, eax

    .elseif uMsg == WM_DESTROY
        invoke PostQuitMessage, NULL
        .if TimerID != 0
            invoke KillTimer, hWnd, TimerID
        .endif

    .elseif uMsg == WM_TIMER
        invoke SendMessage, hwndProgress, PBM_STEPIT, 0, 0
        sub CurrentStep, 10
        .if CurrentStep == 0
            invoke KillTimer, hWnd, TimerID
            mov TimerID, 0
            invoke SendMessage, hwndStatus, SB_SETTEXT, 0, addr Message
            invoke MessageBox, hWnd, addr Message, addr AppName, MB_OK or MB_ICONINFORMATION
            invoke SendMessage, hwndStatus, SB_SETTEXT, 0, 0
            invoke SendMessage, hwndProgress, PBM_SETPOS, 0, 0
        .endif
    .else
        invoke DefWindowProc, hWnd, uMsg, wParam, lParam
        ret
    .endif
    xor eax, eax
    ret
WndProc endp
end start

關鍵技術點解析

1. InitCommonControls 的放置位置證明機制

程式刻意將 InitCommonControls 放在 ExitProcess 之後。即使該函式從未真正執行,程式仍能正常運作,證明其唯一作用確為在 PE 匯入表中註冊 comctl32.dll,由系統載入器自動完成 DLL 載入與類別註冊。

2. 進度條範圍與步進設定

PBM_SETRANGE 的 lParam 低字為最小值、高字為最大值。範例設定 0~1000,步進 10,故需 100 步完成。亦可用 PBM_SETPOS 直接設定位置。

3. 計時器驅動動畫

SetTimer 建立 100ms 間隔計時器,WM_TIMER 處理程式中呼叫 PBM_STEPIT 遞增進度條。當 CurrentStep 歸零時,殺死計時器、顯示完成訊息於狀態列、彈出對話框,最後重置進度條與狀態列文字。

總結

通用控件是 Windows 視窗程式設計的基石。掌握其載入機制、類別名稱、建立方式與 WM_NOTIFY 通訊模型,即可輕鬆構建專業級使用者介面。本文配合完整 MASM 範例,從底層機制到上層應用完整呈現,為深入學習 Windows 核心程式設計奠定堅實基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved