【问题标题】:Add Controls To Specific Tab Page in TabControl in C++ Win32在 C++ Win32 中的 TabControl 中将控件添加到特定选项卡页
【发布时间】:2016-03-13 20:49:55
【问题描述】:

我想在tabcontrol中的标签页中添加一些控件,但是好像会添加到所有页面中,并且默认情况下tabcontrol中没有标签页。

我已阅读下面的这些链接,但它们对我没有帮助,并且在其中的某些部分让我感到困惑。

How to add controls to a Tab control

http://www.cplusplus.com/forum/windows/37161/

https://msdn.microsoft.com/en-us/library/bb760551.aspx

https://msdn.microsoft.com/en-us/library/hh298366.aspx

https://msdn.microsoft.com/en-us/library/ms645398.aspx

这是我的代码:

[代码]:

#define ID_LBL 500              
#define ID_BTN 501              
#define ID_TBC 502              

HWND hWnd;

void InserTabItem(HWND handle, LPWSTR text, int id)
{
TCITEM tci = { 0 };
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = wcslen(text);
SendMessage(handle, TCM_INSERTITEM, id, LPARAM(&tci));
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_CREATE:
{
    HWND button_handle = 0;
    HWND label_handle = 0;
    HWND tab_handle = 0;
    tab_handle = CreateWindowEx(WS_EX_CONTROLPARENT, WC_TABCONTROL, 0, WS_VISIBLE | WS_CHILD, 10, 10, 200, 150, hWnd, HMENU(ID_TBC), 0, 0);
    InserTabItem(tab_handle, L"page1", 0);
    InserTabItem(tab_handle, L"page2", 1);
    button_handle = CreateWindowEx(0, WC_BUTTON, L"test-button-page2", WS_VISIBLE | WS_CHILD, 10, 50, 150, 30, tab_handle, HMENU(ID_BTN), 0, 0);
    label_handle = CreateWindowEx(0, WC_STATIC, L"test-label-page1", WS_VISIBLE | WS_CHILD, 10, 100, 150, 30, tab_handle, HMENU(ID_LBL), 0, 0);
}
break;
case WM_CLOSE:
    DestroyWindow(hWnd);
    break;
case WM_DESTROY:
    PostQuitMessage(0);
    break;
default:
    return DefWindowProc(hWnd, Msg, wParam, lParam);
    break;
}

return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreviewInstance, LPSTR lpcmdline, int ncmdshow)
{
WNDCLASSEX wndexcls;
wndexcls.lpszClassName = L"win";
wndexcls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndexcls.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wndexcls.hCursor = LoadCursor(NULL, IDC_ARROW);
wndexcls.hbrBackground = (HBRUSH)(COLOR_3DSHADOW + 1);
wndexcls.lpszMenuName = NULL;
wndexcls.style = NULL;
wndexcls.hInstance = hInstance;
wndexcls.cbSize = sizeof(WNDCLASSEX);
wndexcls.cbClsExtra = 0;
wndexcls.cbWndExtra = 0;
wndexcls.lpfnWndProc = WndProc;
RegisterClassEx(&wndexcls);

hWnd = CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_CONTROLPARENT, L"win", L"TestApp", WS_OVERLAPPEDWINDOW, 100, 100, 640, 380, 0, 0, hInstance, 0);
ShowWindow(hWnd, ncmdshow);
UpdateWindow(hWnd);



MSG wnd_msg;
while (GetMessage(&wnd_msg, NULL, 0, 0)>0)
{
    TranslateMessage(&wnd_msg);
    DispatchMessage(&wnd_msg);
}
return (int)wnd_msg.wParam;

}

我正在寻找一种安全且适当的实施方式。

感谢您的帮助

================================================ =========

[更新]:

