Ch 07 加上圖示

這一章,將介紹在程式中加入自己設計的『圖示』( icon ),而不使用系統中內定值。加入圖示後的程式可以在桌面上、資料夾媗膆亶o個圖示,同時程式執行時,能在標題欄處顯示這個圖示。


原理

就如同上一章曾輕輕地提到,圖示也是屬於 UI 資源的一種。要在程式中加入圖示,必須先設計好一個新的圖示檔,其副檔名通常是 *.ico,您可以用圖示編輯器自行繪製圖示檔,也可以在網際網路上尋找前輩們已製作好的圖示檔。

製作好或找到中意的圖示檔後,在資源描述檔中加上一段有關圖示的描述,其格式如下:

nameID  ICON    filename

ICON 是固定的,表示此段落是描是圖示之用。nameID 是圖示的名稱或識別碼,可以是一個字串,也可以是一個小於 65535 的正整數,將來在程式中靠 nameID 與程式碼連接。filename 是圖示檔,一般副檔名為 *.ico,假如圖示檔不在現行子目錄內,filename 也可以包含路徑。

在原始程式中則以 LoadIcon API 使程式自 UI 資源中載入圖示。LoadIcon 的原型請參考第二章有關 LoadIcon API 的說明。但此處不是要載入系統預設值,所以必須先在資料斷中設定一個以 NULL 結尾的字串,而此字串就是圖示名稱,也就是在資源描述檔中的 nameID。


原始程式

底下這個例子,將為前一章的 DRAW.EXE 加上圖示,加上圖示後的可執行檔稱為 DRAW2.EXE。所加入的圖示檔名為 elephant.ico,如下圖:

elephant.ico
資源描述檔,DRAW2.RC,內容如下:

#define IDM_Exit        10
#define IDM_Line        21
#define IDM_Ellipse     22
#define IDM_Trace       23
#define IDM_Red         24
#define IDM_Green       25
#define IDM_Blue        26

DRAW    MENU
{

MENUITEM    "離開(&E)",IDM_Exit

POPUP   "繪圖(&D)"
        {
        MENUITEM    "線段(&L)",IDM_Line
        MENUITEM    "橢圓(&E)",IDM_Ellipse
        MENUITEM    "手繪圖(&T)",IDM_Trace
        MENUITEM    SEPARATOR
        MENUITEM    "紅(&R)",IDM_Red
        MENUITEM    "綠(&G)",IDM_Green
        MENUITEM    "藍(&B)",IDM_Blue
        }
}

TinyDraw        ICON    elephant.ico

DRAW2.ASM 來源檔內容如下:

        .386
        .model  flat,stdcall
        option  casemap:none
include         windows.inc
include         user32.inc
include         gdi32.inc
include         kernel32.inc
includelib      user32.lib
includelib      gdi32.lib
includelib      kernel32.lib

IDM_Exit        equ     10      ;12 選項編號
IDM_Line        equ     21
IDM_Ellipse     equ     22
IDM_Trace       equ     23
IDM_Red         equ     24
IDM_Green       equ     25
IDM_Blue        equ     26
red_color       equ     0ffh    ;19 顏色
green_color     equ     0ff00h
blue_color      equ     0ff0000h

WndProc         proto   :HWND,:UINT,:WPARAM,:LPARAM

        .DATA
ClassName       db          'SimpleWinClass',0
AppName         db          '小小繪圖程式',0
AskExit         db          '是否退出此程式?',0
AskExitTitle    db          '詢問',0
MenuName        db          'DRAW',0
IconName        db          'TinyDraw',0
PenDown         db          FALSE       ;31 FALSE︰畫筆提起,TRUE︰畫筆觸紙
command         dw          IDM_Line    ;32 使用者由選單選擇的命令
color           dd          red_color   ;33 使用者由選單選擇的顏色
hMenu           HMENU       ?
hInstance       HINSTANCE   ?
hwnd            HWND        ?
wc              WNDCLASSEX  <30h,?,?,0,0,?,?,?,?,0,offset ClassName,?>
msg             MSG         <?>
start_point     POINT       <?>         ;39 起點座標
end_point       POINT       <?>         ;40 終點座標

        .CODE
