WIN32汇编 状态栏的使用

说起状态栏其实都见过,但是具体不知道是哪个位置(我以前也不知道。。),先来看下图



如上图所示就是状态栏的位置,对于实现状态栏我大致分为如下过程:

资源文件
对话框 IDD_DIALOG1
字符串表 STRINGTABLE
菜单 IDR_MENU1
图标 IDI_ICON1

实现过程
对画框过程 _ProcDlgMain 
改变状态栏大小 _Resize
总体实现 创建显示


介绍了本次程序的大体结构框架,下面来介绍一下,具体遇到的一些问题:

首先是资源文件:这次依旧是使用ResEdit资源编辑工具,相比以往的程序这次需要创建一个字符串表(在使用版本信息的时候也曾经使用过)因为很少使用,感觉有点陌生,我直接在添加资源里面选择了“添加字符串表”选项,在里面依次添加即可,注意格式就行,在完成后会在resourc.h文件中自动为字符串项设置ID,如果想更改的话可以自己以文本的格式打开这个文件修改(他自己分配的ID数值有的貌似很大书写不方便,可以改成方便书写的,方便起见。。),因为需要在鼠标放在命令项(例如菜单中的“文件”“打开文件”按钮)时在对话框的状态栏显示提示字符(也就是对应的字符串表中的字符串),所以字符串表中的字符串要和菜单中的命令项相对应,罗云斌老师的书中介绍的程序代码将菜单和字符串表的命令ID设为一样的,这样确实很方便,但是我在使用ResEdit资源编辑工具编写的时候会出现一些错误,我感觉字符串表还是写成IDS_开头的比较直观一些,容易理解,但是如果这样写还会遇见一个问题,就是在代码中使用 MenuHelp()函数的时候,需要注意第六个参数dwMenuHelp,  我们可以看一下源代码中在.const段中定义了常量:

dwMenuHelp      dd        2,40000,0,0   

 一共是四个双字,第一个双字代表的就是命令项的基数(这个不太容易理解,我刚写的时候很不解,其实就是以字符串表第一项为基数0以后的每项依次加1,而 IDS_OPEN这一项基数就为2,写错的话你就会发现状态栏出现的提示字符是错乱的,张冠李戴了。。。,第二个双字的基数就需要写字符串表第一项的ID值)具体看一下资源脚本代码如下:

// Generated by ResEdit 1.6.6
// Copyright (C) 2006-2015
// http://www.resedit.net


#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include "resource.h"
//
// Menu resources
//
LANGUAGE 1023, 63
IDR_MENU1 MENU
{
    POPUP "文件(&F)"
    {
        MENUITEM "打开文件(&O)", IDM_OPEN
        MENUITEM "文件另存为(&C)", IDM_SAVES
        MENUITEM SEPARATOR
        MENUITEM "页面设置(&P)", IDM_PAGESETUP
        MENUITEM SEPARATOR
        MENUITEM "退出(&X)", IDM_EXIT
    }
    POPUP "查看(&V)"
    {
        MENUITEM "查找字符串(&S)", IDM_FIND
        MENUITEM " 替换字符串(&R)", IDM_REPLACE
        MENUITEM SEPARATOR
        MENUITEM "选择字体(&F)", IDM_SELFONT
        MENUITEM " 选择颜色(&B)", IDM_SELCOLOR
    }
}
//
// Dialog resources
//
LANGUAGE 1023, 63
IDD_DIALOG1 DIALOGEX 0, 0, 186, 95
STYLE WS_POPUP | WS_VISIBLE | WS_SYSMENU | WS_SIZEBOX       ;注意此处风格需要选择可以拖动边框的的WS_THICKFRAME  这个也可以写为WS_SIZEBOX
CAPTION "状态栏示例"
MENU IDR_MENU1
FONT 8, "Ms Shell Dlg", 0, 0, 0
{
}
//
// String Table resources
//
LANGUAGE 1023, 63
STRINGTABLE
{
    IDS_MENUHELP                  "包含文件操作的命令"
    IDS_MENUHELP1                 "包含操作视图的命令"
    IDS_OPEN                      "打开需要编辑的文件"
    IDS_SAVES                     "以另外一个文件名保存文件"
    IDS_PAGESETUP                 "选择打印机以及设置页边距.纸张大小等打印参数"
    IDS_EXIT                      "退出本程序"
    IDS_FIND                      "在窗口中搜索文字"
    IDS_REPLACE                   "在窗口中搜索文字并替换"
    IDS_SELFONT                   "选择窗口中文字使用的字体"
    IDS_SELCOLOR                  "选择窗口的背景颜色"
}
//
// Icon resources
//
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDI_ICON1          ICON           "icon3.ico"


