Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ОС_Шеховцов_1.docx
Скачиваний:
73
Добавлен:
09.11.2019
Размер:
14.73 Mб
Скачать

17.3. Графічний інтерфейс користувача

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

Розрізняють два основних підходи до реалізації графічного інтерфейсу кори­стувача. Для першого характерна тісна інтеграція у систему засобів його підтрим­ки (вони, наприклад, можуть бути реалізовані в режимі ядра). Другий реалізує підтримку такого інтерфейсу із використанням набору застосувань і бібліотек рівня користувача, який ґрунтується на засобах підсистеми введення-виведення. У цьому розділі як приклад інтегрованої підтримки графічного інтерфейсу кори­стувача буде описано віконну і графічну підсистеми Windows ХР, а як приклад реалізації його підтримки в режимі користувача - систему X Window.

17.3.1. Інтерфейс віконної та графічної підсистеми Windows хр

У розділі 2 було розглянуто архітектуру віконної та графічної підсистеми Win­dows ХР. У цьому розділі зупинимося на базових принципах організації програм­ного коду для цієї підсистеми [44] і наведемо невеликий приклад застосування, яке використовує її можливості.

Базовим елементом Win32-застосування, яке використовує можливості вікон­ної та графічної підсистеми, є вікно, де це застосування може відображати свою інформацію. Кожне вікно належить деякому класу вікна (window class), який реєструється у системі. Програма починає своє виконання із реєстрації класу вік­на, після цього об'єкт вікна може бути розміщений у пам'яті та відображений.

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

Таку постійну готовність складно реалізувати із використанням послідовного виконання коду. У Win32 (і більшості систем, що реалізують графічний інтер­фейс користувача) використовують інший підхід до організації програмного ко­ду: в ній реалізовані застосування, керовані повідомленнями (message-driven appli­cations). Усі дії користувача (введення із клавіатури, переміщення миші тощо) ОС перехоплює і перетворює у повідомлення (messages), які скеровує застосуван­ню, що володіє вікном, із яким працював користувач. Код застосування містить цикл обробки повідомлень, де відбуваються очікування повідомлень та їхні необ­хідні перетворення, а також оброблювач повідомлень, що його викликають у разі отримання кожного повідомлення. В оброблювачі реалізовано код, який визначає реакцію застосування на ту чи іншу дію користувача. Цикл обробки повідомлень триває доти, поки в нього не потрапить особливе повідомлення, що змушує за­вершити роботу застосування.

Найпростіший приклад реалізації Win32-зacтocyвaння із використанням гра­фічного інтерфейсу користувача наведено нижче.

Розробка головної функції застосування

Win32-зacтocyвaння із підтримкою вікон відрізняються від консольних застосу­вань, описаних дотепер. Насамперед головну функцію таких застосувань визна­чають інакше. її називають WinMainO:

int WINAPI WinMain( HINSTANCE ih. HINSTANCE ip.

LPSTR cmdline. int cmd_show ) {

// код головної функції застосування

де: ih - дескриптор екземпляра застосування, який можна використати для іден­тифікації виконуваної копії застосування;

cmdline — командний рядок, заданий під час запуску застосування; cmdshow - код, що визначає, як передбачено відображати головне вікно засто­сування (SWMAXIMIZE, SW_MINIMIZE тощо).

У коді WinMainO насамперед потрібно зареєструвати клас головного вікна застосування. Клас вікна відображають структурою WNDCLASS, для якої потрібно задати ряд полів, зокрема:

I pszCl assName - ім'я класу, під яким він буде зареєстрований;

♦ lpfnWndProc — адресу процедури вікна, яку система викликатиме з появою по­відомлень, адресованих цьому вікну;

♦ hi соп — дескриптор піктограми цього вікна (вона відображатиметься у його заголовку або на панелі задач; для отримання такого дескриптора використо­вують функцію Load І соп О);

♦ hCursor — дескриптор курсору, що відображатиметься над вікном; для його от­римання використовують функцію LoadCursorO);

