Застосування класів С спільно з WinApi при програмуванні для Windows, Visual C
Розглянемо принцип роботи найпростішої програми windows
Як видно з лістингу, мінімальні вимоги для роботи програми такі: При запуску програми повинні відбуватися щонайменше дві події - має бути створене вікно і запущено цикл обробки повідомлень, з якого, з настанням якоїсь події, має бути здійснено вихід та робота програми має завершитися. Все це відбувається, як правило, у функції winmain(), яка є стандартною точкою входу у всі програми для windows. Виходячи з цього, ми з'ясували, що нам знадобиться клас, який запускатиме цикл обробки повідомлень. Назвемо цей класtapplication. Далі у програмі йде реєстрація віконного класу та створення вікна на основі цього класу.
Перша з цих функцій реєструє віконні класи, друга створює вікно. Покладемо на плечі класуtapplication реєстрацію віконних класів. Реєстрацією займатиметься функція bool initialize( hinstance hinstance, lpstr lpcmdline, int ncmdshow). Для цього нам знадобиться додати до класу внутрішній параметр hinstance m_hinstance, він буде відповідати за дескриптор зразка програми. Але у функції winmain є ще два інші параметри. Перший з них це командний рядок програми,другий - спосіб відображення вікна на екрані. Додамо ці параметри до нашого класу.
Також нам потрібні функції доступу до цих змінних. Для цього введемо в наш клас функції, які будуть давати можливість звертатися до цих змінних:
У функцію, що реєструє всі віконні класи, ми будемо передавати всі параметри з функції winmain.
Розберемо детальніше, що робить ця функція. Змінні m_hicon таm_hiconsmall нам знадобляться надалі, коли ми захочемо щоб наша програма мала іконку. А поки що задовольнятимемося тим, що нам може надати система. m_hicon = m_hiconsmall = ::loadicon( null, idi_application ); Далі ми присвоюємо всім внутрішнім змінним передані значення. m_lpcmdline = lpcmdline; m_ncmdshow = ncmdshow; m_hinstance = hinstance; Після чого визначаємо структуру wclass містить інформацію про віконний клас який ми реєструватимемо. Заповнюємо цю структуру стандартними значеннями та намагаємося зареєструвати. Тут mainwindowclass це рядок визначений у файлі globals.h:
Staticwindowproc це основна функція, за допомогою якої клас twindow буде взаємодіяти з системою, а система з класом. Ця функція має бути обов'язково оголошена як callback і бути статичною. Інакше програма не працюватиме. Далі у функції winmain йде функція створення вікна.
Її ми поки що пропустимо і перейдемо на кілька рядків нижче. Якщо програма дісталася цих рядків, це означає, що віконний клас був успішно зареєстрований і вікно створено. Після реєстрації класу та створення вікна програма входить до циклу обробки повідомлень. Цим повинен займатися класtapplication. Для цього додамо до цього класу функцію run().
Повний листинг класу tapplication:Заголовний файл
З класом, який відповідає за ініціалізацію програми, реєстрацію віконних класів та запуск циклу обробки повідомлень розібралися. Займемося реалізацією класу, що відповідає за створення вікна та обробку повідомлень. Для початку розберемося, що цей клас повинен являти собою. Як мінімум він повинен мати процедуру обробки повідомлень та функцію створення вікна. Процедура, яка отримуватиме повідомлення від системита їх обробляти, має бути оголошена якstatic і callback. Модифікатор callback вказує на те, що ця функція викликається операційною системою і є функцією зворотного виклику. Ця функція буде глобальною. Тобто для всіх класів успадкованих від twindow ця функція буде однією і тією ж. А як бути з індивідуальними повідомленнями, що посилаються конкретному вікну? Для цього нам знадобляться ще пара допоміжних класів та функцій:tobjectmanager - пов'язаний список, до якого ми будемо заносити покажчики на всі створені класи.tobject - який одночасно буде і вузлом списку tobjectmanager і базовим класом для всіх об'єктів нашої бібліотеки. Що стосується класуtwindow, що є базовим класом для всіх віконних класів, то в нього нам потрібно додати кілька функцій. Але про них згодом. Повні лістинги класівtobject таtobjectmanager.tobject.h
tobjectmanager.h
tobjectmanager.cpp
Розберемося докладніше у наведеному вище листингу. Що має вміти tobjectmanager? * Додавати об'єкти(вікна до списку). * Видаляти ці об'єкти зі списку, маючи можливість видалити не тільки елемент списку, але й зруйнувати сам об'єкт. * Очищати список з усіх об'єктів. * Здійснювати пошук об'єкта у списку. * Очищати список, видаляючи не тільки елементи списку, але й об'єкти також. * Мати внутрішній лічильник об'єктів, збільшуючи або зменшуючи його при додаванні вузла до списку або видаляючи вузол зі списку відповідно. * Мати конструктор та деструктор. Загалом цей клас працює як звичайний пов'язаний список. У лістингу, грубо кажучи, наведено найпростіший список, адаптований для наших потреб.
Ми також додаликласtwindowlist. Він призначений для того, щоб зберігати вказівники на віконні об'єкти. Ми не повинні використовуватиtobjectmanager для віконних об'єктів з таких причин: При руйнуванніtobjectmanager викликає функціюfree () яка видаляє не тільки вузли списку, але й самі об'єкти. На відміну відtwindowlist, який викликає функціюempty (), що не руйнує об'єкти, а тільки очищає список від вузлів.
Що вона робить ? Якщо об'єкт має дескриптор вікна і вікно з цим дескриптором існує, ми повертаємо цей дескриптор, інакше, повертаємо нульове значення. При виклику функціїstaticwindowproc ми шукатимемо це вікно у списку вікон, і передаватимемо повідомлення саме йому. Пошуком вікон займеться процедура
А якщо існує вікно зі своїм дескриптором, але не є класомtwindow або його спадкоємцем, а нам так хотілося б ним керувати, так само як і свої класом. Вихід є, напишемо функцію, що прикріплює дескриптор існуючого вікна до нашого класу. Функцію назвемоattach. У неї ми передаватимемо дескриптор існуючого вікна і параметр який буде визначати руйнувати нам дескриптор вікна при його знищенні чи ні, а повертати вона буде true якщо дескриптор прикріплений до об'єкта або false якщо ні. За замовчуванням нам не потрібно руйнувати дескриптор. Для цього прапора введемо ще одну змінну:
Декларація функції attach: bool attach(hwnd hwnd,bool bdestroy = false); Також наш об'єкт може бути діалогом або дочірнім вікном багатодокументної програми. Додамо й ці змінні.
Реалізуємо функцію визначальну, чи є наш об'єкт діалогом:
Принцип роботи цієї функції такий: Визначаємо ім'я класу вікна з переданого дескриптора. ЯкщоІм'я класу з'ясувати не вдалося, робимо висновки, що дескриптор липовий і нічого не прикріплюючи, виходимо з функції. Якщо ім'я класу вдалося з'ясувати,то визначаємо, чи передано дескриптор дескриптором діалогу. У системі набір символів #32770 говорить про те, що цей клас є діалогом. Якщо є, присвоюємо змінній m_bisdialog статус, за яким будемо надалі визначати, що наш об'єкт - діалог. Далі просто прирівнюємо дескриптор класу до переданого дескриптора та прапор руйнування до переданого прапора. Після чого отримуємо покажчик на стару віконну процедуру та зберігаємо її в змінній m_lpfnoldwndproc. Зробивши ці операції з'ясовуємо, чи існує стара процедура і чи не є вона нашою статичною процедурою. Якщо немає і немає, то призначаємо віконному дескриптору нову віконну процедуру, яка за сумісництвом і статична процедура. Виходимо з функції, повертаючи параметр true, який красномовно говорить про те, що нам вдалося прикріпити переданий дескриптор до нашого об'єкта.
Функція detach від'єднує дескриптор вікна нашого класу. У цій функції ми спочатку визначаємо чи існує стара процедура і чи вона є нашою поточною статичною процедурою. Якщо так і є, то встановлюємо дескриптору вікна стару віконну процедуру. Створюємо тимчасовий дескриптор вікна, прирівнюючи його до поточного. Кажемо, що наш об'єкт більше не є діалогом. Видаляємо поточний дескриптор вікна. Вилучаємо стару віконну процедуру та повертаємо від'єднаний дескриптор. Якщо ж умова не виконана, то повертаємо нульовий покажчик. Ось ми й дісталися місця, де пора б додати функцію створення самого вікна. Назвемо її create();
Усі параметри взято зі стандартної функціїwinapicreatewindowex (). На самому початку варто перевірити, чи не намагаємося ми створити вікно, яке вже було створено раніше. Якщо намагаємося, кидаємо цю витівку і виходимо. Якщо не намагаємося, заповнюємо структуру створення вікнаcreatestruct переданими параметрами. Функція precreatewindow, яка раніше не описується, перевіряє, передано ім'я класу створюваного вікна чи ні. Якщо не передано, то заповнює ім'я за промовчанням взятим з файлу globals.h
Ця функція віртуальна й у успадкованих класах можна буде перевизначити. Наприклад, її можна буде використовувати при створенні свого класу, що не успадковується від класу twindow.
Після виклику цієї функції намагаємося створити вікно на основі заповненої структури. Додаткові дані вікна записуємо покажчик на наш об'єкт. Він нам знадобиться для ідентифікації нашого класу в функцію staticwindowproc. Якщо вікно створено змінюємо прапор руйнування об'єкта на те, що при знищенні об'єкта дескриптор має бути знищений. Отримуємо стару процедуру вікна. Визначаємо, чи існує стара процедура і чи вона не є нашою поточною статичною процедурою. Якщо всі умови дотримані, встановлюємо нову процедуру вікна та виходимо з функції. Залишилось зробити останній штрих і пробувати нашу бібліотеку в дії. Нам треба заповнити процедуру обробки повідомлень від системи.
Ця функція є найважливішою. Якщо ви не зрозумієте, як працює ця функція, ви не зрозумієте, як працює бібліотека.
А працює ця функція досить легко. На початку операції створення вікна система посилає програмі повідомлення wm_nccreate, що говорить про те, що незабаром буде створено не клієнтську частину вікна. У другому параметрі повідомлення передається покажчик на структуру створенняструктури. Пам'ятайте, мипри створенні вікна, функції create(), передали в додатковий параметр покажчик на наш об'єкт. Нам потрібно виділити його з переданої структури та прирівняти його до нашого тимчасового об'єкта.
Приводячи до типу twindow переданий параметр, ми прирівнюємо раніше створений об'єкт до нашого тимчасового об'єкта. Після того, як ми отримали вказівник на наш об'єкт, нам треба прикріпити до нього переданий дескриптор hwnd.
При отриманні покажчика потрібно прикріпити переданий дескриптор до об'єкта. Але перед тим варто перевірити, чи немає такого об'єкта в нашому списку вікон.
Якщо ні, прикріплюємо дескриптор до об'єкта:
Система викликає цю функцію не тільки під час створення вікна, але й при надсиланні повідомлення. Тому, якщо повідомлення wm_nccreate та wm_initdialog не оброблені, ми просто шукаємо об'єкт у нашому списку вікон за переданим дескриптором.
Далі ми перевіряємо чи існує об'єкт або ми так і не отримали вказівник на нього
Якщо існує, починаємо обробляти передані повідомлення.
Обробка повідомлень.
Так як клас twindow є базовим класом, то всі функції, які ми будемо обробляти, повинні бути віртуальними. Це потрібно для того, щоб класи спадкоємці могли їх перевизначити і обробити по-своєму. Функції базового класу при обробці повідомлень повертають значення -1. Це говорить системі що повідомлення не опрацьоване. Спадкоємці при обробці цих повідомлень повинні повертати значення більше або дорівнює нулю.
Запрошую звернути увагу, що при обробці повідомлення wm_create функція базового класу повертає не -1 а -2. Так як якщо ми повернемо -1 система розцінить це повернення як невдачу при створенні вікна і завершить роботу програми.
У цю функцію передається структура створеннявікна. Після обробки повідомлень ми бачимо код повернення функції. Якщо повідомлення не було опрацьовано (lresult=-1) і дескриптор вікна все ще є дійсним, ми викликаємо процедуру вікна за промовчанням.
Якщо оброблене повідомлення wm_ncdestroy. То ми руйнуємо вікно, від'єднуємо дескриптор від об'єкта та видаляємо віконну процедуру. Якщо повідомлення не оброблено ні в статичній функції, ні в функції за замовчуванням, ми надаємо необроблене повідомлення назад у систему.
Лістинг класу twindowtwindow.h
twindow.cpp
Тестування бібліотеки.
Для того щоб протестувати те, що ми написали зробимо наступне. Створимо проект із одним єдиним файлом. Нехай він називатиметься tetswindow.cpp Напишемо в ньому наступне:
Ну і як?. Обсяг написання програми скоротився у рази? Що ж ми тут робимо? 1. Створюємо новий об'єкт tapplication
2. Створюємо об'єкт вікна.
3. Створюємо саме вікно.
4. Входимо в цикл обробки повідомлень