注意上面字符串表的每一项与菜单的命令项都是对应的,次序是一致的,这一点需要注意,很容易犯错误。

关于资源文件的编写还有一点:在创建对话框的时候,以前也写过对话框的程序,但是关于对话框的风格类型(style)以前都没有过多的关注,因为ResEdit工具只要选择就会自动生成资源代码,但是这次写就遇见问题了。刚开始我使用ResEdit编写完资源代码就编译生成22.resc文件然后和程序实现代码生成的22.obj等文件链接起来以后构建并运行,对话框如自己所想出现在屏幕上,但是拖动状态栏下面那个小三角的话窗口的大小并不能改变,刚开始我以为是创建窗口对话框函数的风格指定错误了,但是一个一个参数对照后发现,并不是这的原因,创建状态栏和窗口的函数的参数都没有错误,最后我去查看资源文件对话框的风格(style),我查看了一下资料关于窗口的风格的定义值,发现WS_THICKFRAME这个风格可以使窗口的边框来回拖动改变边框的大小(对话框和窗口的风格通用的),加上以后重新编译连接构建运行,拖动窗口,OK问题解决了,由于这个错误我也顺便看了下关于WS_SYSMENU这个有点陌生的风格,就是加上窗口右上角的叉号,添加上默认的窗口的一些属性。

还有一点就是源代码中:

invoke   CreateStatusWindow,WS_CHILD OR WS_VISIBLE OR SBS_SIZEGRIP,NULL,hWinMain,ID_STATUSBAR

这句代码中SBS_SIZEGRIP这个风格与SBARS_SIZEGRIP这个风格是一样的,都是在状态栏右下角显示一个小三角用来拖动改变父窗口大小,但是在RadASM工具中SBARS_SIZEGRIP并没有显示蓝色(就是像其他声明好的风格一样,但是他依然起作用了)。

下面看一下程序源代码:

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

IDI_ICON1       equ       102
IDD_DIALOG1     equ       100
IDR_MENU1       equ       101
IDM_EXIT        equ       40003
IDS_MENUHELP    equ       40000
ID_STATUSBAR    equ       1
ID_EDIT         equ       2


                .data
hInstance       dd        ?
hWinMain        dd        ?
hWinStatus      dd        ?
hWinEdit        dd        ?
lpsz1           dd        ?
lpsz2           dd        ?


                .const
szClass0        db        'msctls_statusbar32',0
szClass         db        'EDIT',0
szFormat0       db        '%02d:%02d:%02d',0
szFormat1       db        '字节数:%d',0
sz1             db        '插入',0
sz2             db        '改写',0
dwStatusWidth   dd        60,140,172,-1
dwMenuHelp      dd        2,40000,0,0


                .code
_Resize         proc

       LOCAL     @stRect:RECT,@stRect1:RECT
       invoke    MoveWindow,hWinStatus,0,0,0,0,TRUE
       invoke    GetWindowRect,hWinStatus,addr @stRect
       invoke    GetClientRect,hWinMain,addr @stRect1
       mov       ecx,@stRect1.right
       sub       ecx,@stRect1.left
       mov       eax,@stRect1.bottom
       sub       eax,@stRect1.top
       sub  eax,@stRect.bottom
add  eax,@stRect.top
       invoke    MoveWindow,hWinEdit,0,0,ecx,eax,TRUE
       ret


_Resize endp


