65.9K
CodeProject 正在变化。 阅读更多。
Home

Win32 窗口最小化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (61投票s)

2011年6月24日

CPOL

13分钟阅读

viewsIcon

267492

downloadIcon

2779

通过创建最少代码和复杂度的窗口,开始学习 Win32 编程。

简介  

minimalwinapiwindow/endresult.jpg

本文旨在通过创建一个代码量最少、复杂度最低的窗口来介绍 Win32 编程入门。

复杂性的问题

我认为 Visual Studio 中默认的 Win32 项目会自动创建10 个文件*191+ 行代码**,并且使用了我认为是高级 Win32 编程概念的东西,例如多个回调函数、高级 UNICODE、资源文件、预编译头文件、对话框等等。因此,这并不是学习微软 Windows 操作系统编程之美和乐趣的一个好的起点。

通过简化解决

因此,我编写了创建窗口所需的最少代码,将其归纳到一个文件和大约 50 行代码中,目的是揭示 Win32 编程背后的关键概念以及每个窗口的共性,以便用户首先了解每个窗口存在所必须具备的基础知识。

文档通过结构化的注释完成,这些注释假定用户在 Win32 编程方面几乎没有任何经验,并且是为每一行代码都故意注释的,这样,如果用户有任何疑问,他们可以查看前一行注释来了解正在发生的事情。

如果您遵循本文中提供的说明,我认为您可以学到关于 Win32 API 的有价值的东西,以及所有窗口的共性,而无需使用 Visual Studio 自动生成的 win32 应用程序模板中包含的所有额外技巧。 

一个持续存在的 Win32 窗口的最低要求***

#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{

 MSG msg          = {0};
 WNDCLASS wc      = {0}; 
 wc.lpfnWndProc   = WndProc;
 wc.hInstance     = hInstance;
 wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
 wc.lpszClassName = L"minwindowsapp";
 if( !RegisterClass(&wc) )
   return 1;

 if( !CreateWindow(wc.lpszClassName,
                   L"Minimal Windows Application",
                   WS_OVERLAPPEDWINDOW|WS_VISIBLE,
                   0,0,640,480,0,0,hInstance,NULL))
    return 2;

    while( GetMessage( &msg, NULL, 0, 0 ) > 0 )
        DispatchMessage( &msg );

    return 0;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{

 switch(message)
 {
   case WM_CLOSE:
     PostQuitMessage(0);
     break;
   default:
     return DefWindowProc(hWnd, message, wParam, lParam);
 }
 return 0;

}  

下面将解释所有这些,这里只是为一些老程序员准备的。

使用代码

以下是如何使用代码的视觉指南,只需启动您的 Visual Studio 并按照以下图像中的步骤操作即可让代码正常运行,或下载该项目。

步骤 1:创建新项目

minimalwinapiwindow/step1.jpg

步骤 2:选择项目模板、名称和位置

minimalwinapiwindow/step2.jpg

步骤 3:按“下一步”,不要按“完成”

minimalwinapiwindow/step3.jpg

步骤 4:选择应用程序类型、附加选项,然后按“完成”

minimalwinapiwindow/step4.jpg

步骤 5:右键单击“源文件”过滤器,选择“添加”,选择“新建项”

minimalwinapiwindow/step5.jpg

步骤 6:选择 C++ 文件作为模板,填写名称,然后按“添加”

minimalwinapiwindow/step6.jpg

步骤 7:打开 main.cpp,复制粘贴此代码或按 F5 即可尝试微笑 | <img src= " />

minimalwinapiwindow/step7.jpg

此程序功能的详细说明

本节描述并提供有关我们程序代码的额外信息。

#include <windows.h>

这使得访问 Microsoft Windows 特定的数据类型和函数成为可能,这些数据类型和函数使使用 Windows 应用程序进行编程变得更加轻松。(MSGWNDCLASSEX 等)

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

