Win32 汇编学习 (8):菜单

本贴最后更新于 2595 天前,其中的信息可能已经时过境迁

这次我们将在我们的应用程序中加入一个菜单。

理论:

菜单可以说是 WINDOWS 最重要的元素之一。有了它,用户可以方便地选择操作命令.用户只要细读一下所有的菜单项就可以明了应用程序所提供的大概功能,而且可以立即操作,无须去阅读手册了.正因为菜单给了用户一种方便的方式,所以您在应用程序中加入菜单时就要遵守一般的标准.譬如:一般头两个菜单项是"File"和"Edit",最后是"Help",您可以在这中间插入您要定义的菜单项.如果所运行的菜单命令会弹出一个对话框,那么就要在该菜单项后加入省略符(...).菜单是一种资源,除菜单外还有其它像对话框,字符串,图标,位图资源等.在链接时链接程序将把资源加入到可执行程序中去,最后我们的执行程序中就既包括机器指令又包括了资源. 您可以在任何文本编辑器中编写脚本文件,在文件中您可以指定资源呈现出来的外观和其它的一些属性.当然更直观的方法是用资源编辑器,通常资源编辑器都打包在编译环境中,像 Visual C++ 带了资源编辑器. 我们可以按以下方式来定义一个菜单资源:

MyMenu MENU { [menu list here] }

这和 C 语言中的结构体的定义非常相似。 MyMenu 类似于被定义的变量,而 MENU 则类似于关键字。当然您可以用另外一种办法,那就是用 BEGIN 和 END 来代替花括号,这和 PASCAL 语言中的风格相同。
在菜单项的列表中是一大串的 MENUITEMPOPUP 语句。MENUITEM 定义了一个菜单项,当选择后不会激活对话框。它的语法如下:

MENUITEM "&text", ID [,options]

它由关键字 MENUITEM 开头,紧跟在 MENUITEM 后的是指菜单项的名称字符串,符号“&“后的第一个字符将会带下划线,它也是该菜单项的快捷键。ID 的作用当该菜单被选中时,WINDOWS 的消息处理过程用来区分菜单项用的。毫无疑问,ID 号必须唯一。
options 有以下可供选择的属性:

  • GRAYED 代表该菜单项处于非激活状态,即当其被选中时不会产生 WM_COMMAND 消息。该菜单以灰色显示。
  • INACTIVE 代表该菜单项处于非激活状态,即当其被选中时不会产生 WM_COMMAND 消息。该菜单以正常颜色显示。
  • MENUBREAK 该菜单项和随后的菜单项会显示在新列中。
  • HELP 该菜单项和随后的菜单项右对齐。

POPUP 的语法如下:

POPUP "&text" [,options] { [menu list] }

POPUP 定义了一个菜单项当该菜单项被选中时又会弹出一个子菜单。
另外有一种特别类型的 MENUITEM 语句 MENUITEM SEPARATOR,它表示在菜单项位置画一条分隔线。定义完菜单后,您就可以在程序中使用脚本中定义的菜单资源了。您可以在程序的两个地方(或叫做用两种方式)使用它们:

  • WNDCLASSEX 结构体的成员 lpszMenuName 中。譬如,您有一个菜单“FirstMenu“,您可以按如下方法把它联系到您的窗口:
.DATA MenuName db "FirstMenu",0 ........................... ........................... .CODE ........................... mov wc.lpszMenuName, OFFSET MenuName ...........................
  • CreateWindowEx 函数中指明菜单的句柄:
.DATA MenuName db "FirstMenu",0 hMenu HMENU ? ........................... ........................... .CODE ........................... invoke LoadMenu, hInst, OFFSET MenuName mov hMenu, eax invoke CreateWindowEx,NULL,OFFSET ClsName,\ OFFSET Caption, WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,\ NULL,\ hMenu,\ hInst,\ NULL\ ...........................

您也许会问,这两着之间有什么不同呢?**当您用第一种方法时,由于是在窗口类中指定,故所有由该窗口类派生的窗口都将有相同的菜单。如果您想要从相同的类中派生的窗口有不同的菜单那就要使用第二中方法,该方法中通过函数 CreateWindowEx 指定的菜单会“覆盖” WNDCLASSEX 结构体中指定的菜单。**接下来我们看看当用户选择了一个菜单项时它是如何通知 WINDOWS 窗口过程的:当用户选择了一个菜单项时,WINDOWS 窗口过程会接收到一个 WM_COMMAND 消息,传进来的参数 wParam 的低字节包含了菜单项的 ID。好了,上面就是关于菜单项的一切,下面我们就来实践。

例子:

第一个例子显示了指定一个菜单项的第一种方法:

.386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include windows.inc include user32.inc include kernel32.inc includelib user32.lib includelib kernel32.lib .data ClassName db "SimpleWinClass",0 AppName db "Our Sixth Window",0 MenuName db "FirstMenu",0 Test_string db "你选择了测试菜单项",0 Hello_string db "你好",0 Goodbye_string db "再见",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? .const IDM_TEST equ 1 IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_TEXT equ 4 .code start: invoke GetModuleHandle,NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT invoke ExitProcess,eax 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_WINDOW+1 mov wc.lpszMenuName,offset MenuName 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,NULL,addr ClassName,addr AppName,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInst,NULL mov hwnd,eax invoke ShowWindow,hwnd,SW_SHOWNORMAL invoke UpdateWindow,hwnd .while TRUE invoke GetMessage,addr msg,NULL,0,0 .break .if (!eax) 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_DESTROY invoke PostQuitMessage,NULL .elseif uMsg==WM_COMMAND mov eax,wParam .if ax==IDM_TEST invoke MessageBox,NULL,addr Test_string,offset AppName,MB_OK .elseif ax==IDM_HELLO invoke MessageBox,NULL,addr Hello_string,offset AppName,MB_OK .elseif ax==IDM_GOODBYE invoke MessageBox,NULL,addr Goodbye_string,offset AppName,MB_OK .else invoke DestroyWindow,hWnd .endif .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start

