Использование P/Invoke: прячем кнопку Пуск и панель задач в Windows

.NET*
На Хабре уже было несколько статей, рассказывающих об использовании механизма P/Invoke в проектах на C#. В основном, в статьях был сильный уклон в сторону теории и приводились небольшие показательные примеры.
Я же хочу показать более наглядный пример, показывающий возможности неуправляемого кода — мы будем прятать кнопку Пуск и панель задач.

Впервые о возможности использовать системные функции Windows в своих программах я узнал при изучении Visual Basic. Эта тема настолько захватила меня, что я стал собирать в коллекцию примеры использования функций Windows API. Сначала я собирал примеры все подряд. Затем стал подходить к делу более разборчиво и стал отбирать те функции и примеры, которые использовал сам в своих проектах. За несколько лет я набрал более 500 функций, и все примеры к ним для удобства оформил в виде CHM-файла. Демо-версию справочника можно до сих пор скачать с сайта http://rusproject.narod.ru/guide.htm.
Когда появилась платформа .NET Framework и языки C# и Visual Basic.NET, то плавно мигрировал на эти языки. И вот здесь мне очень пригодился свой справочник по функциям Windows API. Как пишут во многих умных книжках, язык C# вобрал в себя лучшее из Java, C++, Visual Basic. В отношении P/Invoke можно сказать, что технология вызова неуправляемого кода из управляемого кода была взята из Visual Basic. Если на C++ вызов системных функций прозрачен для программиста, то для C# нужную функцию требуется объявить (аналог Declare в VB 6.0). Из своей коллекции примеров я решил показать проект, позволяющий скрывать кнопку Пуск и панель задач в Windows XP/Vista/7.

На самом деле, пример не имеет какой-то практической ценности (во всяком случае мне не удалось найти ему стоящее применение). Единственное, что приходит на ум — создание шуточной программы, которая 1 апреля скроет привычные элементы интерфейса на столе неподготовленного пользователя. Но, с другой стороны, пример дает некоторое представление об устройстве Windows, эффектен в глазах начинающих программистов и дает более полное представление об используемых функциях.
Итак, начнем с теории. Во-первых, кнопка Пуск, панель задач, область уведомлений и область часов — это все окна. А значит, получив доступ к такому окну, можно изменить привычное поведение элемента интерфейса. Второе — все эти окна принадлежат определенным классам. И, именно, по имени классов и можно найти дескрипторы требуемых нам окон, чтобы проделать над ними эксперименты. Вот названия классов:
  • Shell_TrayWnd — панель задач
  • Button — кнопка Пуск
  • TrayNotifyWnd — область уведомлений со значками и часами
  • TrayClockWClass — часы

Теперь переходим к функциям. Для наших целей нам понадобятся функции FindWindows, FindWindowEx и ShowWindow. Я не буду здесь приводить описания функций — вы можете самостоятельно узнать о них в интернете или в моем справочнике. Приведу только их объявления и парочку сопутствующих констант:
// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string ClassName, string WindowName);

[DllImport("user32.dll")]
private static extern IntPtr FindWindowEx(
  IntPtr hwndParent, IntPtr hwndChildAfter,
  string className, string windowName);