这是一个前向声明,指向一个窗口过程。窗口过程被极度简化,它是一种“事件处理器”。它接收事件,在 Windows 中,这些事件被称为“消息”。 Windows 中的每个窗口都有一个窗口过程。当我们将达到此声明的实现时,将更深入地介绍这一点。

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{

这是程序的“入口”点,特别是图形化的 Windows 程序。控制台程序使用更简单的main(...)。您看到的这四个参数是在应用程序启动时由 Microsoft Windows 操作系统自动接收的。第一个参数(hInstance,意为实例句柄是最重要的。它充当您启动的该特定程序实例的唯一“标识符”(在 Win32 编程中,这些通常被称为句柄)。在 Windows 上运行的每个程序都有一个,它允许您创建程序的多个窗口实例,而无需担心它已经存在,因为它们都使用自己唯一的实例“句柄”。
更多关于 WinMain 的信息:点击此处
更多关于 hInstance 的信息:点击此处

 MSG msg            = {0};

这是一个包含消息信息的数据结构,例如当用户单击窗口上的鼠标按钮时。这是正常 Win32 应用程序主循环所必需的,您稍后将在代码中看到。它在这里被初始化,以符合我最喜欢的 C89 编码标准,该标准要求所有变量都在作用域开始时声明。请记住,C89 是你的朋友,因为你可以在 C++ 中使用所有 C89。更多关于 MSG 数据结构的更多信息,请点击此处

 WNDCLASS wc      = {0}; 
 wc.lpfnWndProc   = WndProc;
 wc.hInstance     = hInstance;
 wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
 wc.lpszClassName = L"minwindowsapp";

 if( !RegisterClass(&wc) )
   return 1;

窗口类。 这是掌握 Windows 编程的一个重要概念。
在创建窗口之前,必须成功地将一个窗口类注册到 Windows 操作系统。我们将创建自己的自定义窗口类并尝试将其注册到操作系统。尝试将窗口类注册到操作系统并不总是成功的,因为操作系统可能已经没有足够的内存用于任何新的窗口类了。如果发生这种情况,它将返回零而不是一个有意义的类唯一标识符,这就是为什么调用应该始终进行错误检查。

创建窗口所需的 WNDCLASS 结构的最少成员变量如下:

lpfnWndProc

包含一个函数指针,用于将此窗口的消息发送到哪个函数进行处理和响应。

hInstance

我们从程序启动时收到的唯一实例句柄(来自操作系统的第一个入口点参数)将在此绑定到窗口类数据结构中。

hbrBackground

如果不定义此项,我们的窗口将没有背景,这意味着窗口的背景将变成创建窗口时窗口下方的任何内容,当窗口移动时,会导致各种图形错误出现在您的窗口上。

lpszClassName

这是我们即将创建的窗口类的可读名称。此名称在告诉 Windows 我们想要创建哪种“样式”的窗口以及哪个函数接收其窗口消息(在此情况下为WndProc)时起着引用作用。例如,如果我们创建了一个名为“myredwindowclass”的类,并且该类已注册到操作系统,则所有具有红色背景的窗口都可以使用该类来创建。
重要提示: 为什么在普通字符串前加上 L?点击此处。

 if( !CreateWindow(wc.lpszClassName,
                        L"Minimal Windows Application",
                        WS_OVERLAPPEDWINDOW|WS_VISIBLE,
                        0,
                        0,
                        640,
                        480,
                        0,
                        0,
                        hInstance,
                        NULL) )
        return 2;

在完成了上面的必要“铺垫”之后,我们终于准备好尝试创建窗口了。如果失败,我们的程序将以返回码 2 退出。如果函数成功,返回值将是 HWND 类型句柄,指向您的新窗口,您可以例如像这样使用它:ShowWindow(handle,SW_MINIMIZE);  这会将您的窗口最小化到任务栏微笑 | <img src= 

参数列表如下:

lpClassName

在这里,我们需要为 CreateWindow 指定一个有效的、已注册的窗口类名。一个很好的例子是通用控件,比如您在 Windows 中输入文本的编辑控件等。因为那些通常总是已经注册过了,而且Windows 中所有可见的东西都是窗口,甚至按钮!,您可以尝试将 wc.lpszClassName 替换为:L"EDIT",您将看到更改窗口类如何影响窗口。您现在可以选择和取消选择文本,并且整个窗口变成了文本控件!微笑 | <img src= " /> 点击以下超链接并向下滚动到也介绍了 EDIT 的部分,并随意尝试更多通用控件!MSDN 通用控件

lpWindowName

这很直观,您可以自己决定给窗口起什么名字。(更多详情)

x

窗口的初始 X 位置,或者有些人称之为初始水平位置。这里可以使用 CW_USEDEFAULT(更多详情)

窗口的初始 Y 位置。(更多详情)

nWidth

窗口的初始宽度。(更多详情)

nHeight

窗口的初始高度。(更多详情)

hWndParent

这里可以是此窗口父窗口或所有者窗口的“句柄”,这接近高级主题,所以我们暂时搁置它。.(更多详情)

hMenu

这是本教程中一个稍微高级的工具,暂时将其留空。了解它与您在创建 Visual Studio 的默认模板 win32 应用程序时可能看到的那些资源文件有着深刻而有用的联系,这一点对您很有好处。

hInstance

这里没什么新东西。

lpParam

此参数在高级 Windows 编程中也有使用,例如:抽象 WndProc 等功能。

注意: 通常在调用 TranslateMessage 之后会调用 DispatchMessage,但为了让您专注于要学习和掌握的精炼功能而不是一次性学习所有内容,本教程特意省略了它。TranslateMessage 仅为键盘驱动程序映射到 ASCII 字符的按键生成 WM_CHAR 消息。有关更多信息,请参阅此 MSDN 链接: TranslateMessage

    while( GetMessage( &msg, NULL, 0, 0 ) > 0 )
        DispatchMessage( &msg );

    return 0;
}

主循环,也称为:消息循环

Microsoft Windows 操作系统中的程序通过消息进行通信,在某些系统中,这些消息被称为“事件”。发生了某事,例如鼠标单击了窗口右上角的 X 图标,操作系统就会发送一条消息“嘿,那个窗口刚刚关闭了,隐藏它并开始释放它占用的内存,从已注册类名列表中删除它的类名等等。”。

在这里,我们检查是否有发送给我们的窗口的消息,并发送它们,直到我们的窗口收到一个已发布的(posted)消息。DispatchMessage 负责发送。

这里不是响应已接收消息的地方,WndProc 窗口过程才是应该这样做的地方。

当所有消息都已处理完毕,并且我们收到了一个值为 0 或低于 0 的已发布消息(例如 PostQuitMessage(0) 命令产生的那样),我们就返回 0,表示程序成功/正常终止。

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
    case WM_CLOSE:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
 }

 return 0;
}         

