如何从 MFC 选项卡控件 (TabCtrl) 获取额外数据?
How to get extra data from MFC Tab Control (TabCtrl)?
我创建了一个基于 MFC 对话框的应用程序来研究选项卡控件。在选项卡控件中,可以为每个选项卡设置应用程序特定数据。
我正在尝试了解如何 set/retrieve 选项卡控件的各个选项卡的数据。
这是我正在创建的示例应用程序。控件的每个选项卡都应该存储一些 GPU 信息。
据我了解,添加应用程序特定数据需要 3 个步骤。
创建一个用户定义的结构,其第一个成员的类型应为 TCITEMHEADER
。
struct GPU {
std::wstring name;
int busid;
};
struct tabData {
TCITEMHEADER tabItemHeader;
GPU gpu;
};
告诉选项卡控件额外的字节,用户定义的结构将要占用。这是我在 DoDataExchange()
.
中做的
int extraBytes = sizeof(tabData) - sizeof(TCITEMHEADER);
auto status = tabCtrl1.SetItemExtra(extraBytes);
添加选项卡时设置用户定义的数据。
static int tabCtr = 0;
tabData td;
td.tabItemHeader.pszText = _T("TabX");
td.tabItemHeader.mask = TCIF_TEXT;
td.gpu.name = L"AMD NVIDIA";
td.gpu.busid = 101;
TabCtrl_InsertItem(tabCtrl1.GetSafeHwnd(), tabCtr, &td);
现在要获取数据,我们只需调用 TabCtrl_GetItem()
。
tabData td2;
td2.tabItemHeader.pszText = new TCHAR[20];
td2.tabItemHeader.cchTextMax = 20;
td2.tabItemHeader.mask = TCIF_TEXT;
td2.gpu.busid = 0;
TabCtrl_GetItem(tabCtrl1.GetSafeHwnd(), 0, &td2);
但是如下图所示。我确实得到了选项卡文本(pszText 成员 - 图像中的数据项 1),但没有我之前与之关联的额外数据(图像中的数据项 2 和 3)。
我错过了哪一步?
为什么与应用程序定义的数据关联的结构未被填充?
附加信息
这是应用程序的完整代码。
CPP 文件:
// tabCtrlWhosebugDlg.cpp : implementation file
//
#include "stdafx.h"
#include "tabCtrlWhosebug.h"
#include "tabCtrlWhosebugDlg.h"
#include "afxdialogex.h"
#include <string>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
struct GPU {
std::wstring name;
int busid;
};
struct tabData
{
TCITEMHEADER tabItemHeader;
GPU gpu;
};
CtabCtrlWhosebugDlg::CtabCtrlWhosebugDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(IDD_TABCTRLWhosebug_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CtabCtrlWhosebugDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_TAB1, tabCtrl1);
int extraBytes = sizeof(tabData) - sizeof(TCITEMHEADER);
auto status = tabCtrl1.SetItemExtra(extraBytes);
wchar_t *t = status ? L"SetItemExtra() success" : L"SetItemExtra() fail";
GetDlgItem(IDC_STATUSTEXT)->SetWindowTextW(t);
}
BEGIN_MESSAGE_MAP(CtabCtrlWhosebugDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDADDTAB, &CtabCtrlWhosebugDlg::OnBnClickedAddtab)
ON_BN_CLICKED(IDC_GETITEM0, &CtabCtrlWhosebugDlg::OnBnClickedGetitem0)
ON_BN_CLICKED(IDCLOSE, &CtabCtrlWhosebugDlg::OnBnClickedClose)
END_MESSAGE_MAP()
// CtabCtrlWhosebugDlg message handlers
BOOL CtabCtrlWhosebugDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CtabCtrlWhosebugDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
// The system calls this function to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CtabCtrlWhosebugDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CtabCtrlWhosebugDlg::OnBnClickedAddtab()
{
static int tabCtr = 0;
tabData td;
td.tabItemHeader.pszText = _T("TabX");
td.tabItemHeader.mask = TCIF_TEXT;
td.gpu.name = L"AMD NVIDIA";
td.gpu.busid = 101;
int status = TabCtrl_InsertItem(tabCtrl1.GetSafeHwnd(), tabCtr, &td);
wchar_t *t = L"";
if (status == -1)
{
t = L"TabCtrl_InsertItem() Fail";
}
else
{
t = L"TabCtrl_InsertItem() success";
}
GetDlgItem(IDC_STATUSTEXT)->SetWindowTextW(t);
tabCtr++;
}
void CtabCtrlWhosebugDlg::OnBnClickedGetitem0()
{
tabData td2;
td2.tabItemHeader.pszText = new TCHAR[20];
td2.tabItemHeader.cchTextMax = 20;
td2.tabItemHeader.mask = TCIF_TEXT;
td2.gpu.busid = 0;
if (TabCtrl_GetItem(tabCtrl1.GetSafeHwnd(), 0, &td2) == TRUE)
{
std::wstring text = td2.tabItemHeader.pszText;
text += std::wstring(L" ") + td2.gpu.name;
GetDlgItem(IDC_STATUSTEXT)->SetWindowTextW(text.c_str());
}
else
{
GetDlgItem(IDC_STATUSTEXT)->SetWindowTextW(_T("TabCtrl_GetItem()
error"));
}
}
void CtabCtrlWhosebugDlg::OnBnClickedClose()
{
CDialog::OnCancel();
}
头文件:
// tabCtrlWhosebugDlg.h : header file
//
#pragma once
#include "afxcmn.h"
// CtabCtrlWhosebugDlg dialog
class CtabCtrlWhosebugDlg : public CDialogEx
{
// Construction
public:
CtabCtrlWhosebugDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_TABCTRLWhosebug_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
CTabCtrl tabCtrl1;
afx_msg void OnBnClickedAddtab();
afx_msg void OnBnClickedGetitem0();
afx_msg void OnBnClickedClose();
};
解决方案总结
来自 以下是我的代码无法正常工作的 3 个原因。必须阅读他的回答才能更好地理解。
TCIF_PARAM
必须在掩码中设置,同时做TCM_INSERTITEM
和TCM_GETITEM
.
- 我正在使用在堆栈上创建的局部变量(tabData td2;对象)。对该变量的引用一超出范围就变得无效。
- 在用于
TCM_INSERTITEM
的结构中使用 std::wstring。最好使用可以准确确定大小的数据类型(如普通的旧数据类型。)。
正如 Barmak Shemirani 在评论中指出的那样,TCITEMHEADER
的文档很少。他的回答提供了详尽的解释。
与文档冲突
TCITEMHEADER 的文档没有提到使用 TCIF_PARAM
标志。也许这是文档中的错误!
如果在调用默认过程后将 SetItemExtra
移动到 OnInitDialog
会更好。这确保 SetItemExtra
在控件为空时仅调用一次。
结构 GPU
有一个 std::wstring
成员,其数据大小在开始时未知。 TCM_INSERTITEM
除非您具有简单的 POD 结构,否则无法复制此数据。
要将数据存储在选项卡中,请将 std::wstring
替换为 wchar_t name[100]
,以便数据是固定大小的简单 POD 结构。
struct GPU
{
//std::wstring name;
wchar_t name[100];
int busid;
};
struct tabData
{
TCITEMHEADER tabItemHeader;
GPU gpu;
};
void CMyDialog::OnBnClickedAddtab()
{
int index = tab.GetItemCount();
wchar_t tabname[50];
wsprintf(tabname, L"Tab %d", index);
tabData sdata = { 0 };
sdata.tabItemHeader.mask = TCIF_TEXT | TCIF_PARAM;
sdata.tabItemHeader.pszText = tabname;
wsprintf(sdata.gpu.name, L"AMD NVIDIA %d", index);
sdata.gpu.busid = 101;
tab.SendMessage(TCM_INSERTITEM, index, (LPARAM)(TCITEMHEADER*)(&sdata));
}
void CMyDialog::OnBnClickedGetitem0()
{
int index = tab.GetCurSel();
tabData data = { 0 };
wchar_t buf[20] = { 0 };
data.tabItemHeader.pszText = buf;
data.tabItemHeader.cchTextMax = sizeof(buf)/sizeof(wchar_t);
data.tabItemHeader.mask = TCIF_TEXT | TCIF_PARAM;
if(tab.SendMessage(TCM_GETITEM, index, (LPARAM)(TCITEMHEADER*)(&data)))
{
CString str;
str.Format(L"%d %s", data.gpu.busid, data.gpu.name);
GetDlgItem(IDC_STATIC1)->SetWindowText(str);
}
}
替代方法:
如果std::wstring name;
不能用wchar_t
缓冲区代替,我们必须定义一个单独的永久数据,例如使用std::vector
。然后我们使用TCITEM
中的lParam
值指向向量
该方法只需要lParam
的标准4字节,不需要TCITEMHEADER
和SetItemExtra
。您甚至可以定义 std::vector<GPU>
。示例:
std::vector<tabData> m_data;
BOOL CMyDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();
tabData data;
data.gpu.name = L"AMD NVIDIA1";
data.gpu.busid = 101;
m_data.push_back(data);
data.gpu.name = L"AMD NVIDIA2";
data.gpu.busid = 102;
m_data.push_back(data);
return TRUE;
}
void CMyDialog::OnBnClickedAddtab()
{
static int tabCtr = 0;
if(tabCtr >= (int)m_data.size())
return;
TCITEM item = { 0 };
item.pszText = _T("TabX");
item.mask = TCIF_TEXT | TCIF_PARAM;
item.lParam = (LPARAM)&m_data[tabCtr];
tab.InsertItem(tabCtr, &item);
tabCtr++;
}
void CMyDialog::OnBnClickedGetitem0()
{
TCITEM item = { 0 };
item.mask = TCIF_TEXT | TCIF_PARAM;
if(tab.GetItem(tab.GetCurSel(), &item) == TRUE)
{
tabData* ptr = (tabData*)item.lParam;
CString str;
str.Format(L"%d %s", ptr->gpu.busid, ptr->gpu.name.c_str());
GetDlgItem(IDC_STATIC1)->SetWindowText(str);
}
}
我创建了一个基于 MFC 对话框的应用程序来研究选项卡控件。在选项卡控件中,可以为每个选项卡设置应用程序特定数据。 我正在尝试了解如何 set/retrieve 选项卡控件的各个选项卡的数据。
这是我正在创建的示例应用程序。控件的每个选项卡都应该存储一些 GPU 信息。
据我了解,添加应用程序特定数据需要 3 个步骤。
创建一个用户定义的结构,其第一个成员的类型应为
TCITEMHEADER
。struct GPU { std::wstring name; int busid; }; struct tabData { TCITEMHEADER tabItemHeader; GPU gpu; };
告诉选项卡控件额外的字节,用户定义的结构将要占用。这是我在
中做的DoDataExchange()
.int extraBytes = sizeof(tabData) - sizeof(TCITEMHEADER); auto status = tabCtrl1.SetItemExtra(extraBytes);
添加选项卡时设置用户定义的数据。
static int tabCtr = 0; tabData td; td.tabItemHeader.pszText = _T("TabX"); td.tabItemHeader.mask = TCIF_TEXT; td.gpu.name = L"AMD NVIDIA"; td.gpu.busid = 101; TabCtrl_InsertItem(tabCtrl1.GetSafeHwnd(), tabCtr, &td);
现在要获取数据,我们只需调用 TabCtrl_GetItem()
。
tabData td2;
td2.tabItemHeader.pszText = new TCHAR[20];
td2.tabItemHeader.cchTextMax = 20;
td2.tabItemHeader.mask = TCIF_TEXT;
td2.gpu.busid = 0;
TabCtrl_GetItem(tabCtrl1.GetSafeHwnd(), 0, &td2);
但是如下图所示。我确实得到了选项卡文本(pszText 成员 - 图像中的数据项 1),但没有我之前与之关联的额外数据(图像中的数据项 2 和 3)。
我错过了哪一步?
为什么与应用程序定义的数据关联的结构未被填充?
附加信息
这是应用程序的完整代码。
CPP 文件:
// tabCtrlWhosebugDlg.cpp : implementation file // #include "stdafx.h" #include "tabCtrlWhosebug.h" #include "tabCtrlWhosebugDlg.h" #include "afxdialogex.h" #include <string> #ifdef _DEBUG #define new DEBUG_NEW #endif struct GPU { std::wstring name; int busid; }; struct tabData { TCITEMHEADER tabItemHeader; GPU gpu; }; CtabCtrlWhosebugDlg::CtabCtrlWhosebugDlg(CWnd* pParent /*=NULL*/) : CDialogEx(IDD_TABCTRLWhosebug_DIALOG, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CtabCtrlWhosebugDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_TAB1, tabCtrl1); int extraBytes = sizeof(tabData) - sizeof(TCITEMHEADER); auto status = tabCtrl1.SetItemExtra(extraBytes); wchar_t *t = status ? L"SetItemExtra() success" : L"SetItemExtra() fail"; GetDlgItem(IDC_STATUSTEXT)->SetWindowTextW(t); } BEGIN_MESSAGE_MAP(CtabCtrlWhosebugDlg, CDialogEx) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDADDTAB, &CtabCtrlWhosebugDlg::OnBnClickedAddtab) ON_BN_CLICKED(IDC_GETITEM0, &CtabCtrlWhosebugDlg::OnBnClickedGetitem0) ON_BN_CLICKED(IDCLOSE, &CtabCtrlWhosebugDlg::OnBnClickedClose) END_MESSAGE_MAP() // CtabCtrlWhosebugDlg message handlers BOOL CtabCtrlWhosebugDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here return TRUE; // return TRUE unless you set the focus to a control } // If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework. void CtabCtrlWhosebugDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialogEx::OnPaint(); } } // The system calls this function to obtain the cursor to display while the user drags // the minimized window. HCURSOR CtabCtrlWhosebugDlg::OnQueryDragIcon() { return static_cast<HCURSOR>(m_hIcon); } void CtabCtrlWhosebugDlg::OnBnClickedAddtab() { static int tabCtr = 0; tabData td; td.tabItemHeader.pszText = _T("TabX"); td.tabItemHeader.mask = TCIF_TEXT; td.gpu.name = L"AMD NVIDIA"; td.gpu.busid = 101; int status = TabCtrl_InsertItem(tabCtrl1.GetSafeHwnd(), tabCtr, &td); wchar_t *t = L""; if (status == -1) { t = L"TabCtrl_InsertItem() Fail"; } else { t = L"TabCtrl_InsertItem() success"; } GetDlgItem(IDC_STATUSTEXT)->SetWindowTextW(t); tabCtr++; } void CtabCtrlWhosebugDlg::OnBnClickedGetitem0() { tabData td2; td2.tabItemHeader.pszText = new TCHAR[20]; td2.tabItemHeader.cchTextMax = 20; td2.tabItemHeader.mask = TCIF_TEXT; td2.gpu.busid = 0; if (TabCtrl_GetItem(tabCtrl1.GetSafeHwnd(), 0, &td2) == TRUE) { std::wstring text = td2.tabItemHeader.pszText; text += std::wstring(L" ") + td2.gpu.name; GetDlgItem(IDC_STATUSTEXT)->SetWindowTextW(text.c_str()); } else { GetDlgItem(IDC_STATUSTEXT)->SetWindowTextW(_T("TabCtrl_GetItem() error")); } } void CtabCtrlWhosebugDlg::OnBnClickedClose() { CDialog::OnCancel(); }
头文件:
// tabCtrlWhosebugDlg.h : header file // #pragma once #include "afxcmn.h" // CtabCtrlWhosebugDlg dialog class CtabCtrlWhosebugDlg : public CDialogEx { // Construction public: CtabCtrlWhosebugDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_TABCTRLWhosebug_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: HICON m_hIcon; // Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: CTabCtrl tabCtrl1; afx_msg void OnBnClickedAddtab(); afx_msg void OnBnClickedGetitem0(); afx_msg void OnBnClickedClose(); };
解决方案总结
来自
TCIF_PARAM
必须在掩码中设置,同时做TCM_INSERTITEM
和TCM_GETITEM
.- 我正在使用在堆栈上创建的局部变量(tabData td2;对象)。对该变量的引用一超出范围就变得无效。
- 在用于
TCM_INSERTITEM
的结构中使用 std::wstring。最好使用可以准确确定大小的数据类型(如普通的旧数据类型。)。
正如 Barmak Shemirani 在评论中指出的那样,TCITEMHEADER
的文档很少。他的回答提供了详尽的解释。
与文档冲突
TCITEMHEADER 的文档没有提到使用 TCIF_PARAM
标志。也许这是文档中的错误!
如果在调用默认过程后将 SetItemExtra
移动到 OnInitDialog
会更好。这确保 SetItemExtra
在控件为空时仅调用一次。
结构 GPU
有一个 std::wstring
成员,其数据大小在开始时未知。 TCM_INSERTITEM
除非您具有简单的 POD 结构,否则无法复制此数据。
要将数据存储在选项卡中,请将 std::wstring
替换为 wchar_t name[100]
,以便数据是固定大小的简单 POD 结构。
struct GPU
{
//std::wstring name;
wchar_t name[100];
int busid;
};
struct tabData
{
TCITEMHEADER tabItemHeader;
GPU gpu;
};
void CMyDialog::OnBnClickedAddtab()
{
int index = tab.GetItemCount();
wchar_t tabname[50];
wsprintf(tabname, L"Tab %d", index);
tabData sdata = { 0 };
sdata.tabItemHeader.mask = TCIF_TEXT | TCIF_PARAM;
sdata.tabItemHeader.pszText = tabname;
wsprintf(sdata.gpu.name, L"AMD NVIDIA %d", index);
sdata.gpu.busid = 101;
tab.SendMessage(TCM_INSERTITEM, index, (LPARAM)(TCITEMHEADER*)(&sdata));
}
void CMyDialog::OnBnClickedGetitem0()
{
int index = tab.GetCurSel();
tabData data = { 0 };
wchar_t buf[20] = { 0 };
data.tabItemHeader.pszText = buf;
data.tabItemHeader.cchTextMax = sizeof(buf)/sizeof(wchar_t);
data.tabItemHeader.mask = TCIF_TEXT | TCIF_PARAM;
if(tab.SendMessage(TCM_GETITEM, index, (LPARAM)(TCITEMHEADER*)(&data)))
{
CString str;
str.Format(L"%d %s", data.gpu.busid, data.gpu.name);
GetDlgItem(IDC_STATIC1)->SetWindowText(str);
}
}
替代方法:
如果std::wstring name;
不能用wchar_t
缓冲区代替,我们必须定义一个单独的永久数据,例如使用std::vector
。然后我们使用TCITEM
中的lParam
值指向向量
该方法只需要lParam
的标准4字节,不需要TCITEMHEADER
和SetItemExtra
。您甚至可以定义 std::vector<GPU>
。示例:
std::vector<tabData> m_data;
BOOL CMyDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();
tabData data;
data.gpu.name = L"AMD NVIDIA1";
data.gpu.busid = 101;
m_data.push_back(data);
data.gpu.name = L"AMD NVIDIA2";
data.gpu.busid = 102;
m_data.push_back(data);
return TRUE;
}
void CMyDialog::OnBnClickedAddtab()
{
static int tabCtr = 0;
if(tabCtr >= (int)m_data.size())
return;
TCITEM item = { 0 };
item.pszText = _T("TabX");
item.mask = TCIF_TEXT | TCIF_PARAM;
item.lParam = (LPARAM)&m_data[tabCtr];
tab.InsertItem(tabCtr, &item);
tabCtr++;
}
void CMyDialog::OnBnClickedGetitem0()
{
TCITEM item = { 0 };
item.mask = TCIF_TEXT | TCIF_PARAM;
if(tab.GetItem(tab.GetCurSel(), &item) == TRUE)
{
tabData* ptr = (tabData*)item.lParam;
CString str;
str.Format(L"%d %s", ptr->gpu.busid, ptr->gpu.name.c_str());
GetDlgItem(IDC_STATIC1)->SetWindowText(str);
}
}