Обмін даними і інформацією про стан
Передача даних і інформації про стан дозволяє вирішити два завдання. По-перше, немодальні діалогові вікна достатньо часто передають дані своєму батьківському вікну. Діалогове вікно Find, наприклад, повинне повідомити батьківське вікно про вибрані користувачем параметри пошуку, коли він клацає на кнопці Find. По-друге, вивчивши код обробників подій ОПОК і OnCancel базового класу CDialog, можна відмітити, що обидва вони призначені для роботи лише з модальними діалоговими вікнами. В результаті, ці функції нездібні завершити роботу немодального діалогового вікна, тому у власному коді їх завжди доводиться перевизначати. Оскільки ж завершити роботу немодального діалогового вікна? Для цього воно повинне повідомити батьківське вікно про необхідність закрити його. Оскільки передані батьківському вікну дані повинні містити інформацію про те, чи підтвердив користувач операцію або відмінив її (стани ОК або Сапсе1), автор називає це обміном інформацією про стан (communicating state).
Для організації передачі повідомлень між двома вікнами існує декілька можливостей. Оскільки обидва вікна розташовано в одному процесі, можна скористатися як викликом функцій-членів, так і передачею повідомлень. Оскільки виклик функцій класу C++ надзвичайно простий, а застосування обміну повідомленнями надає велику гнучкість, приділимо в цьому розділі увагу останньому підходу. Припустимо, існує клас немодального діалогового вікна на ім'я CMyDialog. Це діалогове вікно, що створюється і відображається батьківським вікном, повинне мати можливість послати дані батьківському вікну. Для реалізації цього підходу необхідно зробити наступне.
1. Спочатку в спільно використовуваному файлі заголовка необхідно помістити визначення нового повідомлення (зазвичай для цього вибирають файл заголовка класу діалогового вікна). Припустимо, належить використовувати вищезазначене немодальне діалогове вікно Find. Приведене нижче визначення повідомлення дозволить звернутися до батьківського вікна і передати йому вибрані користувачем критерії пошуку, а також вказати, що пошук повинен бути виконаний.
#define WM_FINDDATA (WM АРР + 1)
Більшість розробників, пам'ятаючи про те, що повідомлення в діапазоні номерів від 0 до WM_USER - 1 зарезервовані для системних цілей, вважають, що в додатках цілком допустимо використовувати повідомлення, значення яких визначені, починаючи із значення WM_USER. З технічної точки зору це неправильно, а в деяких випадках може привести до проблем, виявити причини яких в коді буде надзвичайне важко. Річ у тому, що діапазон чисел від WM_USER до 0x7FFF призначений спеціально для передачі додатком повідомлень усередині закритого класу вікна, а для повідомлень, що передаються в межах додатку, ці значення використовувати не можна, оскільки в деяких стандартних класах вікна вже визначені повідомлення із значеннями в цьому діапазоні. Ці значення, наприклад, можуть використовувати такі класи елементів управління, як BUTTON, EDIT І LISTBOX. Повідомлення даного діапазону не можна посилати іншим застосуванням, якщо вони не призначені спеціально для обміну повідомленнями саме з цими значеннями.
Таким чином, безпечніше використовувати повідомлення, значення яких визначені в діапазоні від WM_APP до OXBFFF. У документації Microsoft підкреслюється, що цей діапазон зарезервований для обміну повідомленнями усередині додатків і що жодне із значень в цьому діапазоні не співпадає ні з одним з ідентифікаторів системних повідомлень. Щоб закрити тему, опишемо два останні діапазони чисел: від Охссс1 до OXFFFF використовуються для строкових повідомлень, а значення від OXFFFF і далі — зарезервовані для операційної системи Windows.
2. Визначите в класі батьківського вікна обробник повідомлення з новим ідентифікатором. На жаль, середовище розробки Visual Studio не надає для цього ніяких засобів на зразок майстра. Тому всі елементи карти повідомлень доведеться ввести уручну. Для цього у файлі заголовка необхідно за допомогою ключового слова afx_msg визначити обробник повідомлення, що повертає тип long і що приймає параметри WPARAM (типу UINT) і LPARAM (типу LONG):
afx_msg long OnFindData(UINT wParam, LONG IParam);
Якщо відкомпілювати файл, що використовує ключове слово afx_msg, можна відмітити, що в останньому випуску Visual C++ це ключове слово не означає фактично, нічого. Але у визначенні це ключове слово бажане використовувати, щоб уникнути неприємностей при можливих подальших змінах Visual C++.
Визначивши прототип функції, в карту повідомлень необхідно додати новий елемент. В даному випадку новий елемент карти повідомлень призначає повідомленню WM_FINDDATA, як обробник, функцію OnFindData класу батьківського вікна (а саме: класу CMyView, похідного від CView).
BEGIN_MESSAGE__MAP (CMyView, CView)
ON__MESSAGE(WM_FINDDATA, OnFindData)
END_MESSAGE_MAP()
Реалізуйте в класі батьківського вікна обробник OnFindData:
long CMyView::OnFindData(UINT wParam, LONG IParam) {
// Отримати необхідні дані
// параметрів wParam і/або IParam.
//Після закінчення .,.
return 0L;
}
Тепер необхідно упевнитися, що немодальне діалогове вікно здатне взаємодіяти з батьківським. Як вже не раз було сказано, для цього при створенні екземпляра класу діалогового вікна йому досить передати покажчик на батьківське вікно this. Але навіщо конструктору діалогового вікна указувати на це спеціально, адже значенням параметра: за умовчанням є якраз батьківське вікно.
Проблема в тому, що якщо клас батьківського вікна визначений не самостійно, то MFC автоматично вкаже як батьківське вікно для модального діалогового вікна головне вікно додатку. Для діалогового застосування це не проблема, а отже, при створенні екземпляра модального діалогового вікна без вказівки батьківського ніяких неприємностей не буде. Для зв'язку з батьківським діалоговим вікном досить буде викликати функцію GetParent. Але якщо клас уявлення буде батьком немодального діалогового вікна, то можуть виникнути певні проблеми. А саме: відсутність явної передачі покажчика на об'єкт класу уявлення як на батьківське вікно змусить MFC вважати батьківським фреймове вікно, оскільки чисто технічно саме воно і є головним вікном додатку. В результаті, якщо немодальне діалогове вікно створить уявлення і покажчик this на нього не буде переданий явно, то спроба немодального діалогового вікна зв'язатися з вікном уявлення за допомогою функції GetParent опиниться приречена на невдачу, оскільки всі повідомлення йтимуть до фреймового вікна уявлення (яке, нагадаємо, і є формально головним вікном додатку).
Підведемо підсумок. Якщо батьківським є головне діалогове вікно діалогового застосування, то немодапьному діалоговому вікну не потрібний покажчик на батьківське вікно. Для більшості решти випадків передача покажчика this на батьківське вікно потрібна. Тому, щоб зробити свій код діалогового вікна більш живучим, автор вважає за краще передавати покажчик завжди.
Отже, зміните конструктор діалогового вікна так, щоб видалити задану за умовчанням частину вказівки параметрів, і заміните її власною, де батьківське вікно буде визначено явно, Нижче приведений приклад коди до і після зміни:
// ДО
CFindDlg(CWnd* pParent = NULL);
// після
CFindDlg(CWnd* pParent);
Тепер немодальне діалогове вікно може без проблем спілкуватися з батьківським вікном за допомогою функції GetParent і повідомлень із заздалегідь узгодженими ідентифікаторами:
void CFindDlg::OnBnClickedOk ()
{
if (UpdateDataO )
{
CWnd* pParent = GetParent ();
ASSERT(pParent);
if (pParent) pParent->SendMessage(WM_FINDDATA, 0, 0);
}
}
}
Само собою зрозуміло, що передані в параметрах LPARAM і WPARAM дані специфічні для додатку і зрозумілі як батьківському вікну, так і немодальному діалоговому вікну. Зазвичай, якщо передавати доводиться щось відмінне від простого числового значення (яке можна привести до вмісту параметрів LPARAM і WPARAM), то немодальне діалогове вікно заповнює структуру, також зрозумілу обом вікнам (зазвичай її визначають у файлі заголовка класу діалогового вікна), і передає батьківському вікну покажчик на неї.
Коли одне вікно розміщує в пам'яті дані, що підлягають пересилці іншому вікну, завжди виникає питання: "Хто саме нестиме відповідальність за звільнення цієї пам'яті?". Відповідь залежить від області видимості (scope) використовуваних даних. Нагадаємо, існують два способи передачі повідомлень між вікнами: за допомогою функції SendMessage і функції PostMessage. Функція SendMessage подібна до посильного, такого, що доставив пакет і чекаючому під дверима, поки йому не вручать у відповідь послання. Таким чином, робота функції, що викликала функцію SendMessage, буде блокована до тих пір, поки повідомлення не буде гарантовано передано і відповідний обробник не завершить свою роботу. Функція PostMessage, навпаки, подібна до поштової скриньки: опустив лист і пішов далі. Буде воно доставлено одержувачеві чи ні, залежить від пошти, але головне, що передавальна послання сторона нічого не чекатиме, а отже, не буде заблокована. Таким чином, обмін повідомленнями в цьому випадку здійснюється асинхронно.
Так хто ж повинен звільняти пам'ять? у разі застосування функції SendMessage все залежатиме від того, чи збирається вікно одержувача зберігати ці дані для подальшого використання. Оскільки що викликає функція опиниться заблокована до завершення роботи обробника повідомлення, і якщо обробник повідомлення є останнім кодом, що використовує ці дані, то після закінчення його роботи зухвала функція може очистить пам'ять, займану цими даних. З іншого боку, якщо одержуюче повідомлення вікно збирається зберегти ці дані для подальшого використання те очищати займану ними пам'ять потрібно буде тоді, коли ці дані опиняться вже не потрібні. У разі застосування функції PostMessage все значно простіше. Оскільки обмін повідомленнями відбувається асинхронно, очищення пам'яті здійснює приймаюча сторона.
Вивчивши передачу даних від немодального діалогового вікна його батьківському вікну, розглянемо передачу даних про стан. Насправді вона здійснюється точно так, як і передача будь-яких інших даних. Досить визначити повідомлення, у відповідь на яке батьківське вікно видалить немодальне. Автор в своїх застосуваннях організовує зазвичай для цього виклик спеціальної функції в обробнику ОnOк класу немодального діалогового вікна. У даному прикладі з немодальним діалоговим вікном Find батьківському вікну передається повідомлення WM_FINDDATA Так, щоб повідомити батьківське вікно про те, що користувач клацнув в діалоговому вікні на кнопці Close, пошлемо йому заздалегідь певне повідомлення WM__DIALOGCLOSE. В результаті, приведений нижче обробник батьківського вікна видалить діалогове.
long CMyView::OnpialogClose(UINT wParam, LONG IParam)
{
// використовується змінна-член, що ідентифікує
// немодальне діалогове вікно
m_pDlg->DestroyWindow();
return 0L;
}
Таким чином, немодальне діалогове вікно буде видалено коректно.
Але в даному конкретному застосуванні батьківському вікну знадобиться передавати повідомлення про події ОnOк і OnCancel. Тут можливі два підходи. Можна або визначити два окремі повідомлення, або визначити одне повідомлення, але для обох випадків. Оскільки визначення окремих повідомлень вже було описане, розглянемо останній випадок. Як вже було сказано, в передаваному повідомленні містяться дані, за допомогою яких і можна передати батьківському вікну константи Windows IDOK або IDCANCEL, виступаючі в ролі ідентифікатора єдиного повідомлення. Припустимо, наприклад, що в додатку визначено повідомлення WM_DlALOGCLOSE. Згодом немодальне діалогове вікно може передати це повідомлення одним з наступних способів:
// передати батько скому окну повідомлення ОК
pParent->PostMessage(WM_DIALOGCLOSE, IDOK);
// передати батьківському вікну повідомлення CANCEL
pParent->PostMessage(WM_DIALOGCLOSE, IDCANCEL)/
Відповідний обробник повідомлення WM_DIALOGCLOSE в класі батьківського вікна може виглядати таким чином:
long CMyView.-.OnDialogClose (UINT wParam, LONG IParam) { if (wParam == IDOK)
else if (wParam == IDCLOSE)
else ASSERT(FALSE); // перевірка допустимості
p01g->DestroyWindow(); return OL;
)