_ProcDlgMain    proc      uses ebx edi esi hWnd,wMsg,wParam,lParam
                LOCAL     @szBuffer[128]:byte
                LOCAL     @stST:SYSTEMTIME
                LOCAL     @stPoint:POINT,@stRect:RECT
                mov       eax,wMsg
                .if       eax  ==  WM_TIMER
                 invoke GetLocalTime,addr @stST
                 movzx  eax,@stST.wHour
                 movzx  ebx,@stST.wMinute
                 movzx  ecx,@stST.wSecond
                 invoke wsprintf,addr @szBuffer,addr szFormat0,eax,ebx,ecx
                 invoke SendMessage,hWinStatus,SB_SETTEXT,0,addr @szBuffer
       .elseif   eax  ==  WM_CLOSE
         invoke   KillTimer,hWnd,1
         invoke   EndDialog,hWnd,NULL
       .elseif   eax  ==  WM_INITDIALOG
         mov      eax,hWnd
         mov      hWinMain,eax
         invoke   CreateStatusWindow,WS_CHILD OR WS_VISIBLE OR SBS_SIZEGRIP,NULL,hWinMain,ID_STATUSBAR    ;参数SBS_SIZEGRIP和SBARS_SIZEGRIP是一样的效果
         mov      hWinStatus,eax
         invoke   SendMessage,hWinStatus,SB_SETPARTS,4,offset dwStatusWidth
         mov      lpsz1,offset sz1
         mov      lpsz2,offset sz2
         invoke   SendMessage,hWinStatus,SB_SETTEXT,2,lpsz1
         invoke   CreateWindowEx,WS_EX_CLIENTEDGE,addr szClass,NULL,WS_CHILD OR WS_VISIBLE OR ES_MULTILINE OR\
                  ES_WANTRETURN OR WS_VSCROLL OR ES_AUTOHSCROLL,0,0,0,0,hWnd,ID_EDIT,hInstance,NULL
         mov      hWinEdit,eax
         call     _Resize
         invoke   SetTimer,hWnd,1,300,NULL
       .elseif   eax  ==  WM_COMMAND
         mov      eax,wParam
         .if      ax  ==  IDM_EXIT
            invoke  EndDialog,hWnd,NULL
         .elseif  ax  ==  ID_EDIT
            invoke  GetWindowTextLength,hWinEdit
            invoke  wsprintf,addr @szBuffer,addr szFormat1,eax
            invoke  SendMessage,hWinStatus,SB_SETTEXT,1,addr @szBuffer
         .endif
       .elseif   eax  ==  WM_MENUSELECT
                 invoke   MenuHelp,WM_MENUSELECT,wParam,lParam,lParam,hInstance,hWinStatus,offset dwMenuHelp
       .elseif   eax  ==  WM_SIZE
         call     _Resize
         
       .elseif   eax  ==  WM_NOTIFY
                 .if  wParam  ==  ID_STATUSBAR
                      mov eax,lParam
                      mov eax,[eax + NMHDR.code]
              .if eax  ==  NM_CLICK
                invoke   GetCursorPos,addr @stPoint
                invoke   GetWindowRect,hWinStatus,addr @stRect
                mov      eax,@stRect.left
                mov      ecx,eax
                add      eax,140
                add      ecx,172
                .if      (@stPoint.x >= eax) && (@stPoint.x <= ecx)
                   mov      eax,lpsz1
                   xchg     eax,lpsz2
                   mov      lpsz1,eax
                   invoke   SendMessage,hWinStatus,SB_SETTEXT,2,lpsz1
                .endif
              .endif
         .endif
       .else  
        
        mov     eax,FALSE
        ret
       .endif  
       mov     eax,TRUE
       ret          


_ProcDlgMain endp
start:
                invoke  InitCommonControls
                invoke  GetModuleHandle,NULL
                mov     hInstance,eax
                invoke  DialogBoxParam,hInstance,IDD_DIALOG1,NULL,offset _ProcDlgMain,NULL
                invoke  ExitProcess,NULL
                end  start

源代码中还有一处值得注意:

看下面代码:

Resize         proc

       LOCAL     @stRect:RECT,@stRect1:RECT
       invoke    MoveWindow,hWinStatus,0,0,0,0,TRUE
       invoke    GetWindowRect,hWinStatus,addr @stRect
       invoke    GetClientRect,hWinMain,addr @stRect1
       mov       ecx,@stRect1.right
       sub       ecx,@stRect1.left
       mov       eax,@stRect1.bottom
       sub       eax,@stRect1.top
       sub  eax,@stRect.bottom
add  eax,@stRect.top
       invoke    MoveWindow,hWinEdit,0,0,ecx,eax,TRUE
       ret
_Resize endp

这个子函数功能是为了实现改变状态栏的大小及位置

