【问题标题】:Windows API dialogs without using resource files不使用资源文件的 Windows API 对话框
【发布时间】:2010-09-08 20:45:41
【问题描述】:

我正在尝试使用 C++ 和 Windows API 创建一个对话框,但我不希望在资源文件中定义该对话框。我在网上找不到任何好的东西,而且我读过的例子似乎都没有以编程方式定义对话框。

我该怎么做?

一个简单的例子就可以了。我还没有做任何复杂的事情。

【问题讨论】:

  • 你为什么反对 rc 文件?
  • 我希望能够在运行时修改它。
  • 我认为的另一个原因,任何人都可以打开您的可执行文件并探索您的资源,并且可以编辑它,更改属性!我希望它被隐藏
  • @BrianR.Bondy 好吧,既然你问了,老实说,我只是想看看是否有一种方法可以在 winapi 中只使用 C 语言来制作 GUI。为了好玩,我正在研究如何在 winapi 之上构建 .NET 来制作 Windows 窗体,我只是决定尽可能从低级别和手动开始;从概念上了解窗口的真正含义。

标签: c++ winapi dialog


【解决方案1】:
【解决方案2】:

如果您只想显示一个带有控件的窗口,则可以在不使用资源 (.rc) 文件/脚本的情况下创建一个窗口。

这与对话框不同,但它可能比以编程方式创建对话框更容易。

首先,关于这是如何完成的几点说明:

  • 您可以手动使用CreateWindow(或CreateWindowEx)来创建主窗口的子窗口,而不是在rc 文件中设计对话框。 (对于.NET Windows Forms 程序员,这些窗口就像Controls)。

  • 这个过程根本不是图形化的(您需要手动输入每个窗口的位置和大小),但我认为这是了解如何在后台创建对话框的好方法。

  • 不使用真正的对话框有一些缺点,即在控件之间切换时选项卡将不起作用。


关于示例:

  • 此示例具有一个带有两个按钮的对话框、一个编辑框(.NET Windows 窗体程序员会将其视为TextBox)和一个复选框。

已经在以下条件下进行了测试:

  • x86 构建
  • x64 构建
  • Unicode 构建(UNICODE_UNICODE 已定义)
  • 非 Unicode 版本(UNICODE_UNICODE 未定义)
  • 使用 Visual Studio 的 C 编译器构建
  • 使用 Visual Studio 的 C++ 编译器构建
  • 操作系统:Windows 10 64 位

注意:UNICODE

  • 截至撰写本文时,UTF-8 在 Windows 10 中仍处于测试阶段
    • 如果您没有启用此设置,您应该假设任何char* 都是 ACP,而不是 UTF-8,这也适用于标准库函数
    • 即使在 Linux 中,相同的标准库函数也是 UTF-8。
    • 遗憾的是,某些 C++ 标准库功能仅适用于 char*(例如,异常消息)。
    • 您仍然可以在没有设置选项的情况下在 Windows 中使用 UTF-8,您只需在调用 winapi 函数之前将其编码回 UTF-16。
    • 这是一个 reddit thread 的回复,来自声称曾在 Windows 上使用 UTF-8 的人的回复,其中包含一些很好的信息。
  • UNICODE 在 Windows 中的意思是“UTF-16”,而不是“UTF-8”。
  • 对于不是very old 的任何版本的Windows,强烈建议使用某种Unicode。
    • 请注意,如果您不使用 Unicode,您的程序可能完全无法打开包含 Unicode 字符的文件名、处理具有非 ACP 字符的目录(例如用户名)等。
    • 使用 ACP 函数(SendMessageA 等)而不以某种方式验证 UTF-8 已启用(默认情况下禁用)可能是一个错误。
    • 为了获得最大的可移植性/灵活性,我建议使用 UTF-16 和所有 API 函数的 W 版本,translating from UTF-8 to UTF-16 at the last minute。请仔细阅读此页面。

现在是代码:

请注意,已添加大量 cmets 以尝试记录 windows 功能,我建议将其复制/粘贴到文本编辑器中,以获得最佳效果。

// This sample will work either with or without UNICODE, it looks like
// it's recommended now to use UNICODE for all new code, but I left
// the ANSI option in there just to get the absolute maximum amount
// of compatibility.
//
// Note that UNICODE and _UNICODE go together, unfortunately part
// of the Windows API uses _UNICODE, and part of it uses UNICODE.
//
// tchar.h, for example, makes heavy use of _UNICODE, and windows.h
// makes heavy use of UNICODE.
#define UNICODE
#define _UNICODE
//#undef UNICODE
//#undef _UNICODE

#include <windows.h>
#include <tchar.h>

// I made this struct to more conveniently store the
// positions / size of each window in the dialog
typedef struct SizeAndPos_s
{
    int x, y, width, height;
} SizeAndPos_t;

// Typically these would be #defines, but there
// is no reason to not make them constants
const WORD ID_btnHELLO = 1;
const WORD ID_btnQUIT = 2;
const WORD ID_CheckBox = 3;
const WORD ID_txtEdit = 4;
const WORD ID_btnShow = 5;

