Многие из Вас наверняка видели в Windows программах окна нестандартной формы (круглые, треугольные и т.д.) и задавали себе вопрос: как мне сделать такое окно? Если прочитать документацию по Visual Basic, то можно сделать вывод, что стандартные средства языка не предоставляют такой возможности. А что же делать, если очень хочется? Тогда следует вспомнить, что в распоряжении программиста на VB есть еще и Windows API, который должен нам в этом помочь.
Теоретические основы
Для начала давайте разберемся, как это можно сделать теоретически. Из документации Windows видно, что каждое окно в системе описывается множеством параметров, из которых нас с Вами интересует . Видимая область окна в системе, создаваемое Visual Basic имеет вид прямоугольника, но, в принципе, ничто не мешает изменить форму этой области. Данная область окна описывается с помощью специального объекта, который называется Region. Регион можно представить в виде поверхности, ограниченной координатами, описываемыми угловые точки этой области. Проще говоря, можно описать область любой формы, затем создать из неё, с помощью специальных функций, регион и его к нужому нам окну.
СОЗДАЁМ ОКНО — C++ WINAPI ЧАСТЬ #1
Существует несколько функций Windows API для создания регионов, основными из которых являются следующие:
- CombineRgn — Комбинирует два региона между собой
- CreateEllipticRgn — Создает регион в виде эллипса или окружности
- CreatePolygonRgn — Создает регион в виде многоугольника
- CreateRectRgn — Создает прямоугольный регион
- CreateRoundRectRgn — Создает регион со скругленными краями из прямоугольной области
- SetWindowRgn — Прикрепляет регион к указанному окну
Я не буду приводить подробное описание этих функций, так как его можно найти в описании Win32 API. Кроме этих функций существуют ещё несколько функций для работы с регионами, но нам они не потребуются.
Создание простых нестандартных окон
Теперь, когда нам известны основные функции, для создания регионов, мы можем применить полученные знания на практике. Загрузите проект pTestRgn и внимательно изучите его код. В этом проете, для изменения формы окна на овальную, используется всего три строки кода и три функции Win32 API. Вначале с помощью CreateEllipticRgn создается регион, затем он прикрепляется к окну и, наконец, завершающая фаза удаление, ставшего ненужным, созданного нами региона. Если же Вы не удалите ненужный Вам больше объект, то Windows, создав регион для Вас будет хранить его в своих и ждать дальнейших указаний по его использованию. В общем, нехорошо выделенную память, и настигнет Вас кара небесная, и затянется небо тучами синими, и будет страшный суд над всеми неверующими: Короче код выглядит так:
Private Sub cmbCreateOval_Click() Dim lRgn As Long lRgn = CreateEllipticRgn(0, 0, Me.ScaleWidth / Screen.TwipsPerPixelX, _ Me.ScaleHeight / Screen.TwipsPerPixelY) SetWindowRgn Me.hwnd, lRgn, True DeleteObject lRgn End Sub
Так же всё просто, скажете Вы? Да, на первый взгляд всё очень просто, но это только кажется. Тот пример, который Вы только что видели, почти не имеет практического применения в настоящих приложениях Windows.
Язык Си — Как создать и открыть окно с помощью функций WinAPI.
Кому же нужно просто овальное окно, которое к тому же жестко задается на этапе программирования? А вот окно, которое свободно могло бы менять свою форму вполне может потребоваться. Примеры? Пожалуйста, WinAmp, Помощник в Microsoft Office и другие программы. Как же там всё это реализовано?
Давайте разберемся с таким применением регионов.
Создание сложных нестандартных окон
Допустим, что у нас есть рисунок в BMP формате, из которого нужно сделать форму, а белый цвет (например) на нём означает . Как же сделать форму? Очень просто, нужно взять все пиксели на рисунке, создать из их координат регион и прикрепить его к нужному нам окну. Анализировать пиксели можно GetPixel, эта функция по координатам возвращает его цвет.
Давайте теперь напишем такой алгоритм для анализа BMP матрицы. Я думаю, что такой алгоритм Вам известен, и мы не будем его подробно разбирать, отмечу только, что анализ производится построчно и Pixel-и добавляются в регион не по одному, а группами построчно. Такой подход сильно экономит ресурсы процессора, выигрыш в производительности достигает 100%.
Public Function lGetRegion(pic As PictureBox, lBackColor As Long) As Long Dim lRgn As Long Dim lSkinRgn As Long Dim lStart As Long Dim lX As Long Dim lY As Long Dim lHeight As Long Dim lWidth As Long ‘создаем пустой регион, с которого начнем работу lSkinRgn = CreateRectRgn(0, 0, 0, 0) With pic ‘подсчитаем размеры рисунка в Pixel lHeight = .Height / Screen.TwipsPerPixelY lWidth = .Width / Screen.TwipsPerPixelX For lX = 0 To lHeight — 1 lY = 0 Do While lY < lWidth ‘ищем нужный Pixel Do While lY < lWidth And GetPixel(.hDC, lY, lX) = lBackColor lY = lY + 1 Loop If lY < lWidth Then lStart = lY Do While lY < lWidth And GetPixel(.hDC, lY, lX) <>lBackColor lY = lY + 1 Loop If lY > lWidth Then lY = lWidth ‘нужный Pixel найден, добавим его в регион lRgn = CreateRectRgn(lStart, lX, lY, lX + 1) CombineRgn lSkinRgn, lSkinRgn, lRgn, RGN_OR DeleteObject lRgn End If Loop Next End With lGetRegion = lSkinRgn End Function
Итак, для проверки на практике этого алгоритма загрузите пример pTestRgnSkin и внимательно изучите его код.
В этом проекте нужный нам рисунок, для удобства, в файле ресурсов, кроме того проект запускается процедурой Main, в которой и происходят все преобразования. Вначале загружается форма, затем в PictureBox из ресурсов загружается нужный нам рисунок, далее вызывается функция, которая создает регион и, наконец, завершающий этап — прикрепление региона к нужному нам окну. Для удобства здесь же вызывается функция, помещающая окно , чтобы оно у Вас на рабочем столе Windows. Кроме того, для нормальной работы программы необходимо, чтобы для PictureBox свойство AutoRedraw было установленно в True, иначе ничего не получится.
Sub Main() Dim lRgn As Long Load frmTestRgnSkin frmTestRgnSkin.pic.Picture = LoadResPicture(101, vbResBitmap) lRgn = lGetRegion(frmTestRgnSkin.pic, vbWhite) SetWindowRgn frmTestRgnSkin.hWnd, lRgn, True DeleteObject lRgn frmTestRgnSkin.Show SetFormPosition frmTestRgnSkin.hWnd, True End Sub
Теперь можно запускать проект. О, знакомое лицо, скажите Вы, это же из Microsoft Office. Да, похож, но не совсем, двигается, а этот нет. Что же нужно сделать, чтобы это окно динамически изменяло свою форму по рисунку, отображаемому в данный момент времени в PictureBox?
Динамическое изменение формы окна
Существуют программы в которых необходимо динамически во время работы изменять форму окна (например анимированный персонаж из Microsoft Office). Все это не очень сложно реализовать, нужно в событие PictureBox.Change добавить следующий код:
lRgn = lGetRegion(frmTestRgnSkin.pic, vbWhite) SetWindowRgn frmTestRgnSkin.hWnd, lRgn, True DeleteObject lRgn SetFormPosition frmTestRgnSkin.hWnd, True
В принципе всё готово, осталось только добавить код для изменения картинки на форме, и оживёт. В нашем примере изменять рисунок будем в Timer циклически, т.е. анимация будет непрерывна, так проще. Итак, добавим на форму Timer и поместим небольшой код, отвечающий за изменения рисунка в PictureBox. Рисунков в файле ресурсов десять штук, поэтому I должно изменяться от 101 до 110. Код изменения выглядит так:
Static i As Long If i < 101 Then i = 101 If i >110 Then i = 101 frmAnimateForm.pic.Picture = LoadResPicture(i, vbResBitmap) i = i + 1
Готово, можно запускать проект, и если Вы счастливый обладатель Pentium III или Athlon, то Вам улыбнется удача, так как будет двигаться. Но если Ваш процессор Pentium II и ниже, то компьютер не сможет выполнять необходимые расчеты за нужное нам время, так как для плавной анимации необходимо (для нашего случая) показывать порядка 15 кадров в секунду, а точнее каждые 80 милисекунд по кадру и ещё оставлять время для других задач компьютера. Как мы видим наши алгоритмы явно не тянут для таких задач и предназначены для не требующих таких быстрых изменений формы окна, так как, например на Celeron 333 один кадр формируется около 100 милисекунд. Что же делать?
Оптимизация алгоритма для быстрой анимации
Анализ работы алгоритма показывает, что наибольшие затраты времени приходятся на функцию GetPixel. Это происходит потому, что анализ картинки идет непосредственно на экране. Единственный путь увеличения быстродействия алгоритма, это перенос анализа в память компьютера и использование при этом Win 32 API. Такие алгоритмы существуют, но это тема отдельного разговора, скажу только, что для оптимизации работы алгоритм пишется отдельно для каждой глубины цвета и при применении такой схемы быстродействие увеличивается почти в четыре раза и позволяет делать практически любую анимацию.
Источник: codenet.ru
Создание полноценной оконной процедуры в Win32 API (Часть 1)
В первой статье по WINAPI мы разобрали программу, которая выводит окно с сообщением. Кроме того, после нажатия «ОК» окно закрывалось. И это всё, что она могла. Нетрудно догадаться, что при помощи Win32 API можно реализовывать более информативные окна. Поговорим о создании настоящей оконной процедуры.
Обобщённый алгоритм создания оконной процедуры в WINAPI:
- Создать две функции: WinMain() – основная функция с параметрами, оговоренными в первой статье – аналог main в консоли, и функцию обрабатывающую процессы (название своё, пусть будет традиционно – WndProc() ), потоки сообщений к/от ОС Windows.
- Создать дескриптор окошка hMainWnd и зарегистрировать класс окошка WNDCLASSEX (то есть указать характеристики окна, которое будем создавать). Должен содержаться в WinMain.
- Создать оболочку нашего окна. Должна содержаться в WinMain.
- Создать циклы для обработки сообщений. Содержатся в функции WndProc.
- Создать функцию для вывода окна ShowWindow и другие вспомогательные.
Теперь будет трудновато, потому что сейчас появятся различные функции с кучей параметров, классы, которые следует знать, понимать, ну и некоторые другие премудрости языков С и С++.
Для начала опишем общую структуру функции WinMain() :
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPreviousInst, LPSTR lpCommandLine, int nCommandShow ) < // создаём дескриптор окна // описываем класс окна // создаём окошко, показываем его на экране // возвращаем значение при неудаче или при выходе >
Для начала создадим дескриптор окна:
HWND hMainWnd; // создаём дескриптор будущего окошка // и т.д.
Чтобы инициализировать поля класса окна, нужно создать его экземпляр, далее через него заполнить поля класса.
Объявление этого класса в windows.h выглядит так:
struct tagWNDCLASSEX< UINT cbSize; // величина структуры (в байтах) UINT style; // стиль класса окошка WNDPROC WndProc; // указатель на имя пользовательской функции int cbWndExtra; // число освобождаемых байтов в конце структуры int cbClsExtra; // число освобождаемых байтов при создании экземпляра приложения HICON hIcon; // дескриптор значка HICON hIconMini; // . маленького значка (в трэе) HCURSOR hCursor; // . курсора мыши HBRUSH hbrBack; // . цвета фона окошка HINSTANCE hInst; // . экземпляра приложения LPCTSTR lpszClassName; // указатель на const-строку, содержащюю имя класса LPCTSTR lpszMenuName; // указатель на const-строку, содержащюю имя меню, применяемого для класса >WNDCLASSEX;
То есть мы должны создать переменную типа WNDCLASSEX , обычно это wc , затем через неё инициализировать поля класса, примерно так:
// создавали дескриптор окна WNDCLASSEX wc; // создаём экземпляр, для обращения к членам класса WNDCLASSEX wc.cbSize = sizeof(wc); // размер структуры (в байтах) wc.lpfnWndProc = WndProc; // указатель на пользовательскую функцию // и т.д.
Это необходимо, чтобы использовать этот класс в дальнейшем (если мы захотим). Это будет шаблоном для создания кучи окон. Конечно, на первых порах нам не нужно столько окошек.
Но регистрация нужна однозначно! Это — формальная сторона. Мы даже можем задать параметры по умолчанию при инициализации полей, а не придумывать каким должно быть окно (о параметрах, которыми должны инициализировать поля класса мы обсудим в следующем уроке).
Итак, ещё раз: если хоть одно поле класса не будет инициализировано, то окно не создастся. Для проверки существует полезная функция RegisterClassEx(). Это и есть следующий этап после заполнения полей класса WNDCLASSEX : обязательная проверка регистрации класса:
// регистрировали класс if(!RegisterClassEx( // в случае отсутствия регистрации класса: MessageBox(NULL, L»Не получилось зарегистрировать класс!», L»Ошибка», MB_OK); return NULL; // возвращаем, следовательно, выходим из WinMain >// и т.д.
Как видим, в этом коде всё просто. Если RegisterClassEx() возвращает адекватный ATOM (таково описание:
ATOM WINAPI RegisterClassEx(const WNDCLASSEX *lpWindowClass);
), то мы выводим соответствующее сообщение, которое мы разбирали в первой статье. Её параметр – это ссылка на экземпляр класса WNDCLASSEX wc .
Следующее, что мы обязаны сделать в функции WinMain() – вызвать функцию CreateWindow() и присвоить её значение дескриптора, который мы создавали на первом этапе. Примерно это так (описание всех этих параметров будет в следующем уроке):
// проверяли, зарегистрирован ли класс hMainWnd = CreateWindow(szClassName, // имя класса L»Полноценная оконная процедура», // имя окна (то что сверху) WS_OVERLAPPEDWINDOW | WS_VSCROLL, // режимы отображения окошка CW_USEDEFAULT, // положение окна по оси х (по умолчанию) NULL, // позиция окна по оси у (раз дефолт в х, то писать не нужно) CW_USEDEFAULT, // ширина окошка (по умолчанию) NULL, // высота окна (раз дефолт в ширине, то писать не нужно) HWND(NULL), // дескриптор родительского окошка (у нас нет род. окон) NULL, // дескриптор меню (у нас его нет) HINSTANCE(hInst), // . экземпляра приложения NULL); // ничего не передаём из WndProc // и т.д.
Описание CreateWindow() в windows.h выглядит так:
HWND CreateWindow( LPСTSTR lpClassName, // имя нашего класса LPCTSTR lpWindowName, // название окошка (надпись сверху) DWORD dwStyle, // стиль окошка int x, // позиция окошка по оси х int y, // позиция окна по оси у (отсчёт вниз) int nWidth, // ширина окошка int nHeight, // высота окошка HWND hWndParent, // идентификатор родительского окошка HMENU hMenu, // . меню HINSTANCE hInst, // дескриптор экз-ра прил-ния LPVOID lParam ); // указатель на данные, передаваемые из пользовательской функции
В случае удачи, функция в переменную hMainWnd возвратит не NULL . Наверное, не трудно догадаться, что надо сделать проверку (по аналогии с RegisterClassEx()) :
// создали окно if(!hMainWnd) < // в случае некорректного создания окна (неверные параметры и тп): MessageBox(NULL, L»Не получилось создать окно!», L»Ошибка», MB_OK); return NULL; // выходим из приложения >// и т.д.
После чего нам необходимо вызвать две функции:
// проверяли, создано ли окно ShowWindow(hMainWnd, nCommandShow); UpdateWindow(hMainWnd); // и т.д.
Подробно описывать их нет надобности, так как имеют несколько параметров.
Первая функция отображает окно на экране ПК. Её первый параметр – дескриптор окошка (он возвращался CreateWindow() ). Второй параметр – стиль отображения. При первом запуске окна должен быть равен последнему параметру функции WinMain() , а в последующие разы можно вписывать свои данные (об этом в следующих статьях).
Вторая функция – видно по названию, отвечает за обновления окошка на экране при сворачиваниях или при динамической информации. Его параметр – всё тот же дескриптор окна.
В функции остался цикл и возвращение значение функции:
// показывали, обновляли окно while(GetMessage( TranslateMessage( DispatchMessage( >return msg.wParam; // всё!
Так как объекты структуры MSG связаны с рассылкой сообщений системному окну и наоборот и это всё содержится в пользовательской функции WndProc , поэтому описание этих функций будет в следующем уроке.
Где-то за 3 урока программа, выводящая полноценную оконную процедуру на экран, будет разобрана и у Вас появятся сведения о том, каким образом они создаются.
Напоследок модифицированная программа, выводящая окно с сообщением (из первой статьи) с другими кнопками:
#include // содержит API // Основная функция: int WINAPI WinMain (HINSTANCE hInst, // дескриптор экземпляра приложения HINSTANCE hPreviousInst, // в Win32 не используется, но объявление нужно LPSTR lpCommandLine, // нужен для запуска окошка в режиме командной строки int nCommandShow) // режим отображения окна < int result = MessageBox(NULL, L»Вам нравится WINAPI?!», L»Задача», MB_ICONQUESTION | MB_YESNO); switch (result) < case IDYES: MessageBox (NULL, L»Продолжайте в том же духе. «, L»Ответ», MB_OK| MB_ICONASTERISK); break; case IDNO: MessageBox (NULL, L»Очень жаль. «, L»Ответ», MB_OK| MB_ICONSTOP); break; >return NULL; >
Скомпилируйте данный код, и Вы обнаружите, что с функцией MessageBox() тоже можно поэкспериментировать.
Черточки между параметрами в строках 10,14 и 17 означают, что параметры используются вместе.
Третьим параметром мы можем записать следующие идентификаторы:
MB_ABORTRETRYIGNORE — три кнопки: ABORT, RETRY, IGNORE MB_CANCELTRYCONTINUE — три кнопки: CANCEL, TRY, CONTINUE MB_HELP MB_OK MB_OKCANCEL — 2 кнопки: OK, CANCEL MB_RETRYCANCEL — 2 кнопки: RETRY, CANCEL MB_YESNO — 2 кнопки: YES, NO MB_YESNOCANCEL — три кнопки: YES, NO, CANCEL
MB_ICONSTOP — выводит крестик MB_ICONQUESTION — …….знак вопроса MB_ICONEXCLAMATION — …….восклицательный знак в треугольнике MB_ICONASTERISK — …….восклицательный знак
А возвращать значения при нажатии вышеуказанных кнопок функция MessageBox будет такие:
IDABORT — при нажатии на ABORT IDCANCEL – …….на кнопку CANCEL IDCONTINUE – ……..на кнопку CONTINUE IDIGNORE – …….на кнопку IGNORE IDNO – …….на кнопку NO IDOK – …….на кнопку OK IDRETRY – …….на кнопку RETRY IDTRYAGAIN – …….на кнопку TRY AGAIN IDYES – …….на кнопку YES
Поэкспериментируйте с данными идентификаторами. С параметрами отображения мы будем иметь дело во многих функциях. На этом всё.
Источник: cppstudio.com
Создание окна в WinAPI
Любое графическое приложение Windows начинается с создания окна. В этой статье я расскажу, как создать простейшее окно на C++ с помощью WinAPI .
1. Для начала создадим пустой проект в Visual Studio.
2. Добавим файл main.cpp
Исходные файлы -> Добавить -> Создать элемент
3. Изменим тип подсистемы на Windows.
Проект -> Свойства -> Свойства конфигурации -> Компоновщик -> Система -> Подсистема -> Windows
4. Чтобы начать работу с WinAPI, подключим заголовочный файл Windows.h
5. Точка входа
Точка входа в WinAPI отличается от точки входа консольного приложения. Она может быть WinMain или wWinMain .
int WINAPI WinMain (
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
);
int WINAPI wWinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nShowCmd
);
Разберем отдельно каждый параметр.
1. hInstance. Это дескриптор нашего приложения. Каждое приложение, запущенное в Windows, имеет свой дескриптор. На самом деле, это просто адрес в памяти, на котором располагается наше приложение.
2. hPrevInstance . Этот параметр всегда равен нулю. Он использовался во времена 16-разрядного Windows, но сейчас не имеет смысла
3. lpCmdLine . Это указатель на командную строку, с которой запускается приложение. Это то же самое, что argv[0] в консоли. В случае WinMain это ASCII-строка, в случае wWinMain — Unicode-строка.
4. nShowCmd. Это флаг, указывающий, будет ли главное окно приложения свернуто, развернуто или показано обычным образом.
*WINAPI — Это соглашение о вызовах. Оно определяет, каким образом в функцию передаются параметры. То же самое, что __stdcall. Можете об этом не задумываться.
6. Класс окна
Прежде чем создать окно, мы должны зарегистрировать его класс. Вот кусок кода:
WNDCLASSW wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = DefWindowProcW;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = L»MAIN_WINDOW_CLASS»;
Разберем отдельно каждую строчку.
1. WNDCLASS. Это структура, которая хранит информацию о классе окна. В именах WinAPI постфикс «W» означает, что мы будем использовать Unicode , а постфикс «A» — ASCII . Мы будем использовать Unicode , значит пишем WNDCLASSW .
2. wc.style — стиль класса окна. CS_HREDRAW означает, что окно перерисовывается, если изменяется ширина окна, а CS_VREDRAW — если изменяется высота
3. wc.lpfnWndProc — указатель на оконную процедуру . Оконная процедура — это процедура, которая обрабатывает сообщения, которые операционная система посылает нашему окну. Саму процедуру мы напишем в следующей статье, а пока укажем в оконную процедуру по умолчанию — DefWindowProc .
4. wc.cbClsExtra — это для прошаренных, просто ставим 0.
5. wc.cbWndExtra — это тоже для прошаренных, просто ставим 0.
6. wc.hInstance — это дескриптор приложения, которое содержит оконную процедуру.
7. wc.hIcon — это дескриптор иконки нашего окна. Функция LoadIconW(NULL, IDI_APPLICATION) загружает иконку по умолчанию.
8. wc.hCursor — это дескриптор курсора нашего окна. Функция LoadCursorW(NULL, IDC_ARROW) загружает курсор по умолчанию.
9. wc.hbrBackground — это дескриптор кисти, которое закрашивает наше окно. Пока ставим NULL .
10. wc.lpszMenuName — это имя меню окна. У нас нет меню, ставим NULL .
11. wc.lpszClassName — это имя класса окна. Мы будем использовать его при создании окна. Префикс «L» означает, что мы используем Unicode.
12. RegisterClassW(MAIN_WINDOW_CLASS», L»Simple Window», WS_OVERLAPPEDWINDOW, 200, 200, 1280, 720, NULL, NULL, hInstance, NULL);
- Первый параметр — имя класса окна, которое мы указали в wc.lpszClassName.
- Второй параметр — имя самого окна. Этот текст будет написан в заголовке окна.
- Третий параметр — стиль окна. WS_OVERLAPPEDWINDOW — стиль перекрывающегося окна. Подробнее про стили окон можно прочитать в официальной документации Microsoft .
- Четвертый и пятый параметры — координаты левого верхнего угла окна по горизонтали и вертикали соответственно. Точка отсчета располагается в левом верхнем углу экрана
- Шестой и седьмой параметры — ширина и высота окна соответственно.
- Восьмой параметр — дескриптор родительского окна. Он используется при создании дочерних окон. Так как наше окно не дочернее, ставим NULL.
- Девятый параметр — дескриптор меню. Ставим NULL.
- Десятый параметр — дескриптор нашего приложения.
- Одиннадцатый параметр — указатель на значение, которое будет передано окну при его создании. Не используем его, ставим NULL.
Функция CreateWindow возвращает дескриптор окна, которое было создано. Дескриптор окна имеет тип HWND. Сохраним его в переменную hWnd — она нам еще понадобится.
NB: Если вы используете WNDCLASS A , то создавайте окно с помощью CreateWindow A , а если WNDCLASS W , то с помощью CreateWindow W .