♦ hbrBackground — дескриптор спеціального об'єкта (пензля, brush), що визначає фоновий колір цього вікна (стандартний колір фону може бути заданий дода­ванням одиниці до наперед визначеної константи C0L0RWIND0W).

Ось приклад підготовки класу вікна:

WNDCLASS wc = { 0 }: wc.lpszClassName - "myclass";

wc.1pfnWndProc - wnd_proc: // визначення wnd_proc() див. нижче

// стандартна піктограма застосування

wc.hlcon = LoadlconCNULL. IDI_APPLICATION);

// стандартний курсор-стрілка

wc.hCursor = LoadCursor(NULL. IDC_ARR0W):

wc.hbrBackground = (HBRUSH)(C0L0R_WIND0W+1);

Після визначення цієї структури покажчик на неї передається у функцію

RegisterClassO:

RegisterClass(Swc)

Реєстрація класу вікна дає змогу розміщувати у пам'яті та відображати вікна такого класу. Для розміщення вікна у пам'яті використовують функцію Create-Wi ndowO:

HWND CreateWindow(LPCTSTR classname. LPCTSTR title. DWORD style,

int x. int y. int width, int height. HWND ph. HMENU mh.

HINSTANCE ih, LPVOID param );

де: classname — ім'я, під яким був зареєстрований клас цього вікна;

title - текст, відображуваний у заголовку вікна;

style - стиль вікна, який визначає спосіб його відображення (WSOVERLAPPED-

WIND0W - стандартне вікно із заголовком і керуючим меню);

х і у - координати лівого верхнього кута вікна, wi dth і hei ght - його ширина

і висота (ці величини задають у спеціальних віртуальних одиницях, які спро­щують масштабування);

і h - дескриптор застосування, що створює вікно (як значення передають від­-

повідний параметр WinMainO).

Ця функція повертає дескриптор вікна (значення типу HWND), за допомогою якого можна дістати доступ до розміщеного у пам'яті об'єкта вікна, наприклад для його відображення.

HWND hwnd = CreateWindowt"myclass". "Приклад застосування".

WS_OVERLAPPEDWINDOW. 0. 0. 300. 200. NULL, NULL, hinst, NULL );

Для відображення вікна використовують функцію

ShowWindowO: ShowWindowthwnd. cmd_show);

Після того як вікно було відображене, потрібно організувати цикл обробки повідомлень. У ньому необхідно отримати повідомлення за допомогою функції GetMessage(), перетворити його за допомогою функції TranslateMessage() і переда­ти у функцію вікна для подальшої обробки викликом DispatchMessage(). Усі ці функції використовують структуру повідомлення, що належить до типу MSG.

Використання функції TranslateMessageC) необхідне під час обробки повідомлень від клавіатури (натискання клавіш перетворюються у повідомлення, що містять введені символи).

Цикл завершується, коли в нього надходить повідомлення завершення (із ко­дом WMQUIT), внаслідок чого GetMessageO поверне нуль.

MSG msg;

While(GetMessage( &msg. NULL. 0. 0 )) {

TranslateMessaget &msg ):

DispatchMessageC &msg ):

}

Значенням, яке поверне WinMainO, має бути значення поля wParam структури повідомлення:

return (int)msg.wParam;

Розробка функції вікна

Функція вікна визначається так:

