Windows API Dialogs without using resource files

2019-02-01 11:20发布

I'm trying to create a dialog box using C++ and the windows API, but I don't want the dialog defined in a resource file. I can't find anything good on this on the web, none of the examples I've read seem to define the dialog programmatically.

Anyone know how to do this? A simple example is fine, I'm not doing anything complicated with it yet.

6条回答
做个烂人
2楼-- · 2019-02-01 11:43

If all you want to do is show a window with controls, it's possible to create a window without using resource (.rc) files / scripts.

This isn't the same as a dialog, but it might be easier than creating a dialog programmatically.

First, a few notes about how this is done:

  • Instead of designing the dialog in the rc file, you could manually use CreateWindow (or CreateWindowEx) to create child windows of a main window. (for .NET Winforms programmers, these windows are like Controls).

  • This process will not be graphical at all (you will need to manually type in the location and size of each window), but I think this can be a great way to understand how dialogs are created under the hood.

  • There are some disadvantages to not using a real dialog, namely that tab will not work when switching between controls.


About the example:

  • This example features a dialog box with two buttons, an edit box (.NET winforms programmers would think of it as a TextBox), and a check box.

Screenshot of window

It has been tested under the following conditions:

  • x86 build
  • x64 build
  • Unicode build (UNICODE and _UNICODE defined)
  • Non-unicode build (UNICODE and _UNICODE not defined)
  • Built with Visual Studio's C compiler
  • Built with Visual Studio's C++ compiler
  • OS: Windows 10 64 bit

Now for the code:

Note that a large amount of comments have been added to try to document the windows functions, I recommend copy/pasting this into a text editor, for best results.

// 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 UNOCODE.
#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 necesary 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 };
    mainWindowClass.lpszClassName = TEXT("JRH.MainWindow"); // you can set the main window name to anything, but typically you should prefix custom window classes with something that makes it unique.
    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/

Further reading

The builtin set of window classes are rather limited, so you might be curious as to how you can define your own window classes ("Controls") using winapi, see the articles below:

NOTE: I originally intended this post to cover the creation of dialogs programmatically. Due to a mistake on my part I didn't realize that you can't just "show" a window as a dialog. I'm working on an edit for this post to make it more relevant for dialogs. See the comments on this post.

查看更多
虎瘦雄心在
3楼-- · 2019-02-01 11:52

I think Raymond Chen has a pretty good example here: http://blogs.msdn.com/oldnewthing/archive/2005/04/29/412577.aspx

查看更多
爷的心禁止访问
4楼-- · 2019-02-01 11:54

Salam, Hi, here you can find how to use Windows API Dialogs without using resource files. The Winapi (C Win32 API, No MFC) tutorial: http://zetcode.com/gui/winapi/

查看更多
时光不老,我们不散
5楼-- · 2019-02-01 11:56

Try to search MSDN for "dialog templates in memory"

See this for example: http://msdn.microsoft.com/en-us/library/ms632588(VS.85).aspx

查看更多
女痞
7楼-- · 2019-02-01 12:07

Take a look at this toolkit that describes how to create dialogs without resource files. It's in WTL, however I'm sure you can pick apart the internals to achieve the same thing using win32 API directly.

查看更多
登录 后发表回答