Летающий Пуск
Вспоминаю, как я первый раз увидел Windows 95. Мне так
понравилась кнопка Пуск, что я полюбил ее до глубины
Выключить компьютер. Вскоре в нашем институте обновили парк
машин, и на них тоже поставили Windows 95. Мне так захотелось подшутить над
своими однокурсниками, что я решил написать программку, которая подбрасывала бы
кнопку Пуск.
Сказано — сделано, написал (на Delphi ) и
запустил на всех машинах. С каждым взлетом кнопки Пуск ламеры
испуганно взлетали вместе с ней. А через некоторое время я увидел и в Интернете
подобный прикол.
Сейчас я повторю свой старый подвиг и покажу вам, как самому
написать такую программу с использованием Visual C++. Так что усаживайтесь
поудобнее, наша кнопка Пуск взлетает на высоту 100 пикселов!
В этом примере мы пойдем на небольшую хитрость и будем
подбрасывать не саму кнопку Пуск, а окно, в котором будет
нарисовано изображение кнопки. Чуть позже мы рассмотрим пример реального доступа
к системной кнопке, а пока ограничимся таким трюком, потому что это даже
интересней.
Но прежде чем начать, нужно подготовить картинку с изображением
кнопки Пуск. Для этого вы можете нарисовать ее своими руками в
любом графическом редакторе. Ну, а если вы IBM-совместимый человек, то можете
нажать клавишу <PrintScrn> (или <PrintScreen>), чтобы запомнить
изображение экрана в буфере обмена, а потом выполнить вставку содержимого буфера
в любом графическом редакторе. Далее простыми манипуляциями вырезать изображение
кнопки и сохранить его в отдельном файле.
У меня получилась картинка размером 50x20, и вы найдете ее на
компакт-диске в каталоге Demo/Chapter2/Start Button/Start.bmp. Можете
воспользоваться этим файлом.
Создайте новый проект в Visual C++ типа Win32
Project с именем Start Button и добавьте в него нашу картинку. Для
этого откройте ресурсы, дважды щелкнув по файлу Start Button.rc. Перед вами
откроется окно с деревом ресурсов (рис. 2.1).
Рис. 2.2. Окно
выбора типа создаваемого ресурса
В этом разделе будет создан новый ресурс для хранения
изображения. Под окном просмотра ресурсов вы можете видеть окно свойств
изображения . Здесь нужно первым делом изменить свойство
Colors (Количество цветов), установив значение 256
Color или True Color. Ширину и высоту (свойства
Width и Height) нужно указать в соответствии с
вашей картинкой.
Откройте изображение кнопки Пуск в любом
графическом редакторе (например, Paint) и скопируйте его в буфер обмена (чаще
всего для этого нужно выделить изображение и выбрать меню
Edit/Copy). Вернитесь в редактор Visual C++ и выполните команду
Edit/Paste.
Теперь переходим непосредственно к программированию. На этом
примере мы рассмотрим некоторые приемы, которые будем использовать в дальнейшем
достаточно часто.
Откройте файл Start Button.cpp. Для этого найдите его в окне
Solution Explorer в разделе Source Files и
дважды щелкните на строке с именем. В самом начале файла найдите раздел
глобальных переменных, который начинается с комментария Global Variables: После этого комментария добавим две
переменные:
// Global Variables:
HWND hWnd;
HBITMAP startBitmap;
Первая переменная — hWnd — имеет тип HWND, который используется для хранения
указателей на окна. В ней мы и сохраним указатель на созданное в примере окно,
чтобы в любой момент можно было получить к нему доступ. Вторая переменная —
startBitmap — имеет тип
HBITMAP, который
используется для хранения картинок, и мы поместим сюда наше изображение кнопки
Пуск.
Теперь переходим в функцию _tWinMain. В ней, после загрузки из ресурсов текста окна и
имени класса окна, добавим следующую строчку кода:
startBitmap = (HBITMAP)::LoadImage(hInstance,
MAKEINTRESOURCE(IDB_BITMAP1), IMAGE_BITMAP,
0, 0, LR_DEFAULTCOLOR);
Здесь мы переменной startBitmap
присваиваем загруженную из ресурсов картинку. Для этого вызывается функция LoadImage, которой (в скобках) нужно передать следующие
параметры:
Больше в этой функции мы пока ничего изменять не будем. Чуть
позже мы еще сюда вернемся и добавим пару строчек, а сейчас переходим в функцию
Initlnstance. Она будет выглядеть как в листинге 2.1.
Листинг 2.1. Обновленная функция
InitInstance |
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
// Следующие строки добавлены нами
int Style;
Style = GetWindowLong(hWnd, GWL_STYLE);
Style=Style || WS_CAPTION;
Style=Style || WS_SYSMENU;
SetWindowLong(hWnd, GWL_STYLE, Style);
return TRUE;
}
Код, добавленный нами, начинается с объявления
переменной Style, которая будет иметь тип int (целое число). В следующей строке этой переменной
присваивается результат выполнения функции GetWindowLong. Она возвращает настройки окна и в скобках
нужно передать два значения:
Зачем нам нужен стиль? Просто окно по умолчанию имеет
заголовок, кнопки максимизации и минимизации, а нам все это не нужно. Для этого
из полученного стиля в следующих двух строках удаляется заголовок окна и
системное меню, которое содержит кнопки.
Теперь выполняем функцию SetWindowLong, которая записывает значения обратно в
настройки окна. Если сейчас запустить программу, то вы увидите только клиентскую
часть — серый квадрат без заголовка, кнопок и обрамления.
Переходим в функцию WndProc, где у
нас обрабатываются все события. Нас будет интересовать рисование, поэтому
добавим следующий обработчик события:
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
Rectangle(hdc, 1,1,10,10);
hdcBits=::CreateCompatibleDC(hdc);
SelectObject(hdcBits,startBitmap);
BitBlt(hdc, 0, 0, 50, 20, hdcBits, 0, 0, SRCCOPY);
DeleteDC(hdcBits);
EndPaint(hWnd, &ps);
break;
Полный вариант функции WndProc вы можете увидеть в листинге 2.2.
Листинг 2.2. Функция WndProc с
обработчиком для рисования |
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
HDC hdcBits;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
Rectangle(hdc, 1,1,10,10);
hdcBits=::CreateCompatibleDC(hdc);
SelectObject(hdcBits,startBitmap);
BitBlt(hdc, 0, 0, 50, 20, hdcBits, 0, 0, SRCCOPY);
DeleteDC(hdcBits);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Чтобы начать рисование, надо знать, где мы
будем это делать. У каждого окна есть контекст, в котором можно рисовать
средствами Windows. Чтобы получить его для текущего окна, нужно вызвать функцию
BeginPaint. Эта функция как раз и вернет нам
указатель на контекст окна, указанного в качестве первого параметра.
Но чтобы отобразить изображение нашей кнопки
Пуск, надо еще подготовить картинку. В WinAPI нет готовой
функции для рисования растрового изображения, но есть возможность выбрать
изображение в контекст и возможность копирования между контекстами. Для этого
сначала надо создать контекст рисования, совместимый с тем, что использует окно,
чтобы можно было без проблем производить копирование. Воспользуемся функцией
CreateCompatibleDC,
которой нужно передать контекст окна, а она нам вернет новый контекст,
совместимый с указанным.
Следующим шагом мы должны выбрать в новый контекст нашу
картинку. Для этого можно вызвать функцию SelectObject, у которой два параметра:
Вот теперь можно производить копирование с помощью функции
BitBlt. У нее в скобках нужно указать следующие
параметры:
После рисования нам уже не нужен
контекст, который мы создали для картинки, и хорошим тоном было бы удалить его.
Для этого вызываем функцию DeleteDC и в качестве параметра указываем наш контекст рисования.
Завершаем рисование вызовом метода EndPaint. Таким образом мы ставим точку в начатое функцией
BeginPaint рисование.
Теперь в нашем окне в левом верхнем углу будет рисоваться
изображение кнопки Пуск. Остается сделать самую малость —
уменьшить размер окна до размеров изображения, чтобы пользователь видел только
картинку, и заставить окно двигаться. Для этого мы должны написать функцию DrawStartButton (листинг 2.3), Желательно, до функци _tWinMain.
Листинг 2.3. Функция , заставляющая
окно двигаться |
void DrawStartButton()
{
int i;
HANDLE h;
int toppos=GetSystemMetrics(SM_CYSCREEN)-23;
//Отображаем окно
ShowWindow(hWnd, SW_SHOW);
//Установить верхнюю позицию окна в левый нижний угол экрана.
SetWindowPos(hWnd, HWND_TOPMOST, 4, toppos, 50, 20, SWP_SHOWWINDOW);
UpdateWindow(hWnd);
//Создаем пустой указатель h, который будем использовать для задержки.
h=CreateEvent(0, true, false, "et");
// Сейчас будем поднимать кнопку
// От 1 до 50 выполнять действия для изменения положения окна
for (i=0; i<50; i++)
{
toppos=toppos-4;
SetWindowPos(hWnd, HWND_TOPMOST, 4, toppos, 50, 20,
SWP_SHOWWINDOW);
WaitForSingleObject(h,15);//Задержка в 5 миллисекунд
}
// Опускаем кнопку вниз
for (i=50; i>0; i--)
{
toppos=toppos+4;
SetWindowPos(hWnd, HWND_TOPMOST, 4, toppos, 50, 20,
SWP_SHOWWINDOW);
WaitForSingleObject(h,15);//Задержка в 5 миллисекунд
}
}
Чтобы правильно расположить окно с нашей
кнопкой на экране компьютера, мы должны знать его разрешение. Для этого
выполняется следующая строка кода:
int toppos = GetSystemMetrics(SM_CYSCREEN)-23;
Здесь вызывается функция GetSystemMetrics, которая возвращает значение определенного
системного параметра. В скобках указывается параметр, который нас интересует (в
данном случае SM_CYSCREEN, высота экрана). Из
результата вычитаем число 23 (высота картинки + еще 3 пиксела) и сохраняем
результат в переменной toppos.
Таким образом, мы вычислили верхнюю позицию окна с изображением
кнопки, и можем его туда переместить. Еще необходимо, чтобы наше окно всегда
было поверх остальных. Обе эти операции можно сделать, вызвав только одну
функцию SetWindowPos. У нее 7 параметров:
После этого прорисовываем окно в новой позиции с помощью
вызова функции UpdateWindow(hWnd). В скобках указано окно, которое надо отобразить.
Последний штрих — создание пустого события с помощью вызова
функции СreateEvent. Это событие мы будем
использовать чуть позже, и нас устраивает, что оно пустое.
Теперь наше окно расположено в нужном месте, и можно приступить
к его анимации (движению по экрану). Для этого запускаем цикл от 0 до 50, внутри
которого выполняются следующие действия:
for (i=0; i<50; i++)
{
toppos=toppos-4;
SetWindowPos(hWnd, HWND_TOPMOST, 4, toppos, 50, 20,
SWP_SHOWWINDOW);
WaitForSingleObject(h,15);//Задержка в 5 миллисекунд
}
Сначала уменьшается значение переменной
toppos на четыре пиксела.
Таким образом, окно будет ползти вверх по экрану. Потом перемещаем это окно в
новую позицию.
Самое интересное здесь — это последняя строчка кода, в которой
выполняется функция WaitForSingleObject. Она ожидает
наступления события, определенного первым параметром. Второй параметр указывает
количество миллисекунд, которые надо ожидать. Так как событие пустое, оно
никогда не наступит, и функция прождет его ровно указанное во втором параметре
время. Таким образом, мы делаем задержку между движениями окна, которая не
загружает систему. Некоторые любят для задержек использовать циклы с
математическими операциями, но это нагружает процессор бесполезной работой, что
является плохим тоном. По моим наблюдениям, использование WaitForSingleObject в наименьшей степени нагружает
компьютер и отлично работает.
Итак, наш цикл двигает кнопку вверх по экрану. После этого мы
должны вернуть кнопку на место, для чего запускается еще один цикл, в котором
тем же способом кнопка движется в обратном направлении.
Теперь у нас все готово, и мы должны вернуться в функцию _tWinMain и написать там вызов функции DrawStartButton. Я рекомендую сделать этот вызов перед
циклом обработки сообщений и внутри него:
DrawStartButton();
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
DrawStartButton();
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Теперь при старте программы наша лже-кнопка будет
взлетать и возвращаться на место поверх настоящей кнопки. Если вы попытаетесь
навести на настоящую кнопку мышку, то она пройдет поверх окна лже-кнопки, и наша программа получит сообщение от мышки, а значит, выполнится
цикл обработки событий, в котором функция DrawStartButton снова подбросит нашу
кнопку на некоторую высоту.
Эффект получается красивым, и обязательно посмотрите на
результат (рис. 2.5). Ничего разрушительного в этом нет, но вашим друзьям
понравится.
Рис. 2.5. Пример
работы программы