GetWindowRect,hWinStatus,addr @stRect   这个函数都知道是为了获取窗口的左上角以及右下角的坐标(但是这个函数需要注意的是它的坐标系是以屏幕的左上角为原点的,这个需要好好品味一下),注意在源代码中第一个参数指定的是状态栏的句柄,也就是说获取的是状态栏的大小及其坐标,(不要认为是主窗口的大小,注意句柄,我就理解错了。。)

 GetClientRect,hWinMain,addr @stRect1 这个函数而是以当前窗口的左上角的点为原点建立的坐标系(说的都有点俗。。。)这样的话右下角的坐标(.right)其实也是这个窗口的宽(以前就很纠结这个怎么把坐标直接当成宽了,现在才知道。。。),对于这个函数必须知道客户区(窗口除了标题栏和工具栏剩下的就是客户区)注意在源代码中第一个参数指定的是主窗口的句柄,也就是说这个函数返回的是主窗口的客户区的大小及坐标,也就是除了标题栏和菜单的其它区域。

 MoveWindow,hWinEdit,0,0,ecx,eax,TRUE  这个函数的第五个参数在我写的时候可是很纠结,

 mov       ecx,@stRect1.right
        sub       ecx,@stRect1.left

上面这两句代码是为了的得到主窗口客户区的宽度,也就是状态栏的宽度,这个好理解,但是下面:

                mov       eax,@stRect1.bottom        
        sub       eax,@stRect1.top                  ;得到主窗口客户区的高度
        sub   eax,@stRect.bottom                 
add   eax,@stRect.top                  ;后两句的意思就是eax+(@[email protected])

                                                                              意思就是主窗口客户区的高度减去状态栏的高度正好是文本编辑

                                                                              区的高度。

这个因为一点理解错就会全盘搞错,注意。



代码依然


下面介绍一下一些陌生的API函数和结构:


NMHDR

消息通过下面的宏定义之:

ON_NOTIFY( wNotifyCode, idControl, memberFxn )

wNotifyCode

通知消息标识符代码,如 TBN_BEGINADJUST。

idControl

发送通知的控件的标识符。

memberFxn

接收到通知时调用的成员函数。


API函数

      

MoveWindow()

功能:  

改变指定窗口的位置和大小.对顶窗口来说,位置和大小取决于屏幕的左上角;对子窗口来说,位置和大小取决于父窗口客户区的左上角.对于Owned窗口,位置和大小取决于屏幕左上角.    

原型:

BOOL MoveWindow( HWND hWnd, int X, int Y, int nWidth, int nHeight, BOOL bRepaint )        

参数:

hWnd指定了窗口的句柄

x指定了CWnd的左边的新位置。

y指定了CWnd的顶部的新位置。

nWidth指定了CWnd的新宽度。

nHeight指定了CWnd的新高度。

bRepaint指定了是否要重画CWnd。如果为TRUE,则CWnd象通常那样在OnPaint消息处理函数中接收到一条WM_PAINT消息。如果这个参数为FALSE,则不会发生任何类型的重画操作。这应用于客户区、非客户区(包括标题条和滚动条)和由于CWnd移动而露出的父窗口的任何部分。当这个参数为FALSE的时候,应用程序必须明确地使CWnd和父窗口中必须重画的部分无效或重画。

MoveWindow给窗口发送WM_WINDOWPOSCHANGING,WM_WINDOWPOSCHANGED,WM_MOVE,WM_SIZE和WM_NCCALCSIZE消息。

返回值:无


GetWindowRect()

功能:

该函数返回指定窗口的边框矩形的尺寸。该尺寸以相对于屏幕坐标左上角的屏幕坐标给出。

原型:

BOOL GetWindowRect(HWND hWnd,LPRECT lpRect);

参数:

hWnd:窗口句柄。(注意句柄,返回指定句柄的窗口,而不是当前窗口句柄)

lpRect:指向一个RECT结构的指针,该结构接收窗口的左上角和右下角的屏幕坐标。

返回值:

如果函数成功,返回值为非零:如果函数失败,返回值为零。


CreateStatusWindow()

功能:

创建一个状态栏

原型:

HWND CreateStatusWindow(
  LONG style,      ;风格  与窗口风格通用
  LPSTR lpszText,      ;要在状态栏显示的字符串
  HWND hwndParent,   ;父窗口
  WORD wID         ;状态栏控键ID
);

参数:

LONG style : 可以是一下取值的组合:

SBSAR_SIZEGRIP(同SBS_SIZEGRIP)-------显示状态栏右下角的斜条,用户可以拖动这里来改变主窗口的大小。

CCS_TOP,CCS_BOTTOM或CCS_NOMOVEY---------代表状态栏的初始位置,分别表示位于主窗口的上方,下方(默认值),和禁止沿 Y      方向移动。

