<Программирование для Windows NT© Александр Фролов, Григорий ФроловТом 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр. Рецензия PC WEEK Приложение MultiMDIВ предыдущем разделе мы привели исходные тексты однооконного мультизадачного приложения. Больший интерес, на наш взгляд, имеет создание многооконного MDI-приложения XE "MDI-приложение" , в котором для каждого окна создается своя задача. В качестве шаблона для создания такого приложения вы можете взять исходные тексты приложения MultiMDI, которые мы приведем в этом разделе. В главном окне приложения MultiMDI вы можете создать дочерние MDI-окна, в которых выполняется циклическое отображение эллипсов случайной формы и цвета (рис. 2.4). Рис. 2.4. Мультизадачное MDI-приложение MultiMDI Кроме того что вы можете создавать дочерние окна и с помощью стандартного для MDI-приложений меню Window изменять расположение дочерних окон и представляющих их в минимизированном виде пиктограмм, у вас есть возможность управлять классом приоритета процесса, в рамках которого выполняется приложение, а также устанавливать относительный приоритет отдельных задач, приостанавливать их, возобновлять выполнение приостановленных задач, удалять задачи и закрывать дочерние окна. С помощью меню Priotitry class, показанном на рис. 2.5, вы можете устанавливать один из четырех классов приоритета текущего процесса. Рис. 2.5. Меню Priotitry class для установки приоритета процесса Если сделать щелчок правой клавишей мыши во внутренней области дочернего MDI-окна, на экране появится плавающее меню, с помощью которого можно управлять задачей, запущенной для данного окна. Это меню показано на рис. 2.6. Рис. 2.6. Плавающее меню, предназначенное для управления задачей, запущенной для MDI-окна С помощью строки Suspend можно приостановить работу задачи, а с помощью строки Resume - восстановить. Так как для каждой задачи система создает счетчик приостановок, то если вы два раза подряд приостановили задачу, выбрав строку Suspend, то для возобновления ее работы вам придется два раза выбрать строку Resume. Изменяя относительный приоритет отдельных окон, вы можете заметить, что скорость перерисовки эллипсов также изменяется. Если вы работаете на быстром компьютере, эффект будет заметнее, если создать не менее 20 - 30 дочерних MDI-окон. Выбрав строку Get Priority, вы можете узнать текущий относительный приоритет для любого дочернего MDI-окна. Значение относительного приоритета будет показано в отдельной диалоговой панели (рис. 2.7). Рис. 2.7. Просмотр относительного приоритета задачи, запущенной для MDI-окна Выбрав строку Kill Thread, вы принудительно завершите работу соответствующей задачи, после чего она не будет отзываться на любые команды. С помощью строки Close Window вы можете закрыть любой дочернее MDI-окно, в том числе то, для которого было выполнено принудительное завершение работы задачи. Дочернее окно можно также закрыть, сделав двойной щелчок левой клавишей мыши по системному меню дочернего окна. В меню Window есть строка Close all, позволяющая закрыть сразу все дочерние MDI-окна. Исходные тексты приложенияВ качестве прототипа для создания исходных текстов приложения MultiMDI мы взяли исходные тексты приложения MDIAPP, описанного в 17 томе “Библиотеки системного программиста”, который называется “Microsoft Windows 3.1 для программиста. Дополнительные главы”. В этом томе описаны принципы работы MDI-приложений, которые в Microsoft Windows NT остались такими же, что и в Microsoft Windows версии 3.1. В те фрагменты кода, которые выполняют создание дочерних MDI-окон, мы внесли небольшие изменения, связанные с использованием мультизадачного режима работы. В частности, для создания MDI-окна мы использовали функцию CreateMDIWindow, которая, как сказано в документации SDK, позволяет создавать для дочерних окон отдельные задачи. Кроме того, для обеспечения необходимой синхронизации главной задачи и задач, запущенных для дочерних MDI-окон, мы создаем критические секции (по одной для каждого дочернего MDI-окна). Главный файл исходных текстов приложения MultiMDI приведен в листинге 2.5. Листинг 2.5. Файл multimdi/multimdi.c #define STRICT #include <windows.h> #include <windowsx.h> #include <stdio.h> #include "resource.h" #include "afxres.h" #include "multimdi.h" // Имена классов окна char const szFrameClassName[] = "MDIAppClass"; char const szChildClassName[] = "MDIChildAppClass"; // Заголовок окна char const szWindowTitle[] = "Multithread MDI Application"; HINSTANCE hInst; HWND hwndFrame; // окно Frame Window HWND hwndClient; // окно Client Window HWND hwndChild; // окно Child Window // Структура, которая создается для каждого дочернего окна typedef struct _CHILD_WINDOW_TAG { // Признак активности задачи BOOL fActive; // Критическая секция для рисования в окне CRITICAL_SECTION csChildWindowPaint; // Идентификатор задачи HANDLE hThread; } CHILD_WINDOW_TAG; typedef CHILD_WINDOW_TAG *LPCHILD_WINDOW_TAG; // ===================================== // Функция WinMain // ===================================== int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями hInst = hInstance; // сохраняем идентификатор приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // Создаем главное окно приложения - Frame Window hwndFrame = CreateWindow( szFrameClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, 0, // задаем размеры и расположение CW_USEDEFAULT, 0, // окна, принятые по умолчанию 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные параметры // Если создать окно не удалось, завершаем приложение if(!hwndFrame) return FALSE; // Рисуем главное окно ShowWindow(hwndFrame, nCmdShow); UpdateWindow(hwndFrame); // Запускаем цикл обработки сообщений while(GetMessage(&msg, NULL, 0, 0)) { // Трансляция для MDI-приложения if(!TranslateMDISysAccel(hwndClient, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // Регистрируем класс для главного окна приложения // (для окна Frame Window) memset(&wc, 0, sizeof(wc)); wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)FrameWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APP_ICON"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1); wc.lpszClassName = (LPSTR)szFrameClassName; aWndClass = RegisterClass(&wc); if(!aWndClass) return FALSE; // Регистрируем класс окна для //дочернего окна Document Window memset(&wc, 0, sizeof(wc)); wc.lpszMenuName = 0; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)ChildWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APPCLIENT_ICON"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = (LPSTR)szChildClassName; aWndClass = RegisterClass(&wc); if(!aWndClass) return FALSE; return TRUE; } // ===================================== // Функция FrameWndProc // ===================================== LRESULT CALLBACK FrameWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HWND hwndChild; HANDLE hThread; DWORD dwIDThread; CHAR szBuf[255]; // Структура для создания окна Client Window CLIENTCREATESTRUCT clcs; // Указатель на структуру для хранения // состояния дочернего окна LPCHILD_WINDOW_TAG lpTag; switch (msg) { // При создании окна Frame Window создаем // окно Client Window, внутри которого будут создаваться // дочерние окна Document Window case WM_CREATE: { // Получаем и сохраняем в структуре clcs идентификатор // временного меню Window. Так как это третье слева // меню, его позиция равна 2 (меню File имеет позицию 0) clcs.hWindowMenu = GetSubMenu(GetMenu(hwnd), 2); // Идентификатор первого дочернего окна Document Window clcs.idFirstChild = 500; // Создаем окно Client Window hwndClient = CreateWindow( "MDICLIENT", // имя класса окна NULL, // заголовок окна WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL, 0, 0, 0, 0, hwnd, // идентификатор родительского окна (HMENU)1, // идентификатор меню hInst, // идентификатор приложения (LPSTR)&clcs);// указатель на дополнительные параметры break; } // Обработка сообщений от главного меню приложения case WM_COMMAND: { switch (wParam) { // Создание нового окна Document Window case CM_FILENEW: { // Используем функцию CreateMDIWindow, которая // специально предназначена для работы с // многозадачными приложениями MDI hwndChild = CreateMDIWindow( (LPSTR)szChildClassName, // класс окна "MDI Child Window", // заголовок окна 0, // дополнительные стили CW_USEDEFAULT, CW_USEDEFAULT, // размеры окна CW_USEDEFAULT, CW_USEDEFAULT, // Document Window hwndClient, // идентификатор окна Client Window hInst, // идентификатор приложения 0); // произвольное значение // Получаем память для структуры, в которой будет // хранится состояние окна lpTag = malloc(sizeof(CHILD_WINDOW_TAG)); // Устанавливаем признак активности lpTag->fActive = 1; // Инициализируем критическую секцию InitializeCriticalSection( &(lpTag->csChildWindowPaint)); // Устанавливаем адрес структуры состояния в // памяти окна SetWindowLong(hwndChild, GWL_USERDATA, (LONG)lpTag); // Создаем задачу для дочернего окна hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadRoutine, (LPVOID)hwndChild, 0,(LPDWORD)&dwIDThread); if(hThread == NULL) { MessageBox(hwnd,"Ошибка при создании задачи", szWindowTitle, MB_OK | MB_ICONEXCLAMATION); } // Сохраняем идентификатор созданной задачи lpTag->hThread = hThread; // Отображаем идентификатор задачи в заголовке // дочернего окна sprintf(szBuf, "Thread ID = %lX", dwIDThread); SetWindowText(hwndChild, szBuf); break; } // Размещение окон Document Window рядом друг с другом case CM_WINDOWTILE: { SendMessage(hwndClient, WM_MDITILE, 0, 0); break; } // Размещение окон Document Window с перекрытием case CM_WINDOWCASCADE: { SendMessage(hwndClient, WM_MDICASCADE, 0, 0); break; } // Размещение пиктограм минимизированых окон // Document Window в нижней части окна Client Window case CM_WINDOWICONS: { SendMessage(hwndClient, WM_MDIICONARRANGE, 0, 0); break; } // Уничтожение всех окон Document Window case CM_WINDOWCLOSEALL: { HWND hwndTemp; // Скрываем окно Client Window для того чтобы // избежать многократной перерисовки окон // Document Window во время их уничтожения ShowWindow(hwndClient, SW_HIDE); while(TRUE) { // Получаем идентификатор дочернего окна // для окна Client Window hwndTemp = GetWindow(hwndClient, GW_CHILD); // Если дочерних окон больше нет, выходим из цикла if(!hwndTemp) break; // Пропускаем окна-заголовки while(hwndTemp && GetWindow(hwndTemp, GW_OWNER)) hwndTemp = GetWindow(hwndTemp, GW_HWNDNEXT); // Удаляем дочернее окно Document Window if(hwndTemp) { // Завершаем задачу, запущенную для окна lpTag = (LPCHILD_WINDOW_TAG)GetWindowLong( hwndTemp, GWL_USERDATA); lpTag->fActive = 0; SendMessage(hwndClient, WM_MDIDESTROY, (WPARAM)hwndTemp, 0); } else break; } // Отображаем окно Client Window ShowWindow(hwndClient, SW_SHOW); break; } // Устанавливаем классы приоритета процесса case ID_PRIORITYCLASS_REALTIME: { SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); break; } case ID_PRIORITYCLASS_HIGH: { SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); break; } case ID_PRIORITYCLASS_NORMAL: { SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); break; } case ID_PRIORITYCLASS_IDLE: { SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS); break; } case CM_HELPABOUT: { MessageBox(hwnd, "Демонстрация использования мультизадачности\n" "в MDI-приложениях\n" "(C) Alexandr Frolov, 1996\n" "Email: frolov@glas.apc.org", szWindowTitle, MB_OK | MB_ICONINFORMATION); break; } // Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); break; } default: break; } // Определяем идентификатор активного окна // Document Window hwndChild = (HWND)LOWORD(SendMessage(hwndClient, WM_MDIGETACTIVE, 0, 0l)); // Если это окно, посылаем ему сообщение WM_COMMAND if(IsWindow(hwndChild)) SendMessage(hwndChild, WM_COMMAND, wParam, lParam); return DefFrameProc( hwnd, hwndClient, msg, wParam, lParam); } case WM_DESTROY: { PostQuitMessage(0); break; } default: break; } return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam); } // ===================================== // Функция ChildWndProc // ===================================== LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc; LPCHILD_WINDOW_TAG lpMyWndTag; HMENU hmenuPopup; POINT pt; CHAR szBuf[256]; switch (msg) { case WM_PAINT: { // Получаем адрес структуры состояния окна lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA); // Входим в критическую секцию EnterCriticalSection(&(lpMyWndTag->csChildWindowPaint)); // Перерисовываем внутреннюю область дочернего окна hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rc); DrawText(hdc, "Child Window", -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hwnd, &ps); // Выходим из критической секции LeaveCriticalSection(&(lpMyWndTag->csChildWindowPaint)); break; } case WM_CLOSE: { // Сбрасываем признак активности задачи lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA); lpMyWndTag->fActive = 0; break; } // Когда пользователь нажимает правую кнопку мыши // в дочернем окне, отображаем плавающее меню case WM_RBUTTONDOWN: { pt.x = LOWORD(lParam); pt.y = HIWORD(lParam); ClientToScreen(hwnd, &pt); hmenuPopup = GetSubMenu( LoadMenu(hInst, "IDR_POPUPMENU"), 0); TrackPopupMenu(hmenuPopup, TPM_CENTERALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, hwnd, NULL); DestroyMenu(hmenuPopup); break; } // Обрабатываем команды, поступающие от плавающего меню case WM_COMMAND: { switch (wParam) { // Приостановка выполнения задачи case ID_THREADCONTROL_SUSPEND: { lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong( hwnd, GWL_USERDATA); // Входим в критическую секцию EnterCriticalSection( &(lpMyWndTag->csChildWindowPaint)); SuspendThread(lpMyWndTag->hThread); // Выходим из критической секции LeaveCriticalSection( &(lpMyWndTag->csChildWindowPaint)); break; } // Возобновление выполнения задачи case ID_THREADCONTROL_RESUME: { lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong( hwnd, GWL_USERDATA); ResumeThread(lpMyWndTag->hThread); break; } // Изменение относительного приоритета case ID_THREADCONTROL_PRIORITYLOWEST: { lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong( hwnd, GWL_USERDATA); SetThreadPriority(lpMyWndTag->hThread, THREAD_PRIORITY_LOWEST); break; } case ID_THREADCONTROL_PRIORITYNORMAL: { lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong( hwnd, GWL_USERDATA); SetThreadPriority(lpMyWndTag->hThread, THREAD_PRIORITY_NORMAL); break; } case ID_THREADCONTROL_PRIORITYHIGHEST: { lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong( hwnd, GWL_USERDATA); SetThreadPriority(lpMyWndTag->hThread, THREAD_PRIORITY_HIGHEST); break; } // Определение и отображение относительного приоритета case ID_THREADCONTROL_GETPRIORITY: { lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong( hwnd, GWL_USERDATA); strcpy(szBuf, "Thread priority: "); switch (GetThreadPriority(lpMyWndTag->hThread)) { case THREAD_PRIORITY_LOWEST: { strcat(szBuf, "THREAD_PRIORITY_LOWEST"); break; } case THREAD_PRIORITY_BELOW_NORMAL: { strcat(szBuf, "THREAD_PRIORITY_BELOW_NORMAL"); break; } case THREAD_PRIORITY_NORMAL: { strcat(szBuf, "THREAD_PRIORITY_NORMAL"); break; } case THREAD_PRIORITY_ABOVE_NORMAL: { strcat(szBuf, "THREAD_PRIORITY_ABOVE_NORMAL"); break; } case THREAD_PRIORITY_HIGHEST: { strcat(szBuf, "THREAD_PRIORITY_HIGHEST"); break; } } MessageBox(hwnd, szBuf, szWindowTitle, MB_OK | MB_ICONINFORMATION); break; } // Удаление задачи case ID_THREADCONTROL_KILLTHREAD: { lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong( hwnd, GWL_USERDATA); TerminateThread(lpMyWndTag->hThread, 5); break; } // Уничтожение дочернего окна case ID_THREADCONTROL_CLOSEWINDOW: { SendMessage(hwndClient, WM_MDIDESTROY, (WPARAM)hwnd, 0); break; } default: break; } break; } default: break; } return DefMDIChildProc(hwnd, msg, wParam, lParam); } // ===================================== // Функция ThreadRoutine // Задача, которая выполняется для каждого // дочернего окна // ===================================== DWORD ThreadRoutine(HWND hwnd) { LONG lThreadWorking = 1L; HDC hDC; RECT rect; LONG xLeft, xRight, yTop, yBottom; short nRed, nGreen, nBlue; HBRUSH hBrush, hOldBrush; LPCHILD_WINDOW_TAG lpMyWndTag; // Определение адреса структуры, содержащей состояние // дочернего окна lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA); // Инициализация генератора случайных чисел srand((unsigned int)hwnd); // Задача работает в бесконечном цикле, // который будет прерван при удалении дочернего окна while(TRUE) { // Получаем признак активности задачи // Если он не равен 1, завершаем задачу if(!lpMyWndTag->fActive) break; // Входим в критическую секцию EnterCriticalSection(&(lpMyWndTag->csChildWindowPaint)); // Отображаем эллипс, имеющий случайный цвет, // форму и расположение // Получаем контекст отображения hDC = GetDC(hwnd); // Получаем случайные цветовые компоненты nRed = rand() % 255; nGreen = rand() % 255; nBlue = rand() % 255; // Получаем случйные размеры эллипса GetWindowRect(hwnd, &rect); xLeft = rand() % (rect.left + 1); xRight = rand() % (rect.right + 1); yTop = rand() % (rect.top + 1); yBottom = rand() % (rect.bottom + 1); // Создаем кисть на основе случайных цветов hBrush = CreateSolidBrush(RGB(nRed, nGreen, nBlue)); // Выбираем кисть в контекст отображения hOldBrush = SelectObject(hDC, hBrush); // Рисуем эллипс Ellipse(hDC, min(xLeft, xRight), min(yTop, yBottom), max(xLeft, xRight), max(yTop, yBottom)); // Выбираем старую кисть в контекст отображения SelectObject(hDC, hOldBrush); // Удаляем созданную кисть DeleteObject(hBrush); // Освобождаем контекст отображения ReleaseDC(hwnd, hDC); // Выходим из критической секции LeaveCriticalSection(&(lpMyWndTag->csChildWindowPaint)); // Выполняем задержку на 1 мс Sleep(1); } // Перед обычным завершением задачи удаляем критическую // секцию и освобождаем память, полученную для // хранения состояния дочернего окна DeleteCriticalSection(&(lpMyWndTag->csChildWindowPaint)); free(lpMyWndTag); // Оператор return завершает выполнение задачи return 0; } Прототипы функций, определенных в приложении MultiMDI, определены в файле multimdi.h (листинг 2.6). Листинг 2.6. Файл multimdi/multimdi.h // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK FrameWndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM); DWORD ThreadRoutine(HWND hwnd); VOID AddThreadToList(HANDLE hThread); Файл resource.h (листинг 2.7), создаваемый автоматически, содержит определение констант для ресурсов приложения MultiMDI. Листинг 2.7. Файл multimdi/resource.h //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by MDIAPP.RC // #define CM_HELPABOUT 100 #define CM_FILEEXIT 101 #define CM_FILENEW 102 #define CM_WINDOWTILE 103 #define CM_WINDOWCASCADE 104 #define CM_WINDOWICONS 105 #define CM_WINDOWCLOSEALL 106 #define ID_THREADCONTROL_SUSPEND 40001 #define ID_THREADCONTROL_RESUME 40002 #define ID_THREADCONTROL_PRIORITYLOWEST 40003 #define ID_THREADCONTROL_PRIORITYNORMAL 40004 #define ID_THREADCONTROL_PRIORITYHIGHEST 40005 #define ID_THREADCONTROL_GETPRIORITY 40006 #define ID_THREADCONTROL_KILLTHREAD 40007 #define ID_THREADCONTROL_CLOSEWINDOW 40008 #define ID_PRIORITYCLASS_REALTIME 40009 #define ID_PRIORITYCLASS_NORMAL 40010 #define ID_PRIORITYCLASS_HIGH 40011 #define ID_PRIORITYCLASS_IDLE 40012 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40013 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif Ресурсы приложения MultiMDI определены в файле mdiapp.rc, который приведен в листинге 2.8. Листинг 2.8. Файл multimdi/mdiapp.rc //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ////////////////////////////////////////////////////////////// // Menu // APP_MENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&New", CM_FILENEW MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END POPUP "Priority class" BEGIN MENUITEM "&Realtime", ID_PRIORITYCLASS_REALTIME MENUITEM "&High", ID_PRIORITYCLASS_HIGH MENUITEM "&Normal", ID_PRIORITYCLASS_NORMAL MENUITEM "&Idle", ID_PRIORITYCLASS_IDLE END POPUP "&Window" BEGIN MENUITEM "&Tile", CM_WINDOWTILE MENUITEM "&Cascade", CM_WINDOWCASCADE MENUITEM "Arrange &Icons", CM_WINDOWICONS MENUITEM "Close &All", CM_WINDOWCLOSEALL END POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END IDR_POPUPMENU MENU DISCARDABLE BEGIN POPUP "&Thread Control" BEGIN MENUITEM "&Suspend", ID_THREADCONTROL_SUSPEND MENUITEM "&Resume", ID_THREADCONTROL_RESUME MENUITEM SEPARATOR MENUITEM "Priority &Lowest", ID_THREADCONTROL_PRIORITYLOWEST MENUITEM "Priority &Normal", ID_THREADCONTROL_PRIORITYNORMAL MENUITEM "Priority &Highest",ID_THREADCONTROL_PRIORITYHIGHEST MENUITEM SEPARATOR MENUITEM "Get Priority", ID_THREADCONTROL_GETPRIORITY MENUITEM SEPARATOR MENUITEM "&Kill Thread", ID_THREADCONTROL_KILLTHREAD MENUITEM "&Close Window", ID_THREADCONTROL_CLOSEWINDOW END END ////////////////////////////////////////////////////////////// // Icon // // Icon with lowest ID value placed first to ensure // application icon // remains consistent on all systems. APP_ICON ICON DISCARDABLE "multimdi.ico" APPCLIENT_ICON ICON DISCARDABLE "mdicl.ico" #ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources ////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. // ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED Определения и глобальные переменныеТак как для запуска задач в приложении MultiMDI мы пользуемся функцией CreateThread, нам не нужно включать файл process.h. В глобальных переменных szFrameClassName и szChildClassName хранятся указатели, соответственно, на имена классов для окна Frame Window и окна Child Window. Адрес заголовка приложения хранится в глобальной переменной szWindowTitle. Переменная hInst используется для хранения идентификатора приложения. Переменные hwndFrame, hwndClient и hwndChild используются, соответственно, для хранения идентификаторов окон Frame Window, Client Window и дочернего окна (в момент его создания). Для каждого дочернего MDI-окна мы создаем структуру типа CHILD_WINDOW_TAG, в которой сохраняем такую информацию, как признак активности задачи, запущенной для этого окна, критическую секцию для рисования в окне, а также идентификатор задачи, запущенной для окна: typedef struct _CHILD_WINDOW_TAG { BOOL fActive; CRITICAL_SECTION csChildWindowPaint; HANDLE hThread; } CHILD_WINDOW_TAG; Кроме того, мы определили указатель на эту структуру: typedef CHILD_WINDOW_TAG *LPCHILD_WINDOW_TAG; Описание функций приложенияПриведем описание функций, определенных в нашем приложении. Для экономии места мы не будем подробно описывать фрагменты кода, связанные с созданием дочерних MDI-окон. При необходимости обращайтесь к 17 тому “Библиотеки системного программиста”, в котором приведена подробная информация о создании MDI-приложений. Функция WinMainФункция WinMain характерна для MDI-приложений. В ней вызывается функция инициализации приложения InitApp, которая регистрирует классы главного окна Frame Window и дочернего MDI-окна Document Window. Затем создается и отображается главное окно, а затем запускается цикл обработки сообщений. В этом цикле вызывается функция трансляции TranslateMDISysAccel, котурую используют MDI-приложения. Функция FrameWndProcЭта функция обрабатывает сообщения для главного окна приложения Frame Window, в частности, сообщения, которые поступают от главного меню приложения. При создании окна функция FrameWndProc получает сообщение WM_CREATE. В ответ на это сообщение она сохраняет в структуре clcs типа CLIENTCREATESTRUCT XE "CLIENTCREATESTRUCT" идентификатор временного меню Window. При создании дочерних MDI-окон это меню будет расширяться. Затем обработчик сообщения WM_CREATE сохраняет в структуре clcs идентификатор первого дочернего MDI-окна, для которого выбрано произвольное число 500. После этого выполняется создание дочернего MDI-окна, для чего вызывается функция CreateWindow. Описание этой функции вы найдете в 11 томе “Библиотеки системного программиста”. Обработчик сообщения WM_COMMAND получает управление, когда пользователь выбирает строки главного меню приложения. Создание нового дочернего окнаПри выборе из меню File строки New создается новое дочернее MDI-окно и задача для него. Первое действие выполняется с помощью функции CreateMDIWindow, специально созданной для мультизадачных MDI-приложений. Задача создается при помощи функции CreateThread XE "CreateThread" . Рассмотрим процесс создания дочернего окна подробнее. Параметры фукнции CreateMDIWindow являются комбинацией значений, сохраняемых в структуре MDICREATESTRUCT XE "MDICREATESTRUCT" полюс идентификатор окна Client Window. Напомним, что адрес этой структуры передается через второй параметр сообщения WM_MDICREATE XE "WM_MDICREATE" , предназначенного для создания дочерних MDI-окон в однозадачных приложениях. Вот как создается такое окно в нашем приложении: hwndChild = CreateMDIWindow( LPSTR)szChildClassName, // класс окна "MDI Child Window", // заголовок окна 0, // дополнительные стили CW_USEDEFAULT, CW_USEDEFAULT, // размеры окна CW_USEDEFAULT, CW_USEDEFAULT, // Document Window hwndClient, // идентификатор окна Client Window hInst, // идентификатор приложения 0); // произвольное значение Вы можете сравнить это со способом, описанным нами в 17 томе “Библиотеки системного программиста” в разделе “Создание и уничтожение окна Document Window”. Для каждого дочернего окна мы создаем и инициализируем структуру типа CHILD_WINDOW_TAG, содержащую такие значения, как идентификатор задачи, запущенной для окна, признак активности задачи и критическую секцию. Память для структуры мы получаем простейшим способом - с помощью функции malloc XE "malloc" : lpTag = malloc(sizeof(CHILD_WINDOW_TAG)); Далее в процессе инициализации этой структуры мы устанавливаем начальное знзачение для признака активности и выполняем инициализацию критической секции: lpTag->fActive = 1; InitializeCriticalSection(&(lpTag->csChildWindowPaint)); Это нужно сделать обязательно до запуска задачи, которая будет пользоваться критической секцией и проверять флаг активности. Адрес структуры мы сохраняем в области данных окна, используя для этого функцию SetWindowLong XE "SetWindowLong" : SetWindowLong(hwndChild, GWL_USERDATA, (LONG)lpTag); Впоследствии этот адрес будет извлечен функцией задачи. На следующем шаге мы выполняем создание задачи для дочернего MDI-окна, которая будет заниматься рисованием произвольных эллипсов. Для запуска используется функция CreateThread XE "CreateThread" : hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadRoutine, (LPVOID)hwndChild, 0,(LPDWORD)&dwIDThread); В качестве третьего параметра мы передаем этой функции адрес функции задачи ThreadRoutine, а в качестве четвертого - идентификатор дочернего MDI-окна, который необходим для выполенния рисования. Заметим, что мы могли бы передать через четвертый параметр функции CreateThread XE "CreateThread" адрес структуры CHILD_WINDOW_TAG, дополнив эту структуру полем для хранения идентификатора дочернего MDI-окна. При этом функция задачи получила бы доступ ко всем необходимым ей параметрам. Однако в нашем приложении мы продемонстрировали оба способа, связанных с использованием параметра функции задачи и памяти окна. После того как задача создана, ее идентификатор сохраняется в структуре CHILD_WINDOW_TAG: lpTag->hThread = hThread; В дальнейшем это значение будет использовано для управления задачей - для ее приостановки, возобновления выполнения, изменения относительного приоритета и так далее. На заключительном шаге в заголовке дочернего MDI-окна отображается системный номер задачи, который записывается функцией CreateThread XE "CreateThread" в переменную dwIDThread: sprintf(szBuf, "Thread ID = %lX", dwIDThread); SetWindowText(hwndChild, szBuf); Напомним, что системный номер задачи и ее идентификатор - разные по смыслу и значению величины. Обработка сообщений от меню WindowМеню Window предназначено для управления дочерними MDI-окнами. Выбирая строки этого меню, пользователь может упорядочить расположение окон одним из двух способов, упорядочить расположение пиктограмм минимизированных окон, а также закрыть все дочерние MDI-окна. Все эти операции выполняются посылкой соответствующих сообщений окну Client Window. Для экономии места мы не будем останавливаться на подробном описании соответствующих фрагментов кода. При необходимости обратитесь к 17 тому “Библиотеки системного программиста”. Обработка сообщений от меню PriorityМеню Priority позволяет изменить класс приоритета процесса, в рамках которого работает приложение. Процессам в нашей книге посвящена отдельная глава, однако изменение класса приоритета - достаточно простая операция, которая выполняется с помощью функции SetPriorityClass XE "SetPriorityClass" : SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); В качестве первого параметра этой функции необходимо передать идентификатор процесса, класс приоритета которого будет изменяться. Мы получаем идентификатор текущего процесса с помощью функции GetCurrentProcess. Через второй параметр функции SetPriorityClass XE "SetPriorityClass" передается новое значение класса приоритета. Функция ChildWndProcФункция ChildWndProc обрабатывает сообщения, поступающие в дочерние MDI-окна. Обработка сообщения WM_PAINTОбработчик сообщения WM_PAINT получает управление, когда возникает необходимость перерисовать внутреннюю область дочернего MDI-окна. Однако заметим, что в то же самое время во внутренней области дочернего окна может рисовать функция задачи, запущенная для этого окна. В результате возникает необходимость выполнить синхронизацию главной задачи приложения и задачи дочернего окна. Так как задачи, запущенные для дочерних окон, работают независимо друг от друга и от главной задачи приложения, мы будем выполнять синхронизацию каждой задачи при помощи отдельной критической секции. Эта критическая секция располагается в структуре типа CHILD_WINDOW_TAG и ее инициализация была выполнена перед созданием соответствующей задачи. Для получения адреса структуры, который был записан в память дочернего окна, мы вызываем функцию GetWindowLong: lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA); После этого выполняется вход в критическую секцию, получение контекста отображения, рисование во внутренней области дочернего MDI-окна строки Child Window, освобождение контекста отображения и выход из критической секции: EnterCriticalSection(&(lpMyWndTag->csChildWindowPaint)); hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rc); DrawText(hdc, "Child Window", -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hwnd, &ps); LeaveCriticalSection(&(lpMyWndTag->csChildWindowPaint)); Обработка сообщения WM_CLOSEЭто сообщение поступает в функцию дочернего окна при уничтожении последнего (например, если пользователь сделал двойной щелчок левой клавишей мыши по пиктограмме системного меню дочернего MDI-окна). Задача обработчика сообщения WM_CLOSE заключается в сбросе признака активности задачи, в результате чего задача завершает свою работу: lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA); lpMyWndTag->fActive = 0; Обработка сообщения WM_RBUTTONDOWNДля управления задачами, запущенными в дочерних MDI-окнах, в нашем приложении используется плавающее меню. Это меню создается когда пользователь делает щелчок правой клавишей мыши во внутренней области дочернего MDI-окна. Плавающее меню определено в ресурсах приложения с идентификатором IDR_POPUPMENU. Оно отображается на экране с помощью функции TrackPopupMenu. Соответствующая техника была нами описана в главе “Меню” 13 тома “Библиотеки системного программиста”. Обработка сообщения WM_COMMANDСообщение WM_COMMAND поступает в функцию дочернего MDI-окна, когда пользователь выбирает строки плавающего меню. Если пользователь выбирает из этого меню, например, строку Suspend, выполняется приостановка работы задачи, запущенной для данного дочернего MDI-окна. Приостановка выполняется при помощи функции SuspendThread XE "SuspendThread" . Идентификатор задачи, необходимый для нее, извлекается из поля hThread структуры типа CHILD_WINDOW_TAG: lpMyWndTag = LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA); EnterCriticalSection(&(lpMyWndTag->csChildWindowPaint)); SuspendThread(lpMyWndTag->hThread); LeaveCriticalSection(&(lpMyWndTag->csChildWindowPaint)); Для возобновления выполнения приостановленной задачи мы использовали функцию ResumeThread XE "ResumeThread" : ResumeThread(lpMyWndTag->hThread); Заметим, что перед выполнением приостановки задачи мы входим в критическую секцию. Это необходимо для того, чтобы избежать полной блокировки главной задачи процесса в момент, когда блокировка рисующей задачи выполняется после входа одной из задач в критическую секцию. В самом деле, если этой произойдет, главная задача не сможет войти в критическую секцию, так как она уже занята другой задачей. Если при этом задача, вошедшая в критическую секцию, оказалась заблокирована до своего выхода из критической секции, главная задача так и останется в состоянии ожидания. При этом пользователь не сможет, например, работать с меню приложения. Относительный приоритет задачи изменяется фукнцией SetThreadPriority XE "SetThreadPriority" , как это показано ниже: SetThreadPriority(lpMyWndTag->hThread, THREAD_PRIORITY_LOWEST); Если выбрать из плавающего меню строку Get priority, с помощью функции GetThreadPriority XE "GetThreadPriority" определяется текущий относительный приоритет задачи, запущенной для данного дочернего MDI-окна. Значение этого приоритета отображается затем на экране при помощи простейшей диалоговой панели, создаваемой функцией MessageBox. При выборе из плавающего меню строки Kill Thread задача будет принудительно уничтожена функцией TerminateThread XE "TerminateThread" : TerminateThread(lpMyWndTag->hThread, 5); В качестве кода завершения здесь передается произвольно выбранное нами значение 5. С помощью плавающего меню вы можете удалить дочернее MDI-окно, завершив работу соответствующей задачи. Для этого окну Client Window посылается сообщение WM_MDIDESTROY XE "WM_MDIDESTROY" : SendMessage(hwndClient, WM_MDIDESTROY, (WPARAM)hwnd, 0); Все остальное делает обработчик сообщения WM_CLOSE, который получит управление при удалении дочернего MDI-окна. А именно, этот обработчик сбрасывает признак активности задачи, в результате чего задача завершает свою работу. Функция задачи ThreadRoutineЗадача ThreadRoutine запускается для каждого вновь создаваемого дочернего MDI-окна. Ее функция получает один параметр - идентификатор этого дочернего окна, который необходим для выполнения рисования. Другие параметры, нужные для работы функции задачи ThreadRoutine, извлекаются из структуры типа CHILD_WINDOW_TAG. В свою очередь, адрес этой структуры извлекается из памяти окна перед началом цикла рисования: lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA); Бесконечный цикл, в котором работает задача, прерывается в том случае, если признак активности задачи не равен единице: if(!lpMyWndTag->fActive) break; В цикле задача выполняет рисование эллипса случайной формы и расположения. Перед получением контекста отображения задача входит в критическую секцию, которая находится в структуре CHILD_WINDOW_TAG. После выполенния рисования задача освобождает контекст отображения, выходит из критической секции и выполняет небольшую задержку. После прерывания цикла функция задачи удаляет ненужную более критическую секцию и освобождает память, заказанную для структуры CHILD_WINDOW_TAG функцией malloc XE "malloc" : DeleteCriticalSection(&(lpMyWndTag->csChildWindowPaint)); free(lpMyWndTag); Затем задача завершает свое выполнение с помощью оператора return. |