窗口过程 - 也称为:回调函数。
如果消息(来自 DispatchMessage)是针对您的窗口的,那么这些消息最终会到达这里。例如:如果用户单击窗口右上角的 X,您的窗口过程将收到 WM_CLOSE 消息,它实际上是一个巧妙伪装的数字,但稍后将详细介绍。

您可以在这里拦截许多种类的消息,并创建功能和响应,例如按键、鼠标单击和窗口大小调整等。所有窗口消息都以 WM_ 开头,例如 WM_CLOSEWM_CLOSEWM_KEYUP 以及 WM_RBUTTONUP 一样,是一个默认的窗口消息。您也可以拥有自己的自定义窗口消息,这些消息最常用于创建自己的默认 GUI 组件或菜单以在单击时产生结果,但这更高级,不过并不难,所以不用担心。

hWnd 是您的窗口句柄

message 是接收到的 WM_ 窗口消息,您可以选择捕获它

wParamlParam 的值会根据您接收和处理的消息而变化。请参阅我下面的 WM_MOUSEMOVE 示例,了解这方面的信息。

当我们在收到的消息(WM_KEYDOWNWM_SIZE)上没有指定任何操作时,会调用 DefWindowProc,它会将消息传递给其他处理程序。
我希望您能从这段代码中受益、学习,甚至享受其中的乐趣!

那么,现在我有一个窗口了,接下来呢?- 扩展

有很多方法可以扩展这个基本窗口的功能,
接下来我将提出一些我的建议。如果您想了解更多关于扩展此示例的信息,请在下面的讨论区告诉我。

捕获基本的键盘输入

导航到这行代码

