Как получить дополнительные данные из MFC Tab Control (TabCtrl)?
Я создал диалоговое приложение MFC для изучения управления вкладками. В элементе управления вкладками можно установить специфические данные приложения для каждой вкладки. Я пытаюсь понять, как установить / получить данные для отдельных вкладок элемента управления вкладками.
Вот пример приложения, которое я создаю. Каждая вкладка элемента управления должна хранить некоторую информацию о графическом процессоре.
Как я понимаю, есть 3 шага для добавления данных, специфичных для приложения.
Создайте пользовательскую структуру, чей 1-й член должен иметь тип
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:
// tabCtrlStackruDlg.cpp : implementation file // #include "stdafx.h" #include "tabCtrlStackru.h" #include "tabCtrlStackruDlg.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; }; CtabCtrlStackruDlg::CtabCtrlStackruDlg(CWnd* pParent /*=NULL*/) : CDialogEx(IDD_TABCTRLSTACKOVERFLOW_DIALOG, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CtabCtrlStackruDlg::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(CtabCtrlStackruDlg, CDialogEx) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDADDTAB, &CtabCtrlStackruDlg::OnBnClickedAddtab) ON_BN_CLICKED(IDC_GETITEM0, &CtabCtrlStackruDlg::OnBnClickedGetitem0) ON_BN_CLICKED(IDCLOSE, &CtabCtrlStackruDlg::OnBnClickedClose) END_MESSAGE_MAP() // CtabCtrlStackruDlg message handlers BOOL CtabCtrlStackruDlg::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 CtabCtrlStackruDlg::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 CtabCtrlStackruDlg::OnQueryDragIcon() { return static_cast<HCURSOR>(m_hIcon); } void CtabCtrlStackruDlg::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 CtabCtrlStackruDlg::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 CtabCtrlStackruDlg::OnBnClickedClose() { CDialog::OnCancel(); }
Заголовочный файл:
// tabCtrlStackruDlg.h : header file // #pragma once #include "afxcmn.h" // CtabCtrlStackruDlg dialog class CtabCtrlStackruDlg : public CDialogEx { // Construction public: CtabCtrlStackruDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_TABCTRLSTACKOVERFLOW_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; объект). Ссылка на эту переменную становится недействительной, как только она выходит из области видимости.
- Использование std::wstring в структуре, используемой для
TCM_INSERTITEM
, Лучше использовать типы данных, размер которых можно точно определить (например, простые старые типы данных).
Как отмечает в комментариях Бармак Шемирани, документация для TCITEMHEADER
мало Его ответ дает подробное объяснение.
1 ответ
Конфликт с документацией
В документации для 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
, Затем мы используем lParam
значение в TCITEM
указать на вектор.
Этот метод требует только стандартные 4 байта lParam
не требует 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);
}
}