感谢cmets,但没有详细回答:(

虽然这不是我正在寻找的实现(NotDialogBased),但是从我提到的第四个链接:

如何创建选项卡式对话框:

https://msdn.microsoft.com/en-us/library/hh298366.aspx

这是我该页面的代码:

[resource.h]:

#define IDD_Page1                       101
#define IDD_Page2                       102
#define IDD_Page3                       103
#define IDD_Main_Dialog                 104
#define IDC_BTN_Page1                   1001
#define IDC_BTN2_Page1                  1002
#define IDC_BTN_Page2                   1013
#define IDC_BTN_Page3                   1014

[资源.rc]:

#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

 //////////////////////////////////////////////////
 //
 // Dialog
  //

 IDD_Page1 DIALOGEX 0, 0, 313, 178
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
 PUSHBUTTON      "Button2-Page1",IDC_BTN2_Page1,129,107,67,22
 PUSHBUTTON      "Button-Page1",IDC_BTN_Page1,127,77,67,22
 END

 IDD_Page2 DIALOGEX 0, 0, 309, 177
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
 PUSHBUTTON      "Button-Page2",IDC_BTN_Page2,120,77,60,18
 END

 IDD_Page3 DIALOGEX 0, 0, 309, 177
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
 PUSHBUTTON      "Button-Page3",IDC_BTN_Page3,120,73,64,25
 END

 IDD_Main_Dialog DIALOGEX 0, 0, 309, 177
 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
 END


////////////////////////////////////////////
//
// DESIGNINFO
 //

 #ifdef APSTUDIO_INVOKED
 GUIDELINES DESIGNINFO
 BEGIN
 IDD_Page1, DIALOG
 BEGIN
    LEFTMARGIN, 7
    RIGHTMARGIN, 306
    TOPMARGIN, 7
    BOTTOMMARGIN, 171
END

IDD_Page2, DIALOG
BEGIN
    LEFTMARGIN, 7
    RIGHTMARGIN, 302
    TOPMARGIN, 7
    BOTTOMMARGIN, 170
END

IDD_Page3, DIALOG
BEGIN
    LEFTMARGIN, 7
    RIGHTMARGIN, 302
    TOPMARGIN, 7
    BOTTOMMARGIN, 170
END

IDD_Main_Dialog, DIALOG
BEGIN
    LEFTMARGIN, 7
    RIGHTMARGIN, 302
    TOPMARGIN, 7
    BOTTOMMARGIN, 170
END
END
#endif    // APSTUDIO_INVOKED

#endif    

[Main.cpp]:

#include <windows.h>
#include <CommCtrl.h>
#include "resource.h"
#pragma comment(lib, "ComCtl32.lib")
#define C_PAGES 3 

INT_PTR CALLBACK DialogProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);

HWND Win_Handle;
HWND Dailog_Handle;
HINSTANCE hInstance_Win_Global;

typedef struct {
WORD      dlgVer;
WORD      signature;
DWORD     helpID;
DWORD     exStyle;
DWORD     style;
WORD      cDlgItems;
short     x;
short     y;
short     cx;
short     cy;
WORD      pointsize;
WORD      weight;
BYTE      italic;
BYTE      charset;
} DLGTEMPLATEEX;

typedef struct tag_dlghdr {
HWND hwndTab;       // tab control 
HWND hwndDisplay;   // current child dialog box 
RECT rcDisplay;     // display rectangle for the tab control 
DLGTEMPLATEEX *apRes[C_PAGES];
} DLGHDR;

void InserTabItem(HWND handle, LPWSTR text, int id)
{
TCITEM tci = { 0 };
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = wcslen(text);
SendMessage(handle, TCM_INSERTITEM, id, LPARAM(&tci));
}

DLGTEMPLATEEX* DoLockDlgRes(LPCTSTR lpszResName)
{
HRSRC hrsrc = FindResource(NULL, lpszResName, RT_DIALOG);
HGLOBAL hglb = LoadResource(hInstance_Win_Global, hrsrc);
return (DLGTEMPLATEEX *)LockResource(hglb);
}

VOID WINAPI OnChildDialogInit(HWND hwndDlg)
{
HWND hwndParent = GetParent(hwndDlg);
DLGHDR *pHdr = (DLGHDR *)GetWindowLong(
    hwndParent, GWL_USERDATA);
SetWindowPos(hwndDlg, NULL, pHdr->rcDisplay.left,
    pHdr->rcDisplay.top,//-2,
    (pHdr->rcDisplay.right - pHdr->rcDisplay.left),
    (pHdr->rcDisplay.bottom - pHdr->rcDisplay.top),
    SWP_SHOWWINDOW);

return;
} 
VOID OnSelChanged(HWND hwndDlg)
{
DLGHDR *pHdr = (DLGHDR *)GetWindowLong(hwndDlg, GWL_USERDATA);
int iSel = TabCtrl_GetCurSel(pHdr->hwndTab);
if (pHdr->hwndDisplay != NULL)
    DestroyWindow(pHdr->hwndDisplay);
pHdr->hwndDisplay = CreateDialogIndirect(hInstance_Win_Global,
    (DLGTEMPLATE *)pHdr->apRes[iSel], hwndDlg,DialogProc);
}
HRESULT OnTabbedDialogInit(HWND hwndDlg)
{
INITCOMMONCONTROLSEX iccex;
DWORD dwDlgBase = GetDialogBaseUnits();
int cxMargin = LOWORD(dwDlgBase) / 4;
int cyMargin = HIWORD(dwDlgBase) / 8;

TCITEM tie;
RECT rcTab;
HWND hwndButton;
RECT rcButton;
int i;

iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&iccex);

