Этот материал ранее был опубликован в виде статьи на http://www.codeproject.com (на английском языке) после обсуждения на форуме http://sql.ru/forum/C++/.
Здесь фактически воспроизводится итоговый материал обсуждения из того же форума:
Июль 2009 года
Продемонстрирована возможность параметризации вызываемых по умолчанию не параметризованных конструкторов в шаблонах документа MFC приложения.
Загрузить демонстрационный файл с www.codeproject.com (требуется регистрация (бесплатная) на этом сайте):
ModifiedListCtrl02.zip - 76.2 Kb
Загрузить демонстрационный файл с sql.ru (сначала просто зайдите на этот сайт, затем воспользуйтесь ссылкой):
ModifiedListCtrl02.zip - 76.2 Kb
Загрузить тот же файл отсюда (переименуйте расширение в zip):
ModifiedListCtrl.002 - 76.2 Kb
В предыдущей статье мы показали модифицированные CListCtrl классы, отображаемые на вкладках потомков CFormView. Однако на различных вкладках отображались одинаковые контролы. Поэтому хотелось бы иметь различные варианты потомков CListCtrl на формах диалогов. Например, как показано на рис. 1.
Чтобы этого достичь, можно предложить несколько вариантов решения. Например,
1. Написать собственный вариант CMultiDocTemplate, использующий параметризованные конструкторы нужных классов. Но этот путь не кажется таким уж необходимым, его можно оставить на крайний случай.
2. Создать несколько потомков классов используемых в CMultiDocTemplate, отличающихся только внутренней параметризацией. Этот способ проще, но более громоздкий. Если мы пожелаем использовать в своей программе сотни таблиц, как это делается, например, в русской учетной программе «1C», версии 7.7, написанной на MFC, то данный метод уже не покажется хорошим.
3. Перейти с MFC на Qt, как иногда советуют некоторые программисты. Но, лично меня это не устраивает.
4. Попробовать передать параметр в не параметризованный конструктор неявно. Скажем так. Допустим, в коде класса CMainApp мы имеем:
CMultiDocTemplate *pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_MAINTYPE, RUNTIME_CLASS(CMainDoc), RUNTIME_CLASS(CTableFrame), // Custom MDI child frame RUNTIME_CLASS(CMainView) ); AddDocTemplate(pDocTemplate);
И мы хотим передать параметр UINT nTable (номер таблицы) в класс CTableFrame. Явным образом этого сделать нельзя, так как функция CMultiDocTemplate вызывает не параметризованный конструктор CTableFrame::CTableFrame() даже если у нас определен параметризованный конструктор. Но если присмотреться внимательно к макросу RUNTIME_CLASS, то увидим, что:
#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
В структуре CTableFrame::classCTableFrame для наших целей можно воспользоваться переменной – членом classCTableFrame.m_pNextClass, при условии, что мы не будем использовать список шаблонов посредством функции AddDocTemplate(pDocTemplate), а вместо этого просто определим массив шаблонов. Итак, мы можем передать нужный нам параметр nTable:
CTableFrame::classCTableFrame.m_pNextClass = reinterpret_cast<CRuntimeClass *>(nTable);
Причем эта строчка кода должна располагаться перед строкой:
CMultiDocTemplate *pDocTemplate;
Чтобы вытащить эту переменную в классе CTableFrame проделаем обратную операцию:
///////////////////////////////////////////////////////////////////////// // CTableFrame construction ///////////////////////////////////////////////////////////////////////// CTableFrame::CTableFrame() { m_nTable = (UINT) reinterpret_cast<DWORD>( classCTableFrame.m_pNextClass); }
Таким образом, мы получили как бы параметризованный конструктор CTableFrame::CTableFrame(). Аналогичную процедуру можно проделать для остальных классов используемых в функции CMultiDocTemplate.
Этот метод вполне работоспособный и его можно было бы использовать, если бы не еще один способ неявной параметризации, причем достаточно очевидный и простой, но почему-то не пришедший на ум сразу .
5. Передача параметров в шаблоны классов с помощью глобальных переменных приложения. Только вместо переменой UINT nTable мы воспользуемся перечислением, определенным в Main.h для удобства манипуляции большим количеством таблиц:
///////////////////////////////////////////////////////////////////////// // ETABLE ///////////////////////////////////////////////////////////////////////// enum ETABLE { e_NULL = 0, // Empty Form Index e_First, e_Second, e_Third, e_MAX };
и т.д., для наших демонстрационных целей достаточно этого. Далее, определим в Main.h глобальную переменную ETABLE m_eTable, которая по умолчанию равна e_NULL. Для примера воспользуемся обработчиком:
///////////////////////////////////////////////////////////////////////// // OnFileNew ///////////////////////////////////////////////////////////////////////// void CMainApp::OnFileNew() { m_eTable = (ETABLE) ((UINT) m_eTable + 1); if(m_eTable >= e_MAX) { _M("No data for new table!"); return; } CWinApp::OnFileNew(); } // OnFileNew
Получить доступ к этой переменной не представляет никаких сложностей. Например, в классе CMainView:
///////////////////////////////////////////////////////////////////////// // CMainView construction ///////////////////////////////////////////////////////////////////////// CMainView::CMainView() : CFormView(CMainView::IDD) { //*** Main Application Pointer CMainApp *pMainApp = reinterpret_cast<CMainApp *>(AfxGetApp()); if(!pMainApp) { _M("CMainView: Empty object of the CMainApp class!"); return; } //*** Table Id m_eTable = pMainApp->m_eTable; //*** The Meta Table Structure m_MetaTable = pMainApp->m_aMetaTable[m_eTable]; } // CMainView
Здесь параметры ETABLE m_eTable и META_TABLE m_MetaTable определены в MainView.h. Сама структура META_TABLE определена в StdAfx.h:
//*** The Meta Table Structure typedef struct { TCHAR *szTblName; // Table name DWORD dwStyle; // Table style DWORD dwExStyle; // Extended table style RECT *pFrmRect; // Frame rectangle pointer RECT *pViewRect; // View rectangle pointer CFont *pHdrFont; // Table header font pointer CFont *pListFont; // Table list font pointer UINT nHdrHeight; // Table header height UINT nListHeight; // Table list height UINT nColCount; // Table header columns count UINT nRowCount; // Table list row count TCHAR **apRowText; // Table rows text array META_HEADER *apMetaHeader; // Meta table header pointer } META_TABLE;
Там же определена и структура META_HEADER:
//*** The Meta Table Header Structure typedef struct { TCHAR *szHdrName; // Column name // TCHAR *szFormat; // Table list data format DWORD nAdjust; // Text formatting UINT nWidth; // Column width } META_HEADER;
Пример использования этих параметров показан в следующем коде:
///////////////////////////////////////////////////////////////////////// // OnCreate ///////////////////////////////////////////////////////////////////////// int CMainView::OnCreate(LPCREATESTRUCT pCS) { if(CFormView::OnCreate(pCS) == -1) return -1; //*** Create table CListCtrlEx *pTable = new CListCtrlEx; if(!pTable) { _M("Empty a CListCtrlEx object!"); return -1; } //*** CListCtrlEx initialization if(!pTable->Create( m_MetaTable.dwStyle, *m_MetaTable.pViewRect, this, m_eTable)) { _M("Failed to create a CListCtrlEx object!"); return -1; } //*** Sets extended table style pTable->SetExtendedStyle(m_MetaTable.dwExStyle); //*** Creates a table header CHeaderCtrlEx *pHeader = new CHeaderCtrlEx; if(!pHeader) { _M("Empty CHeaderCtrlEx object!"); return -1; } //*** The CHeaderCtrlEx handle HWND hHeader = pHeader->m_hWnd; //HWND hHeader = pHeader->GetSafeHwnd(); CHeaderCtrl *pOldHeader = pTable->GetHeaderCtrl(); if(!pOldHeader) { _M("Empty CHeaderCtrl object!"); return -1; } //*** The CHeaderCtrl handle HWND hOldHeader = pOldHeader->m_hWnd; //HWND hOldHeader = pOldHeader->GetSafeHwnd(); //*** The table header sub classing if(!pHeader->SubclassWindow(hOldHeader)) { _M("Failed to Subclass a table header!"); return -1; } //*** The structure of a table header cell HDITEM HDItem = {0}; HDItem.mask |= HDI_FORMAT; // The fmt member is valid HDItem.mask |= HDI_TEXT; // The pszText and cchTextMax members are // valid HDItem.mask |= HDI_WIDTH; // The cxy member is valid and specifies the // item's width HDItem.cchTextMax = MAXITEMTEXT; //*** Creates table columns for(UINT i = 0; i < m_MetaTable.nColCount; i++) { META_HEADER *apMetaHeader = m_MetaTable.apMetaHeader; HDItem.pszText = (LPTSTR) apMetaHeader[i].szHdrName; HDItem.fmt = apMetaHeader[i].nAdjust; HDItem.cxy = apMetaHeader[i].nWidth; //*** Calls CHeaderCtrlEx::DrawItem HDItem.fmt |= HDF_OWNERDRAW; //*** Sends too message HDM_LAYOUT pTable->InsertColumn( i, HDItem.pszText, HDItem.fmt, HDItem.cxy ); //*** Reset the first column if(i == 0) pHeader->SetItem(i, &HDItem); } //*** Sets the table rows count in the virtual mode (LVS_OWNERDATA) //*** Send messages LVN_GETDISPINFOW & HDM_LAYOUT //*** Cals the CListCtrlEx::DrawItem pTable->SetItemCount(2*m_MetaTable.nRowCount); // REALLY MUST BE // m_MetaTable.nRowCount return 0; } // OnCreate
Программа показывает три лист-контрола оформленных по-разному с помощью одного класса. Все они вызываются по Ctrl-N. Данные для этих демо списков оформлены в виде статических переменных в классе CMainApp. В реальном приложении, информация будут браться из некой базы данных.