long CALLBACK wnd_proc(HWND hwnd. UINT msgcode. WPARAM wp. LPARAM Ip) {

// обробка повідомлень, адресованих вікну

де: hwnd - дескриптор відповідного вікна;

msgcode — код повідомлення, що надійшло;

wp, — додаткові дані, які можуть супроводжувати повідомлення (відпові-

­дають полям wParam і 1 Param структури повідомлення).

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

switch (msgcode) {

case код-повідомленняі:

II дії з обробки повідомленняі

case код-повідомлення2:

// дії з обробки повідомлення2 і т. д.

Є багато різних повідомлень, тут реалізуємо виконання дій у разі одержання двох із них:

♦ WMPAINT — надходить вікну щоразу, коли його вміст потрібно перемалювати (у разі відображення, активізації, переміщення тощо);

♦ WMCL0SE — надходить у разі закриття вікна користувачем.

Про обробку WMPAINT ітиметься окремо, а поки що зупинимося на діях після

отримання WMCL0SE та обробці повідомлень за замовчуванням.

У разі отримання повідомлення WMCL0SE необхідно припинити виконання за­-

стосування, для чого потрібно перервати цикл обробки повідомлень. Стандартний спосіб реалізувати таке переривання - скористатися функцією PostQuitMessage(), яка поміщає в цикл повідомлення із кодом WMQUIT. Як параметр ця функція приймає значення, що стане кодом повернення застосування.

switch (msgcode) { case WM CLOSE:

case WM CLOSE:

PostQuitMessage(O):

return 0

Коли у вікно надійшло повідомлення, яке функція вікна не може обробити, у ній потрібно забезпечити обробку такого повідомлення, передбачену ОС (об­робку за замовчуванням). Для її реалізації необхідно викликати стандартну функ­цію вікна, тобто виконати функцію DefWindowProc(), куди передати ті самі пара­метри, що були отримані функцією вікна.

switch (msgcode) {

// обробка відомих повідомлень

// обробка всіх інших повідомлень

return DefWindowProcChwnd, msgcode. wp, lp);

Відображення графічної інформації та контекст пристрою

Залишилося розглянути особливості обробки повідомлення WMPAINT. Під час цієї обробки потрібно відобразити у вікні певну графічну інформацію. Повідомлення WMPAINT надходитиме у функцію вікна щоразу, коли таке відображення стане не­обхідним, наприклад, вікно повідомлення буде виведене поверх інших вікон. Роз­глянемо, як у Win32 АРІ реалізоване відображення графічної інформації.

Для цього використовують стандартні засоби графічного виведення, які за­безпечує GDI. Основною їхньою особливістю є незалежність від пристрою: на­приклад, один і той самий код можна використати для відображення інформації на екрані і принтері. Найважливішою концепцією відображення при цьому є кон­текст пристрою (device context).

Такий контекст є внутрішньою структурою даних, що описує поточні власти­вості пристрою відображення. Перед виконанням відображення застосування має отримати дескриптор контексту пристрою (об'єкт типу HDC) і передати його як один із параметрів у функцію відображення; після виконання цей контекст потріб­но вивільнити, внаслідок чого у систему повернуться ресурси, які були потрібні для відображення.

Для отримання контексту пристрою найчастіше використовують дві функції: BeginPaintO викликають із коду оброблювача повідомлення WM_PAINT, GetDCO -в усіх інших випадках. Першим параметром у BeginPaint() передають дескриптор вікна, другим - покажчик на структуру PAINTSTRUCT, яку заповнюють інформаці­єю про ділянку відображення (наприклад, про її розміри). Для вивільнення кон­тексту використовують відповідно функції EndPaintO і ReleaseDCO:

PAINTSTRUCT ps: HDC hdc;

switch (msgcode) {

case WM_PAINT:

hdc = BeginPaint( hwnd, &ps ):

II ... відображення графіки з використанням hdc

EndPaintt hwnd, &ps );

return 0;

Після отримання дескриптора контексту пристрою його можна передавати у різні функції відображення графічної інформації (GDI-функції). їх дуже багато, для прикладу опишемо використання найпростішої функції відображення текс­ту - TextOutO:

BOOL TextOut(HDC hdc. int x, int y. LPCTSTR str, int len);

де: hdc — дескриптор контексту пристрою;

x, у — координати точки, із якої почнеться виведення тексту;

str — виведений рядок;

len — довжина рядка без завершального нуля.

Виведення тексту у вікно застосування виглядатиме так:

TextOut(hdc, 10, 10, "Hello, World!", 13):