1.2. Параметризация класса CListCtrlEx в MDI шаблонах функции CMultiDocTemplate

Этот материал ранее был опубликован в виде статьи на http://www.codeproject.com (на английском языке) после обсуждения на форуме http://sql.ru/forum/C++/.

Здесь фактически воспроизводится итоговый материал обсуждения из того же форума:

Параметризация класса CListCtrlEx в MDI шаблонах функции CMultiDocTemplate

Июль 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.

Различные варианты потомков CListCtrl на формах диалогов
Рис. 1 Различные варианты потомков CListCtrl на формах диалогов.

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

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




Сайт создан в системе uCoz