start:  invoke  GetModuleHandle,NULL
        mov     hInstance,eax
        mov     wc.style,CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc,offset WndProc
        mov     eax,hInstance
        mov     wc.hInstance,eax
        mov     wc.hbrBackground,COLOR_WINDOW+1
        invoke  LoadIcon,hInstance,offset IconName
        mov     wc.hIcon,eax
        mov     wc.hIconSm,eax
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor,eax
        invoke  LoadMenu,hInstance,offset MenuName      ;55 取得選單
        mov     hMenu,eax
        invoke  RegisterClassEx,offset wc
        invoke  CreateWindowEx,NULL,offset ClassName,\ 
                offset AppName,WS_OVERLAPPEDWINDOW,0,\
                0,400,400,0,hMenu,hInstance,NULL        ;60 加上選單
        mov     hwnd,eax
        invoke  ShowWindow,hwnd,SW_SHOWNORMAL
        invoke  UpdateWindow,hwnd

.while  TRUE
        invoke  GetMessage,offset msg,NULL,0,0
.break  .if     !eax
        invoke  DispatchMessage,offset msg
.endw        
        mov     eax,msg.wParam
        invoke  ExitProcess,eax
;---------------------------------------
;計算 EAX 內的座標,存於 EDX 所指的位址,EAX︰高位元為 Y 座標,低位元為 X 座標
get_crd proc
        push    eax
        and     eax,0ffffh
        mov     [edx],eax
        pop     eax
        shr     eax,10h
        mov     [edx+4],eax
        ret
get_crd endp
;---------------------------------------
WndProc proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
        local   rectangle:RECT
        local   PS:PAINTSTRUCT
        local   hdc:HDC
        local   newPen:DWORD
        local   oldPen:DWORD
.if     uMsg==WM_COMMAND
        mov     eax,wParam
        mov     PenDown,FALSE
        .if     ax==IDM_Exit
                jmp     exit
        .elseif (ax==IDM_Line)||(ax==IDM_Ellipse)||(ax==IDM_Trace)
                mov     command,ax
        .elseif ax==IDM_Red
                mov     color,red_color
        .elseif ax==IDM_Green
                mov     color,green_color
        .elseif ax==IDM_Blue
                mov     color,blue_color
        .endif

.elseif uMsg==WM_LBUTTONDOWN                    ;105 按下滑鼠左鍵
        .if     (command==IDM_Line)||(command==IDM_Ellipse)
                .if     PenDown
                        mov     eax,lParam      ;108 畫筆觸紙時
                        mov     edx,offset end_point
                        call    get_crd
                        invoke  GetClientRect,hWnd,addr rectangle
                        invoke  InvalidateRect,hWnd,addr rectangle,FALSE
                .else
                        mov     eax,lParam      ;114 畫筆提起時
                        mov     edx,offset start_point
                        call    get_crd
                .endif

        .elseif command==IDM_Trace
                .if     (!PenDown)
                        mov     eax,lParam
                        mov     edx,offset start_point
                        call    get_crd
                .endif                   
        .endif
        xor     PenDown,1       ;118 切換畫筆提起或觸紙

.elseif uMsg==WM_MOUSEMOVE
        .if     (command==IDM_Trace)&&(PenDown)
                mov     eax,lParam
                mov     edx,offset end_point
                call    get_crd
                invoke  GetClientRect,hWnd,addr rectangle
                invoke  InvalidateRect,hWnd,addr rectangle,FALSE
        .endif

