Как сделать окно без рамки winapi

Добавил пользователь Alex
Обновлено: 05.10.2024

Казалось бы, что WinAPI уходит в прошлое. Давно уже существует огромное количество кросс-платформенных фреймфорков, Windows не только на десктопах, да и сами Microsoft в свой магазин не жалуют приложения, которые используют этого монстра. Помимо этого статей о том, как создать окошки на WinAPI, не только здесь, но и по всему интернету, исчисляется тысячами по уровню от дошколят и выше. Весь этот процесс разобран уже даже не по атомам, а по субатомным частицам. Что может быть проще и понятнее? А тут я еще…

Но не все так просто, как кажется.

Почему о WinAPI сейчас?

В один прекрасный момент, изучая потроха одной из игр в весьма неплохом эмуляторе NES, я подумал: Вроде неплохой такой эмуль, а в отладчике нет такой простой вещи, как навигация по кнопкам клавиатуры, которая есть в любом нормальном отладчике.

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

О чем это я? А вот об этом кусочке кода:

Ответ такой: так делать нельзя!

И, возвращаясь, к изначальному вопросу о WinAPI: очень много популярных, и не очень, проектов продолжают его использовать и в настоящее время, т.к. лучше, чем на чистом API многие вещи не сделать (тут можно бесконечно приводить аналогии вроде сравнения высокоуровневых языков и ассемблера, но сейчас не об этом). Да и мало ли почему? Просто используют и все тут.

О проблеме

Обойти такие мелкие неприятности просто. Есть, как минимум, два вполне легальных способа:

Tutorials?


Здесь действительно все просто:

И ниже приводится пример правильного цикла.

Стоит сказать, что в шаблонах VS для Win32 приложений, написан именно такой неправильный цикл. И это очень печально. Ведь мало кто будет вникать в то, что сделали сами авторы, ведь это априори правильно. И неправильный код множится вместе с багами, которые очень сложно отловить.

После этого фрагмента кода, как правило, следует рассказ про акселераторы, и добавляется пара новых строчек (учитывая замечание в MSDN, предлагаю сразу писать правильный цикл):


Этот вариант я видел чаще всего. И он (та-дам) снова неправильный!

Сперва о том, что изменилось (потом о проблемах этого кода):

Ясно, что TranslateAccelerator надо вызывать для нашего созданного окна:


И вроде все хорошо и замечательно теперь: мы разобрали все детально и все должно работать идеально.

И снова нет. :-) Это будет работать правильно, пока у нас ровно одно окно — наше. Как только появится немодальное новое окно (диалог), все клавиши, которые будут в нем нажаты оттранслируются в WM_COMMAND и отправляться куда? И опять же правильно: в наше главное окно.

На этом этапе предлагаю не городить костылей по решению этой тупиковой ситуации, а предлагаю рассмотреть вещи, которые уже реже (или почти не встречаются) в туториалах.

IsDialogMessage

На самом деле, делает она чуть больше, чем следует из названия. А именно:

  • Осуществляет навигацию по дочерним контролам кнопками Tab/Shift+Tab/вверх/вниз/вправо/влево. Плюс еще кое-что, но этого нам достаточно
  • По нажатии на ESC формирует WM_COMMAND( IDCANCEL )
  • По нажатии на Enter формирует WM_COMMAND( IDOK ) или нажатие на текущую кнопку по умолчанию
  • Переключает кнопки по умолчанию (рамочка у таких кнопок чуть ярче остальных)
  • Ну и еще разные штуки, которые облегчают пользователю работу с диалогом

Во-вторых, она нам облегчит жизнь по всем остальным пунктам, перечисленным в списке (и даже немного больше).

Вообще, она используется где-то в недрах Windows для обеспечения работы модальных диалоговых окон, а программистам дана, чтобы вызывать ее для немодальных диалогов. Однако мы ее можем использовать где угодно:

Although the IsDialogMessage function is intended for modeless dialog boxes, you can use it with any window that contains controls, enabling the windows to provide the same keyboard selection as is used in a dialog box.

