Этот материал ранее был опубликован в виде статьи на 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. В реальном приложении, информация будут браться из некой базы данных.