Я создал приложение на основе диалогового окна MFC для изучения управления вкладками. В элементе управления вкладками для каждой вкладки можно установить данные, специфичные для приложения. Я пытаюсь понять, как установить / получить данные для отдельных вкладок элемента управления вкладками.

Вот пример приложения, которое я создаю. Каждая вкладка элемента управления должна хранить некоторую информацию о графическом процессоре.

Tab Control

Насколько я понимаю, есть 3 шага для добавления данных для конкретного приложения.

  1. Создайте определяемую пользователем структуру, 1-й член которой должен иметь тип TCITEMHEADER.

    struct GPU {
        std::wstring name;
        int busid;
    };
    struct tabData {
        TCITEMHEADER tabItemHeader;
        GPU gpu;
    };
    
  2. Сообщите элементу управления вкладкой о дополнительных байтах, которые будет занимать определяемая пользователем структура. Я делаю это в DoDataExchange().

    int extraBytes = sizeof(tabData) - sizeof(TCITEMHEADER);
    auto status = tabCtrl1.SetItemExtra(extraBytes);
    
  3. Установите пользовательские данные при добавлении вкладок.

    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 на изображении).

Tab Control get Item

Какой шаг мне не хватает?
Почему не заполняется структура, связанная с данными, определенными приложением?

Дополнительная информация

Вот полный код приложения.

Файл CPP:

// tabCtrlStackOverflowDlg.cpp : implementation file
//

#include "stdafx.h"
#include "tabCtrlStackOverflow.h"
#include "tabCtrlStackOverflowDlg.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;
};



CtabCtrlStackOverflowDlg::CtabCtrlStackOverflowDlg(CWnd* pParent /*=NULL*/)
  : CDialogEx(IDD_TABCTRLSTACKOVERFLOW_DIALOG, pParent)
{
  m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CtabCtrlStackOverflowDlg::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(CtabCtrlStackOverflowDlg, CDialogEx)
  ON_WM_PAINT()
  ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDADDTAB, &CtabCtrlStackOverflowDlg::OnBnClickedAddtab)
    ON_BN_CLICKED(IDC_GETITEM0, &CtabCtrlStackOverflowDlg::OnBnClickedGetitem0)
    ON_BN_CLICKED(IDCLOSE, &CtabCtrlStackOverflowDlg::OnBnClickedClose)
END_MESSAGE_MAP()


// CtabCtrlStackOverflowDlg message handlers

BOOL CtabCtrlStackOverflowDlg::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 CtabCtrlStackOverflowDlg::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 CtabCtrlStackOverflowDlg::OnQueryDragIcon()
{
  return static_cast<HCURSOR>(m_hIcon);
}



void CtabCtrlStackOverflowDlg::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 CtabCtrlStackOverflowDlg::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 CtabCtrlStackOverflowDlg::OnBnClickedClose()
{
    CDialog::OnCancel();
}


Заголовочный файл:

// tabCtrlStackOverflowDlg.h : header file
//

#pragma once
#include "afxcmn.h"


// CtabCtrlStackOverflowDlg dialog
class CtabCtrlStackOverflowDlg : public CDialogEx
{
// Construction
public:
  CtabCtrlStackOverflowDlg(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();
};


Сводка решения

Из ответа Бармака Шемирани вот 3 причины, по которым мой код не работал. Обязательно прочтите его ответ для лучшего понимания.

  1. TCIF_PARAM должен быть установлен в маске при выполнении TCM_INSERTITEM и TCM_GETITEM.
  2. Я использовал локальные переменные, созданные в стеке (tabData td2; object). Ссылка на эту переменную становилась недействительной, как только она выходила за пределы области видимости.
  3. Использование std :: wstring в структуре, используемой для TCM_INSERTITEM. Лучше использовать типы данных, размер которых можно точно определить (например, простые старые типы данных).

Как отмечает Бармак Шемирани в комментариях, документации для TCITEMHEADER недостаточно. Его ответ дает подробное объяснение.

2
Sahil Singh 2 Янв 2018 в 12:09

1 ответ

Лучший ответ

Конфликт с документацией

Документация для TCITEMHEADER < / a> не упоминает использование флага 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);
    }
}
2
Barmak Shemirani 4 Янв 2018 в 15:24