Menu.rc

#define IDM_TEST 1 #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4 FirstMenu MENU { POPUP "&PopUp" { MENUITEM "&Say Hello",IDM_HELLO MENUITEM "Say &GoodBye", IDM_GOODBYE MENUITEM SEPARATOR MENUITEM "E&xit",IDM_EXIT } MENUITEM "&Test", IDM_TEST }

分析:
我们先来分析资源文件:

#define IDM_TEST 1 /* equal to IDM_TEST equ 1*/ #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4

上面的几行定义了菜单项的 ID 号。只要注意菜单项 ID 号必须唯一外,您可以给 ID 号任何值。

FirstMenu MENU

用关键字 MENU 定义菜单。

POPUP "&PopUp" { MENUITEM "&Say Hello",IDM_HELLO MENUITEM "Say &GoodBye", IDM_GOODBYE MENUITEM SEPARATOR MENUITEM "E&xit",IDM_EXIT }

定义一个有四个菜单项的子菜单,其中第三个菜单项是一个分隔线。

MENUITEM "&Test", IDM_TEST

定义主菜单中的一项。下面我们来看看源代码。

MenuName db "FirstMenu",0 Test_string db "你选择了测试菜单项",0 Hello_string db "你好",0 Goodbye_string db "再见",0

MenuName 是资源文件中指定的菜单的名字。因为您可以在脚本文件中定义任意多个菜单,所以在使用前必须指定您要使用那一个,接下来的行是在选中菜单项时显示在相关对话框中的字符串。

IDM_TEST equ 1 IDM_HELLO equ 2 IDM_GOODBYE equ 3 IDM_EXIT equ 4

定义用在 WINDOWS 窗口过程中的菜单项 ID 号。这些值必须和脚本文件中的相同。

.ELSEIF uMsg==WM_COMMAND mov eax,wParam .IF ax==IDM_TEST invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK .ELSEIF ax==IDM_HELLO invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK .ELSEIF ax==IDM_GOODBYE invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK .ELSE invoke DestroyWindow,hWnd .ENDIF

在本窗口过程中我们处理 WM_COMMAND 消息。当用户选择了一个菜单项时,该菜单项的 ID 放入参数 wParam 中被同时送到 WINDOWS 的窗口过程,我们把它保存到 eax 寄存器中以便和预定义的菜单项 ID 比较用。前三种情况下,当我们选中 Test、Say Hello、Say GoodBye 菜单项时,会弹出一个对话框其中显示一个相关的字符串,选择 Exit 菜单项时,我们就调用函数 DestroyWindow,其中的参数是我们窗口的句柄,这样就销毁了窗口。就像您所看到的,通过在一个窗口类中指定菜单名的方法来给一个应用程序生成一个菜单是简单而直观的。除此方法外您还可以用另一种方法,其中资源文件是一样的,源文件中也只有少数的改动,这些改动如下:

.data? hInstance HINSTANCE ? CommandLine LPSTR ? hMenu HMENU ? ; handle of our menu

定义了一个变量来保存我们的菜单的句柄,然后:

invoke LoadMenu, hInst, OFFSET MenuName mov hMenu,eax INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\ hInst,NULL

调用 LoadMenu 函数,该函数需要实例句柄和菜单名的字符串,调用的结果返回指向菜单的句柄,然后传给函数 CreateWindowEx 刚返回的菜单句柄就可以了。

  • asm
    15 引用 • 5 回帖
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    226 引用 • 476 回帖
  • 阅读
    86 引用 • 244 回帖 • 4 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
Akkuman
python与二次元爱好者,开车司机 荆州

推荐标签 标签

  • 叶归
    5 引用 • 16 回帖 • 9 关注
  • 分享

    有什么新发现就分享给大家吧!

    247 引用 • 1794 回帖
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖 • 2 关注
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖
  • 创业

    你比 99% 的人都优秀么?

    82 引用 • 1395 回帖 • 2 关注
  • SQLite

    SQLite 是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是全世界使用最为广泛的数据库引擎。

    5 引用 • 7 回帖
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    41 引用 • 130 回帖 • 253 关注
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 590 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    76 引用 • 389 回帖
  • Maven

    Maven 是基于项目对象模型(POM)、通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具。

    186 引用 • 318 回帖 • 260 关注
  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    1440 引用 • 10067 回帖 • 489 关注
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    28 引用 • 197 回帖 • 28 关注
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    12 引用 • 54 回帖 • 168 关注
  • 一些有用的避坑指南。

    69 引用 • 93 回帖
  • gRpc
    11 引用 • 9 回帖 • 89 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 542 关注
  • uTools

    uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。

    7 引用 • 27 回帖 • 1 关注
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 17 关注
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 116 关注
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖 • 1 关注
  • 人工智能

    人工智能(Artificial Intelligence)是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门技术科学。

    157 引用 • 289 回帖
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    169 引用 • 1527 回帖 • 1 关注
  • Excel
    31 引用 • 28 回帖
  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 253 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    127 引用 • 1708 回帖
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 297 关注
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    131 引用 • 869 回帖