Рейтинговые книги
Читем онлайн Программирование на Visual C++. Архив рассылки - Алекс Jenter

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 92 93 94 95 96 97 98 99 100 ... 156

Вот и всё. Никакой дополнительной инициализации механизм DDX в WTL не требует. Чтобы выполнить обмен данными, используйте функцию DoDataExchange. Вот её прототип:

BOOL DoDataExchange(BOOL bSaveAndValidate = FALSE, UINT nCtlID = (UINT)-1)

Параметр bSaveAndValidate задаёт направление обмена (FALSE или DDX_LOAD соответствует записи значений из переменных в контролы, а TRUE или DDX_SAVE – из контролов в переменные). Второй параметр задаёт идентификатор контрола, с которым необходимо произвести обмен. Значение по умолчанию (-1) соответствует всем контролам, упомянутым в карте DDX. Функция DoDataExchange возвращает TRUE, если обмен данными был успешным, или FALSE в противном случае.

ПРИМЕЧАНИЕ

В MFC обмен данными осуществляет функция CWnd::UpdateData, похожая на DoDataExchange из wtl. Отличие в том, что функция UpdateData не позволяет задавать идентификатор контрола. Вместо этого она всегда воздействует на все контролы, прописанные в функции CWnd::DoDataExchange. Реализация в wtl несколько гибче, но было бы ещё лучше, если бы разработчики WTL предусмотрели разбиение карты DDX на подкарты (как это сделано для карт сообщений). Часто в реальной программе требуется выполнить обмен данными не с одним контролом и не со всеми контролами, а с некоторым их подмножеством.

Иногда в процессе обмена данными возникают ошибки. Их делят на две разновидности: ошибки обмена (data exchange errors) и ошибки валидации (data validation errors). Ошибки обмена возникают, когда контрол не содержит значения, соответствующего типу связанной с ним переменной (например, поле ввода, связанное с переменной типа int, содержит пробелы или другие нецифровые символы). Ошибки валидации фиксируются в случае несоответствия передаваемого значения и наложенных на него ограничений (максимальная длина строки, минимальное и максимальное значение числа). В случае возникновения ошибки обмена вызывается виртуальная функция OnDataExchangeError, а при возникновении ошибки валидации – виртуальная функция OnDataValidateError. Дальнейший процесс обмена данными прерывается, а DoDataExchange возвращает FALSE, сигнализируя о неуспехе операции.

Класс CWinDataExchange<> предоставляет свои реализации функций OnDataExchangeError и OnDataValidateError. Они обе совершенно одинаковы.

// Overrideables

void OnDataExchangeError(UINT nCtrlID, BOOL /*bSave*/) {

 // Override to display an error message

 ::MessageBeep((UINT)-1);

 T* pT = static_cast<T*>(this);

 ::SetFocus(pT->GetDlgItem(nCtrlID));

}

void OnDataValidateError(UINT nCtrlID, BOOL /*bSave*/, _XData& /*data*/) {

 // Override to display an error message

 ::MessageBeep((UINT)-1);

 T* pT = static_cast<T*>(this);

 ::SetFocus(pT->GetDlgItem(nCtrlID));

}

Как видим, эти функции издают звуковой сигнал и устанавливают фокус ввода на контрол, в котором содержится неверное значение. Вы можете изменить это поведение на любое другое. Обратите внимание на структуру _XData, которая передаётся в функцию OnDataValidateError. Она содержит информацию об ограничении, которое было нарушено. Вот как описана эта структура в файле atlddx.h.

// Helpers for validation error reporting

enum _XDataType {

 ddxDataNull = 0,

 ddxDataText = 1,

 ddxDataInt = 2,

 ddxDataFloat = 3,

 ddxDataDouble = 4

};

struct _XTextData {

 int nLength;

 int nMaxLength;

};

struct _XIntData {

 long nVal;

 long nMin;

 long nMax;

};

struct _XFloatData {

 double nVal;

 double nMin;

 double nMax;

};

struct _XData {

 _XDataType nDataType;

 union {

  _XTextData textData;

  _XIntData intData;

  _XFloatData floatData;

 };

};

Соответственно, в функции OnDataValidateError нужно проанализировать значение поля nDataType и выбрать в зависимости от него структуру textData, intData или floatData, которая и будет содержать информацию о нарушенном ограничении.

ПРИМЕЧАНИЕ

MFC не позволяет повлиять на отображение ошибки валидации. Если вы используете функции DDV_*, вы всегда будете получать сообщение об ошибке валидации в виде message box'а. Изменить это поведение нельзя, можно только отказаться от DDV_*  и использовать для валидации функции "собственного изготовления".

Как это всё работает

