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

    На Хабре уже было несколько статей, рассказывающих об использовании механизма 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.
    Счастливого вам программирования!

    Средняя зарплата в IT

    113 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 10 037 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

    Комментарии 31

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

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

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

                      Spy++

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

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


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

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

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

                                    Самое читаемое