[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

const int SW_HIDE = 0;
const int SW_SHOW = 5;


* This source code was highlighted with Source Code Highlighter.


А далее все очень просто. С помощью функций FindWindow и FindWindowEx по имени класса находятся нужные дескрипторы, которые затем используются в функции ShowWindow для скрытия или показа нужного элемента.

bool show = false;

// Прячем панель задач и кнопку Пуск
private void butHideTaskbar_Click(object sender, EventArgs e)
{
  // Получим дескриптор панели задач      
  IntPtr taskBarWnd = FindWindow("Shell_TrayWnd", null);
  // Получим дескриптор кнопки ПУСК      
  IntPtr startWnd = FindWindow("Button", null);

  // Прячем/показываем панель задач
  if (taskBarWnd != IntPtr.Zero)
  {
    ShowWindow(taskBarWnd, show ? SW_SHOW : SW_HIDE);
  }

  // Прячем/показываем кнопку ПУСК
  if (startWnd != IntPtr.Zero)
  {
    ShowWindow(startWnd, show ? SW_SHOW : SW_HIDE);
  }
  show = !show;
}

// Прячем отдельные части панели задач
private void butHide_Click(object sender, EventArgs e)
{
  // Описатель панели задач      
  IntPtr taskBarWnd = FindWindow("Shell_TrayWnd", null);
  //ShowWindow(taskBarWnd, SW_HIDE);

  // Описатель области уведомлений
  IntPtr tray = FindWindowEx(taskBarWnd, IntPtr.Zero, "TrayNotifyWnd", null);

  // Прячем область уведомлений
  // ShowWindow(tray, SW_HIDE);

  // Описатель системных часов
  IntPtr trayclock = FindWindowEx(tray, IntPtr.Zero, "TrayClockWClass", null);
  // Прячем системные часы
  ShowWindow(trayclock, SW_HIDE);
}

* This source code was highlighted with Source Code Highlighter.


Для примера я расположил две кнопки на форме. При нажатии на первую кнопку исчезает панель задач и кнопка Пуск одновременно. При желании, вы можете закомментировать отдельные строчки кода и скрывать только панель задач или только кнопку Пуск. Повторное нажатие на кнопку позволит снова увидеть любимую кнопку Пуск и панель задач.

Upd. Важное дополнение: в Windows XP и ниже кнопка Пуск является дочерним окном панели задач, и для получения ее описателя нужно использовать код
<code>
IntPtr startWnd = FindWindowEx(taskBarWnd, IntPtr.Zero, "BUTTON", null);
</code>


При нажатии на вторую кнопку вы можете скрывать отдельные элементы панели задач — я закомментировал часть кода, оставив только скрытие области с часами. Будьте осторожны —пример написан в учебных целях. Если вы скроете часы, то сами они уже не появятся. Вам придется перезагрузить компьютер, чтобы вернуть все как-было. Поэтому самостоятельно доработайте пример, чтобы иметь возможность показывать скрываемые элементы.

Без скриншотов, наверное, не обойтись. Здесь я скрыл панель задач, но оставил кнопку Пуск

А здесь я скрыл только область часов.


Несколько слов о кнопке Пуск. В Windows 98/XP скрыть кнопку можно было без проблем (смотри замечание выше). В Windows Vista вместо прямоугольной кнопки появилась круглая и изменилось ее поведение. Она уже не является дочерним окном панели задач. И приведенный здесь код работает наполовину. Можно скрыть одновременно панель задач и Пуск, или только панель задач. А скрыть Пуск, но оставить панель задач не получается. Кнопка не исчезает, а скукоживается. В поисках решения проблемы я набрел на парочку статей на CodeProject (http://www.codeproject.com/KB/miscctrl/Hide_Vista_Start_Orb_Simp.aspx и http://www.codeproject.com/KB/miscctrl/hide_vista_start_orb.aspx. Но, к сожалению, у меня эти примеры не заработали. Они предназначены для американской Windows 7, на русской Windows эффекта не наблюдается (попытка изменить в коде слово Start на Пуск успеха не имела).

Этот и другие примеры с функциями Windows API в среде .NET Framework можно найти в моем справочнике на сайте http://developer.alexanderklimov.ru/guide.php. Сам справочник платный, но, если вы обладаете свободным временем, то можете найти примеры к любой функции в интернете (Гугл вам в помощь). А также могу посоветовать очень полезный ресурс на английском pinvoke.net: the interop wiki!, на котором по принципу Википедии энтузиасты добавляют примеры, связанные с P/Invoke.
Счастливого вам программирования!

+3
3 сентября 2009, 00:36
11
tehnolog 19,3

комментарии (31)

0
Devgru #
"(попытка изменить в коде слово Start на Пуск успеха не имела)"
Помнится, был код на Win32API, который открывал/закрывал лоток дисковода, и суть его была в отправке сообщения open/close через mciSendString. В каком-то русском сборнике рецептов по дельфи (или это VB был, не помню), был описан этот метод. Фишка в том, что из-за того что переводчики перевели и сообщения на открыть/закрыть, пример работать перестал.

К чему это я… Помнится, в старом апи была функция, перечисляющая всех детей некого окна по hwnd. Поищите, может у вас-таки получится найти правильный hwnd для манипуляции пуском?
0
tehnolog #
hwnd по идее правильный. проблема в названии. как условный вариант/идея — слово Пуск написано в кодировке Win-1251, а я подсовываю ему Unicode. по хорошему, надо утилиту типа Spy+ натравить на кнопку и узнать, что там написано.
+1
GMM #
EnumChildWindows Function ()
The EnumChildWindows function enumerates the child windows that belong to the specified parent window by passing
Такая?
–1
Devgru #
Боюсь уже не вспомню, я лет 5 назад последний раз Win32API трогал.
0
iley #
В данном случае проще использовать FindWindowEx
0
jfkz #
да такая, и в msdn даже есть «плохой» пример на неё =) msdn.microsoft.com/en-us/library/ms632598(VS.85).aspx
+3
khizhaster #
Всё-таки Win32 API он и в Африке Win32 API. Вот правда, не в обиду, если поменять «C#» на «Делфи» в вашем примере ничего не изменится. ;-)
0
tehnolog #
А почему я должен обижаться?
–2
Setti #
загуглил IntPtr
Заголовок из выдачи: «IntPtr — what is this!?!?»
Один в один моя эмоция!
+2
tehnolog #
))). Гугл крут. попробуйте набрать pigs can't fly и посмотрите на подсказку. twitpic.com/etim5
0
Skiminok #
Не сочтите за оскорбление или грубость, но что-то сильно Фленовым попахивает…
+1
tehnolog #
не счел
0
jfkz #
здесь должен быть комментарий от Флёнова ;)
0
tehnolog #
а почему Фленовым попахивает?
+3
YasonBy #
Вы точно-точно уверены, что это работает?
// Получим дескриптор кнопки ПУСК      
  IntPtr startWnd = FindWindow(«Button», null);