Теперь посмотрим, как механизм DDX выглядит "изнутри". К счастью, в его реализации нет ничего сложного. От класса CWinDataExchange<> ваш класс наследует функции DDX_Text, DDX_Int, DDX_Float, DDX_Control, DDX_Check и DDX_Radio, которые и выполняют собственно обмен данными. Некоторые из них перегружены, а DDX_Int и вовсе оформлена как шаблон, что позволяет работать с самыми разными целыми типами.

После обработки препроцессором карта DDX превращается в функцию DoDataExchange. Макросы BEGIN_DDX_MAP и END_DDX_MAP создают пролог и эпилог этой функции. "Заготовка" карты:

BEGIN_DDX_MAP(CMyDialog)

 // Другие макросы карты DDX

END_DDX_MAP()

превращается в:

BOOL DoDataExchange(BOOL bSaveAndValidate = FALSE, UINT nCtlID = (UINT)-1) {

 bSaveAndValidate;

 nCtlID;

 // Другие макросы карты DDX

 return TRUE;

}

Что касается остальных макросов DDX_*, то все они реализованы примерно одинаково. Сначала они сравнивают свой идентификатор контрола nID с идентификатором nCtlID, который был передан в функцию DoDataExchange. Если идентификаторы равны или nCtlID равен -1, макрос вызывает соответствующую функцию DDX_*. Далее проверяется возвращаемое значение, и если оно равно FALSE, обмен данными прекращается. Рассмотрим для примера макросы DDX_TEXT и DDX_TEXT_LEN. Обратите внимание, что они используют одну и ту же функцию DDX_Text, но передают ей разные параметры.

#define DDX_TEXT(nID, var)

 if (nCtlID == (UINT)-1 || nCtlID == nID)

 {

  if (!DDX_Text(nID, var, sizeof(var), bSaveAndValidate))

   return FALSE;

 }

#define DDX_TEXT_LEN(nID, var, len)

 if (nCtlID == (UINT)-1 || nCtlID == nID)

 {

  if (!DDX_Text(nID, var, sizeof(var), bSaveAndValidate, TRUE, len))

  return FALSE;

 }

Теперь мы знаем, как устроены карты DDX. Это может помочь нам писать их более эффективно. Например, мы можем написать в карте DDX следующее:

BEGIN_DDX_MAP(CMyDialog)

 ...

 for (int i=0; i<100; i++)

  DDX_INT(IDC_BASE+i, m_numbers[i]);

 ...

 END_DDX_MAP()

Это гораздо удобнее, чем вставлять в карту 100 записей.

Использование DDX_TEXT

Если с макросами DDX_INT, DDX_UINT и DDX_FLOAT проблем обычно не возникает, то макрос DDX_TEXT может стать источником неприятностей. Чтобы с ними разобраться, рассмотрим реализацию функции DDX_Text.

BOOL DDX_Text(UINT nID, LPTSTR lpstrText, int nSize, BOOL bSave, BOOL bValidate = FALSE, int nLength = 0) {

 T* pT = static_cast<T*>(this);

 BOOL bSuccess = TRUE;

 if (bSave) {

  HWND hWndCtrl = pT->GetDlgItem(nID);

  int nRetLen = ::GetWindowText(hWndCtrl, lpstrText, nSize);

  if (nRetLen < ::GetWindowTextLength(hWndCtrl)) bSuccess = FALSE;

 }

 …

 return bSuccess;

}

Как видим, размер буфера задаётся параметром nSize. Но рассчитывается этот размер по меньшей мере странно:

#define DDX_TEXT(nID, var)

 if (nCtlID == (UINT)-1 || nCtlID == nID)

 {

  if (!DDX_Text(nID, var, sizeof(var), bSaveAndValidate))

   return FALSE;

 }

#define DDX_TEXT_LEN(nID, var, len)

 if (nCtlID == (UINT)-1 || nCtlID == nID)

 {

  if (!DDX_Text(nID, var, sizeof(var), bsaveandvalidate, true, len))

   return FALSE;

 }

Другими словами, за размер буфера принимается размер переменной var, которая связывается с контролом. Отсюда следует два вывода. Во-первых, переменная var может быть только статическим массивом, а динамическим – нет. Во-вторых, в программе, использующей набор символов Unicode, этот размер будет всегда определяться неправильно. Выход в том и в другом случае – отказаться от макроса DDX_TEXT и обратиться к функции DDX_Text напрямую, передав ей правильный размер. Замечу также, что при передаче строки из переменной в контрол размер буфера значения не имеет, так что если вы передаёте данные только в этом направлении, DDX_TEXT использовать можно.

С набором символов Unicode связана ещё одна интересная проблема. Посмотрим на следующую карту DDX:

1 ... 92 93 94 95 96 97 98 99 100 ... 156
На этой странице вы можете бесплатно читать книгу Программирование на Visual C++. Архив рассылки - Алекс Jenter бесплатно.
Похожие на Программирование на Visual C++. Архив рассылки - Алекс Jenter книги

Оставить комментарий