DLGHDR *pHdr = (DLGHDR *)LocalAlloc(LPTR, sizeof(DLGHDR));
SetWindowLong(hwndDlg, GWL_USERDATA, (LONG)pHdr);

pHdr->hwndTab = CreateWindow(
    WC_TABCONTROL, L"",
    WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE,
    0, 0, 300, 200,
    hwndDlg, NULL, hInstance_Win_Global, NULL
    );
if (pHdr->hwndTab == NULL)
{
    return HRESULT_FROM_WIN32(GetLastError());
}

tie.mask = TCIF_TEXT | TCIF_IMAGE;
tie.iImage = -1;
tie.pszText = L"First";
TabCtrl_InsertItem(pHdr->hwndTab, 0, &tie);
tie.pszText = L"Second";
TabCtrl_InsertItem(pHdr->hwndTab, 1, &tie);
tie.pszText = L"Third";
TabCtrl_InsertItem(pHdr->hwndTab, 2, &tie);

pHdr->apRes[0] = DoLockDlgRes(MAKEINTRESOURCE(IDD_Page1));
pHdr->apRes[1] = DoLockDlgRes(MAKEINTRESOURCE(IDD_Page2));
pHdr->apRes[2] = DoLockDlgRes(MAKEINTRESOURCE(IDD_Page3));

SetRectEmpty(&rcTab);
for (i = 0; i < C_PAGES; i++)
{
    if (pHdr->apRes[i]->cx > rcTab.right)
        rcTab.right = pHdr->apRes[i]->cx;
    if (pHdr->apRes[i]->cy > rcTab.bottom)
        rcTab.bottom = pHdr->apRes[i]->cy;
}

MapDialogRect(hwndDlg, &rcTab);

TabCtrl_AdjustRect(pHdr->hwndTab, TRUE, &rcTab);
OffsetRect(&rcTab, cxMargin - rcTab.left, cyMargin - rcTab.top);

CopyRect(&pHdr->rcDisplay, &rcTab);
TabCtrl_AdjustRect(pHdr->hwndTab, FALSE, &pHdr->rcDisplay);

SetWindowPos(pHdr->hwndTab, NULL, rcTab.left, rcTab.top,
    rcTab.right - rcTab.left, rcTab.bottom - rcTab.top,
    SWP_NOZORDER);

hwndButton = GetDlgItem(hwndDlg, IDC_BTN_Page1);
SetWindowPos(hwndButton, NULL,
    rcTab.left, rcTab.bottom + cyMargin, 0, 0,
    SWP_NOSIZE | SWP_NOZORDER);

GetWindowRect(hwndButton, &rcButton);
rcButton.right -= rcButton.left;
rcButton.bottom -= rcButton.top;

hwndButton = GetDlgItem(hwndDlg, IDC_BTN2_Page1);
SetWindowPos(hwndButton, NULL,
    rcTab.left + rcButton.right + cxMargin,
    rcTab.bottom + cyMargin, 0, 0,
    SWP_NOSIZE | SWP_NOZORDER);

SetWindowPos(hwndDlg, NULL, 0, 0,
    rcTab.right + cyMargin + (2 * GetSystemMetrics(SM_CXDLGFRAME)),
    rcTab.bottom + rcButton.bottom + (2 * cyMargin)
    + (2 * GetSystemMetrics(SM_CYDLGFRAME))
    + GetSystemMetrics(SM_CYCAPTION),
    SWP_NOMOVE | SWP_NOZORDER);

OnSelChanged(hwndDlg);

return S_OK;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_CREATE:
{
    Dailog_Handle = CreateDialogParam(hInstance_Win_Global, MAKEINTRESOURCE(IDD_Main_Dialog), hWnd, DialogProc, 0);
    ShowWindow(Dailog_Handle, SW_SHOWDEFAULT);
    UpdateWindow(Dailog_Handle);
    SetWindowPos(Dailog_Handle, 0, 10, 10, 500, 300, SWP_NOZORDER);

}
break;
case WM_CLOSE:
    DestroyWindow(hWnd);
    break;
case WM_DESTROY:
    PostQuitMessage(0);
    break;
default:
    return DefWindowProc(hWnd, Msg, wParam, lParam);
    break;
}

return 0;
}
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_INITDIALOG:
{

    OnTabbedDialogInit(hWnd);
    OnChildDialogInit(hWnd);

    return (INT_PTR)TRUE;
}
    break;
