К вопросу о переменных
Глобальные переменные в многопоточных программах разделяются между всеми потоками в программе. Локальные статические переменные функций также разделяются между всеми потоками, использующими эту функцию. Локальные автоматические переменные в функции являются уникальными для каждого потока, потому что они хранятся в стеке, а каждый поток имеет свой собственный стек.
– 79 –
ЛЕКЦИЯ 8. МНОГООКОННЫЙ ИНТЕРФЕЙС
В настоящее время любые программы, работающие с документами, позволяют одновременно открывать два, три и более файлов. Естественно предположить, что операционная система должна как-то поддерживать эту возможность и предоставлять программам специальные функции API по работе с этим интерфейсом.
Многооконный интерфейс (Multiple Document Interface, MDI) является спецификацией для приложений, которые обрабатывают документы в Microsoft Windows. Спецификация описывает структуру окон и пользовательский интерфейс, который позволяет пользователю работать с несколькими документами внутри одного приложения (с документами в текстовом процессоре или с таблицами в программе электронных таблиц). Точно также, как Windows поддерживает несколько окон приложений на одном экране, приложение MDI поддерживает несколько окон документов в одной рабочей области. Первым приложением MDI для Windows была первая версия Microsoft Excel. Microsoft Word for Windows и Microsoft Access являются приложениями MDI. Хотя спецификация MDI была введена, уже начиная с Windows 2, в то время писать приложения MDI было трудно, и от программиста требовалось большая очень сложная работа. Однако, начиная с Windows 3, большая часть этой работы была сделана. Windows 95 добавляет только одну новую функцию и одно новое сообщение к набору функций, структурам данных и сообщениям, которые существуют специально для упрощения создания приложений MDI.
Элементы MDI
Главное окно приложения программы MDI обычно: в нем имеется строка заголовка, меню, рамка изменения размера, значок системного меню и значки свертывания. Рабочая область, однако, не используется непосредственно для вывода выходных данных программы. В этой рабочей области может находиться несколько дочерних окон, в каждом из которых отображается какой - документ.
Эти дочерние окна выглядят совершенно так же, как обычные окна приложений. В них имеется строка заголовка, рамка изменения размера, значок системного меню, значки свертывания и, возможно, полосы прокрутки. Однако, ни в одном из окон документов нет меню. Меню главного окна приложения относится и к окнам документов.
В каждый конкретный момент времени только одно окно документа активно (об этом говорит выделенная подсветкой строка заголовка) и находится над всеми остальными окнами документов. Все дочерние окна документов находятся только в рабочей области главного окна приложения и никогда не выходят за ее границы.
Поначалу, MDI для Windows-программиста кажется совершенно понятным. Все, что нужно сделать — это создать для каждого документа окно WS_CHILD, делая главное окно приложения родительским окном для окна документа. Но при более близком знакомстве с приложением MDI, таким как Microsoft Excel, обнаруживаются определенные трудности, требующие сложного программирования. Например:
Окно документа MDI может быть свернуто. Соответствующий значок выводится в нижней части рабочей области ( правило: в приложении MDI для главного окна приложения и для каждого типа окна документа будут использоваться разные значки).
Окно документа MDI может быть развернуто. В этом случае строка заголовка окна документа (обычно используется для вывода в окне имени файла документа) исчезает, и имя файла оказывается присоединенным к имени приложения в строке заголовка окна приложения. Значок системного меню окна документа становится первым пунктом строки основного меню окна приложения. Значок для восстановления размера окна документа становится последним пунктом строки основного меню и оказывается крайним справа.
Системные быстрые клавиши для закрытия окна документа те же , что и для закрытия главного окна, за исключением того, что клавиша <Ctrl> используется вместо клавиши <Alt>. Таким образом, комбинация <Alt>+<F4> закрывает окно приложения, а комбинация <Ctrl>+<F4> закрывает окно документа. Вдобавок к остальным быстрым клавишам, комбинация <Ctrl>+<F6> позволяет переключаться между
– 81 –
дочерними окнами документов активного приложения MDI. Комбинация <Alt>+<Spacebar>, как обычно, вызывает системное меню главного окна. Комбинация <Alt>+<-> () вызывает системное меню активного дочернего окна документа .
При использовании клавиш управления курсором для перемещения по пунктам меню, обычно происходит переход от системного меню к первому пункту строки меню. В приложении MDI порядок перехода изменяется: от системного меню приложения — к системному меню активного документа — и далее к первому пункту строки меню.
Если приложение имеет возможность поддерживать несколько типов дочерних окон (электронные таблицы и диаграммы в Microsoft Excel), то меню должно отражать операции, ассоциированные с каждым типом документа. Для этого требуется, чтобы программа изменяла меню программы, когда становится активным окно документа другого типа. Кроме этого, при отсутствии окна документа, в меню должны быть представлены только операции, связанные с открытием нового документа.
В строке основного меню имеется пункт Window. По соглашению, он является последним пунктом строки основного меню, исключая Help. В подменю Window обычно имеются опции для упорядочивания окон документов внутри рабочей области. Окна документов можно расположить cascaded, начиная от верхнего левого угла , или tiled так , что окно каждого документа будет полностью видимо .
Кроме того, в этом подменю имеется перечень всех окон документов. При выборе одного из окон документов, оно выходит на передний план.
Все эти аспекты MDI поддерживаются в Windows.
Windows и MDI
При знакомстве с поддержкой MDI в Windows требуется новая терминология. Окно приложения в целом называется главным окном (frame window). Также как в традиционной программе для Windows, это окно имеет стиль WS_OVERLAPPEDWINDOW.
Приложение MDI также создает окно - (client window) на основе предопределенного класса окна MDICLIENT. Окно создается с помощью вызова функции CreateWindow с использованием этого класса окна и стиля WS_CHILD. Последним параметром функции CreateWindow является указатель на небольшую структуру типа CLIENTCREATESTRUCT. Это окно - охватывает всю рабочую область главного окна и обеспечивает основную поддержку MDI. Цветом окна - является системный цвет COLOR_APPWORKSPACE.
Рис . 11.1. Иерархия родительских и дочерних окон приложения MDI в Windows
Окна документов называются дочерними окнами (child windows). Эти окна создаются путем инициализации структуры типа MDICREATESTRUCT и посылки окну - сообщения WM_MDICREATE с указателем на эту структуру.
Окна документов являются дочерними окнами окна-клиента, которое, в свою очередь, является дочерним окном главного окна. Эта иерархия показана на рис. 11.1
Для главного окна и для каждого типа дочерних окон, которые поддерживаются в приложении, необходим класс окна ( оконная процедура ). Для окна администратора - оконная процедура не нужна, поскольку ее класс окна предварительно зарегистрирован в системе.
Для поддержки MDI в Windows имеется один класс окна, пять функций, две структуры данных и двенадцать сообщений. О классе окна MDICLIENT и структурах данных CLIENTCREATESTRUCT и MDICREATESTRUCT уже упоминалось. Две из пяти функций заменяют в приложениях MDI функцию DefWindowPro c: вместо вызова функции DefWindowProc для всех необрабатываемых сообщений, оконная процедура главного окна вызывает функцию DefFramePro c, а оконная процедура дочернего окна вызывает функцию DefMDIChildPro c. Другая характерная функция MDI TranslateMDISysAccel используется также, как функция TranslateAccelerato r, о которой рассказывалось в Лекции 10.
Если в дочерних окнах MDI выполняются протяженные во времени операции, рассмотрите возможность их запуска в отдельных потоках. Это позволит пользователю покинуть дочернее окно и продолжить работу в другом окне, пока первое дочернее окно решает свою задачу в фоновом режиме. В Windows специально для этой цели имеется новая функция CreateMDIWindo w. Поток вызывает функцию CreateMDIWindow для создания дочернего окна MDI; таким образом окно действует исключительно внутри контекста потока. В программе, имеющей один поток, для создания дочернего окна функция CreateMDIWindow не требуется, поскольку то же самое выполняет сообщение WM_MDICREATE.
Приведем пример - потоковую программы, в которой будет показано девять из двенадцати сообщений MDI. Эти сообщения имеют префикс WM_MDI. Главное окно посылает одно из этих сообщений окну -для выполнения операции над дочерним окном или для получения информации о дочернем окне (главное окно посылает сообщение WM_MDICREATE окну - для создания дочернего окна). Исключение
– 83 –
составляет сообщение WM_MDIACTIVATE: в то время, как главное окно может послать это сообщение окну – для активизации одного из дочерних окон, окно - также посылает сообщение тем дочерним окнам, которые будут активизированы и тем, которые потеряют активность, чтобы проинформировать их о предстоящем изменении.
Пример программы
#include <windows.h>
#include< stdlib.h>
#include "mdidemo.h"
// Предварительное объявление функций
LRESULT CALLBACK FrameWndProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK CloseEnumProc(HWND, LPARAM);
LRESULT CALLBACK HelloWndProc (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK RectWndProc (HWND, UINT, WPARAM, LPARAM);
// Глобальные переменнве
char szFrameClass[] = "MdiFrame";
char szHelloClass[] = "MdiHelloChild";
char szRectClass[] = "MdiRectChild";
HINSTANCE hInst;
HMENU hMenuInit, hMenuHello, hMenuRect;
HMENU hMenuInitWindow, hMenuHelloWindow, hMenuRectWindow;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
HACCEL hAccel;
HWND hwndFrame, hwndClient;
MSG msg;
WNDCLASSEX wndclass;
hInst = hInstance;
if(!hPrevInstance)
{
// Регистрация класса главного окна
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = FrameWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground =(HBRUSH)(COLOR_APPWORKSPACE + 1);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szFrameClass;
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass);
// Регистрация класса окна, в котором выводится фраза "Привет, мир!"
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = HelloWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = sizeof(HANDLE);
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
– 85 –
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szHelloClass;
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass);
// Регистрация класса окна, в котором выводится прямоугольник
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = RectWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = sizeof(HANDLE);
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground =(HBRUSH) GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szRectClass;
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass);
}
// Формирование описателей меню для разных окон
hMenuInit = LoadMenu(hInst, "MdiMenuInit");
hMenuHello = LoadMenu(hInst, "MdiMenuHello");
hMenuRect = LoadMenu(hInst, "MdiMenuRect");
hMenuInitWindow = GetSubMenu(hMenuInit, INIT_MENU_POS);
hMenuHelloWindow = GetSubMenu(hMenuHello, HELLO_MENU_POS);
hMenuRectWindow = GetSubMenu(hMenuRect, RECT_MENU_POS);
// Чтение горячих клавиш
hAccel = LoadAccelerators(hInst, "MdiAccel");
// Создание главного окна
hwndFrame = CreateWindow(szFrameClass, "MDI Demonstration",
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, hMenuInit, hInstance, NULL);
hwndClient = GetWindow(hwndFrame, GW_CHILD);
ShowWindow(hwndFrame, iCmdShow);
UpdateWindow(hwndFrame);
// Цикл обработки сообщений
while(GetMessage(&msg, NULL, 0, 0))
{
if(!TranslateMDISysAccel(hwndClient,& msg) &&
!TranslateAccelerator(hwndFrame, hAccel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Удаление меню
DestroyMenu(hMenuHello);
– 87 –
DestroyMenu(hMenuRect);
return msg.wParam;
}
// оконная процедура главного окна
LRESULT CALLBACK FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hwndClient;
CLIENTCREATESTRUCT clientcreate;
HWND hwndChild;
MDICREATESTRUCT mdicreate;
switch(iMsg)
{
case WM_CREATE : // Создание клиентского окна
clientcreate.hWindowMenu = hMenuInitWindow;
clientcreate.idFirstChild = IDM_FIRSTCHILD;
hwndClient = CreateWindow("MDICLIENT", NULL,
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
0, 0, 0, 0, hwnd,(HMENU) 1, hInst, (LPSTR) &clientcreate);
return 0;
case WM_COMMAND:
switch(wParam)
{
case IDM_NEWHELLO: // Создание дочернего окна, в котором выводится фраза "Привет, мир!"
mdicreate.szClass = szHelloClass;
mdicreate.szTitle = "Hello";
mdicreate.hOwner = hInst;
mdicreate.x = CW_USEDEFAULT;
mdicreate.y = CW_USEDEFAULT;
mdicreate.cx = CW_USEDEFAULT;
mdicreate.cy = CW_USEDEFAULT;
mdicreate.style = 0;
mdicreate.lParam = 0;
hwndChild =(HWND) SendMessage(hwndClient, WM_MDICREATE, 0,
(LPARAM)(LPMDICREATESTRUCT)& mdicreate);
return 0;
case IDM_NEWRECT : // Создание дочернего окна , в котором выводится прямоугольник
mdicreate.szClass = szRectClass;
mdicreate.szTitle = "Rectangles";
mdicreate.hOwner = hInst;
mdicreate.x = CW_USEDEFAULT;
mdicreate.y = CW_USEDEFAULT;
mdicreate.cx = CW_USEDEFAULT;
mdicreate.cy = CW_USEDEFAULT;
mdicreate.style = 0;
mdicreate.lParam = 0;
hwndChild =(HWND) SendMessage(hwndClient, WM_MDICREATE, 0,
(LPARAM)(LPMDICREATESTRUCT)& mdicreate);
return 0;
case IDM_CLOSE : // Закрыти активного окна
hwndChild =(HWND) SendMessage(hwndClient, WM_MDIGETACTIVE, 0, 0);
if (SendMessage(hwndChild, WM_QUERYENDSESSION, 0, 0))
SendMessage(hwndClient, WM_MDIDESTROY, (WPARAM) hwndChild, 0);
return 0;
case IDM_EXIT: // Выход из программы
SendMessage(hwnd, WM_CLOSE, 0, 0);
– 89 –
return 0;
//Сообщения о сортироке окон
case IDM_TILE :
SendMessage(hwndClient, WM_MDITILE, 0, 0);
return 0;
case IDM_CASCADE :
SendMessage(hwndClient, WM_MDICASCADE, 0, 0);
return 0;
case IDM_ARRANGE :
SendMessage(hwndClient, WM_MDIICONARRANGE, 0, 0);
return 0;
case IDM_CLOSEALL : // Закрыть все дочерние окна
EnumChildWindows(hwndClient,& CloseEnumProc, 0);
return 0;
default :
hwndChild =(HWND) SendMessage(hwndClient, WM_MDIGETACTIVE, 0, 0);
if(IsWindow(hwndChild))
SendMessage(hwndChild, WM_COMMAND, wParam, lParam);
break; // ...and then to DefFrameProc
}
break;
case WM_QUERYENDSESSION:
case WM_CLOSE: // Attempt to close all children
SendMessage(hwnd, WM_COMMAND, IDM_CLOSEALL, 0);
if(NULL != GetWindow(hwndClient, GW_CHILD))
return 0;
break;
case WM_DESTROY :
PostQuitMessage(0);
return 0;
}
// Использоваие DefFrameProc(not вместо DefWindowProc
return DefFrameProc(hwnd, hwndClient, iMsg, wParam, lParam);
}
// Функция закрытия окон
BOOL CALLBACK CloseEnumProc(HWND hwnd, LPARAM lParam)
{
if(GetWindow(hwnd, GW_OWNER)) return 1;
SendMessage(GetParent(hwnd), WM_MDIRESTORE,(WPARAM) hwnd, 0);
if(!SendMessage(hwnd, WM_QUERYENDSESSION, 0, 0)) return 1;
SendMessage(GetParent(hwnd), WM_MDIDESTROY,(WPARAM) hwnd, 0);
return 1;
}
// Оконная процедура первого окна
LRESULT CALLBACK HelloWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static COLORREF clrTextArray[] = { RGB(0, 0, 0), RGB(255, 0, 0),
RGB(0, 255, 0), RGB( 0, 0, 255),
RGB(255, 255, 255) };
static HWND hwndClient, hwndFrame;
HDC hdc;
HMENU hMenu;
LPHELLODATA lpHelloData;
PAINTSTRUCT ps;
– 91 –
RECT rect;
switch(iMsg)
{
case WM_CREATE :
// Save some window handles
hwndClient = GetParent(hwnd);
hwndFrame = GetParent(hwndClient);
return 0;
case WM_PAINT :
// Рисование
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd,& rect);
DrawText(hdc, " Привет , Мир !", -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd,& ps);
return 0;
case WM_MDIACTIVATE :
// Установка нового меню
if(lParam ==(LPARAM) hwnd)
SendMessage(hwndClient, WM_MDISETMENU,
(WPARAM) hMenuHello,(LPARAM) hMenuHelloWindow);
DrawMenuBar(hwndFrame);
return 0;
case WM_QUERYENDSESSION :
case WM_CLOSE :
if(IDOK != MessageBox(hwnd, "OK to close window?", "Hello",
MB_ICONQUESTION | MB_OKCANCEL))
return 0;
break;
case WM_DESTROY :
return 0;
}
return DefMDIChildProc(hwnd, iMsg, wParam, lParam);
}
// Оконная процедура второго дочернего окна
LRESULT CALLBACK RectWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hwndClient, hwndFrame;
HBRUSH hBrush;
HDC hdc;
LPRECTDATA lpRectData;
PAINTSTRUCT ps;
int xLeft, xRight, yTop, yBottom;
short nRed, nGreen, nBlue;
switch(iMsg)
{
case WM_CREATE :
// Save some window handles
hwndClient = GetParent(hwnd);
hwndFrame = GetParent(hwndClient);
return 0;
case WM_PAINT : // Рисование
hdc = BeginPaint(hwnd, &ps);
Rectangle(hdc,10,10,200,200);
EndPaint(hwnd,& ps);
return 0;
– 93 –
case WM_MDIACTIVATE : // Установка меню.
if(lParam ==(LPARAM) hwnd)
SendMessage(hwndClient, WM_MDISETMENU,(WPARAM) hMenuRect,
(LPARAM) hMenuRectWindow);
else
SendMessage(hwndClient, WM_MDISETMENU,(WPARAM) hMenuInit,
(LPARAM) hMenuInitWindow);
DrawMenuBar(hwndFrame);
return 0;
case WM_DESTROY :
return 0;
}
// Пересылка необработанных сообщений в DefMDIChildProc
return DefMDIChildProc(hwnd, iMsg, wParam, lParam);
}
Рис . 11.2 Текст программы MDIDEMO
Программа MDIDEMO поддерживает два типа предельно простых окон документов: в одном в центре рабочей области выводится фраза "Hello, World!", а во втором отображается прямоугольник. С каждым из этих двух типов окон документов связано свое меню.
Три меню
Начнем анализ программы с файла описания ресурсов MDIDEMO.RC. В нем определяются три шаблона меню, используемые в программе. Программа выводит на экран меню MdiMenuInit при отсутствии окон документов. Это меню просто позволяет создать новый документ или завершить программу. Меню MdiMenuHello связано с окном документа, в котором выводится фраза "Hello, World!". Подменю File позволяет открыть новый документ любого типа, закрыть активный документ и завершить программу. В подменю Window имеются опции для упорядочивания окон документов в каскадном или мозаичном виде, упорядочивания значков документов и закрытия всех окон. В этом подменю также имеется список всех открытых окон документов.
Меню MdiMenuRect связано с документом с прямоугольником. В заголовочном файле MDIDEMO.Н все идентификаторы меню определяются как три константы:
#define INIT_MENU_POS 0
#define HELLO_MENU_POS 2
#define RECT_MENU_POS 1
Эти идентификаторы задают положение подменю Window в каждом из трех шаблонов меню. Эта информация необходима программе, чтобы информировать окно -клиент о том , когда должен появиться список документов. Конечно, в меню MdiMenuInit нет подменю Window, поэтому в файле обозначено, что список должен быть присоединен к первому подменю. Однако, фактически здесь список никогда не появится. ( он нужен станет ясно при дальнейшем анализе программы .)
Идентификатор IDM_FIRSTCHILD не соответствует ни одному пункту меню. Этот идентификатор будет связан с первым окном документа в списке, который появляется в подменю Window. Значение этого идентификатора должно быть больше, чем значение всех остальных идентификаторов меню.
Дата добавления: 2016-11-22; просмотров: 477;