switch(message)  
{ 

并添加以下代码

case WM_KEYUP:
{
    switch(wParam)
    {
        case VK_ESCAPE:
        {
        PostQuitMessage(0);
        }
        break;
    }
    break;
}
break;

然后重新编译并按 F5 运行,您就可以按下 Escape 键,窗口将自动关闭!

我强烈建议使用 MSDN 作为 Windows API 的来源,阅读有关我们可以利用的精彩虚拟键的信息,以及关于 WM_KEYDOWN 以补充我们已经看到的 WM_KEYUP。这里是 MSDN 的示例链接,其中更深入地描述了虚拟键以及 keyup 窗口消息:

点击此处查看 MSDN 上的虚拟键码

点击此处查看 MSDN 上的 WM_KEYUP 窗口消息

捕获基本的鼠标输入

鼠标输入的功能与键盘输入很相似,但我将向您展示一个我学到的技巧,可以比其他教程更容易地从中获取有用的鼠标移动信息。我们将捕获鼠标坐标并将结果打印到您的 Visual Studio“调试输出”窗口,并在单击窗口时显示“Hello World”消息。

只需将此代码添加到您的 WndProc 函数的开头

WCHAR mouseCoordinates[4] = {0};
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    
    switch(message)
    {
    case WM_MOUSEMOVE:
        wsprintf(mouseCoordinates, L"X:%d Y:%d\n", HIWORD(lParam), LOWORD(lParam));
        OutputDebugString(mouseCoordinates);
        break;
    case WM_LBUTTONUP:
        {
            MessageBox(0,L"Hello World!",0,0);
        }
        break;
这次修改我留给您自行探索。微软 Windows 操作系统中蕴藏着大量有用的功能,我希望我能成功地激起您深入挖掘的兴趣,并且没有让您感到沮丧 微笑 | <img src=

关注点

这是我为 Code Project 写的第一个文章,我从中学习了很多关于如何写文章的知识。这些东西很有趣!...而且希望我也创作了一些有用的东西!微笑 | <img src=

写这篇文章也增加了我对 Microsoft Windows API 的了解,因为我不得不深入研究 16 位 Windows 时代,以便更准确地理解 HINSTANCEHMODULE 的历史和用法,以便更好地描述和理解它微笑 | <img src= http://blogs.msdn.com/b/oldnewthing/archive/2004/06/14/155107.aspx

事实证明,学习如何向他人描述技术问题也对我自己来说是一次很好的学习经历,并且物有所值微笑 | <img src=  

进一步阅读材料

MSDN:创建 Win32 应用程序
theForger 的 Win32 API 教程
FunctionX Win32 编程课程

参考文献

* 6 个 .h/.cpp 文件,2 个 .ico 文件,1 个 .rc 文件,1 个 .txt 文件。
** (仅在 main.cpp 中)
*** 前向声明和 WM_CLOSE 不是必需的,但我认为每个程序都应该有一个开始和结束(WM_CLOSE)。前向声明使程序看起来对初学者更友好,有一个容易识别的 main() 函数。

历史

  • 版本 0.95 + 更新了 Visual Studio 和 Mingw 项目文件以匹配文章中的更新代码。 
  • 版本 0.94 + 对行数进行了微调,以减少对读者和用户的视觉干扰。 
  • 版本 0.93 + 修复了 David Nash 先生(Win32++ 项目)指出的一个 FAILED 宏问题。 
  • 版本 0.92 + 添加了 Code::Blocks GnuGCC 项目,供没有 Visual Studio 的用户使用。 
  • 版本 0.91 + 根据 Alain Rist 的反馈,添加了关于 TranslateMessage 的注释。 
  • 版本 0.90 + 向文章添加了进一步阅读材料的链接。
  • 版本 0.89 + 根据 Richard MacCutchan 的反馈重新安排了代码和描述。 
  • 版本 0.88 + 修复了一些拼写错误,整理了一些措辞,重新表述了 wndproc 的内容。
  • 版本 0.87 + 完成了首次重排草稿。调整了图像大小。上传了新项目。
  • 版本 0.86 + 删除了代码中的所有注释。将尝试根据反馈进行重排。
  • 版本 0.85 + 修复了拼写错误,精简了介绍性文字等。
  • 版本 0.84 + 将最基本代码片段移到上面,方便高级用户快速获取。
  • 版本 0.83 + 精简了问题和解决方案的描述,删除了关于我的内容。
  • 版本 0.82 + 也缩进了最终结果和介绍性句子。
  • 版本 0.81 + 添加了最终结果图片。添加了 Visual Studio 项目版本号。
  • 版本 0.80 + 添加了参考文献,重写了部分介绍,以及其他一些小修复。
  • 版本 0.70 + 为每个步骤添加了更好的描述。更早地添加了代码链接。
  • 版本 0.60 + 删除了第二句话,听起来像是在吹嘘什么的。
  • 版本 0.50 + 添加了鼠标输入和移动扩展说明。
  • 版本 0.40 + 添加了键盘输入扩展说明。
  • 版本 0.30 + 修复了代码格式,添加了未注释版本,对所有内容进行了微调。
  • 版本 0.20 + 改进了文章代码格式,格式仍在进行中,暂时请下载项目。
  • 版本 0.10 + 上传到 Code Project。哦,顺便说一句,这是我的第一篇文章微笑 | <img src= Hello World!微笑 | <img src=
© . All rights reserved.