case WM_NOTIFY:
{
    switch (((LPNMHDR)lParam)->code)
    {
    case TCN_SELCHANGE:
    {
        OnSelChanged(hWnd);
    }
    break;
    default:
        break;
    }

}
break;
case WM_CLOSE:
    DestroyWindow(hWnd);
    break;
case WM_DESTROY:
    PostQuitMessage(0);
    break;
}
return (INT_PTR)FALSE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreviewInstance, LPSTR lpcmdline, int ncmdshow)
  {
  WNDCLASSEX wndexcls;
  wndexcls.lpszClassName = L"win";
  wndexcls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wndexcls.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
  wndexcls.hCursor = LoadCursor(NULL, IDC_ARROW);
  wndexcls.hbrBackground = (HBRUSH)(COLOR_3DSHADOW + 1);
  wndexcls.lpszMenuName = NULL;
  wndexcls.style = NULL;
  wndexcls.hInstance = hInstance;
  wndexcls.cbSize = sizeof(WNDCLASSEX);
  wndexcls.cbClsExtra = 0;
  wndexcls.cbWndExtra = 0;
  wndexcls.lpfnWndProc = WndProc;
  RegisterClassEx(&wndexcls);

 Win_Handle = CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_CONTROLPARENT, L"win", L"TestApp", WS_OVERLAPPEDWINDOW, 100, 100, 640, 380, 0, 0, hInstance, 0);
 hInstance_Win_Global = hInstance;
 ShowWindow(Win_Handle, SW_SHOWDEFAULT);
 UpdateWindow(Win_Handle);


MSG wnd_msg;
while (GetMessage(&wnd_msg, NULL, 0, 0)>0)
{
    TranslateMessage(&wnd_msg);
    DispatchMessage(&wnd_msg);
}
return (int)wnd_msg.wParam;
}

问题是,调试后,应用程序退出并且不显示任何内容。如果我评论 OnSelChanged(hWnd) 和 OnChildDialogInit(hWnd) 应用程序正常启动并显示选项卡控件,但不显示页面上的控件。看来问题出在这里了。

输出日志:

  First-chance exception at 0x00BE1886 in testcppapp.exe: 0xC0000005: Access violation reading location 0x00000014.
 The program '[16220] testcppapp.exe' has exited with code 0 (0x0).

我已阅读以下有关访问冲突读取位置的链接:

http://www.cplusplus.com/forum/general/17094/

但我无法解决问题。

请发表您的答案并解释一下,而不仅仅是在 cmets 中简要说明!

感谢您的帮助。

【问题讨论】:

  • 选项卡控件实际上没有“页面”,它只是显示选项卡。您负责将控件划分为页面并根据需要显示或隐藏它们。通常,为每个页面使用子对话框比单独创建所有控件更容易。
  • @JonathanPotter :任何好的学习资源或示例代码。我提到的第四个链接令人困惑。任何单独创建所有控件的示例?谢谢
  • 正确设置选项卡控件是一项工作。你知道如何创建对话框吗?如果是这样,那么创建一个对话框作为子窗口很容易;只需添加 DS_CONTROLWS_CHILD 样式。你也知道WM_NOTIFY的用法吗?您需要它知道何时单击了选项卡,因为您自己负责显示和隐藏实际页面。 (稍后我可能会写一个关于创建选项卡控件的答案。它应该是社区 wiki 的事情吗?)
  • How to Create a Tabbed Dialog Box 解释了如何为每个页面分配一个对话框模板。对话框模板从 .rc 脚本编译成可执行映像。如果您想了解对话框模板如何成为手动创建控件的快捷方式,请阅读 Charles Petzold 的 Programming Windows®
  • @andlabs:不涉及全局内存。只是资源 API 仍然带有 16 位实现的名称,但不共享它们的实现。在 Win32 中,资源被映射到地址空间,并且没有分配内存(LockResource 提供了一些提示)。更多详细信息请参见What’s the difference between FreeResource and, say, DestroyAcceleratorTable

标签: winapi tabcontrol


【解决方案1】:

我正在测试并且看起来很有希望的一种方法是为模式对话框中选项卡控件的每个窗格使用对话框模板。

为了进行测试,我生成了一个启动 Win32 GUI 应用程序,然后劫持了由IDM_ABOUT 触发的About 框显示,以显示我自己的带有选项卡控件的对话框,而不是标准的“关于”对话框。

        case IDM_ABOUT:
//          DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            {
                CDialogTabTest x;
                x.CreateModal (hWnd, hInst);
            }
            break;