Т.е. теперь, если мы оформим цикл так:


То наше окошко будет иметь навигацию, как в родном диалоге Windows. Но теперь мы получили два недостатка:

Пора поговорить о том, чего нет в туториалах и ответах.

Как правило (как правило! Если кому-то захочется большего, то можно регистрировать свой класс для диалогов и работать так. И, если же, кому-то это интересно, я могу дополнить этим статью) WM_KEYDOWN хотят тогда, когда хотят обработать нажатие на клавишу, которая выполнит функцию в независимости от выбранного контрола в окне — т.е. некая общая функция для всего данного конкретного диалога. А раз так, то почему бы не воспользоваться богатыми возможностями, которые нам сама WinAPI и предлагает: TranslateAccelerator.

Везде используют ровно одну таблицу акселераторов, и только для главного окна. Ну действительно: цикл GetMessage-loop один, значит и таблица одна. Куда еще их девать?

На самом деле, циклы GetMessage-loop могут быть вложенными. Давайте еще раз посмотрим описание PostQuitMessage:

The PostQuitMessage function posts a WM_QUIT message to the thread's message queue and returns immediately; the function simply indicates to the system that the thread is requesting to quit at some time in the future.

Таким образом, выход из GetMessage-loop осуществится, если мы вызовем PostQuitMessage в процедуре окна. Что это значит?

Мы можем для каждого немодального окна в нашей программе создавать свой собственный подобный цикл. В данном случае DialogBoxParam нам не подходит, т.к. оно крутит свой собственный цикл и повлиять мы на него не можем. Однако если создадим диалог через CreateDialogBoxParam или окно через CreateWindow, то можно закрутить еще один цикл. При этом в каждом таком окне и диалоге мы должны вызывать PostQuitMessage:


Обратите внимание: теперь для каждого нового окна в нашей программе мы можем добавить в обработку собственную таблицу акселераторов. WM_QUIT будет выхватывать GetMessage из цикла для диалога, а внешний цикл его даже не увидит. Почему так происходит?

Делаем красиво

Т.к. правильная постановка задачи является половиной решения, то сперва надо эту самую задачу правильно же и поставить.

Создадим простой std::map, который будет мапить дескриптор окна в дескриптор таблицы акселераторов. Вот так:


И по мере создания окон будем в него добавлять новые окна с дескриптором на свою любимую таблицу (или нуль, если такая обработка не требуется).


Ну и после закрытия окна удалять. Вот так:


Теперь, как создаем новый диалог/окно, вызываем AddAccelerators( hNewDialog, IDR_MY_ACCEL_TABLE ). Как закрываем: DelAccel( hNewDialog ).


Значительно лучше! Что же там в HandleAccelArray и зачем там GetActiveWindow()?

Есть две функции, возвращающих дескриптор активного окна GetForegroundWindow и GetActiveWindow. Отличие первой от второй вполне доходчиво описано в описании второй:

The return value is the handle to the active window attached to the calling thread's message queue. Otherwise, the return value is NULL.


Теперь каждое дочернее окно вправе добавить себе любимую таблицу акселераторов и спокойно ловить и обрабатывать WM_COMMAND с нужным кодом.

А что там еще об одной строчке в коде обработчика WM_COMMAND?

To differentiate the message that this function sends from messages sent by menus or controls, the high-order word of the wParam parameter of the WM_COMMAND or WM_SYSCOMMAND message contains the value 1.

Обычно код обработки WM_COMMAND выглядит так:


Теперь можно написать так:

P.S.: Мало кто знает, но можно создавать свою собственную таблицу акселераторов, а теперь и применять ее прямо налету.

P.P.P.S.: После вызова HandleAccelWindow мап l_mAccelTable может измениться, т.к. TranslateAccelerator или IsDialogMessage вызывают DispatchMessage, а там может встретиться AddAccelerators или DelAccel в наших обработчиках! Поэтому лучше его после этой функции не трогать.

