Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лекція № 20

.doc
Скачиваний:
2
Добавлен:
16.12.2018
Размер:
80.9 Кб
Скачать

Лекція № 20

Тема: Немодальні діалогові вікна

План

  1. Немодальні діалогові вікна

  2. Створення і відображення

  3. Обмін даними і інформацією про стан

Немодальні діалогові вікна

На відміну від модальних діалогових вікон, при відображенні немодальних діалогових вікон операційна система Windows допускає взаємодію користувача з батьківським вікном цього діалогового вікна. При взаємодії користувача з батьківським вікном немодальне діалогове вікно продовжує відображатися на екрані, а також залишатися вище за батьківське вікно в списку порядку накладення вікон (Z-order). І це не дивлячись на те, що фокус має батьківське вікно.

Визначення, створення і контроль за тривалістю існування немодального діалогового вікна здійснюються впродовж семи етапів. Етапи, що є специфічними для немодальних діалогових вікон, детальніше описуються у ряді наступних розділів, а також на прикладі повнофункціонального демонстраційного застосування, що ілюструє ці етапи у дії.

1. Створення ресурсу шаблону діалогового вікна. Звернете увагу, діалогові вікна в немодальному режимі не мають ніяких спеціальних стилів.

2. Створення для шаблону класу, похідного від CDialog. Як і у разі модальних діалогових вікон, для створення класу, похідного від CDialog, застосовується майстер ClassWizard.

3. Оголошення екземпляра класу батьківського вікна. Коли буде ухвалено рішення про той, який саме з класів буде батьком даного немодального діалогового вікна (звичайно це клас уявлення або іншого діалогового вікна), в його складі необхідно визначити переменную-член що або є екземпляром класу немодального діалогового вікна, або містить покажчик на клас немодального діалогового вікна.

4. Створення екземпляра класу діалогового вікна в батьківському класі. Параметри, передані в конструкторі немодального діалогового вікна, зазвичай залежать від способу його взаємодії з батьківським вікном. Діалоговому вікну передають покажчик на батьківське вікно this, що дозволяє йому передавати дані назад екземпляру батьківського вікна.

5. Відображення немодального діалогового вікна. Для відображення немодального діалогового вікна батьківське вікно викликає функцію CDialog : : Create (а також, можливо, і функцію CWnd: : ShowWinclow), докладніша інформація по цій темі приведена в розділі "Створення і відображення немодальних діалогових вікон".

6. Обмін інформацією між кодом батьківського і немодального діалогових вікон. Більшості немодальних діалогових вікон необхідні засоби обміну даними (комунікації) між ним і батьківським вікном. Як правило, для цього застосовуються два підходи. Вони мають на увазі передачу конструктору діалогового вікна покажчика this на батьківське вікно, що дозволить діалоговому вікну безпосередньо викликати функції-члени класу батьківського вікна. Інший підхід, що також має на увазі передачу діалоговому вікну покажчика this на батьківське вікно, полягає в передачі батьківському вікну спеціальних повідомлень. Докладніша інформація по цій темі приведена в розділі "Обмін даними і інформацією про стан".

7. Маніпулювання немодальним діалоговим вікном з батьківського вікна. Мова йде про тому факті, що немодальні діалогові вікна не можуть бути видалені просто так, наприклад, за допомогою виклику функції CDialog: :OnOK або CDialog: .-OnCancel. Ці функції підходять лише для модальних діалогових вікон, а немодальні діалогові вікна, при необхідності видалення, вимушені зв'язуватися з батьківським вікном. Це саме те, що автор має на увазі під терміном обмін інформацією про стан (communicating state). Оскільки для передачі цій інформації батьківському вікну використовується та ж технологія, що і для передачі будь-яких інших даних, вона також розглядається в розділі "Обмін даними і інформацією про стан".

Створення і відображення немодальних діалогових вікон

Як тільки екземпляр класу немодального діалогового вікна буде створений, він викликає функцію Create, що створює і відображає діалогове вікно. Як можна відмітити нижче, функція Create має дві переобтяжені версії.

BOOL Create(LPCTSTR IpszTemplateName, CWnd* pParentWnd = NULL);

BOOL Create(UINT nIDTemplate

CWnd* pParentWnd = NULL);