.elseif uMsg==WM_PAINT
        .if     command==IDM_Line
                invoke  BeginPaint,hWnd,addr PS
                mov     hdc,eax
                invoke  CreatePen,PS_SOLID,1,color
                mov     newPen,eax
                invoke  SelectObject,hdc,newPen
                mov     oldPen,eax
                invoke  MoveToEx,hdc,start_point.x,start_point.y,NULL
                invoke  LineTo,hdc,end_point.x,end_point.y
                invoke  SelectObject,hdc,oldPen
                invoke  DeleteObject,newPen
                invoke  EndPaint,hdc,addr PS
        .elseif command==IDM_Ellipse
                invoke  BeginPaint,hWnd,addr PS
                mov     hdc,eax
                invoke  CreatePen,PS_SOLID,1,color
                mov     newPen,eax
                invoke  SelectObject,hdc,newPen
                mov     oldPen,eax
                mov     eax,start_point.x
                add     eax,end_point.x
                shr     eax,1
                push    eax
                invoke  Arc,hdc,start_point.x,start_point.y,\   ;161 畫出半橢圓
                        end_point.x,end_point.y,eax,400,eax,0
                pop     eax
                invoke  Arc,hdc,start_point.x,start_point.y,\   ;164 畫出另外一半橢圓
                        end_point.x,end_point.y,eax,0,eax,400
                invoke  SelectObject,hdc,oldPen
                invoke  DeleteObject,newPen
                invoke  EndPaint,hdc,addr PS
        .elseif command==IDM_Trace
                invoke  BeginPaint,hWnd,addr PS
                mov     hdc,eax
                invoke  CreatePen,PS_SOLID,1,color
                mov     newPen,eax
                invoke  SelectObject,hdc,newPen
                mov     oldPen,eax
                invoke  MoveToEx,hdc,start_point.x,start_point.y,NULL
                invoke  LineTo,hdc,end_point.x,end_point.y
                invoke  SelectObject,hdc,oldPen
                invoke  DeleteObject,newPen
                invoke  EndPaint,hdc,addr PS
                mov     eax,end_point.x
                mov     edx,end_point.y
                mov     start_point.x,eax
                mov     start_point.y,edx
        .else
                jmp     def
        .endif

.elseif uMsg==WM_CLOSE
exit:   invoke  MessageBox,NULL,offset AskExit,\
                offset AskExitTitle,MB_YESNO or MB_ICONQUESTION
        .if     eax==IDYES
                invoke  DestroyWindow,hWnd
        .endif

.elseif uMsg==WM_DESTROY
        invoke  PostQuitMessage,NULL

.else
def:    invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
.endif
        xor     eax,eax
        ret
WndProc endp

end     start

DRAW2.MAK 之內容如下:

# 產生 DRAW2.EXE
ALL : DRAW2.EXE

# 組譯器及連結器
DRAW2.EXE : DRAW2.ASM DRAW2.RES
# 組譯器會自動傳 DRAW2.OBJ 給連結器,故 /link 之後不用再加上 DRAW2.OBJ
    ml /coff DRAW2.ASM /link /SUBSYSTEM:WINDOWS DRAW2.RES 

# 資源編譯器
DRAW2.RES : DRAW2.RC elephant.ico
    rc DRAW2.RC

組譯時,僅需在 DOS 模式下輸入

d:\homepage\source>nmake -f draw2.mak [Enter]

Microsoft (R) Program Maintenance Utility   Version 1.50
Copyright (c) Microsoft Corp 1988-94. All rights reserved.

        ml /coff DRAW2.ASM /link /SUBSYSTEM:WINDOWS DRAW2.RES
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: DRAW2.ASM
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/SUBSYSTEM:WINDOWS /SUBSYSTEM:WINDOWS
"DRAW2.obj"
"/OUT:DRAW2.exe"
"DRAW2.RES"

d:\homepage\source>

就可以得到 DRAW2.EXE 檔,打開 DRAW2.EXE 所在資料夾,檢視看看是否圖示已經換成 elephant.ico 了:

檢視 DRAW.EXE 圖示是否正確顯示
您可以看到,右上角和中央下面各有兩個大象的圖示,下面的是 elephant.ico 圖示檔,右上角的是 DRAW2.EXE 檔,執行看看
果真在標題欄中圖示的位置,也已經換成和 elephant.ico 一樣的樣子了。到此小木偶已經成功的為執行檔加上圖示了。

程式幾乎和上一章相同,因此其他部份就不再解釋,至於加入圖示的部份,您只要注意原始程式中紅色的部份就可以了。


到第六章回到首頁到第八章