Как утверждает MSDN, FindWindow ищет только среди окон верхнего уровня. Элементарная проверка с помощью Spy++ показывает, что кнопка «Start» является дочерним окном Shell_TrayWnd — то есть FindWindow её не найдёт.

Spy++

А если бы даже FindWindow искала по всем окнам — результат был бы непредсказуем: Button — очень распространённый оконный класс, к нему относятся практически все кнопки в системе, не только «Пуск».
+1
YasonBy #
Кроме того, часть кода, который «если работает, то написан Александром Климовым», удивительно похожа на код примера для Win7, который у Вас «не заработал».
0
tehnolog #
Этот кусок не просто похож, а взят оттуда. Если вы внимательно читали статью, то там есть ссылка на нее. Я специально заменил свой код на код из CodeProject, чтобы было легче понять проблему с кнопкой.
0
tehnolog #
Упс. Вы правы. Это копировал кусок из CodeProject, пытаясь решить проблему с кнопкой в Win7 и забыл вернуть свой прежний код. Вечером вернусь к примеру и поправлю. Спасибо, глаз замылен и не увидел подвоха.
0
tehnolog #
Упс. Вы правы. Это копировал кусок из CodeProject, пытаясь решить проблему с кнопкой в Win7 и забыл вернуть свой прежний код. Вечером вернусь к примеру и поправлю. Спасибо, глаз замылен и не увидел подвоха.
0
tehnolog #
Вы ввели меня в заблуждение. Ваша картинка относится к WinXP, где Пуск действительно является дочерним окном Shell_TrayWnd. Но в Vista/7 кнопка уже не является дочерним окном и поэтому мой код правильный.
0
YasonBy #
Вы правы, картинка относится к WinXP. Однако, не могу согласится с Вашей претензией: в заблуждение Вы ввели себя сами.

Сначала Вы пишете:
… Я решил показать проект, позволяющий скрывать кнопку Пуск и панель задач в Windows XP/Vista/7.
Потом приводите (чужой) код для Vista/7.
Потом (upd ещё не было) пишете:
В Windows 98/XP скрыть кнопку можно было без проблем. В Windows Vista… изменилось ее поведение. Она уже не является дочерним окном панели задач. И приведенный здесь код работает наполовину. [...] Скрыть Пуск, но оставить панель задач не получается. [...]
В поисках решения проблемы я набрел на парочку статей на CodeProject [...]Но, к сожалению, у меня эти примеры не заработали.


Иными словами: сначала Вы утверждаете, что пример универсален, и приводите код для Висты/7; затем говорите, что он без проблем работал для XP, а в Vista только наполовину. И как финальный аккорд — утверждаете, что пример для Vista (код которого Вы и привели) — у Вас не заработал.
Сворачивая всё это: Вы утверждаете, что приведённый Вами код у Вас же и не заработал, — что звучит несколько странно :)

Нелогичность текста исчезнет, если нынешний код примера (для Vista/7) заменить на код для XP (т.е. с учётом дочерности кнопки). Ну, или перелопатить весь текст, чтобы он соответствовал коду.
0
tehnolog #
да, я не слишком аккуратно обошелся с текстом, так как писал глубокой ночью. претензий к вам нет. наоборот, даже благодарен ))).
0
neoroma #
Практическая ценность может появиться тогда, когда понадобится сделать приложение в котором действия клиента очень ограничены, для библиотек например, всяких там справочных…
0
neoroma #
Вот бы еще был хороший способ при crash-е программы обратно все это хозяйство показывать.
0
YasonBy #
Теоретически, достаточно через Task Manager прибить процесс explorer.exe (не путать с iexplore.exe), а затем запустить его снова.
–1
Krofes #
А у кнопки пуск разве есть hWnd?
0
Krofes #
А, прошу прощения. Есть.
0
tehnolog #
А интересно, почему вы решили сначала, что у кнопки нет hWnd?
0
Krofes #
слишком долго пытался достучаться до кнопок на тулбарах )
–2
Angelina_Joulie #
а о чём сообственно статья?
О том, что у Windows есть well-known window classes? или о том, что такое p/Invoke?
+1
YasonBy #
Чтобы понять, о чём статья, неплохо бы её прочитать.
В правильно составленных статьях достаточно прочесть введение.
В данном случае — название и первый абзац. Не осилили?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.