Летающий Пуск

Вспоминаю, как я первый раз увидел 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. Пример работы программы

free-templates.ru
Сайт управляется системой uCoz