经过一些研究和重构和开发,我有以下方法。

我创建了三个实现类:CDialogTest(处理对话框功能)、CDialogTabTest(处理对话框中的选项卡控件)和CDialogTabPane(处理显示在选项卡控件)。

CDialogTest 类提供基本对话框行为的功能。它允许两种对话框,用于创建实际模态对话框的模态对话框和用于创建选项卡控制窗格的非模态对话框。

CDialogTest 类旨在用作子类,并可扩展用于特定对话框或选项卡控制窗格。

CDialogTabTest 类扩展 CDialogTest 以实现模式对话框。 CDialogTabPane 类扩展 CDialogTest 以实现一个非模态对话框,用作选项卡控制选项卡窗格。

对于 MFC 程序员来说,有些名字可能听起来有点熟悉,因为在解决这个问题时,我现在了解了对话框的 MFC 类的一些机制。

我在 stdafx.h 文件中添加了一些额外的包含文件,因为这是放置它们以满足我需要的中心位置。

// TODO: reference additional headers your program requires here

#include "commctrl.h"
#include "windowsx.h"

#include <vector>

DialogTest.h

#pragma once

class CDialogTest
{
private:
    static INT_PTR CALLBACK DlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);

public:
    HINSTANCE m_hInst;
    HWND      m_hParent;
    HWND      m_hDlg;

    CDialogTest();
    CDialogTest(HINSTANCE hInst, HWND hParent = NULL);
    virtual ~CDialogTest(void);

    HWND CreateThing (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst);
    INT_PTR WINAPI CreateModal (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst);

    virtual int DoDataExchange (int iDir) { return 0; }
    virtual int OnWmCommandInit () { return 0; }
    virtual int OnWmNotify (LPNMHDR    pNmhdr) { return 0; }
    virtual int OnWmCommand (WPARAM wParam, LPARAM lParam) { return 0; }

};

和 DialogTest.cpp

#include "StdAfx.h"
#include "DialogTest.h"

CDialogTest::CDialogTest(void)
{
}

CDialogTest::CDialogTest(HINSTANCE hInst, HWND hParent /* = NULL */) : 
    m_hInst (hInst), m_hParent(hParent)
{
}

CDialogTest::~CDialogTest(void)
{
}

INT_PTR CALLBACK CDialogTest::DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_INITDIALOG:
        {
            // Set the provided object pointer into the dialog box user area
            // so that we can use it to provide the necessary notifications
            // to what ever object which has derived from this class.
            ::SetWindowLongPtr (hDlg, DWLP_USER, lParam);

            // ensure that the object knows its dialog handle.
            CDialogTest  *pObj = (CDialogTest *)lParam;
            pObj->m_hDlg = hDlg;

            // call the objects handlers to initialize the dialog further
            // and to then populate the data displayed in the dialog.
            pObj->OnWmCommandInit ();
            pObj->DoDataExchange (1);
        }
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        // if this command is an Ok or Cancel button press then we are
        // done. other command messages are routed to the derived object's
        // how command message handler for further processing.
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            if (LOWORD(wParam) == IDOK) {
                LPARAM lP = ::GetWindowLongPtr (hDlg, DWLP_USER);
                CDialogTest  *pObj = (CDialogTest *)lP;

                pObj->DoDataExchange (0);
            }

            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        } else {
            LPARAM lP = ::GetWindowLongPtr (hDlg, DWLP_USER);
            CDialogTest  *pObj = (CDialogTest *)lP;

            pObj->OnWmCommand (wParam, lParam);
        }
        break;

    case WM_NOTIFY:
        {
            LPARAM lP = ::GetWindowLongPtr (hDlg, DWLP_USER);
            CDialogTest  *pObj = (CDialogTest *)lP;
            LPNMHDR    pNmhdr = ((LPNMHDR)lParam);

            pObj->OnWmNotify (pNmhdr);
        }
        break;
    }
    return (INT_PTR)FALSE;
}

// We need two different kinds of dialog create functions as we support
// both a modal dialog and a modeless dialog. The modal dialog is used to
// display an actual dialog box. The modeless dialog is used to display
// a tab control pane using a dialog template.
INT_PTR WINAPI CDialogTest::CreateModal (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst)
{
    m_hInst = hInst;
    m_hParent = hWnd;

    // display the modal dialog box and return the identifier for the button clicked.
    return DialogBoxParam(hInst, lpTemplateName, hWnd, &CDialogTest::DlgProc, (LPARAM)this);
}