Пощупать код можно здесь. За основу был взят код, генерируемый из стандартного шаблона MS VS 2017.

Программы написанные на Win API имеют большую производительность и небольшой размер. С помощью функций Win API можно получить доступ к различным объектам Windows. Из этой статьи вы узнаете как создать окно на чистом Win API. Узнаете, как работают программы в Windows. Заметите какой размер имеет программа на Win API и программа с VCL.

2. Функция CreateWindowEx

Создает окно с заданными свойствами. Функция CreateWindowEx выглядит так:

Определения других констант вы сможете найти в справке по win32api.

  • lpClassName - Имя класса окна. Вы можете создавать свои классы при помощи функции RegisterClassEx или использовать предопределённые: edit, button, static, scrollbar, combobox и другие;
  • lpWindowName - текст, который появится в заголовке окна (если окно с заголовком), на кнопке (если класс окна button), в поле ввода текста (если класс окна edit);
  • dwStyle - список основных стилей окна. Содержит несколько следующих констант, соединённых оператором or:
    • WS_BORDER - окно будет иметь тонкую рамку;
    • WS_CAPTION - окно будет иметь заголовок;
    • WS_CHILD или WS_CHILDWINDOW - окно будет дочерним, то есть целиком располагаться внутри некоторого другого окна;
    • WS_CLIPCHILDREN - площадь занимаемая дочерними окнами не будет перерисовываться;
    • WS_CLIPSIBLINGS - перерисовка одного дочернего окна не влияет на другие;
    • WS_DISABLED - окно создается недоступным, его можно разблокировать при помощи функции EnableWindow;
    • WS_DLGFRAME - создается окно с рамкой как у диалоговых окон;
    • WS_GROUP - для дочернего окна (со стилем WS_CHILD) определяет первый элемент в группе, при нажатии на Tab именно он получит фокус, группа простирается до следующего дочернего окна с тем же стилем, внутри группы можно перемещаться при помощи клавиш управления курсором;
    • WS_HSCROLL - создается окно с горизонтальной полосой прокрутки;
    • WS_ICONIC или WS_MINIMIZE - создаваемое окно изначально минимизировано;
    • WS_MAXIMIZE - создаваемое окно изначально максимизировано;
    • WS_MAXIMIZEBOX - создаваемое окно имеет кнопку максимизации;
    • WS_MINIMIZEBOX - создаваемое окно имеет кнопку минимизации;
    • WS_OVERLAPPED - создается перекрывающееся окно, имеет заголовок и рамку;
    • WS_OVERLAPPEDWINDOW - комбинация флагов WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX и WS_MAXIMIZEBOX;
    • WS_POPUP - создается окно не имеющее изначально рамки и заголовка, не может использоваться со стилем WS_CHILD;
    • WS_SIZEBOX или WS_THICKFRAME - создается окно, размер которого можно изменять;
    • WS_SYSMENU - создается окно со значком системного меню, должен употребляться с флагом WS_CAPTION;
    • WS_TABSTOP - создается дочернее окно, которое может получать фокус ввода при нажатии на Tab;
    • WS_TILEDWINDOW - комбинация флагов WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX и WS_MAXIMIZEBOX;
    • WS_VISIBLE - создается окно, которое изначально видимо. Если вы не укажете это флаг для окна, то вы его никогда не увидите (если только не воспользуетесь функцией ShowWindow);
    • WS_VSCROLL - создаваемое окно будет иметь вертикальную полосу прокрутки;

    3. Функция ShowWindow

    Эта функция показывает или прячет окно.

    • hWnd - Описатель нужного окна;
    • nCmdShow - Константа, определяющая, что будет сделано с окном:
      • SW_HIDE - окно будет скрыто;
      • SW_SHOWNORMAL - окно будет показано и активировано, если окно было минимизировано или максимизировано, то оно будет восстановлено в исходную позицию и размер;
      • SW_SHOWMINIMIZED - активизирует и сворачивает (минимизирует) окно;
      • SW_SHOWMAXIMIZED - активизирует и максимизирует окно;
      • SW_MAXIMIZE - максимизирует окно;
      • SW_SHOWNOACTIVATE - то же самое, что SW_SHOWNORMAL, только окно не активизируется;
      • SW_SHOW - отображает окно в его текущей позиции;
      • SW_MINIMIZE - минимизирует окно и активизирует следующее по Z-списку;
      • SW_SHOWMINNOACTIVE - то же самое, что и SW_SHOWMINIMIZED, только окно не активизируется;
      • SW_SHOWNA - то же самое, что SW_SHOW, только окно не активизируется;
      • SW_RESTORE - восстанавливает окно из максимизированного или минимизированного состояния;
      • SW_SHOWDEFAULT - отображает окно так, как оно было отображено при старте соответствующего приложения;
      • SW_MAXIMIZE - максимизирует окно.

      4. Структура типа TWndClassEx

      Структура типа TWndClassEx имеет следующий вид: tagWNDCLASSEXA = packed record cbSize: UINT; style: UINT; lpfnWndProc: TFNWndProc; cbClsExtra: Integer; cbWndExtra: Integer; hInstance: HINST; hIcon: HICON; hCursor: HCURSOR; hbrBackground: HBRUSH; lpszMenuName: PAnsiChar; lpszClassName: PAnsiChar; hIconSm: HICON; end;

      В качестве оконного обработчика событий служит функция WindowProc.

      6. Исходный текст программы

      С теорией мы разобрались, давайте приступим к реализации нашей идеи "Сделать окно на Win API".

      1. Запускаем Delphi. Появится пустое окно.

      2. Project - View Source.

      3. Удаляем всё кроме : Program, Uses, Var, Begin , End.

      7. Размер программы на Win API

      Теперь когда программа у нас готова. Компилируем и запускаем программу. Появится окошко синего цвета.

      Узнаем размер приложения. Для этого заходим в меню Project - Information for "имя программы".

      Смотрим, размер файла (File size) равен 8704 байт. или 8,5 килобайт. Теперь запускаем создаём новое приложение.

      Компилируем и запускаем. Заходим в меню Project - Information for "имя программы".

      Смотрим, размер файла (File size) равен 359454 байт или 351 килобайт.

      Вы увидели, что пустая форма на Win API имеет гораздо меньший размер чем с VCL.

      8. Заключение

      Писать большие программы на чистом Win API очень сложно, хотя они и имеют маленький размер. Но с использованием функций Win API можно получить доступ к различным объектам Windows. Win API может показаться сложным для начинающих программистов, но не стоит боятся. Учите, исправляйте ошибки, не останавливайтесь на половине пути. Если у вас, что то не получается то не останавливайтесь, ищите пути решения.

      Конечно, это не дает кнопок с минимальным/максимизирующим эффектом, но, с другой стороны, если я хочу, чтобы кнопки с уменьшением/максимизацией, я должен сделать:

      Почему эта комбинация кажется необходимой? Сначала я, вероятно, хочу WS_MAXIMIZEBOX | WS_MINIMIZEBOX , так как хочу эти кнопки.

      Итак, что делать?

      ОТВЕТЫ

      Ответ 1

      Программы удаляют неклиентскую область (панель заголовков) и имеют кучу пользовательской обработки для воспроизведения оконных кнопок, значков, системного меню и т.д. Преимущество этого заключается в том, что они могут рисовать новую строку заголовка ", который фактически является частью стандартной клиентской области, добавлением вкладок или других настраиваемых элементов управления.

      Следующие две статьи покажут вам, как это сделать в Vista и выше (с использованием DWM):

      • Настройка настраиваемой строки заголовка в Windows Vista/7
      • Настройка настраиваемой строки заголовка - повторение В этом есть демонстрационное приложение, показывающее результат нескольких вариантов/вариантов.

      Это очень сложно сделать и получить право, поэтому эти две статьи неоценимы. Автор, должно быть, вложил в них много работы! Обе ссылки имеют пример кода, написанный в Delphi, но его должно быть достаточно легко перевести на С++ - понятия идентичны, это просто синтаксис.

      Вы также можете быть заинтересованы в общих ресурсах по Glass и DWM, так как все это тесно связано. Вы увидите две приведенные выше ссылки: >

      Ответ 2

      Вы можете создать окно с надписью или без нее - все, что более уместно с точки зрения желаемой настройки (то есть "без", вы хотите сделать это "без заголовка", как вы говорите), и важный бородавка - это то, что вы берете живопись неклиентской области - это ключевая вещь.

      Вы также можете проверить ссылку на Visual Styles Reference, чтобы использовать API рисования с поддержкой темы, например DrawThemeBackground .

      Простым примером этой операции является добавление дополнительной кнопки в заголовок, например, подробно описанный здесь: CCaptionButton (кнопки для заголовка).

      Ответ 3

      Я считаю, что они создают нормальное окно, а затем рисуют над заголовком свои пользовательские виджеты/вкладки. Это видно в Firefox, так как когда он зависает, вы можете видеть, что обычная строка заголовка Windows отображается на вкладках.

      Это dwExStyle.
      А работает, скорее всего потому, что и не WS_OVERLAPPED и нет WS_CAPTION.

      Posted via ActualForum NNTP Server 1.4

      Это dwExStyle.
      А работает, скорее всего потому, что и не WS_OVERLAPPED и нет WS_CAPTION.
      Posted via ActualForum NNTP Server 1.4

      Тут еще и приоритет операций неправильный. Код эквивалентен:
      (~WS_CAPTION)|WS_MAXIMIZEBOX|WS_MAXIMIZEBOX == ~WS_CAPTION

      Posted via ActualForum NNTP Server 1.4

      Что значит нет? WS_EX_DLGMODALFRAME передается через параметр dwExStyle
      функции CreateWindowEx, а ты упражняешься с параметром dwStyle. На самом
      деле, это флаг диалогового окна DS_ABSALIGN, не имеющий отношения к
      обычным окнам.

      В остальном - мне кажется, пусть даже это и работает, это подход не с
      той стороны. Поведение не описано, и гарантий, что такое будет
      продолжать работать в будущих версиях системы, когда микрософту надоест
      поддерживать совместимость - нет. По мне так, если нужно создать окно
      без заголовка - нужно СОЗДАВАТЬ окно без заголовка, пользуясь
      документированными средствами (WS_POPUP). Но, конечно, хозяин - барин.

      Posted via ActualForum NNTP Server 1.4

      Да, решение. Окно поди таскается, на двойной клик отзывается, на
      alt+space тоже, если закрыть другим окошком, а потом открыть тоже красиво.

      > Расскажите мне или где почитать скажите про создание POPUP окошек, и ему
      > подобных.

      kvakvs
      А кнопка максимизации в правом верхнем углу?) Двойной щелчок по title, комбинация Win+Up/Win+Down? Убрав sizeble border проблему полностью не решить.

      KVADRO
      > А кнопка максимизации в правом верхнем углу?)

      Это тоже указывается в стиле окна, флаг WS_MAXIMIZEBOX, убери его и будет счастье.

      =A=L=X=
      Остается "Двойной щелчок по title, комбинация Win+Up/Win+Down?". А как с этим бороться?

      KVADRO
      > Остается "Двойной щелчок по title, комбинация Win+Up/Win+Down?". А как с этим
      > бороться?

      Это должно само отвалится, ибо это шоткаты над MAXIMIZEBOX.

      просто при создании не указывай флаг, позволяющий менять размеры окна
      =A=L=X= даже сказал, какой

      =A=L=X=
      >Это должно само отвалится, ибо это шоткаты над MAXIMIZEBOX.
      Спасибо большое! Не знал.) Проблема решена.

      Читайте также: