如何从MFC选项卡控件(TabCtrl)获取额外数据?

时间:2018-01-02 09:09:01

标签: visual-c++ mfc ctabctrl

我创建了一个基于MFC对话框的应用程序来研究选项卡控件。在选项卡控件中,可以将应用程序特定数据设置到每个选项卡。 我试图了解如何设置/检索选项卡控件的各个选项卡的数据。

这是我正在创建的示例应用程序。控件的每个选项卡都应该存储一些GPU信息。

Tab Control

据我了解,添加应用程序特定数据有3个步骤。

  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);
    
  4. 现在要获取数据,我们只需调用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();
    };
    


    解决方案摘要

    Barmak Shemirani's answer开始,以下是我的代码无效的3个原因。必须阅读他的答案才能更好地理解。

      在执行TCIF_PARAMTCM_INSERTITEM时,必须在掩码中设置
    1. TCM_GETITEM
    2. 我正在使用在堆栈上创建的局部变量(tabData td2; object)。一旦超出范围,对该变量的引用就会变得无效。
    3. 在用于TCM_INSERTITEM的结构中使用std :: wstring。最好使用可以准确确定大小的数据类型(如普通的旧数据类型)。
    4. 正如Barmak Shemirani在评论中指出的那样,TCITEMHEADER的文件很少。他的回答提供了彻底的解释。

1 个答案:

答案 0 :(得分:2)

与文档冲突

TCITEMHEADER的文档未提及使用TCIF_PARAM标记。也许这在文档中是个错误!

如果在调用默认过程后SetItemExtra移动到OnInitDialog,情况会更好。这样可以确保在控件为空时只调用SetItemExtra一次。

结构GPU有一个std::wstring成员,其数据大小在开始时是未知的。除非您有简单的POD结构,否则TCM_INSERTITEM无法复制此数据。

要在选项卡中存储数据,请将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值来指向向量。

此方法只需要lParam的标准4字节,它不需要TCITEMHEADERSetItemExtra。您甚至可以定义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);
    }
}