Дві переобтяжені версії функції Create відповідають двом переобтяженим версіям конструктора CDialog, що створює екземпляр модального діалогового вікна. Причина цілком логічна. Коли додаток створює і відображає модальне діалогове вікно, робота зухвалої функції блокується (blocked) аж до завершення роботи функції DoModal. Отже, створення і відображення діалогового вікна фактично здійснюється усередині тієї ж самої функції. Але відображення немодального діалогового вікна відбувається асихронно (asynchronous), тобто коли зухвала функція викличе функцію Create і немодальне діалогове вікно відобразиться на екрані, функція Create відразу завершить свою роботу. Отже, зухвала функція також, найвірогідніше, встигне завершити свою роботу, тоді як діалогове вікно все ще буде видиме. Проблема полягає в наступному: якби об'єкт класу CDialog був створений в стеку, то при виході з області видимості (out of scope) він був би видалений. В результаті, через декілька мілісекунд після відображення діалогове вікно зникне, оскільки деструкція знищить його. Тому цілком звичайною практикою в додатках стало створення об'єкту класу, похідного від CDialog, в одній функції, збереження покажчика на цей об'єкт діалогового вікна і виклик його функції Create в іншій функції. Як це зробити на практиці, будете продемонстровано на прикладі додатку в справжньому розділі.

Зазвичай виклик функції Create приводить до негайного відображення немодального діалогового вікна, але це відбувається не завжди. Якщо в шаблоні діалогового вікна властивості visible (стиль WS__VISIBLE) немодального діалогового вікна привласнено значення False, то, фактично, вікно буде створено, але видимим на екрані воно не стане. Отже, щоб уникнути подібної ситуації і гарантовано відобразити немодальне діалогове вікно (навіть якщо його властивість visible встановлена в стан False), функцію ShowWindow слід викликати із значенням SW_NORMAL.

dig.Create(IDD_MY_MODELESS_DIALOG);

dig.ShowWindow(SW_NORMAL);

Як відомо, для модального діалогового вікна визначати ресурс шаблону діалогового вікна зазвичай не потрібний. Це пов'язано з тим, що при використанні майстра ClassWizard для створення на підставі шаблону класу, похідного від CDialog, їм буде автоматично створений код, передавальний при зверненні до конструктора CDialog ідентифікатор шаблону Діалогового вікна. Тут відразу дві переваги. По-перше, це ще одне завдання, про виконання якої можна не хвилюватися, створюючи код додатку, і по-друге, це позбавляє розробника від вірогідних помилок в коді створення екземпляра діалогового вікна. По тих Же самих причинах достатні багато розробників, використовуючи немодальні діалогові вікна визначають успадковану функцію Create як захищену (protected) або закриту (private) і створюють свою власну, переобтяжену версію цієї функції, що не приймає параметрів. Переобтяжена версія викликає успадковану, передаючи їй коректний ідентифікатор. Цей підхід використаний і в демонстраційному застосуванні.

І ще одна особливість немодальних діалогових вікон, про яку має сенс згадати. Повернувшись до батьківського вікна, користувач цілком може зробити спробу викликати немодальне діалогове вікно ще раз. Існують два способи вирішення цієї проблеми. Перший з них має на увазі відключення елементу призначеного для користувача інтерфейсу (VI) що дозволяє викликати немодальне діалогове вікно (якщо воно вже відображене на екрані) Наприклад, якщо в меню View (Вигляд) присутній пункт Find (Знайти), що дозволяє відобразити діалогове вікно пошуку, то фрагмент коди обробника даної команди призначеного для користувача інтерфейсу (COMMANDUI), що відключає цей пункт меню до тих пір, поки не буде закрито діалогове вікно пошуку, може виглядати таким чином:

void CMyView::OnUpdateEditFind(CCmdUI *pCmdUI)

{

pCmdUI->Enable(!m_pDlg->GetSafeHwnd();

}

У цій функції використовується клас уявлення (батьківського вікна немодального діалогового вікна), в якому визначена змінна-член, що містить покажчик на немодальне діалогове вікно (m_pDlg). Як вже було сказано, після виконання функції Create і до тих пір, поки діалогове вікно не буде закрито, клас діалогового вікна міститиме цілком допустиме значення дескриптора HWND. Щоб використовувати цей дескриптор або просто з'ясувати, чи активне ще дане діалогове вікно, досить викликати функцію GetSafeHwnd цього класу, що дозволяє легко маніпулювати станом елементів управління (пунктом меню в даному випадку) на підставі наявності або відсутності вікна.

Але авторові особисто цей підхід не подобається, він вважає за краще просто передавати фокус вже відкритому діалоговому вікну, якщо користувач знову вибирає цей пункт:

void CMyView::OnEditFind () {

ASSERT(m_pDlg); if (m_pDlg)

{'

if (!ra_pDlg->GetSafeHwnd()) {

m_pDlg->Create(IDD_FIND, this); m_pDlg->ShowWindow(SW_SHOW); }

else {

m_pDlg->SetFocu.s() ;

}

}

Обмін даними і інформацією про стан

Передача даних і інформації про стан дозволяє вирішити два завдання. По-перше, немодальні діалогові вікна достатньо часто передають дані своєму батьківському вікну. Діалогове вікно 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;

)

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]