HWND CDialogTest::CreateThing (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst)
{
    m_hInst = hInst;
    m_hParent = hWnd;

    // create a modeless dialog box to be used as a tab control pane or similar
    // non-modal dialog box use.
    m_hDlg =  ::CreateDialogParam(hInst, lpTemplateName, hWnd, &CDialogTest::DlgProc, (LPARAM)this);

    return m_hDlg;
}

CDialogTabTest 类创建作为主应用程序消息处理程序中IDM_ABOUT 菜单选择消息的结果而显示的模式对话框。

DialogTabTest.h

#pragma once

#include "stdafx.h"

#include "DialogTest.h"
#include "DialogTabPane.h"

#include "resource.h"

struct CCheckBoxData
{
    UINT          iCntrlId;
    int           iCheck;
    std::wstring  prompt;
};

typedef std::vector<CCheckBoxData> CCheckBoxVector;


class CDialogTabTest : public CDialogTest
{
protected:
    CDialogTabPane           m_hTabPane1;
    CDialogTabPane           m_hTabPane2;
    CDialogTabPane           m_hTabPane3;
    enum {IDD_DIALOG = IDD_DIALOG_TAB00};

public:
    CDialogTabTest(void);
    virtual ~CDialogTabTest(void);

    INT_PTR WINAPI CreateModal (HWND hWnd, HINSTANCE hInst);

    static int SetValues (CCheckBoxData &x, HWND hDlg);
    static int GetValues (CCheckBoxData &x, HWND hDlg);

    virtual int DoDataExchange (int iDir);
    virtual int OnWmCommandInit ();
    virtual int OnWmNotify (LPNMHDR    pNmhdr);
};

和 DialogTabTest.cpp 创建模态对话框并对其进行初始化。

#include "StdAfx.h"

#include "DialogTabTest.h"


CDialogTabTest::CDialogTabTest(void)
{
}

CDialogTabTest::~CDialogTabTest(void)
{
}

int CDialogTabTest::SetValues (CCheckBoxData &x, HWND hDlg)
{
    HWND  hWnd = GetDlgItem (hDlg, x.iCntrlId);
    ::SetWindowText (hWnd, x.prompt.c_str());

    Button_SetCheck (hWnd, x.iCheck);
    return 0;
}

int CDialogTabTest::GetValues (CCheckBoxData &x, HWND hDlg)
{
    HWND  hWnd = GetDlgItem (hDlg, x.iCntrlId);
    x.iCheck = Button_GetCheck (hWnd);
    return 0;
}

int CDialogTabTest::OnWmCommandInit ()
{
    // get the window handle for the tab control. This is going to
    // be the parent window for all of the tab panes that are inserted
    // into the tab control. By making the tab control the parent,
    // the tab panes will be contained within the tab control and
    // will flow with it should it be moved about.
    HWND hTabCntrl = GetDlgItem (m_hDlg, IDC_TAB1);

    TCITEM tie = {0};
    tie.mask = TCIF_TEXT | TCIF_IMAGE; 
    tie.iImage = -1;

    tie.pszText = L"First";
    m_hTabPane1.CreateThing (&tie, 0, MAKEINTRESOURCE(IDD_DIALOG_TAB01), m_hInst, hTabCntrl);

    tie.pszText = L"Second";
    m_hTabPane2.CreateThing (&tie, 1, MAKEINTRESOURCE(IDD_DIALOG_TAB02), m_hInst, hTabCntrl);

    tie.pszText = L"Third";
    m_hTabPane3.CreateThing (&tie, 2, MAKEINTRESOURCE(IDD_DIALOG_TAB03), m_hInst, hTabCntrl);

    // set the first tab to be displayed.
    NMHDR   Nmhdr = {hTabCntrl, 0, TCN_SELCHANGE};
    m_hTabPane1.OnWmNotify (&Nmhdr);

    return 0;
}

int CDialogTabTest::OnWmNotify (LPNMHDR    pNmhdr)
{
    // propagate the notification to the various panes in our tab control
    // so that they can do what they need to do.
    m_hTabPane1.OnWmNotify (pNmhdr);
    m_hTabPane2.OnWmNotify (pNmhdr);
    m_hTabPane3.OnWmNotify (pNmhdr);

    return 0;
}

