Продолжаем шутить над Панелью задач
Как мы уже знаем, Панель задач — это такое же окно, и над ним можно жестоко издеваться всеми доступными функциями для работы с окном. Когда мы писали первый пример, который подбрасывал лже-кнопку, то мы поднимали свое окно с изображением кнопки Пуск. Кто нам теперь мешает модифицировать этот пример и подбросить реальную кнопку Пуск, когда мы уже знаем, как получить к ней доступ.
Но не все так просто, и сейчас мы рассмотрим пример, в котором увидим несколько интересных приемов, позволяющих шутить над реальной кнопкой Пуск.
Создайте новый проект StartEnable и добавьте в раздел глобальных переменных следующие строки:
HWND hWnd;
HWND hTaskBar, hButton;
HMENU MainMenu;
В этом месте мы объявляем три переменные, которые будут ссылаться на окна:
HWnd — будет хранить указатель на наше окно, чтобы мы могли его использовать в любой точке кода;
hTaskBar и hButton — будут хранить указатели на Панель задач и кнопку Пуск;
MainMenu — сюда мы загрузим меню нашей программы, чтобы в дальнейшем использовать его.
Теперь переходим в функцию _tWinMain и в ней добавляем код из листинга 2.7 до обработчика событий.
Листинг 2.7. Код , который нужно добавить в функцию _tWinMain |
hTaskBar= FindWindow("Shell_TrayWnd",NULL); hButton= GetWindow(hTaskBar, GW_CHILD); MainMenu=LoadMenu(hInstance, (LPCTSTR)IDC_STARTENABLE);
SetParent(hButton, 0);
int i; HANDLE h; int toppos=GetSystemMetrics(SM_CYSCREEN)-23;
//Установить верхнюю позицию окна в левый нижний угол экрана. SetWindowPos(hButton, HWND_TOPMOST, 4, toppos, 50, 20, SWP_SHOWWINDOW); UpdateWindow(hButton); //Создаю пустой указатель h который буду использовать для задержки. h=CreateEvent(0, true, false, "et");
// Сейчас будем подымать кнопку // Цикл по изменению позиции кнопки for (i=0; i0; i--) { toppos=toppos+4; SetWindowPos(hButton, HWND_TOPMOST, 4, toppos, 50, 20, SWP_SHOWWINDOW); WaitForSingleObject(h,15);//Задержка в 5 миллисекунд } SetParent(hButton, hTaskBar);
Первые две строки нам уже знакомы. Здесь мы находим Панель задач и кнопку Пуск, сохраняя значения в глобальных переменных. Почему в глобальных, а не прямо в процедуре? Просто эти переменные мы будем использовать и дальше в этой программе (для других шуток). Поэтому, чтобы не выполнять один и тот же поиск, который всегда будет выдавать один и тот же результат, лучше сохраним один раз полученные значения в глобальной памяти программы.
В третьей строке загружается меню в переменную MainMenu с помощью функции LoadMenu. У этой функции два параметра — указатель на экземпляр и имя загружаемого меню. Это меню будет использоваться позже, а пока мы только подготовили переменную на будущее.
Теперь в этой функции будем подбрасывать кнопку Пуск. Но прежде, чем это сделать, надо вспомнить, где она находится. Кнопка принадлежит Панели задач (расположена на ней), а значит, если мы начнем двигать что-то сейчас, то кнопка будет стоять на месте. Почему? Потому что кнопка не сможет оторваться от своей панели. Первым делом необходимо разорвать связь между кнопкой и Панелью задач. Для этого выполняем функцию SetParent со следующими параметрами:
окно, родителя которого нужно изменить, — наша кнопка;
новый родитель указанного окна — указываем нуль.
Таким образом, у кнопки после выполнения этой функции будет "нулевой" родитель, т. е. связь будет разрушена.
Вот теперь можно двигать кнопку, как угодно. Поэтому следующий код будет вам знаком. Он практически не отличается от кода из листинга 2.3, где мы двигали окно, но в данном случае двигается кнопка, поэтому в функции SetWindowPos в качестве первого параметра используется указатель на кнопку, чтобы двигать именно ее.
После подъема и опускания кнопки Пуск мы должны вернуть ее на место, поэтому снова выполняем функцию SetParent, чтобы установить в качестве родителя для кнопки Панель задач.
На 2.6 можно увидеть результат работы программы, который вы получите, если уже сейчас запустите пример. Обратите внимание, что на том месте, где должна быть кнопка Пуск, — пустота.
2.6. Результат работы программы
Для реализации всех шуток в одной программе мы создадим несколько пунктов меню, с помощью которых будем вызывать разные команды. Для создания меню откройте ресурсы и выберите соответствующий пункт дерева ( 2.7).
2.7. Ресурсы и пункт дерева , под которым прячется меню
Вы увидите свое меню, по которому можно двигаться. В конце каждого списка меню есть пустой пункт с именем Tуре Hеrе, в котором имя написано на белом фоне. Выберите самый правый пункт с именем Tуре Hеrе и введите новое имя Our menu . Название этого пункта и цвет изменятся, и таким способом мы получим новое меню, а справа от него, под ним появится новый подпункт с именем Tуре Hеrе. Таким образом мы создаем новые пункты (подпункты) меню, которые будут видны в программе.
Выберите пункт Our menu и перетащите его мышкой, чтобы он оказался перед пунктом Help , как на 2.8.
2.8. В центре окна редактор меню
Теперь выделите пункт Our menu . Перейдите на пункт меню Tуре Hеrе, который находится ниже, и наберите новое имя Move window to System Tray . Имя изменится, а строчкой ниже появится новый пункт Tуре Hеrе. Выделите его и создайте новый пункт Enable System Tray . Таким же образом добавьте еще два пункта: Disable System Tray и Insert menu. В результате ваше меню должно выглядеть, как на 2.9.
2.9. Результат создания меню
Теперь приступаем к программированию. Перейдите в исходный код программы и найдите функцию WndProc. Когда пользователь выбирает какой-то пункт меню, то генерируется событие, и мы должны его обрабатывать в этой функции.
Полный код функции WndProc вы можете увидеть в листинге 2.8. Просмотрите его и приведите свою функцию к такому же виду.
Листинг 2.8. Функция обработки сообщений WndProc |
switch (message) { case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); // Parse the menu selections: switch (wmId) { //Обрабатываем меню Move window to System Tray case ID_OURMENU_MOVEWINDOWTOSYSTEMTRAY: SetParent(hWnd, hTaskBar); break; //Пункт меню Enable System Tray case ID_OURMENU_ENABLESYSTEMTRAY133: EnableWindow(hTaskBar, true); break; //Пункт меню Disable System Tray case ID_OURMENU_DISABLESYSTEMTRAY: EnableWindow(hTaskBar, false); //Пункт меню Insert menu break; case ID_OURMENU_INSERTMENU: SetMenu(hTaskBar, MainMenu); break; case IDM_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); break; 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... EndPaint(hWnd, ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
Большая часть из приведенного в листинге 2. 8 кода была сгенерирована мастером при создании проекта. Нами добавлено не так уж и много, и сейчас нам предстоит это разобрать по частям.
Для первого пункта меню (Move window to System Tray) в данной функции выполняется следующий код:
// Пункт меню Move window to System Tray case ID_OURMENU_MOVEWINDOWTOSYSTEMTRAY: SetParent(hWnd, hTaskBar); break;
Оператор case проверяет пришедшее сообщение с константой ID_OURMENU_MOVEWINDOWTOSYSTEMTRAY. Если вы перейдете в редактор ресурсов и выберете созданный нами пункт меню Move window to System Tray, то в окне свойств (справа внизу) в свойстве ID увидите именно эту константу. Если она отличается, то для вас Visual C++ сгенерировал другое имя (у нас может отличаться версия среды разработки), и вы должны подкорректировать исходный код. Если проверка прошла успешно, то выполнится весь код до оператора break. Здесь идет вызов только одной функции SetParent. Мы уже знаем, что эта функция изменяет родителя окна, указанного в качестве первого параметра, делая его подчиненным по отношению к окну, заданному вторым параметром. Первым у нас указано главное окно, а вторым — Панель задач. Получается, что наше окно становится подчиненным по отношению к Панели задач.
На 2.10 показан результат работы, который вы сможете получить, если выберете этот пункт меню. Я специально раздвинул Панель задач, чтобы вы видели, что окно StartEnable стало располагаться внутри этой панели. Вы больше не сможете выдвинуть окно программы за пределы Панели задач, пока не смените родителя на "нулевого".
2.10. Главное окно нашей программы стало подчиненным по отношению к Панели задач
При выборе пункта меню Disable System Tray выполняется следующий код:
//Пункт меню Disable System Tray case ID_OURMENU_DISABLESYSTEMTRAY1333: EnableWindow(hTaskBar, false); break;
Здесь мы выполняем функцию EnableWindow, которая делает доступным или недоступным какое-то окно. В качестве первого параметра мы передаем указатель на окно, а второй параметр равен true (сделать доступным) или false (сделать недоступным). В данном случае мы делаем недоступной Панель задач. Вы сколько угодно можете нажимать на кнопки панели, но ничего кроме звукового сигнала об ошибке не услышите. Но можно было бы указать и hButton , чтобы заблокировать только кнопку Пуск, а не всю Панель задач.
Если выбрать пункт меню Disable System Tray, выполнится та же функция EnableWndow, только теперь мы сделаем окно доступным.
Выбор пункта меню Insert menu активизирует функцию SetMenu, которая устанавливает меню для окна. Первым параметром определяется окно, а во втором параметре указывается загруженное меню. Вот и пригодилась переменная MainMenu, в которую мы в самом начале загрузили меню.
2.11. Меню в Панели задач — нонсенс или реальность?
Посмотрите на 2.11, там показан результат работы программы после выбора этого пункта меню. Самое интересное, что мышкой вы его выбрать не можете. Единственный вариант войти в него — это клавиатура. Чтобы выбрать меню с помощью клавиатуры, нужно сделать активным окно (щелкните на Панели задач) и нажать кнопку Alt. Первый пункт меню должен подсветиться, и теперь вы можете перемещаться по пунктам меню с помощью клавиатуры и мышки.
Примечание |
Исходный код и запускаемый файл этого примера вы можете найти на компакт - диске в каталоге \Demo\Chapter2\StartEnable. |