CCS_NOPARENTALIGN--------状态栏只自动设置自己的高度,不自动设置自己的宽度,也不自动移动位置。

CCS_NORESIZE----------禁止状态栏所有的自动移动和自动设置自己大小的特性,并禁止CCS_TOP,CCS_BOTTOM,CCS_NOMOVEY和CCS_NOPARENTALIGN风格。


LPSTR lpszText:指向一个初始化的时候显示在状态栏上的字符串。

HWND hwndParent:指明状态栏的父窗口。

WORD wID :状态栏控件的ID,这个ID值可以用来在WM_NOTIFY消息中判断消息是否来自与状态栏。

返回值:

状态栏的句柄


MenuHelp()

功能:

很多程序都是有菜单和状态栏,当用鼠标点开菜单移动选择各菜单项的时候,会在状态栏显示关于该菜单项的说明。这个功能MFC是封装好的,用向导生成即可,但用API编程就得自己编写代码了,查了一下MSDN,有个MenuHelp函数可以做到

原型:

void MenuHelp(

UINT uMsg,

WPARAM wParam,

LPARAM lParam,

HMENU hMainMenu,

HINSTANCE hInst,

HWND hwndStatus,

LPUINT lpwIDs

);

参数用法:

前面的参数好理解,关键是最后一个参数lpwIDs,MSDN说明是一个UINT数组指针,数组元素必须是成对的,第一个是字符串资源ID,第二个是菜单句柄,想想好象比较麻烦,得在程序加载菜单后得到每个菜单项的句柄然后填写这个数组?不厌其烦写了代码一试,却满不是那么回事,根本不起作用。于是看了看MFC的源代码,晕,居然没用这个函数,是自己处理的。再找其他SDK写的例程,发现这个数组只有4个元素,两对,第二对是0,0,标志数组结束,第一对中的字符串资源ID在资源中根本没有,第二个菜单句柄值为0x1100,代码中也没有再对这个数组进行操作了,但却能正常工作,这下更晕了。仔细研究了这个"神奇"的程序,终于知道了MenuHelp的用法:

UINT wIDs[] = {MH_BASE,0x1100,0,0}

这个MH_BASE可以随便定义一个数值,不过必须保证该值加上菜单命令ID的数值后不能超过0xFFFF。

然后在资源中加入帮助字符串,该字符串的资源ID必须是MH_BASE加上相应的菜单命令ID。而子菜单的字符串的资源ID必须是0x1100加上相应的序号,这个子菜单的帮助字符串是指:比如菜单上一般都会有"文件"这个菜单项,点开"文件"会弹出下拉菜单,显示"打开","关闭"等,鼠标移到这两个选项上在状态栏分别显示"打开文件"和"关闭",而移出这两个选项并且下拉菜单未关闭,则会显示这个子菜单的帮助字符串"文件操作"。第二个子菜单比如"编辑"的帮助字符串资源ID就是0x1100+1。

比如菜单中"打开"和"关闭"的命令ID分别为CM_OPEN和CM_CLOSE,就需要在资源中如下定义字符串:

STRINGTABLE DISCARDABLE

BEGIN

MH_BASE + CM_OPEN "打开文件"

MH_BASE + CM_CLOSE "关闭"

0x1100 + 0 "文件操作"

END

程序中响应WM_MENUSELECT消息,在其中调用MenuHelp(WM_MENUSELECT,wParam,lParam,GetMenu(hWnd),hInstance,hStatusWnd,wIDs);就可以了。

返回值:无


GetCursorPos()

功能:

该函数检取光标的位置,以屏幕坐标表示。

原型:

BOOL GetCursorPos(LPPOINT lpPoint)

参数:

IpPoint:POINT结构指针,该结构接收光标的屏幕坐标。

返回值:

如果成功,返回值非零;如果失败,返回值为零


InitCommonControls()

功能:

注册并初始化通用控件窗口类

原型:

void InitCommonControls(VOID);

参数:无

返回值:无

说明:

函数InitCommonControls是个空函数,不做任何事情。

但如果你调用了该函数,则链接器会将你的程序链接到comctl32.lib,然后在程序启动时,会加载comctl32.dll。

真正初始化的工作是在该库的入口点处做的,在这里会注册通用控件窗口类。

然后应用程序就可以创建控件窗口,就象创建其它的子窗口控件一样。

















你可能感兴趣的:(Win32,汇编,menu,对话框)