int CDialogTabTest::DoDataExchange (int iDir)
{
    if (iDir) {
        CCheckBoxData  iVal = {IDC_CHECK1, BST_UNCHECKED, L"DoData: Check Box 1 Set and Not Checked"};

        SetValues (iVal, m_hTabPane1.m_hDlg);

        iVal.iCntrlId = IDC_CHECK2;
        iVal.iCheck = BST_CHECKED;
        iVal.prompt = L"DoData: Check Box 2 is Set and Checked.";
        SetValues (iVal, m_hTabPane1.m_hDlg);

        iVal.iCntrlId = IDC_CHECK4;
        iVal.iCheck = BST_CHECKED;
        iVal.prompt = L"DoData: Check Box 4 is Set, Checked Tab 3.";
        SetValues (iVal, m_hTabPane3.m_hDlg);
    } else {
        CCheckBoxData  iVal = {IDC_CHECK1, BST_UNCHECKED, L""};
        GetValues (iVal, m_hTabPane1.m_hDlg);
    }

    return 0;
}

INT_PTR WINAPI CDialogTabTest::CreateModal (HWND hWnd, HINSTANCE hInst)
{
    // this is a modal dialog box so we use the CreateModal() method
    // in order to create the dialog box and display it.
    return CDialogTest::CreateModal(hWnd, MAKEINTRESOURCE(IDD_DIALOG), hInst);
}

最后是 CDialogTabPane 类,它实现了各个选项卡控制选项卡窗格的功能。

DialogTabPane.h

#pragma once

#include "stdafx.h"
#include "DialogTest.h"

class CDialogTabPane : public CDialogTest
{
protected:
    int  m_iTab;

public:
    DWORD  m_dwLastError;

    CDialogTabPane(void);
    virtual ~CDialogTabPane(void);

    HWND CreateThing (LPTCITEM pTie, int iTab, LPCWSTR lpTemplateName, HINSTANCE hInstance, HWND hWndParent);

//  virtual int DoDataExchange (int iDir);
//  virtual int OnWmCommandInit ();
    virtual int OnWmNotify (LPNMHDR    pNmhdr);
};

和 DialogTabPane.cpp

#include "StdAfx.h"

#include "DialogTabPane.h"


CDialogTabPane::CDialogTabPane(void)
{
}


CDialogTabPane::~CDialogTabPane(void)
{
}


HWND CDialogTabPane::CreateThing (LPTCITEM pTie, int iTab, LPCWSTR lpTemplateName, HINSTANCE hInstance, HWND hWndParent)
{
    // insert the new tab into the tab control with the specified parameters.
    TabCtrl_InsertItem(hWndParent, iTab, pTie);
    m_iTab = iTab;

    // We need to figure out the adjustment of the dialog window position
    // within the tab control display area so that when the dialog and its
    // controls are displayed for a given tab the tab selection list can
    // still be seen above the top of the displayed dialog.
    // The tab selection list must be visible so that the user can select
    // any of the specified tabs.
    RECT  tabDisplay = {0};
    GetWindowRect (hWndParent, &tabDisplay);
    RECT  dialogDisplay = tabDisplay;
    // NOTE: TabCtrl_AdjustRect() - This message applies only to tab controls that
    //       are at the top. It does not apply to tab controls that are on the
    //       sides or bottom.
    //       But then tab controls on the side or bottom are an abomination.
    TabCtrl_AdjustRect (hWndParent, FALSE, &dialogDisplay);
    dialogDisplay.left -= tabDisplay.left; dialogDisplay.top -= tabDisplay.top;

    m_dwLastError = 0;

    // create our dialog and then position it within the tab control properly.
    CDialogTest::CreateThing(hWndParent, lpTemplateName, hInstance);
    if (!::SetWindowPos (m_hDlg, HWND_TOP, dialogDisplay.left, dialogDisplay.top, 0, 0, SWP_NOSIZE)) {
        m_dwLastError = GetLastError ();
    }

    return m_hDlg;
}

int CDialogTabPane::OnWmNotify (LPNMHDR    pNmhdr)
{
    int currentSel = TabCtrl_GetCurSel(pNmhdr->hwndFrom);
    switch (pNmhdr->code)
    {
        case TCN_SELCHANGING:
            if (currentSel == m_iTab)
                ::ShowWindow (m_hDlg, SW_HIDE);
            break;
        case TCN_SELCHANGE:
            if (currentSel == m_iTab)
                ::ShowWindow (m_hDlg, SW_SHOW);
            break;
    }

    return 0;
}

用于各个选项卡控制选项卡窗格的对话框资源相似,如下所示:

IDD_DIALOG_TAB01 DIALOGEX 0, 0, 186, 115
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    LTEXT           "First tab",IDC_STATIC,68,7,96,14
    CONTROL         "Check1",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,21,150,13
    CONTROL         "Check2",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,36,150,13
    CONTROL         "Check3",IDC_CHECK3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,53,150,13
    CONTROL         "Check4",IDC_CHECK4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,69,150,13
    CONTROL         "Check5",IDC_CHECK5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,84,150,13