//                                    x,      y,      width,  height
const SizeAndPos_t mainWindow   =   { 150,    150,    300,    300 };

const SizeAndPos_t btnHello     =   { 20,     50,     80,     25 };
const SizeAndPos_t btnQuit      =   { 120,    50,     80,     25 };
const SizeAndPos_t chkCheck     =   { 20,     90,     185,    35 };

const SizeAndPos_t txtEdit      =   { 20,     150,    150,    20 };
const SizeAndPos_t btnShow      =   { 180,    150,    80,     25 };

HWND txtEditHandle = NULL;

// hwnd:    All window processes are passed the handle of the window
//         that they belong to in hwnd.
// msg:     Current message (e.g., WM_*) from the OS.
// wParam:  First message parameter, note that these are more or less
//          integers, but they are really just "data chunks" that
//          you are expected to memcpy as raw data to float, etc.
// lParam:  Second message parameter, same deal as above.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

    switch (msg)
    {

    case WM_CREATE:
        // Create the buttons
        //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

        // Note that the "parent window" is the dialog itself. Since we are
        // in the dialog's WndProc, the dialog's handle is passed into hwnd.
        //
        //CreateWindow( lpClassName,        lpWindowName,       dwStyle,                                x,          y,          nWidth,         nHeight,            hWndParent,     hMenu,              hInstance,      lpParam
        //CreateWindow( windowClassName,    initial text,       style (flags),                          xPos,       yPos,       width,          height,             parentHandle,   menuHandle,         instanceHandle, param);
        CreateWindow(   TEXT("Button"),     TEXT("Hello"),      WS_VISIBLE | WS_CHILD,                  btnHello.x, btnHello.y, btnHello.width, btnHello.height,    hwnd,           (HMENU)ID_btnHELLO, NULL,           NULL);

        CreateWindow(   TEXT("Button"),     TEXT("Quit"),       WS_VISIBLE | WS_CHILD,                  btnQuit.x,  btnQuit.y,  btnQuit.width,  btnQuit.height,     hwnd,           (HMENU)ID_btnQUIT,  NULL,           NULL);

        // Create a checkbox
        //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        CreateWindow(  TEXT("button"),      TEXT("CheckBox"),   WS_VISIBLE | WS_CHILD | BS_CHECKBOX,    chkCheck.x, chkCheck.y, chkCheck.width, chkCheck.height,    hwnd,           (HMENU)ID_CheckBox, NULL,           NULL);

        // Create an edit box (single line text editing), and a button to show the text
        //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        //Handle        = CreateWindow(windowClassName,    windowName,           style,                              xPos,       yPos,       width,          height,             parentHandle,   menuHandle,         instanceHandle, param);
        txtEditHandle   = CreateWindow(TEXT("Edit"),       TEXT("Initial Text"), WS_CHILD | WS_VISIBLE | WS_BORDER,  txtEdit.x,  txtEdit.y,  txtEdit.width,  txtEdit.height,     hwnd,           (HMENU)ID_txtEdit,  NULL,           NULL);

        //CreateWindow( windowClassName,    windowName,         style,                                  xPos,      yPos,      width,          height,           parentHandle,   menuHandle,         instanceHandle, param);
        CreateWindow(   TEXT("Button"),     TEXT("Show"),       WS_VISIBLE | WS_CHILD,                  btnShow.x, btnShow.y, btnShow.width, btnShow.height,    hwnd,           (HMENU)ID_btnShow,  NULL,           NULL);

        // Create an Updown control. Note that this control will allow you to type in non-number characters, but it will not affect the state of the control

        break;

    // For more information about WM_COMMAND, see
    // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647591(v=vs.85).aspx
    case WM_COMMAND:

        // The LOWORD of wParam identifies which control sent
        // the WM_COMMAND message. The WM_COMMAND message is
        // sent when the button has been clicked.
        if (LOWORD(wParam) == ID_btnHELLO)
        {
            MessageBox(hwnd, TEXT("Hello!"), TEXT("Hello"), MB_OK);
        }
        else if (LOWORD(wParam) == ID_btnQUIT)
        {
            PostQuitMessage(0);
        }
        else if (LOWORD(wParam) == ID_CheckBox)
        {
            UINT checked = IsDlgButtonChecked(hwnd, ID_CheckBox);

            if (checked)
            {
                CheckDlgButton(hwnd, ID_CheckBox, BST_UNCHECKED);
                MessageBox(hwnd, TEXT("The checkbox has been unchecked."), TEXT("CheckBox Event"), MB_OK);
            }
            else
            {
                CheckDlgButton(hwnd, ID_CheckBox, BST_CHECKED);
                MessageBox(hwnd, TEXT("The checkbox has been checked."), TEXT("CheckBox Event"), MB_OK);
            }
        }
        else if (LOWORD(wParam) == ID_btnShow)
        {
               int textLength_WithNUL = GetWindowTextLength(txtEditHandle) + 1;
               // WARNING: If you are compiling this for C, please remember to remove the (TCHAR*) cast.
               TCHAR* textBoxText = (TCHAR*) malloc(sizeof(TCHAR) * textLength_WithNUL);

               GetWindowText(txtEditHandle, textBoxText, textLength_WithNUL);

               MessageBox(hwnd, textBoxText, TEXT("Here's what you typed"), MB_OK);

               free(textBoxText);
        }
        break;

    case WM_DESTROY:

        PostQuitMessage(0);
        break;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}


// hInstance: This handle refers to the running executable
// hPrevInstance: Not used. See https://blogs.msdn.microsoft.com/oldnewthing/20040615-00/?p=38873
// lpCmdLine: Command line arguments.
// nCmdShow: a flag that says whether the main application window
//           will be minimized, maximized, or shown normally.
//
// Note that it's necessary to use _tWinMain to make it
// so that command line arguments will work, both
// with and without UNICODE / _UNICODE defined.
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    MSG  msg;
    WNDCLASS mainWindowClass = { 0 };

    // You can set the main window name to anything, but
    // typically you should prefix custom window classes
    // with something that makes it unique.
    mainWindowClass.lpszClassName = TEXT("JRH.MainWindow");

    mainWindowClass.hInstance = hInstance;
    mainWindowClass.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    mainWindowClass.lpfnWndProc = WndProc;
    mainWindowClass.hCursor = LoadCursor(0, IDC_ARROW);

    RegisterClass(&mainWindowClass);

    // Notes:
    // - The classname identifies the TYPE of the window. Not a C type.
    //   This is a (TCHAR*) ID that Windows uses internally.
    // - The window name is really just the window text, this is
    //   commonly used for captions, including the title
    //   bar of the window itself.
    // - parentHandle is considered the "owner" of this
    //   window. MessageBoxes can use HWND_MESSAGE to
    //   free them of any window.
    // - menuHandle: hMenu specifies the child-window identifier,
    //               an integer value used by a dialog box
    //               control to notify its parent about events.
    //               The application determines the child-window
    //               identifier; it must be unique for all
    //               child windows with the same parent window.

    //CreateWindow( windowClassName,                windowName,             style,                            xPos,         yPos,       width,              height,            parentHandle,   menuHandle,  instanceHandle, param);
    CreateWindow(   mainWindowClass.lpszClassName,  TEXT("Main Window"),    WS_OVERLAPPEDWINDOW | WS_VISIBLE, mainWindow.x, mainWindow.y, mainWindow.width, mainWindow.height, NULL,           0,           hInstance,      NULL);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

// This code is based roughly on tutorial code present at  http://zetcode.com/gui/winapi/

进一步阅读

内置的窗口类集相当有限,因此您可能对如何使用 Windows API 定义自己的窗口类(“Controls”)感到好奇,请参阅以下文章:

注意:我最初打算在这篇文章中介绍以编程方式创建对话框。由于我的错误,我没有意识到您不能只是将窗口“显示”为对话框。不幸的是,我无法让 Raymond Chen 提到的设置正常工作。即使看WINE的来源,也不是很清楚。

【讨论】:

  • 语法挑剔:我不知道说“在winapi中”还是“在winapi中”是否合适。也可能不言而喻,但要小心使用 winapi 编程,无论你做什么,都不要混合使用 unicode 和非 unicode 版本的函数。您可能只想使用所有 W 形式的函数而忘记(老式)ANSI 兼容性。我只是一个复古书呆子,不能忽略 ANSI 兼容性。此外,winapi 函数往往不能很好地容忍不正确的句柄类型被传递给它们,例如,不要将 hInstance 传递给期望 HWND 的东西。
  • Due to a misunderstanding on my part,这个答案创建了一个window,而不是一个dialog;仅仅“显示”一个窗口并不像我想象的那么容易。我正在编辑以解决此问题。
  • 更新:可以将对话框显示为窗口(例如,Charles Petzold 在 Programming Windows 中的 HEXCALC 示例,第 5 版第 11 章),但这与再次显示对话框不同,不幸的是,它有一些严重的局限性。
【解决方案3】:

看看this toolkit,它描述了如何在没有资源文件的情况下创建对话框。

它在 WTL 中。但是,我相信您可以直接使用 Win32 API 分离内部结构以实现相同的目标。

【讨论】:

    【解决方案4】:

    您可以在此处了解如何在不使用资源文件的情况下使用 Windows API 对话框。

    Windows API(只有C Win32 API,没有MFC)教程:

    Windows API tutorial

    【讨论】:

    • 到目前为止,这似乎是唯一一个实际上提供使用 CreateWindow 而不是 rc 文件的代码的答案。
    • ... 但是,正如我在回答中发现的那样,使用 CreateWindow 创建的对话框与使用 VS 对话框编辑器创建的对话框的行为并不完全相同。该死。
    【解决方案5】:

    尝试在MSDN 中搜索“内存中的对话模板”。

    例如:Dialog Boxes

    【讨论】:

    • 注意:在链接页面上没有直接的例子来说明如何做到这一点,它只是一个你可以用来制作它的函数列表。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多