END

包含选项卡控件的实际对话框的资源如下所示:

IDD_DIALOG_TAB00 DIALOGEX 0, 0, 336, 179
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog Tab Test"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,279,7,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,279,24,50,14
    CONTROL         "",IDC_TAB1,"SysTabControl32",0x0,47,35,226,137
END

显示对话框,其中选项卡 1 被选中,然后选项卡 3 被选中。

【讨论】:

    【解决方案2】:

    这里有个问题:

    pHdr->hwndDisplay = CreateDialogIndirect(hInstance_Win_Global, 
        (DLGTEMPLATE*)pHdr->apRes[iSel], hwndDlg, DialogProc);
    

    您正在为主对话框和子对话框重复使用相同的对话框过程。主对话框创建子对话框,子对话框使用相同的过程来创建子对话框......也没有错误检查。

    除此之外,这段代码太复杂了。只需使用一个对话框作为主窗口。创建一个新对话框IDD_DIALOG1 并在其中拖放一个选项卡控件。为选项卡控件 ID 分配 IDC_TAB1。请尝试从以下代码开始:

    #include <Windows.h>
    #include <CommCtrl.h>
    #include "Resource.h"
    
    #pragma comment(lib,"comctl32.lib")
    #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
    
    HINSTANCE g_hinst;
    
    struct TData {
        HWND page1, page2, page3;
        HWND tab;
    } data;
    
    BOOL CALLBACK DialogPage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
    {
        switch(msg) {
        case WM_COMMAND:
            switch (wp) {
                //...
            }
        }
        return FALSE;
    }
    
    void OnSelChange() {
        int sel = TabCtrl_GetCurSel(data.tab);
        ShowWindow(data.page1, (sel == 0) ? SW_SHOW : SW_HIDE);
        ShowWindow(data.page2, (sel == 1) ? SW_SHOW : SW_HIDE);
    }
    
    BOOL CALLBACK DialogProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
    {
        switch (msg) {
        case WM_INITDIALOG: {
            data.page1 = CreateDialog(g_hinst, MAKEINTRESOURCE(IDD_Page1), hwnd, DialogPage);
            data.page2 = CreateDialog(g_hinst, MAKEINTRESOURCE(IDD_Page2), hwnd, DialogPage);
    
            data.tab = GetDlgItem(hwnd, IDC_TAB1);
            if (data.tab)
            {
                TCITEM tci = { 0 };
                tci.mask = TCIF_TEXT;
                tci.pszText = L"Page1";
                TabCtrl_InsertItem(data.tab, 0, &tci);
                tci.pszText = L"Page2";
                TabCtrl_InsertItem(data.tab, 1, &tci);
    
                RECT rc;//find tab control's rectangle
                GetWindowRect(data.tab, &rc);
                POINT offset = { 0 };
                ScreenToClient(hwnd, &offset);
                OffsetRect(&rc, offset.x, offset.y); //convert to client coordinates
                rc.top += 50;
                SetWindowPos(data.page1, 0, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_HIDEWINDOW);
                SetWindowPos(data.page2, 0, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_HIDEWINDOW);
    
                OnSelChange();
            }
    
            break;
        }
    
        case WM_NOTIFY: {
            switch (((LPNMHDR)lp)->code) {
            case TCN_SELCHANGE:
                OnSelChange();
                break;
            }
        }
        break;
    
        case WM_COMMAND:
            switch (wp) {
            case IDOK: EndDialog(hwnd, wp);  break;
            case IDCANCEL:  EndDialog(hwnd, wp);  break;
            }
        }
    
        return FALSE;
    }
    
    int WINAPI wWinMain(HINSTANCE hinst, HINSTANCE, LPWSTR, int)
    {
        g_hinst = hinst;
        DialogBox(hinst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc);
        return 0;
    }
    

    【讨论】:

    • 感谢您的快速回答。为您的帖子 +1 投票。
    • 完美,干净的代码,没有 C++ 类包装垃圾......我喜欢它:) 一个建议虽然......删除该结构 TData 并将内部声明为全局变量,这将使它看起来更好。 . 当我看到该结构时,我想自己会为每个选项卡创建其中一个结构.. 但事实并非如此,它只是所有内容的 1 个结构.. 所以真的不需要它。
    • 另一件事,伙计们总是在单独的线程中创建DialogBox(),否则之后的所有代码都将不起作用..直到对话框终止DestoryWindow(hInst); / EndDialog(...,...)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多