В данной статье будет рассмотрено как создать новый проект в виде классического приложения для работы в Windows. Также в студии есть возможность создавать универсальные приложения (UWP-приложение), но это уже тема другой статьи.
При выполнении примера создания нового проекта будет использована Visual Studio 2017 для объектно-ориентированного языка программирования C#. Если у Вас стоит другая версия, то она также подойдет, думаю разница должна быть не существенной, а может быть будет все также.
Создание нового проекта
Для создания нового проекта, нужно выполнить команду «Файл»-> «Создать»->»Проект» (Ctrl+Shift+N). В открывшемся окне нажать на вкладку Visual C# и далее там в предложенном списке вариантов выбрать «Классическое приложение Windows». После нажатия на этот пункт будут предложены различные варианты шаблонов приложений. Нам нужно выбрать «Приложение Windows Forms (.Net Framework)». Также в этом окне нужно задать желаемое имя, расположение, имя решения (может совпадать с именем проекта, а также выбрать платформу.
В качестве примера создадим приложение HelloWorld, как первое пробное приложение.
В поле «Имя» вводим «HelloWorld«
В поле «Расположение» — «C:\Obuch\HelloWorld\» (или любой другой путь)
Платформу оставил по умолчанию .Net Framework 4.5, но можно выбрать при желании другую версию из выпадающего списка.
Сразу после нажатия кнопки «OK» появится новая заготовка Windows-приложения.
В созданной заготовке содержится только название приложения — Form1 и пустая форма.
Теперь в этой форме создадим две кнопки: первую для вывода какого-либо сообщения пользователю, а другую для выхода из приложения. Для этого с панели элементов перетаскиваем на форму два элемента Button (по умолчанию находится с левой стороны интегрированной среды Visual Studio). Перенесенные кнопки на форму, будут иметь имя button1 и button2 соответственно. Хоть это и пример, и можно было бы имена оставить как есть, но это способствует плохой привычке. В итоге, если так делать, то при написании более большого приложения, будет намного сложнее понимать в последующем код, если будет куча таких дефолтных имен. Первую кнопку назовем btnMessage, а вторую — btnClose. Чтобы поменять имена кнопок, нужно в панели «Свойства» перейти в группу «Разработка» и там изменить дефолтный параметр Name. Также важно задать выводимый текст на кнопках, чтобы пользователю было понятно, что он нажимает. Для этого находим свойство Text и меняем для кнопки btnMessage — «Привет» и для кнопки btnClose — «Закрыть».
После того как мы разместили кнопки и дали им имена, нужно написать обработчики для выполнения необходимого функционала.
Зададим обработчик для кнопки btnMessage. Для этого выполняем двойной щелчок на кнопки. После этого среда создаст заготовку обработчика, а нам остается только вписать нужный код. Вводим в обработчик: MessageBox.Show(«Привет мир!»). В итоге обработчик будет выглядеть как в примере ниже.
private void btnMessage_Click(object sender, EventArgs e)
{
MessageBox.Show(«Привет мир!»);
}
Теперь осталось написать обработчик для второй кнопки. Проделываем тоже самое как и для первой кнопки и вводим метод закрытия приложения- Close().
private void btnClose_Click(object sender, EventArgs e)
{
Close();
}
На этом написание первой программы в Visual C# завершено. Она достаточно простая, так как имеет минимальный функционал. Чтобы запустить это приложение достаточно нажать F5 на клавиатуре.
В конце статьи дополнительно приложен архив с проектом, который можно загрузить на свой компьютер.
Search code, repositories, users, issues, pull requests…
Provide feedback
Saved searches
Use saved searches to filter your results more quickly
Sign up
Встраивает в проект поддержку для классов в библиотеке активных шаблонов (ATL). Только для консольных приложений Win32.
Remarks
Создав классическое приложение Windows, можно добавить универсальные классы C++ с помощью мастера универсального кода. Можно добавить другие элементы, такие как файлы HTML, файлы заголовков, ресурсы или текстовые файлы.
Классы ATL добавить нельзя, а классы MFC можно добавить только в те типы классических приложений Windows, которые поддерживают MFC (см. предыдущую таблицу).
Источник
Настройка классического приложения для упаковки с помощью MSIX в Visual Studio
Для создания пакета для классического приложения в Visual Studio можно использовать Проект упаковки приложений Windows. Затем вы можете распространить пакет в Microsoft Store, в Интернете, на своем предприятии или с помощью любого другого механизма распространения.
Требуемая версия и рабочая нагрузка Visual Studio
Проект упаковки приложений Windows доступен в приведенных ниже версиях Visual Studio.
Чтобы шаблон проекта упаковки приложений Windows отобразился в меню «Добавление проекта», убедитесь, что у вас установлена хотя бы одна из следующих рабочих нагрузок Visual Studio:
Для оптимальной работы рекомендуем использовать последний выпуск Visual Studio.
Проект упаковки приложений Windows в Visual Studio поддерживается в Windows 10 версии 1607 и более поздних версиях. Его можно использовать только в проектах, предназначенных для юбилейного обновления Windows 10 (10.0; сборка 14393) или более поздней версии.
Ниже приведены некоторые другие действия, которые можно выполнить в проекте упаковки приложений Visual Studio.
✔️ Автоматическое создание визуальных ресурсов
✔️ Внесение изменений в манифест с помощью визуального конструктора
✔️ Создание пакета или набора с использованием мастера
✔️ (При публикации в Microsoft Store) Простое назначение приложению идентификатора на основе имени, уже зарезервированного в Центре партнеров.
Подготовьте свое приложение
Прежде чем приступить к созданию пакета для приложения, ознакомьтесь с этим руководством. Prepare to package a desktop application (Подготовка к упаковке классического приложения).
Приведенные ниже снимки экрана относятся к Visual Studio 2019 16.10.
В Visual Studio откройте решение, содержащее проект вашего классического приложения.
Добавьте Проект упаковки приложения Windows в свое решение.
Вам не потребуется добавлять в него какой-либо код. Он используется только для создания пакета. Мы будем называть этот проект «проектом упаковки».
Задайте целевую версию проекта (любую), но для параметра Минимальная версия укажите значение Юбилейное обновление Windows 10 или более позднюю версию.
Выберите пакет классического приложения и нажмите кнопку ОК.
В пакет можно включить несколько классических приложений, однако, когда пользователи выбирают плитку с вашим приложением, запустить можно только одно из них. В узле Приложения щелкните правой кнопкой мыши приложение, которое пользователи должны запускать, выбирая плитку приложения, и выберите Задать как точку входа.
Соберите проект упаковки, чтобы убедиться, что ошибок нет. При возникновении ошибок откройте Диспетчер конфигурации и убедитесь, что проекты предназначены для той же платформы.
Дальнейшие действия
Упаковка классического приложения в Visual Studio
См. статью Package a desktop or UWP app in Visual Studio (Упаковка классического приложения или приложения UWP в Visual Studio)
Запуск, отладка и тестирование классических приложений
Дополнительные ресурсы
Видео
Call UWP APIs in desktop apps (Вызов API UWP в классических приложениях)
Улучшение классического приложения путем добавления проектов UWP и компоненты среды выполнения Windows
См. в статье Extend your desktop app with modern UWP components (Улучшение классических приложений с помощью современных компонентов UWP).
Распространение приложения
См. статью Distribute your packaged desktop app (Распространение упакованного классического приложения)
Источник
Публикация с помощью ClickOnce
В обозревателе решений щелкните проект правой кнопкой мыши и выберите пункт Опубликовать (или воспользуйтесь командой меню Сборка > Опубликовать).
Если ранее вы настроили какие-либо профили публикации, появится панель Публикация. Нажмите кнопку Создать.
В мастере публикации выберите Папка.
На странице Указанный целевой объект выберите ClickOnce.
Введите путь или выберите Обзор, чтобы выбрать расположение публикации.
На странице Расположение установки выберите место, куда пользователи будут устанавливать приложение.
На странице Параметры можно указать параметры, необходимые для ClickOnce.
Если выбрана установка из UNC-пути или с веб-сайта, на этой странице можно указать, будет ли приложение доступно в автономном режиме. Если этот параметр выбран, приложение будет указано в меню «Пуск» пользователей и будет автоматически обновляться при публикации новой версии. По умолчанию обновления доступны в папке установки. Другое расположение для обновлений можно указать с помощью ссылки «Параметры обновления». Если вы не хотите, чтобы приложение было доступно в автономном режиме, оно будет запускаться из папки установки.
Если выбрана установка с компакт-диска, DVD-диска или USB-накопителя, на этой странице также можно указать, поддерживает ли приложение автоматические обновления. Если выбрана поддержка обновлений, требуется указать расположение обновления, которое должно быть допустимым UNC-путем или веб-сайтом.
С помощью ссылок в верхней части страницы можно указать, какие файлы приложений следует включить в программу установки, какие пакеты необходимых компонентов нужно установить, а также выбрать другие параметры.
Кроме того, на этой странице можно задать версию публикации и будет ли она автоматически увеличиваться при каждой публикации.
Номер версии публикации уникален для каждого профиля ClickOnce. Этот момент следует иметь в виду, если планируется использовать несколько профилей.
На странице Подпись манифестов можно указать, следует ли подписывать манифесты и какой сертификат нужно использовать.
На странице Конфигурация можно выбрать нужную конфигурацию проекта.
Дополнительные сведения о выборе параметров см. в следующих статьях:
Щелкните Готово, чтобы сохранить новый профиль публикации ClickOnce.
На странице Сводка выберите Опубликовать, и Visual Studio создаст проект и опубликует его в указанную папку публикации. На этой странице также показана сводка профиля.
Для повторной публикации выберите Публиковать.
Источник
пошаговое руководство. создание традиционного Windows классического приложения (C++)
в этом пошаговом руководстве показано, как создать традиционное Windows классическое приложение в Visual Studio. в примере приложения, которое вы создадите, будет использоваться Windows API для вывода «Hello, Windows desktop!». «Hello, World!». Код, созданный в этом пошаговом руководстве, можно использовать в качестве шаблона для создания других классических приложений Windows.
Для краткости в тексте пропущены некоторые операторы кода. В разделе Построение кода в конце документа показан полный код.
Предварительные требования
Компьютер под управлением Microsoft Windows 7 или более поздних версий. Для обеспечения оптимальной среды разработки рекомендуется использовать Windows 10.
Базовые значения об использовании интегрированной среды разработки Visual Studio. Если вы уже использовали классические приложения для Windows, вы, вероятно, справитесь. Общие сведения см. в обзоре возможностей интегрированной среды разработки Visual Studio.
Основные навыки владения языком C++. Не волнуйтесь, мы не будем делать ничего сложного.
создание проекта Windows классических приложений
чтобы создать первый проект Windows desktop, выполните следующие действия. в процессе работы вы вводите код рабочего Windows приложения. Чтобы ознакомиться с документацией по предпочтительной версии Visual Studio, используйте селектор Версия. Он находится в верхней части оглавления на этой странице.
создание проекта Windows desktop в Visual Studio 2019
В главном меню выберите Файл > Создать > Проект, чтобы открыть диалоговое окно Создание проекта.
в верхней части диалогового окна задайте для параметра язык значение C++, задайте для параметра платформа значение Windows и задайте для параметра Project тип значение рабочий стол.
Нажмите кнопку Создать, чтобы создать проект.
В диалоговом окне Добавление нового элемента выберите Файл C++ (.cpp). В поле имя введите имя файла, например хелловиндовсдесктоп. cpp. Нажмите кнопку Добавить.
Visual C и плюсом, а также параметр файла c плюсом/с выделенным.» title=»Добавить CPP файл в Десктопапп Project» data-linktype=»relative-path»>
Теперь проект создан и исходный файл открыт в редакторе. Чтобы продолжить, перейдите к созданию кода.
создание проекта Windows desktop в Visual Studio 2017
В меню Файл выберите команду Создать, а затем пункт Проект.
в левой области диалогового окна создание Project разверните узел установленные > Visual C++ и выберите пункт Windows рабочий стол. в средней области выберите мастер рабочего стола Windows.
В поле имя введите имя проекта, например десктопапп. Нажмите кнопку ОК.
Visual C плюс плюс > Windows рабочего стола, выбранный параметр «мастер настольных систем Windows» и десктопапп введено в текстовом поле «имя».» title=»Назовите проект Десктопапп.» data-linktype=»relative-path»>
в диалоговом окне Windows рабочего стола Project в разделе тип приложения выберите Windows приложение (.exe). В поле Дополнительные параметры выберите Пустой проект. Убедитесь, что предварительно скомпилированный заголовок не выбран. Нажмите кнопку ОК, чтобы создать проект.
В диалоговом окне Добавление нового элемента выберите Файл C++ (.cpp). В поле имя введите имя файла, например хелловиндовсдесктоп. cpp. Нажмите кнопку Добавить.
Visual C и плюсом, а также параметр файла c плюсом/с выделенным.» title=»Добавить CPP файл в Десктопапп Project» data-linktype=»relative-path»>
Теперь проект создан и исходный файл открыт в редакторе. Чтобы продолжить, перейдите к созданию кода.
создание проекта Windows desktop в Visual Studio 2015
В меню Файл выберите команду Создать, а затем пункт Проект.
в левой области диалогового окна создание Project разверните узел установленные > шаблоны > Visual C++, а затем выберите пункт Win32. В средней области выберите шаблон Проект Win32.
В поле имя введите имя проекта, например десктопапп. Нажмите кнопку ОК.
> Visual C плюс плюс > win32, выделенный параметр win32 Project и десктопапп, введенные в текстовом поле имя.» title=»Назовите проект Десктопапп.» data-linktype=»relative-path»>
На странице Обзор мастера приложений Win32 нажмите кнопку Далее.
на странице Параметры приложений в разделе тип приложения выберите Windows приложение. В разделе Дополнительные параметры снимите флажок предкомпилированный заголовок, а затем выберите пустой проект. Чтобы создать проект, нажмите кнопку Готово.
В Обозреватель решений щелкните правой кнопкой мыши проект десктопапп, выберите Добавить, а затем выберите новый элемент.
В диалоговом окне Добавление нового элемента выберите Файл C++ (.cpp). В поле имя введите имя файла, например хелловиндовсдесктоп. cpp. Нажмите кнопку Добавить.
Visual C и плюсом, а также параметр файла c плюсом/с выделенным.» title=»Добавить CPP файл в Десктопапп Project» data-linktype=»relative-path»>
Теперь проект создан и исходный файл открыт в редакторе.
Создание кода
далее вы узнаете, как создать код для Windows классического приложения в Visual Studio.
Запуск классического приложения Windows
точно так же, как у каждого приложения C и C++ должна быть main функция в качестве начальной точки, каждое Windows классическое приложение должно иметь WinMain функцию. WinMain имеет следующий синтаксис:
Сведения о параметрах и возвращаемом значении этой функции см. в разделе WinMain Entry Point.
Дополнительные сведения см. в разделе Процедуры окна.
Добавление функциональных возможностей в функцию WinMain
Дополнительные сведения о полях приведенной выше структуры см. в разделе вндклассекс.
зарегистрируйте WNDCLASSEX Windows, чтобы он знал о вашем окне и способах отправки в него сообщений. Воспользуйтесь функцией RegisterClassEx и передайте структуру класса окна в качестве аргумента. Этот _T макрос используется, так как мы используем TCHAR тип.
на этом этапе окно было создано, но нам по-прежнему нужно сообщить Windows, чтобы сделать его видимым. Вот что делает этот код:
для обработки сообщений сначала нужно добавить цикл обработки сообщений для прослушивания сообщений, которые Windows отправляет. Когда приложение получает сообщение, этот цикл отправляет его в вашу WndProc функцию для обработки. Цикл обработки сообщений напоминает приведенный ниже код.
Дополнительные сведения о структурах и функциях, используемых в цикле обработки сообщений, см. в разделах, посвященных MSG, GetMessage, TranslateMessageи DispatchMessage.
На этом этапе функция WinMain должна напоминать приведенный ниже код.
Добавление функциональных возможностей в функцию WndProc
Одно важное сообщение для обработчика — WM_PAINT сообщение. Приложение получает сообщение, WM_PAINT когда часть его отображаемого окна необходимо обновить. Это событие может возникать, когда пользователь перемещает окно перед окном, а затем снова перемещает его. Приложение не знает, когда происходят эти события. только Windows знает, поэтому оно уведомляет ваше приложение с WM_PAINT сообщением. При первом отображении окна его все должно быть обновлено.
Для обработки сообщения WM_PAINT сначала вызовите метод BeginPaint, далее обработайте логику расположения текста, кнопок и других элементов управления в окне, а затем вызовите метод EndPaint. для приложения логика между начальным вызовом и завершающим вызовом отображает строку «Hello, Windows desktop!» «Hello, World!». В следующем коде функция Text используется для вывода строки.
HDC в коде — это обработчик контекста устройства, который используется для рисования в клиентской области окна. Используйте BeginPaint функции и EndPaint для подготовки и завершения рисования в клиентской области. BeginPaint Возвращает маркер контекста устройства отображения, используемый для рисования в клиентской области. EndPaint завершает запрос на рисование и освобождает контекст устройства.
Сборка кода
Как обещано, вот полный код для рабочего приложения.
Сборка примера
Удалите код, введенный в хелловиндовсдесктоп. cpp в редакторе. Скопируйте этот пример кода и вставьте его в хелловиндовсдесктоп. cpp:
В меню Построение выберите Построить решение. Результаты компиляции должны появиться в окне вывод в Visual Studio.
Чтобы запустить приложение, нажмите клавишу F5. окно, содержащее текст «Hello, Windows desktop!» должно отображаться в левом верхнем углу экрана.
Поздравляем! вы выполнили это пошаговое руководство и создали традиционное Windows классическое приложение.
Источник
Adblock
detector
Встраивает в проект поддержку для классов в библиотеке активных шаблонов (ATL). Только для консольных приложений Win32.
Remarks
Создав классическое приложение Windows, можно добавить универсальные классы C++ с помощью мастера универсального кода. Можно добавить другие элементы, такие как файлы HTML, файлы заголовков, ресурсы или текстовые файлы.
Классы ATL добавить нельзя, а классы MFC можно добавить только в те типы классических приложений Windows, которые поддерживают MFC (см. предыдущую таблицу).
Источник
пошаговое руководство. создание традиционного Windows классического приложения (C++)
в этом пошаговом руководстве показано, как создать традиционное Windows классическое приложение в Visual Studio. в примере приложения, которое вы создадите, будет использоваться Windows API для вывода «Hello, Windows desktop!». «Hello, World!». Код, созданный в этом пошаговом руководстве, можно использовать в качестве шаблона для создания других классических приложений Windows.
Для краткости в тексте пропущены некоторые операторы кода. В разделе Построение кода в конце документа показан полный код.
Предварительные требования
Компьютер под управлением Microsoft Windows 7 или более поздних версий. Для обеспечения оптимальной среды разработки рекомендуется использовать Windows 10.
Базовые значения об использовании интегрированной среды разработки Visual Studio. Если вы уже использовали классические приложения для Windows, вы, вероятно, справитесь. Общие сведения см. в обзоре возможностей интегрированной среды разработки Visual Studio.
Основные навыки владения языком C++. Не волнуйтесь, мы не будем делать ничего сложного.
создание проекта Windows классических приложений
чтобы создать первый проект Windows desktop, выполните следующие действия. в процессе работы вы вводите код рабочего Windows приложения. Чтобы ознакомиться с документацией по предпочтительной версии Visual Studio, используйте селектор Версия. Он находится в верхней части оглавления на этой странице.
создание проекта Windows desktop в Visual Studio 2019
В главном меню выберите Файл > Создать > Проект, чтобы открыть диалоговое окно Создание проекта.
в верхней части диалогового окна задайте для параметра язык значение C++, задайте для параметра платформа значение Windows и задайте для параметра Project тип значение рабочий стол.
Нажмите кнопку Создать, чтобы создать проект.
В диалоговом окне Добавление нового элемента выберите Файл C++ (.cpp). В поле имя введите имя файла, например хелловиндовсдесктоп. cpp. Нажмите кнопку Добавить.
Visual C и плюсом, а также параметр файла c плюсом/с выделенным.» title=»Добавить CPP файл в Десктопапп Project» data-linktype=»relative-path»>
Теперь проект создан и исходный файл открыт в редакторе. Чтобы продолжить, перейдите к созданию кода.
создание проекта Windows desktop в Visual Studio 2017
В меню Файл выберите команду Создать, а затем пункт Проект.
в левой области диалогового окна создание Project разверните узел установленные > Visual C++ и выберите пункт Windows рабочий стол. в средней области выберите мастер рабочего стола Windows.
В поле имя введите имя проекта, например десктопапп. Нажмите кнопку ОК.
Visual C плюс плюс > Windows рабочего стола, выбранный параметр «мастер настольных систем Windows» и десктопапп введено в текстовом поле «имя».» title=»Назовите проект Десктопапп.» data-linktype=»relative-path»>
в диалоговом окне Windows рабочего стола Project в разделе тип приложения выберите Windows приложение (.exe). В поле Дополнительные параметры выберите Пустой проект. Убедитесь, что предварительно скомпилированный заголовок не выбран. Нажмите кнопку ОК, чтобы создать проект.
В диалоговом окне Добавление нового элемента выберите Файл C++ (.cpp). В поле имя введите имя файла, например хелловиндовсдесктоп. cpp. Нажмите кнопку Добавить.
Visual C и плюсом, а также параметр файла c плюсом/с выделенным.» title=»Добавить CPP файл в Десктопапп Project» data-linktype=»relative-path»>
Теперь проект создан и исходный файл открыт в редакторе. Чтобы продолжить, перейдите к созданию кода.
создание проекта Windows desktop в Visual Studio 2015
В меню Файл выберите команду Создать, а затем пункт Проект.
в левой области диалогового окна создание Project разверните узел установленные > шаблоны > Visual C++, а затем выберите пункт Win32. В средней области выберите шаблон Проект Win32.
В поле имя введите имя проекта, например десктопапп. Нажмите кнопку ОК.
> Visual C плюс плюс > win32, выделенный параметр win32 Project и десктопапп, введенные в текстовом поле имя.» title=»Назовите проект Десктопапп.» data-linktype=»relative-path»>
На странице Обзор мастера приложений Win32 нажмите кнопку Далее.
на странице Параметры приложений в разделе тип приложения выберите Windows приложение. В разделе Дополнительные параметры снимите флажок предкомпилированный заголовок, а затем выберите пустой проект. Чтобы создать проект, нажмите кнопку Готово.
В Обозреватель решений щелкните правой кнопкой мыши проект десктопапп, выберите Добавить, а затем выберите новый элемент.
В диалоговом окне Добавление нового элемента выберите Файл C++ (.cpp). В поле имя введите имя файла, например хелловиндовсдесктоп. cpp. Нажмите кнопку Добавить.
Visual C и плюсом, а также параметр файла c плюсом/с выделенным.» title=»Добавить CPP файл в Десктопапп Project» data-linktype=»relative-path»>
Теперь проект создан и исходный файл открыт в редакторе.
Создание кода
далее вы узнаете, как создать код для Windows классического приложения в Visual Studio.
Запуск классического приложения Windows
точно так же, как у каждого приложения C и C++ должна быть main функция в качестве начальной точки, каждое Windows классическое приложение должно иметь WinMain функцию. WinMain имеет следующий синтаксис:
Сведения о параметрах и возвращаемом значении этой функции см. в разделе WinMain Entry Point.
Дополнительные сведения см. в разделе Процедуры окна.
Добавление функциональных возможностей в функцию WinMain
Дополнительные сведения о полях приведенной выше структуры см. в разделе вндклассекс.
зарегистрируйте WNDCLASSEX Windows, чтобы он знал о вашем окне и способах отправки в него сообщений. Воспользуйтесь функцией RegisterClassEx и передайте структуру класса окна в качестве аргумента. Этот _T макрос используется, так как мы используем TCHAR тип.
на этом этапе окно было создано, но нам по-прежнему нужно сообщить Windows, чтобы сделать его видимым. Вот что делает этот код:
для обработки сообщений сначала нужно добавить цикл обработки сообщений для прослушивания сообщений, которые Windows отправляет. Когда приложение получает сообщение, этот цикл отправляет его в вашу WndProc функцию для обработки. Цикл обработки сообщений напоминает приведенный ниже код.
Дополнительные сведения о структурах и функциях, используемых в цикле обработки сообщений, см. в разделах, посвященных MSG, GetMessage, TranslateMessageи DispatchMessage.
На этом этапе функция WinMain должна напоминать приведенный ниже код.
Добавление функциональных возможностей в функцию WndProc
Одно важное сообщение для обработчика — WM_PAINT сообщение. Приложение получает сообщение, WM_PAINT когда часть его отображаемого окна необходимо обновить. Это событие может возникать, когда пользователь перемещает окно перед окном, а затем снова перемещает его. Приложение не знает, когда происходят эти события. только Windows знает, поэтому оно уведомляет ваше приложение с WM_PAINT сообщением. При первом отображении окна его все должно быть обновлено.
Для обработки сообщения WM_PAINT сначала вызовите метод BeginPaint, далее обработайте логику расположения текста, кнопок и других элементов управления в окне, а затем вызовите метод EndPaint. для приложения логика между начальным вызовом и завершающим вызовом отображает строку «Hello, Windows desktop!» «Hello, World!». В следующем коде функция Text используется для вывода строки.
HDC в коде — это обработчик контекста устройства, который используется для рисования в клиентской области окна. Используйте BeginPaint функции и EndPaint для подготовки и завершения рисования в клиентской области. BeginPaint Возвращает маркер контекста устройства отображения, используемый для рисования в клиентской области. EndPaint завершает запрос на рисование и освобождает контекст устройства.
Сборка кода
Как обещано, вот полный код для рабочего приложения.
Сборка примера
Удалите код, введенный в хелловиндовсдесктоп. cpp в редакторе. Скопируйте этот пример кода и вставьте его в хелловиндовсдесктоп. cpp:
В меню Построение выберите Построить решение. Результаты компиляции должны появиться в окне вывод в Visual Studio.
Чтобы запустить приложение, нажмите клавишу F5. окно, содержащее текст «Hello, Windows desktop!» должно отображаться в левом верхнем углу экрана.
Поздравляем! вы выполнили это пошаговое руководство и создали традиционное Windows классическое приложение.
Источник
Создание приложения Windows Forms на C# в Visual Studio
В рамках этого краткого знакомства с возможностями интегрированной среды разработки Visual Studio (IDE) вы создадите простое приложение на C# с пользовательским интерфейсом на основе Windows.
Установите Visual Studio бесплатно со страницы скачиваемых материалов Visual Studio, если еще не сделали этого.
Установите Visual Studio бесплатно со страницы скачиваемых материалов Visual Studio, если еще не сделали этого.
На некоторых снимках экрана в этом учебнике используется темная тема. Если вы не используете темную тему, но хотите переключиться на нее, см. страницу Персонализация интегрированной среды разработки и редактора Visual Studio.
Установите Visual Studio бесплатно со страницы скачиваемых материалов Visual Studio 2022, если еще не сделали этого.
Создание проекта
Сначала вы создадите проект приложения на C#. Для этого типа проекта уже имеются все нужные файлы шаблонов, что избавляет вас от лишней работы.
Откройте Visual Studio 2017.
В верхней строке меню последовательно выберите Файл > Создать > Проект.
Запустите Visual Studio.
На начальном экране выберите Создать проект.
В окне Создать проект выберите шаблон Приложение Windows Forms (.NET Framework) для C#.
(При желании вы можете уточнить условия поиска, чтобы быстро перейти к нужному шаблону. Например, введите Приложение Windows Forms в поле поиска. Затем выберите C# в списке языков и Windows в списке платформ.)
Если шаблон Приложение Windows Forms (.NET Framework) отсутствует, его можно установить из окна Создание проекта. В сообщении Не нашли то, что искали? выберите ссылку Установка других средств и компонентов.
Затем нажмите кнопку Изменить в Visual Studio Installer. Вам может быть предложено сохранить результаты работы; в таком случае сделайте это. Выберите Продолжить, чтобы установить рабочую нагрузку. После этого вернитесь к шагу 2 в процедуре Создание проекта.
В поле Имя проекта окна Настроить новый проект введите HelloWorld. Затем нажмите Создать.
Новый проект открывается в Visual Studio.
Запустите Visual Studio.
В окне запуска выберите Создание нового проекта.
В окне Создание проекта выберите шаблон Приложение Windows Forms (.NET Framework) для C#.
(При желании вы можете уточнить условия поиска, чтобы быстро перейти к нужному шаблону. Например, введите Приложение Windows Forms в поле поиска. Затем выберите C# в списке языков и Windows в списке платформ.)
Если шаблон Приложение Windows Forms (.NET Framework) отсутствует, его можно установить из окна Создание проекта. В сообщении Не нашли то, что искали? выберите ссылку Установка других средств и компонентов.
Затем нажмите кнопку Изменить в Visual Studio Installer. Вам может быть предложено сохранить результаты работы; в таком случае сделайте это. Выберите Продолжить, чтобы установить рабочую нагрузку. После этого вернитесь к шагу 2 в процедуре Создание проекта.
В поле Имя проекта окна Настроить новый проект введите HelloWorld. Затем выберите Создать.
Новый проект открывается в Visual Studio.
Создание приложения
Когда вы выберете шаблон проекта C# и зададите имя файла, Visual Studio открывает форму. Форма является пользовательским интерфейсом Windows. Мы создадим приложение Hello World, добавив элементы управления на форму, а затем запустим его.
Добавление кнопки на форму
Выберите Панель элементов, чтобы открыть всплывающее окно «Панель элементов».
(Если параметр для всплывающего окна Панель элементов отсутствует, его можно открыть в строке меню. Для этого выберите Вид > Панель элементов. Либо нажмите клавиши CTRL+ALT+X.)
Выберите значок Закрепить, чтобы закрепить окно Панель элементов.
Выберите элемент управления Кнопка и перетащите его на форму.
В окне Свойства найдите Текст, измените имя с button1 на Click this и нажмите клавишу ВВОД.
(Если окно Свойства не отображается, его можно открыть в строке меню.) Для этого выберите Вид > Окно свойств. Или нажмите клавишу F4.)
В разделе Конструктор окна Свойства измените имя с button1 на btnClickThis и нажмите клавишу ВВОД.
Если список в окне Свойства был упорядочен по алфавиту, button1 появится в разделе Привязки данных.
Добавление метки на форму
Теперь, когда мы добавили элемент управления »Кнопка» для создания действия, давайте добавим элемент управления «Метка», куда можно отправлять текст.
Выберите элемент управления Метка в окне Панель элементов, а затем перетащите его на форму и расположите под кнопкой Нажмите это.
В разделе Конструктор или Привязки данных окна Свойства измените имя label1 на lblHelloWorld и нажмите клавишу ВВОД.
Добавление кода на форму
В окне Form1.cs [Проект] дважды щелкните кнопку Нажмите это, чтобы открыть окно Form1.cs.
(Кроме того, можно развернуть узел Form1.cs в обозревателе решений, а затем выбрать Form1.)
Запуск приложения
Нажмите кнопку Запустить, чтобы запустить приложение.
Будет выполнено несколько операций. В интегрированной среде разработки Visual Studio откроются окна Средства диагностики и Вывод. Кроме того, вне этой среды откроется диалоговое окно Form1. Оно будет содержать вашу кнопку Нажмите это и текст label1.
Закройте диалоговое окно Form1, чтобы завершить работу приложения.
Создание приложения
Когда вы выберете шаблон проекта C# и зададите имя файла, Visual Studio открывает форму. Форма является пользовательским интерфейсом Windows. Мы создадим приложение Hello World, добавив элементы управления на форму, а затем запустим его.
Добавление кнопки на форму
Щелкните Панель элементов, чтобы открыть всплывающее окно «Панель элементов».
(Если параметр для всплывающего окна Панель элементов отсутствует, его можно открыть в строке меню. Для этого выберите Вид > Панель элементов. Либо нажмите клавиши CTRL+ALT+X.)
Щелкните значок Закрепить, чтобы закрепить окно Панель элементов.
Выберите элемент управления Кнопка и перетащите его на форму.
(Если окно Свойства не отображается, его можно открыть в строке меню.) Для этого выберите Вид > Окно свойств. Или нажмите клавишу F4.)
Добавление метки на форму
Теперь, когда мы добавили элемент управления »Кнопка» для создания действия, давайте добавим элемент управления «Метка», куда можно отправлять текст.
Выберите элемент управления Метка в окне Панель элементов, а затем перетащите его на форму и расположите под кнопкой Нажмите это.
В разделе Проект или (DataBindings) окна Свойства измените имя Label1 на lblHelloWorld и нажмите клавишу ВВОД.
Добавление кода на форму
В окне Form1.cs [Проект] дважды щелкните кнопку Нажмите это, чтобы открыть окно Form1.cs.
(Кроме того, можно развернуть узел Form1.cs в обозревателе решений, а затем выбрать Form1.)
Запуск приложения
Нажмите кнопку Запустить, чтобы запустить приложение.
Будет выполнено несколько операций. В интегрированной среде разработки Visual Studio откроются окна Средства диагностики и Вывод. Кроме того, вне этой среды откроется диалоговое окно Form1. Оно будет содержать вашу кнопку Нажмите это и текст Label1.
Закройте диалоговое окно Form1, чтобы завершить работу приложения.
Следующие шаги
Для получения дополнительных сведений перейдите к следующему руководству:
Источник
Вам также понравится
Adblock
detector
{lang: ‘ru’}
В этом посте мне хочется рассказать, как создать простейшее приложение с использованием WinAPI и языка программирования C++. Обычное, пустое окошко Windows. Причины, побудившие меня к этому, очень просты: источники, которые я читал до определенного момента не давали мне полного представления о том, что и как работает в приложении Win32. Понимать я это стал, как ни удивительно, только после того, как тот же материал был освещен на лекциях в универе. Почему-то в том виде, в каком преподносилась информация на лекциях, она лучше откладывалась в памяти, нежели “книжные” записи, пусть даже совсем неплохие. Еще одно обстоятельство, способствовавшее идее освящения данной темы – желание лучше закрепить материал, излагая его в письменной форме и, возможно, даже расширить свои знания, заглянув лишний раз в MSDN, чтобы дополнить что-то.
Как, думаю, стало понятно из вступления, материал этого поста и, вероятно, последующих постов на тему WinAPI и программирования под ОС Windows будут основываться на универских лекциях + MSDN с добавлением чего-то от себя (по делу :)).
Наверное, в начале стоит сказать, что Windows API (Application Programming Interface) – это набор готовых классов, функций, структур и констант, при помощи которых любое приложение может взаимодействовать с операционной системой (ОС) Windows.
Также отмечу, что основные функции Windows API появились еще в Win16 и с каждой версией Windows их набор расширяется, утрачивая, однако, при этом поддержку некоторых других функций. Схематично, это можно представить так:
Итак, для любого Windows приложения требуется написать как минимум 2 функции:
Точка входа в приложение, в которой необходимо:
- зарегистрировать класс окна
- создать окно
- запустить цикл обработки сообщений
Сама точка выглядит примерно так:
// точка входа в программу #include <windows.h> //добавляем к проекту заголовочный файл с основными функциями и макросами Windows API int WINAPI WinMain(HINSTANCE hInst, //хендл на это приложение HINSTANCE hPrev, //оставлен для совместимости с Win16, всегда = 0 LPSTR szCmdLine, //командная строка этого приложения int nShowCmd) //параметры, указывающие, как надо запускать приложение { //здесь будет текст программы } |
Вопрос №1: Что значит LPSTR? В ответе представлю небольшой список “особенностей” строковых типов WINAPI.
char работает с ASCII
wchar_t – с Unicode
Они оба используются в макросе TCHAR, который позволяет их “перещелкивать” в зависимости от настроек проекта (компилируется под ASCII или под Unicode)
Также, в WinAPI зачастую есть 2 по сути одинаковые функции:
FuncNameA – для работы с ASCII
FuncNameW – для работы с Unicode
Строковые типы в WinAPI:
LPSTR – char* LOWSTR – wchar_t* LPISTR – TCHAR* LPCSTR – const char* LPCWSTR – const wchar_t* LPCTSTR – const TCHAR* |
об остальных можно посмотреть в MSDN
Вопрос №2: Что значит WinAPI? Ответ: WinAPI скрывает под собой используемое соглашение вызова — __stdcall, по которому параметры заносятся в стек справа налево (что позволяет вызывать функции с переменным числом параметров), а очищается он вызываемой функцией. Подробнее о различных соглашениях вызова можно почитать в Википедии.
2. Обработчик сообщений.
Начнем по порядку.
Регистрация окна осуществляется в два этапа:
заполнение структуры окна
передача структуры на регистрацию
Когда выглядит примерно так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// 1й этап // регистрируется класс WNDCLASSEX wcx = {0};//обнуляем сразу все поля структуры, чтобы ничего не забыть, т.к. понадобятся нам пока не все// я же говорил что WNDCLASSEX можно не юзать, но MSDN ругается wcx.cbSize = sizeof(WNDCLASSEX); //по размеру структуры Windows определит, какая версия API была использована wcx.style = CS_HREDRAW | CS_VREDRAW;// говорим окну перерисовываться при изменении размеров окна wcx.lpfnWndProc = WndProc;// указываем функцию обработки сообщений wcx.hInstance = hInst; // хендл на наше приложение wcx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // GetStockObject возвращает хендл на белую кисточку, для фона окна wcx.lpszClassName = TEXT(«[lexpenz.com] Win32.»);// имя данного класса. Должно быть уникальным, иначе, если класс с таким именем уже зарегестрирован, то в регистрации будет отказано if ( !RegisterClassEx(&wcx) ) return 1;// регистрируем ( не получилось — уходим по английски ) с кодом ошибки (1) Вопрос №3: Что делает макрос TEXT? Ответ: Он, если идет компиляция с использованием Unicode, добавляет префикс «l» к переданной ему строке, что делает её Unicode—строкой для компилятора. Далее идет создание окна: // 2й этап // создается окно HWND hWnd = CreateWindowEx(0, TEXT(«[lexpenz.com] Win32.»),//имя класса TEXT(«[lexpenz.com] Win32. Первое приложение Win32.»),//заголовок окна WS_OVERLAPPEDWINDOW, //тип окошка (включает отображение системного меню, кнопок в верхнем правом углу и т.п.) CW_USEDEFAULT,0,//место появления окна (координаты х и y). Здесь указано место “по умолчанию”, поэтому второй параметр игнорируется CW_USEDEFAULT,0,//ширина окна (определяется аналогично месту появления) 0, //ссылка на родительское окно 0,//хендл меню hInst, 0);//код, передаваемый с сообщением WM_CREATE if (!hWnd) //проверяем успешность создания окна return 2; // теперь показываем окошко ( nShowCmd — как его показать? минимизированным, обычным или … ) ShowWindow(hWnd, nShowCmd); // говорим окну обновиться UpdateWindow(hWnd); И, наконец, // 3й этап // запуск главного цикла обработки сообщений MSG msg = {0};// создаем структуру сообщения, которую будем обрабатывать while( GetMessage(&msg, 0,//говорим получать сообщения от всех окон 0, 0) )//диапазон значений получаемых сообщений (сейчас получаем все) { // ждем сообщение TranslateMessage(&msg); // преобразуем виртуальную клавишу в ASCII-код и посылаем сообщение WM_CHAR (тут не нужно.Необходимо, если надо работать с текстом, вводимым с клавиатуры) DispatchMessage(&msg); // передаем сообщения для обработки в «главную функцию обработки сообщений» } |
Для завершения основной функции, осталось прописать только код возврата:
return( (int)msg.wParam );// т.к. это функция, то вернем параметр WM_QUIT сообщения (см. PostQuitMessage)
Функция обработки сообщений:
// главная функция обработки сообщений LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_DESTROY:// если этого не сделать, то все ваши жалкие попытки закрыть окно будут проигнорированы PostQuitMessage(0);// отправляет приложению сообщение WM_QUIT. Принимает код ошибки, который заносится в wParam сообщения WM_QUIT break; } return DefWindowProc(hWnd, msg, wParam, lParam);//обрабатываем все остальные сообщения обработчиком «по умолчанию» } |
И, как всегда, в завершении статьи ссылка на архив с примером.
Полезная статья? Их будет больше, если вы поддержите меня!
Disclaimer
Казалось бы, что WinAPI уходит в прошлое. Давно уже существует огромное количество кросс-платформенных фреймфорков, Windows не только на десктопах, да и сами Microsoft в свой магазин не жалуют приложения, которые используют этого монстра. Помимо этого статей о том, как создать окошки на WinAPI, не только здесь, но и по всему интернету, исчисляется тысячами по уровню от дошколят и выше. Весь этот процесс разобран уже даже не по атомам, а по субатомным частицам. Что может быть проще и понятнее? А тут я еще…
Но не все так просто, как кажется.
Почему о WinAPI сейчас?
В один прекрасный момент, изучая потроха одной из игр в весьма неплохом эмуляторе NES, я подумал: Вроде неплохой такой эмуль, а в отладчике нет такой простой вещи, как навигация по кнопкам клавиатуры, которая есть в любом нормальном отладчике.
Здесь я не зря дал ссылку на репозиторий, т.к. видно, что ребята столкнулись с проблемой, о которой речь пойдет ниже, но так и не решили ее.
О чем это я? А вот об этом кусочке кода:
case WM_KEYDOWN:
MessageBox(hwndDlg,"Die!","I'm dead!",MB_YESNO|MB_ICONINFORMATION);
break;
Таким образом, авторы хотели добавить поддержку клавиатуры, но суровая реальность недр архитектуры диалоговых окон в Windows жестко пресекла такую самодеятельность. Те, кто пользовался эмулятором и отладчиком в нем, хоть раз видели это сообщение?
В чем же проблема?
Ответ такой: так делать нельзя!
И, возвращаясь, к изначальному вопросу о WinAPI: очень много популярных, и не очень, проектов продолжают его использовать и в настоящее время, т.к. лучше, чем на чистом API многие вещи не сделать (тут можно бесконечно приводить аналогии вроде сравнения высокоуровневых языков и ассемблера, но сейчас не об этом). Да и мало ли почему? Просто используют и все тут.
О проблеме
Диалоговые окна упрощают работу с GUI, одновременно лишая нас возможности сделать что-то самостоятельно. Например, сообщения WM_KEYDOWN/WM_KEYUP, приходящие в оконную процедуру, «съедаются» в недрах DefDlgProc, беря на себя такие вещи, как: Навигация по Tab, обработка клавиш Esc, Enter, и т.д. Кроме того, диалоги не нужно создавать вручную: проще, ведь, набросать кнопок, списков, в редакторе ресурсов, вызвать в WinMain CreateDialog/DialogBox и все готово.
Обойти такие мелкие неприятности просто. Есть, как минимум, два вполне легальных способа:
- Создать свой собственный класс через RegisterClassEx и в процедуре обработки класса схватывать WM_KEYDOWN, перенаправлять в процедуру обработки самого диалога. Да-да! Можно создавать диалоги со своим собственным классом, и встроенный в VS редактор даже позволяет задавать имя класса для диалога. Вот только кто об этом знает и этим пользуется?
Минус очевиден: Нужно регистрировать еще один класс, иметь на 1 CALLBACK процедуру больше, суть которой будет всего-навсего в трансляции пары сообщений. Кроме того, мы не будем знать куда их транслировать, и придется городить костыли. - Использовать встроенный механизм акселераторов. И нам даже не придется менять код диалоговой процедуры! Ну, разве что, добавить одну строчку в switch/case, но об этом ниже.
Tutorials?
Не побоюсь сказать, что все туториалы по созданию окон через WinAPI начинаются с такого незамысловатого кода, обозначая его, как «цикл обработки сообщений» (опущу детали по подготовке класса окна и прочую обвязку):
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Здесь действительно все просто:
- GetMessage() выхватывает очередное сообщение из очереди, и ключевой момент: блокирует поток, если в очереди пусто.
- TranslateMessage() из WM_KEYDOWN/WM_KEYUP формирует сообщения WM_CHAR/WM_SYSCHAR (они нужны, если кто-то хочет сделать свой редактор текста).
- DispatchMessage() отправляет сообщение в оконную процедуру (если таковая существует).
Начнем с того, что этот код использовать опасно, и вот почему. Обратите внимание на сноску:
Because the return value can be nonzero, zero, or -1, avoid code like this:
while (GetMessage( lpMsg, hWnd, 0, 0)) ...
И ниже приводится пример правильного цикла.
Стоит сказать, что в шаблонах VS для Win32 приложений, написан именно такой неправильный цикл. И это очень печально. Ведь мало кто будет вникать в то, что сделали сами авторы, ведь это априори правильно. И неправильный код множится вместе с багами, которые очень сложно отловить.
После этого фрагмента кода, как правило, следует рассказ про акселераторы, и добавляется пара новых строчек (учитывая замечание в MSDN, предлагаю сразу писать правильный цикл):
HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR));
BOOL bRet = 0;
while ( bRet = GetMessage(&msg, nullptr, 0, 0) )
{
if ( -1 == bRet ) break;
if ( !TranslateAccelerator(msg.hwnd, hAccel, &msg) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Этот вариант я видел чаще всего. И он (та-дам) снова неправильный!
Сперва о том, что изменилось (потом о проблемах этого кода):
В первой строчке из ресурсов загружается таблица клавиш, при нажатии на которые, будет формироваться сообщение WM_COMMAND с соответствующим id команды.
Собственно TranslateAccelerator этим и занимается: если видит WM_KEYDOWN и код клавиши, которые есть в этом списке, то (опять же ключевой момент) будет формировать сообщение WM_COMMAND (MAKEWPARAM(id, 1)) и отправлять в соответствующую для дескриптора окна, указанного в первом аргументе, процедуру обработки.
Из последней фразы, думаю, стало понятно, в чем проблема предыдущего кода.
Поясню: GetMessage выхватывает сообщения для ВСЕХ объектов типа «окно» (в число которых входят и дочерние: кнопки, списки и прочее), а TranslateAccelerator будет отправлять сформированную WM_COMMAND куда? Правильно: обратно в кнопку/список и т.д. Но мы обрабатываем WM_COMMAND в своей процедуре, а значит нам интересно ее получать в ней же.
Ясно, что TranslateAccelerator надо вызывать для нашего созданного окна:
HWND hMainWnd = CreateWindow(...);
HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR));
BOOL bRet = 0;
while (bRet = GetMessage(&msg, nullptr, 0, 0))
{
if ( -1 == bRet ) break;
if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
И вроде все хорошо и замечательно теперь: мы разобрали все детально и все должно работать идеально.
И снова нет. Это будет работать правильно, пока у нас ровно одно окно — наше. Как только появится немодальное новое окно (диалог), все клавиши, которые будут в нем нажаты оттранслируются в WM_COMMAND и отправляться куда? И опять же правильно: в наше главное окно.
На этом этапе предлагаю не городить костылей по решению этой тупиковой ситуации, а предлагаю рассмотреть вещи, которые уже реже (или почти не встречаются) в туториалах.
IsDialogMessage
По названию этой функции можно подумать, что она зачем-то определяет: относится данное сообщение диалогу или нет. Но, во-первых, зачем нам это знать? А во-вторых, что с этой информацией нам делать дальше?
На самом деле, делает она чуть больше, чем следует из названия. А именно:
- Осуществляет навигацию по дочерним контролам кнопками Tab/Shift+Tab/вверх/вниз/вправо/влево. Плюс еще кое-что, но этого нам достаточно
- По нажатии на ESC формирует WM_COMMAND( IDCANCEL )
- По нажатии на Enter формирует WM_COMMAND( IDOK ) или нажатие на текущую кнопку по умолчанию
- Переключает кнопки по умолчанию (рамочка у таких кнопок чуть ярче остальных)
- Ну и еще разные штуки, которые облегчают пользователю работу с диалогом
Что она нам дает? Во-первых, нам не надо думать о навигации внутри окна. Нам и так все сделают. Кстати, навигацию по Tab можно сделать, добавив стиль WS_EX_CONTROLPARENT нашему основному окну, но это топорно и не так функционально.
Во-вторых, она нам облегчит жизнь по всем остальным пунктам, перечисленным в списке (и даже немного больше).
Вообще, она используется где-то в недрах 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.
Т.е. теперь, если мы оформим цикл так:
HWND hMainWnd = CreateWindow(...);
HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR));
BOOL bRet = 0;
while (bRet = GetMessage(&msg, nullptr, 0, 0))
{
if ( -1 == bRet ) break;
if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) )
{
if ( !IsDialogMessage(hMainWnd, &msg) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
То наше окошко будет иметь навигацию, как в родном диалоге Windows. Но теперь мы получили два недостатка:
- Этот код также будет хорошо работать только с одним (немодальным) окном;
- Получив все достоинства диалоговой навигации, мы лишаемся прелестей в виде сообщений WM_KEYDOWN/WM_KEYUP (только для самого окна, а не для дочерних контролов);
И вот на этом этапе вообще все туториалы заканчиваются и начинаются вопросы: How to handle keyboard events in a winapi standard dialog?
Это первая ссылка в гугле, но поверьте: тысячи их. Про предлагаемые решений (лучшее из которых — это создать свой класс диалогов, о чем я писал выше, до subclassing и RegisterHotKey. Где-то я даже видел «лучший» из способов: использовать Windows Hooks).
Пора поговорить о том, чего нет в туториалах и ответах.
Как правило (как правило! Если кому-то захочется большего, то можно регистрировать свой класс для диалогов и работать так. И, если же, кому-то это интересно, я могу дополнить этим статью) 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:
If the function retrieves the WM_QUIT message, the return value is zero.
Таким образом, выход из GetMessage-loop осуществится, если мы вызовем PostQuitMessage в процедуре окна. Что это значит?
Мы можем для каждого немодального окна в нашей программе создавать свой собственный подобный цикл. В данном случае DialogBoxParam нам не подходит, т.к. оно крутит свой собственный цикл и повлиять мы на него не можем. Однако если создадим диалог через CreateDialogBoxParam или окно через CreateWindow, то можно закрутить еще один цикл. При этом в каждом таком окне и диалоге мы должны вызывать PostQuitMessage:
HWND hMainWnd = CreateWindow(...);
HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR));
BOOL bRet = 0;
while (bRet = GetMessage(&msg, nullptr, 0, 0))
{
if ( -1 == bRet ) break;
if ( !TranslateAccelerator(hMainWnd, hAccel, &msg) )
{
if ( !IsDialogMessage(hMainWnd, &msg) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
// ....
LRESULT CALLBACK WndProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
switch( umsg )
{
case WM_MYMESSAGE:
{
HWND hDlg = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_MYDIALOG), hwnd, MyDialogBoxProc);
HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR_FOR_MY_DIALOG));
BOOL bRet = 0, fSavedEnabledState = IsWindowEnabled(hwnd);
EnableWindow(hwnd, FALSE); // disable parent window, as dialog window is modal
while (bRet = GetMessage(&msg, nullptr, 0, 0))
{
if ( -1 == bRet ) break;
if ( !TranslateAccelerator(hDlg, hAccel, &msg) )
{
if ( !IsDialogMessage(hDlg, &msg) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
EnableWindow(hwnd, fSavedEnabledState); // enable parent window. Dialog was closed
break;
}
}
}
INT_PTR CALLBACK MyDlgProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
switch(umsg)
{
case WM_CLOSE:
{
// EndDialog( hwnd, 0 ); -- DONT DO THAT!
// EndDialog is valid ONLY for Modal Dialogs, created with DialogBox(Param)
DestroyWindow( hwnd );
break;
}
case WM_DESTROY:
{
PostQuitMessage( 0 );
break;
}
// ....
}
return 0;
}
Обратите внимание: теперь для каждого нового окна в нашей программе мы можем добавить в обработку собственную таблицу акселераторов. WM_QUIT будет выхватывать GetMessage из цикла для диалога, а внешний цикл его даже не увидит. Почему так происходит?
Дело в том, что внешний цикл «встал» на вызове DispatchMessage, который вызвал нашу процедуру, которая крутит свой собственный внутренний цикл GetMessage с таким же DispatchMessage. Классический вложенный вызов (в данном случае DispatchMessage). Посему внешний цикл не получит WM_QUIT и не завершится на этом этапе. Все будет работать стройно.
Но и тут есть свои недостатки:
Каждый такой цикл будет обрабатывать сообщения только для «своего» окна. Про другие-то мы здесь не знаем. А значит, если где-то объявится еще один цикл, то все остальные окна не будут получать нужной обработки своих сообщений парой TranslateAccelerator/IsDialogMessage.
Что ж, пора учесть все эти замечание и написать наконец правильную обработку всех сообщений от всех окон нашей программы. Хочу заметить, что ниже рассматривается случай для одного потока. Т.к. каждый поток имеет свою очередь сообщений, то для каждого потока придется создавать свои структуры. Делается это весьма тривиальными изменениями в коде.
Делаем красиво
Т.к. правильная постановка задачи является половиной решения, то сперва надо эту самую задачу правильно же и поставить.
Во-первых, было бы логично, что только активное окно принимает сообщения. Т.е. для неактивного окна мы не будем транслировать акселераторы и передавать сообщения в IsDialogMessage.
Во-вторых, если для окна не задана таблица акселераторов, то транслировать нечего, будем просто отдавать сообщение в IsDialogMessage.
Создадим простой std::map, который будет мапить дескриптор окна в дескриптор таблицы акселераторов. Вот так:
std::map<HWND,HACCEL> l_mAccelTable;
И по мере создания окон будем в него добавлять новые окна с дескриптором на свою любимую таблицу (или нуль, если такая обработка не требуется).
Вот так:
BOOL AddAccelerators(HWND hWnd, HACCEL hAccel)
{
if ( IsWindow( hWnd ) )
{
l_mAccelTable[ hWnd ] = hAccel;
return TRUE;
}
return FALSE;
}
BOOL AddAccelerators(HWND hWnd, LPCTSTR accel)
{
return AddAccelerators( hWnd, LoadAccelerators( hInstance, accel ) );
}
BOOL AddAccelerators(HWND hWnd, int accel)
{
return AddAccelerators( hWnd, MAKEINTRESOURCE( accel ) );
}
BOOL AddAccelerators(HWND hWnd)
{
return AddAccelerators( hWnd, HACCEL( NULL ) );
}
Ну и после закрытия окна удалять. Вот так:
void DelAccel(HWND hWnd)
{
std::map<HWND, HACCEL>::iterator me = l_mAccelTable.find( hWnd );
if ( me != l_mAccelTable.end() )
{
if ( me->second )
{
DestroyAcceleratorTable( me->second );
}
l_mAccelTable.erase( me );
}
}
Теперь, как создаем новый диалог/окно, вызываем AddAccelerators( hNewDialog, IDR_MY_ACCEL_TABLE ). Как закрываем: DelAccel( hNewDialog ).
Список окон с нужными дескрипторами у нас есть. Немного модифицируем наш основной цикл обработки сообщений:
// ...
HWND hMainWnd = CreateWindow(...);
AddAccelerators(hMainWnd, IDR_ACCELERATOR);
BOOL bRet = 0;
while (bRet = GetMessage(&msg, nullptr, 0, 0))
{
if ( -1 == bRet ) break;
if ( !HandleAccelArray( GetActiveWindow(), msg ) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// ...
Значительно лучше! Что же там в 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.
Если первая будет возвращать дескриптор любого окна в системе, то последняя только того, которое использует очередь сообщений нашего потока. Т.к. нас интересуют окна только нашего потока (а значит те, которые будут попадать в нашу очередь сообщений), то и возьмем последнюю.
Так вот HandleAccelArray, руководствуясь переданным ей дескриптором на активное окно, ищет это самое окно в нашей мапе, и если оно там есть, отдает это сообщение на трансляцию в TranslateAccelerator, а затем (если первый не увидел нужного) в IsDialogMessage. Если и последняя не обработала сообщение, то возвращаем FALSE, чтобы пройти по стандартной процедуре TranslateMessage/DispatchMessage.
Выглядит так:
BOOL HandleAccelWindow(std::map<HWND,HACCEL>::const_iterator mh, MSG & msg)
{
const HWND & hWnd = mh->first;
const HACCEL & hAccel = mh->second;
if ( !TranslateAccelerator( hWnd, hAccel, &msg ) )
{
// message not for TranslateAccelerator. Try it with IsDialogMessage
if ( !IsDialogMessage( hWnd, &msg ) )
{
// so, do default stuff
return FALSE;
}
}
// ok, message translated. Say to message-loop, to get next message
return TRUE;
}
BOOL HandleAccelArray( HWND hActive, MSG & msg )
{
if ( !hActive )
return FALSE; // no active window. Nothing to do
std::map<HWND, HACCEL>::const_iterator mh = l_mAccelTable.find( hActive );
if ( mh != l_mAccelTable.end() )
{
// Got it! Try to translate this message for the active window
return HandleAccelWindow( mh, msg );
}
return FALSE;
}
Теперь каждое дочернее окно вправе добавить себе любимую таблицу акселераторов и спокойно ловить и обрабатывать WM_COMMAND с нужным кодом.
А что там еще об одной строчке в коде обработчика WM_COMMAND?
Описание в TranslateAccelerator гласит:
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 выглядит так:
switch( HIWORD( wParam ) )
{
case BN_CLICKED: // command from buttons/menus
{
switch( LOWORD( wParam ) )
{
case IDC_BUTTON1: DoButton1Stuff(); break;
case IDC_BUTTON2: DoButton2Stuff(); break;
// ...
}
break;
}
}
Теперь можно написать так:
switch( HIWORD( wParam ) )
{
case 1: // accelerator
case BN_CLICKED: // command from buttons/menus
{
switch( LOWORD( wParam ) )
{
case IDC_BUTTON1: DoButton1Stuff(); break;
case IDC_BUTTON2: DoButton2Stuff(); break;
// ...
}
break;
}
}
И теперь, возвращаясь к тому же fceux, добавив всего одну строчку в код обработки команд от кнопок, мы получим желаемое: управлять дебагером с клавиатуры. Достаточно добавить небольшую обертку вокруг главного цикла сообщений и новую таблицу акселераторов с нужными соответствиями VK_KEY => IDC_DEBUGGER_BUTTON.
P.S.: Мало кто знает, но можно создавать свою собственную таблицу акселераторов, а теперь и применять ее прямо налету.
P.P.S.: Т.к. DialogBox/DialogBoxParam крутит собственный цикл, то от при вызове диалога через них акселераторы работать не будут и наш цикл (или циклы) будет «простаивать».
P.P.P.S.: После вызова HandleAccelWindow мап l_mAccelTable может измениться, т.к. TranslateAccelerator или IsDialogMessage вызывают DispatchMessage, а там может встретиться AddAccelerators или DelAccel в наших обработчиках! Поэтому лучше его после этой функции не трогать.
Пощупать код можно здесь. За основу был взят код, генерируемый из стандартного шаблона MS VS 2017.
Оконные приложения строятся по принципам событийно-управляемого программирования (event-driven programming) — стиля программирования, при котором поведение компонента системы определяется набором возможных внешних событий и ответных реакций компонента на них. Такими компонентами в Windows являются окна.
С каждым окном в Windows связана определенная функция обработки событий – оконная функция. События для окон называются сообщениями. Сообщение относится к тому или иному типу, идентифицируемому определенным кодом (32-битным целым числом), и сопровождается парой 32-битных параметров (WPARAM и LPARAM), интерпретация которых зависит от типа сообщения.
Задача любого оконного приложения — создать главное окно и сообщить Windows функцию обработки событий для этого окна. Все самое интересное для приложения будет происходить именно в функции обработки событий главного окна.
В Windows программа пассивна. После запуска она ждет, когда ей уделит внимание операционная система. Операционная система делает это посылкой сообщений. Сообщения могут быть разного типа, они функционируют в системе достаточно хаотично, и приложение не знает, какого типа сообщение придет следующим. Логика построения Windows-приложения должна обеспечивать корректную и предсказуемую работу при поступлении сообщений любого типа.
Классическое оконное приложение, как правило, состоит по крайней мере из двух функций:
- стартовая функция, создающая главное окно WinMain();
- функция обработки сообщений окна (оконная функция).
Стартовая функция WinMain
В консольной программе на С точкой входа является функция main(). С этого места программа начинает выполняться.
Точкой входа программы для Windows является функция WinMain().
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {…}
Эта функция использует последовательность вызовов API и при завершении возвращает операционной системе целое число.
Аргументы функции:
- hInstance – дескриптор процесса (instance handle) – число, идентифицирующее программу, когда она работает под Windows. Если одновременно работают несколько копий одной программы, каждая копия имеет свое значение hInstance.
- hPrevInstance — предыдущий дескриптор процесса (previous instance) — в настоящее время устарел, всегда равен NULL.
- szCmdLine — указатель на оканчивающуюся нулем строку, в которой содержатся параметры, переданные в программу из командной строки. Можно запустить программу с параметром командной строки, вставив этот параметр после имени программы в командной строке.
- iCmdShow — целое константное значение, показывающее, каким должно быть выведено на экран окно в начальный момент. Задается при запуске программы другой программой. В большинстве случаев число равно 1 (SW_SHOWNRMAL).
Имя | Значение | Описание |
SW_HIDE | 0 | Скрывает окно и делает активным другое окно |
SW_SHOWNORMAL | 1 | Отображает и делает активным окно в его первоначальном размере и положении. |
SW_SHOWMINIMIZED | 2 | Активизирует окно и отображает его в свернутом виде |
SW_SHOWMAXIMIZED | 3 | Активизирует окно и отображает его в полноэкранном виде |
SW_SHOWNOACTIVATE | 4 | Отображает окно аналогично SW_SHOWNORMAL, но не активизирует его |
SW_SHOW | 5 | Отображает и делает активным окно с текущим размером и положением. |
SW_MINIMIZE | 6 | Сворачивает текущее окно и делает активным следующее окно в порядке очереди. |
SW_SHOWMINNOACTIVE | 7 | Сворачивает окно аналогично SW_SHOWMINIMIZED, но не активизирует его. |
SW_SHOWNA | 8 | Отображает окно в текущей позиции аналогично SW_SHOW, но не активизирует его. |
SW_RESTORE | 9 | Отображает и активизирует окно. Если окно было свернуто или развернуто во весь экран, оно отображается в своем первоначальном положении и размере. |
SW_SHOWDEFAULT | 10 | Отображает окно способом, заданным по умолчанию. |
SW_FORCEMINIMIZE | 11 | Применяется для минимизации окон, связанных с различными потоками. |
В структуре стартовой функции Windows можно выделить следующие операции, образующие «скелет» программы:
- регистрация класса окна;
- создание главного окна;
- отображение и перерисовка главного окна;
- цикл обработки очереди сообщений.
Регистрация класса окна
Регистрация класса окна осуществляется функцией
ATOM WINAPI RegisterClass(_In_ const WNDCLASS *lpWndClass);
Прототип функции находится в файле библиотеки user32.dll.
Единственным аргументом функции является указатель на структуру
typedef struct _WNDCLASS {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName; } WNDCLASS;
Члены структуры
style — устанавливает стиль(и) класса. Этот член структуры может быть любой комбинацией стилей класса.
Имя | Значение | Описание |
CS_VREDRAW | 0x01 | Вертикальная перерисовка: осуществлять перерисовку окна при перемещении или изменении высоты окна. |
CS_HREDRAW | 0x02 | Горизонтальная перерисовка: осуществлять перерисовку окна при перемещении или изменении ширины окна. |
CS_KEYCVTWINDOW | 0x04 | В окне будет выполняться преобразование виртуальных клавиш. |
CS_DBLCLKS | 0x08 | Окну будут посылаться сообщения о двойном щелчке кнопки мыши. |
CS_OWNDC | 0x20 | Каждому экземпляру окна присваивается собственный контекст изображения. |
CS_CLASSDC | 0x40 | Классу окна присваивается собственный контекст изображения,который можно разделить между копиями. |
CS_PARENTDC | 0x80 | Классу окна передается контекст изображения родительского окна. |
CS_NOKEYCVT | 0x100 | Отключается преобразование виртуальных клавиш. |
CS_NOCLOSE | 0x200 | Незакрываемое окно: в системном меню блокируется выбор пункта закрытия окна. |
CS_SAVEBITS | 0x800 | Часть изображения на экране, закрытая окном, сохраняется. |
CS_BYTEALIGNCLIENT | 0x1000 | Выравнивание клиентской области окна: использование границы по байту по оси x. |
CS_BYTEALIGNWINDOW | 0x2000 | Выравнивание окна: bспользование границы по байту по оси x. |
CS_PUBLICCLASS CS_GLOBALCLASS | 0x4000 | Определяется глобальный класс окон. |
lpfnWndProc — указатель на оконную процедуру.
cbClsExtra — устанавливает число дополнительных байт, которые размещаются вслед за структурой класса окна. Система инициализирует эти байты нулями, в большинстве случаев равен 0.
cbWndExtra— устанавливает число дополнительных байтов, которые размещаются вслед за экземпляром окна. Система инициализирует байты нулями.
hInstance — дескриптор экземпляра, который содержит оконную процедуру для класса.
hIcon — дескриптор значка класса, дескриптор ресурса значка. Если этот член структуры — NULL, система предоставляет заданный по умолчанию значок.
hCursor — дескриптор курсора класса, дескриптор ресурса курсора. Если этот член структуры — NULL, приложение устанавливает форму курсора всякий раз, когда мышь перемещается в окно прикладной программы.
hbrBackground — дескриптор кисти фона класса, дескриптор физической кисти, которая используется, чтобы красить цветом фона, или код цвета, преобразованный к типу HBRUSH.
lpszMenuName — указатель на символьную строку с символом конца строки (‘’), которая устанавливает имя ресурса меню класса. Можно использовать целое число, чтобы идентифицировать меню с помощью макроса MAKEINTRESOURCE(int). Если этот член структуры — NULL, окна, принадлежащие этому классу, не имеют заданного по умолчанию меню.
lpszClassName — указатель на символьную строку с именем класса, оканчивающуюся ‘’.
Создание окна
Создание окна осуществляется функцией
HWND WINAPI CreateWindow(
_In_opt_ LPCTSTR lpClassName,
_In_opt_ LPCTSTR lpWindowName,
_In_ DWORD dwStyle,
_In_ int x,
_In_ int y,
_In_ int nWidth,
_In_ int nHeight,
_In_opt_ HWND hWndParent,
_In_opt_ HMENU hMenu,
_In_opt_ HINSTANCE hInstance,
_In_opt_ LPVOID lpParam );
Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение – дескриптор создаваемого окна. В случае невозможности создать окно возвращается NULL.
Аргументы функции:
lpClassName – указывает на строку с ‘’ в конце, которая определяет имя класса окна. Имя класса может быть зарегистрированным функцией RegisterClass или любым из предопределенных имен класса элементов управления.
lpWindowName — указывает на строку с ‘’ в конце, которая определяет имя окна.
dwStyle — определяет стиль создаваемого окна.
Имя | Значение | Описание |
WS_BORDER | 0x00800000 | Окно имеет тонкую границу в виде линии. |
WS_CAPTION | 0x00C00000 | Окно имеет строку заголовка. |
WS_CHILD | 0x40000000 | Окно является дочерним. |
WS_DISABLED | 0x08000000 | Окно является изначально неактивным. |
WS_GROUP | 0x00020000 | Окно группирует другие управляющие элементы. |
WS_HSCROLL | 0x00100000 | Окно содержит горизонтальную полосу прокрутки. |
WS_MAXIMIZE | 0x01000000 | Исходный размер окна – во весь экран. |
WS_MINIMIZE | 0x20000000 | Исходно окно свернуто. |
WS_OVERLAPPED | 0x00000000 | Окно может быть перекрыто другими окнами. |
WS_POPUP | 0x80000000 | Всплывающее окно. |
WS_SYSMENU | 0x00080000 | Окно имеет системное меню в строке заголовка. |
WS_VISIBLE | 0x10000000 | Окно изначально видимое. |
WS_VSCROLL | 0x00200000 | Окно имеет вертикальную полосу прокрутки. |
x — определяет координату левой стороны окна относительно левой стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату левой стороны относительно начальной координаты родительского окна. Если установлен как CW_USEDEFAULT, Windows выбирает заданную по умолчанию позицию окна.
у – определяет координату верхней стороны окна относительно верхней стороны экрана. Измеряется в единицах измерения устройства, чаще всего в точках (pt). Для дочернего окна определяет координату верхней стороны относительно начальной координаты родительского окна.
nWidth – определяет ширину окна в единицах измерения устройства. Если параметр соответствует CW_USEDEFAULT, Windows выбирает заданную по умолчанию ширину и высоту для окна.
nHeight – определяет высоту окна в единицах измерения устройства.
hWndParent – дескриптор родительского окна.
hMenu – идентифицирует меню, которое будет использоваться окном. Этот параметр может быть NULL, если меню класса будет использовано.
hInstance — идентифицирует экземпляр модуля, который будет связан с окном.
lpParam — указывает на значение, переданное окну при создании.
Отображение и перерисовка окна
Отображение окна осуществляется функцией
BOOL WINAPI ShowWindow(
_In_ HWND hWnd,
_In_ int nCmdShow);
Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение: 1 – успешное отображение окна, 0 – ошибка.
Аргументы функции:
hWnd – дескриптор отображаемого окна.
nCmdShow – константа, определяющая, как будет отображаться окно согласно таблице.
Перерисовка окна осуществляется функцией
BOOL UpdateWindow(_In_ HWND hWnd);
Прототип функции находится в файле библиотеки user32.dll.
Возвращаемое значение: 1 – успешная перерисовка окна, 0 – ошибка.
Аргумент функции hWnd – дескриптор окна.
Цикл обработки сообщений
После вызова функции UpdateWindow, окно окончательно выведено на экран. Теперь программа должна подготовить себя для получения информации от пользователя через клавиатуру и мышь. Windows поддерживает «очередь сообщений» (message queue) для каждой программы, работающей в данный момент в системе Windows. Когда происходит ввод информации, Windows преобразует ее в «сообщение», которое помещается в очередь сообщений программы. Программа извлекает сообщения из очереди сообщений, выполняя блок команд, известный как «цикл обработки сообщений» (message loop):
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Для получения сообщения из очереди используется функция:
BOOL WINAPI GetMessage(
_Out_ LPMSG lpMsg,
_In_opt_ HWND hWnd,
_In_ UINT wMsgFilterMin,
_In_ UINT wMsgFilterMax);
Прототип функции находится в файле библиотеки user32.dll.
В случае получения из очереди сообщения, отличного от WM_QUIT, возвращает ненулевое значение.
Аргументы функции:
lpMsg — указатель на структуру сообщения.
typedef struct MSG {
HWND hwnd; // дескриптор окна, очередь сообщений которого просматривается
UINT message; // идентификатор сообщения
WPARAM wParam; // дополнительная информация о сообщении,
LPARAM lParam; // зависит от идентификатора сообщения
DWORD time; // время помещения сообщения в очередь
POINT pt; // структура, содержащая координаты курсора в момент помещения сообщения в очередь
} MSG;
Структура POINT имеет вид
typedef struct POINT
{
LONG x; // координата x
LONG y; // координата y
} POINT;
hWnd — дескриптор окна, очередь для которого просматривается.
wMsgFilterMin — нижняя граница фильтра идентификаторов сообщений.
wMsgFilterMax — верхняя граница фильтра идентификаторов сообщений.
Функция
BOOL WINAPI TranslateMessage(_In_ const MSG *lpMsg);
передает аргумент — структуру msg обратно в Windows для преобразования какого-либо сообщения с клавиатуры. Возвращает ненулевое значение в случае успешной расшифровки сообщения, 0 – ошибка.
Функция
LRESULT WINAPI DispatchMessage(_In_ const MSG *lpmsg );
передает аргумент — структуру msg обратно в Windows. Windows отправляет сообщение для его обработки соответствующей оконной процедуре — таким образом, Windows вызывает соответствующую оконную функцию, указанную при регистрации класса окна.
После того, как оконная функция обработает сообщение, оно возвращается в Windows, которая все еще обслуживает вызов функции DispatchMessage. Когда Windows возвращает управление в стартовую функцию WinMain() к следующему за вызовом DispatchMessage коду, цикл обработки сообщений в очередной раз возобновляет работу, вызывая GetMessage.
Возвращает значение, определяемое оконной функцией, которое чаще всего игнорируется.
Прототипы функций находятся в файле библиотеки user32.dll.
Пример стартовой функции, создающей и выводящей окно размером 500х300 точек:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <windows.h>
LONG WINAPI WndProc(HWND, UINT, WPARAM,LPARAM);
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hwnd; // дескриптор окна
MSG msg; // структура сообщения
WNDCLASS w; // структура класса окна
// Регистрация класса окна
memset(&w,0,sizeof(WNDCLASS));
w.style = CS_HREDRAW | CS_VREDRAW;
w.lpfnWndProc = WndProc; // имя оконной функции
w.hInstance = hInstance;
w.hbrBackground = (HBRUSH)(WHITE_BRUSH);
w.lpszClassName = «My Class»;
RegisterClass(&w);
// Создание окна
hwnd = CreateWindow(«My Class»,»Окно пользователя»,
WS_OVERLAPPEDWINDOW, 500, 300, 500, 380, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd,nCmdShow); // отображение
UpdateWindow(hwnd); // перерисовка
// Цикл обработки сообщений
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
Оконная функция — обработка сообщений окна
Оконная функция предназначена для обработки сообщений окна. Функция обработки сообщений окна организована по принципу ветвления, состоящего из последовательной проверки типа сообщения. При совпадении типа сообщения, переданного в структуре Message с соответствующей веткой, осуществляется его обработка. Минимальный вид оконной функции представлен ниже.
1
2
3
4
5
6
7
8
9
10
11
12
LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
{
switch (Message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, Message, wparam, lparam);
}
return 0;
}
4 аргумента оконной функции идентичны первым четырем полям структуры сообщения MSG.
В примере обрабатывается только один тип сообщения WM_DESTROY, которое передается оконной функции при закрытии окна.
Вызов функции DefWindowProc() обрабатывает по умолчанию все сообщения, которые не обрабатывает оконная процедура.
Функция PostQuitMessage() сообщает Windows, что данный поток запрашивает завершение. Аргументом является целочисленное значение, которое функция вернет операционной системе.
Результат выполнения программы, выводящей окно:
Назад: Создание Windows-приложений
Для создания Windows-приложений на C++ можно использовать среду разработки Microsoft Visual Studio 2010 Express с пакетом обновления SP1.
Для создания нового диалогового или оконного (каркасного) приложения запускаем Microsoft Visual Studio 2010 Express и переходим в меню Файл->Создать->Проект
В появившемся окне выбираем Проект Win32 и задаем имя проекта и нажимаем кнопку OK.
В появившемся окне нажимаем кнопку Далее.
В следующем окне отмечаем галочку Дополнительные параметры: Пустой проект и нажимаем кнопку Далее.
В левой части появившегося окна отображается Обозреватель решений. Для добавления нового файла программы в проект выбираем по правой кнопке мыши на папке Файлы исходного кода меню Добавить->Создать элемент.
В появившемся окне выбираем Файл C++ (.cpp), задаем имя файла и нажимаем кнопку Добавить.
В появившемся окне набираем текст программы. Для примера можно взять текст программы, выводящей на экран сообщение «Привет всем!»:
1
2
3
4
5
6
7
8
#include <windows.h>
#include <tchar.h>
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
MessageBox(NULL, _T(«Привет всем!»),
_T(«Программа»), MB_OK);
return 0;
}
Для компиляции программы выбираем меню Отладка->Построить решение.
В случае успешного построения в нижней части окна отображается Построение: успешно 1.
Для запуска приложения выбираем меню Отладка->Начать отладку.
Назад: Создание Windows-приложений
Последнее обновление: 11.11.2022
Для создания графических приложений на C# можно использовать .NET CLI, но также можно
использовать бесплатную и полнофункциональную среду разработки — Visual Studio Community 2022, которая в ряде случаев облегчает проектирование
приложения. Так, загрузим установщик Visual Studio по адресу:
https://www.visualstudio.com/en-us/downloads.
Чтобы добавить в Visual Studio поддержку проектов для Windows Forms и C# и .NET 7, в программе установки среди рабочих нагрузок нужно
выбрать только пункт Разработка классических приложений .NET. Можно выбрать и больше опций или вообще все опции, однако стоит
учитывать свободный размер на жестком диске — чем больше опций будет выбрано, соответственно тем больше места на диске будет занято.
После установки среды и всех ее компонентов, запустим Visual Studio и создадим проект графического приложения.
На стартовом экране выберем Create a new project (Создать новый проект)
На следующем окне в качестве типа проекта выберем Windows Forms App:
Стоит отметить, что среди шаблонов можно увидеть еще тип Windows Forms App (.NET Framework) — его НЕ надо выбирать, необходим именно тип
Windows Forms App.
Далее на следующем этапе нам будет предложено указать имя проекта и каталог, где будет располагаться проект.
В поле Project Name дадим проекту какое-либо название. В моем случае это HelloApp.
На следующем окне Visual Studio предложит нам выбрать версию .NET, которая будет использоваться для проекта. Выберем последнюю на данный момент версию — .NET 7.0 и нажмен на кнопку Create (Создать) для создания проекта.
После этого Visual Studio откроет наш проект с созданными по умолчанию файлами:
Справа находится окно Solution Explorer, в котором можно увидеть структуру нашего проекта. Практически этот тот же проект, который создается с
помощью .NET CLI:
-
Dependencies — это узел содержит сборки dll, которые добавлены в проект по умолчанию.
Эти сборки как раз содержат классы библиотеки .NET, которые будет использовать C# -
Form1.Designer.cs: он содержит определение компонентов формы, добавленных
на форму в графическом дизайнере -
Далее идет файл единственной в проекте формы — Form1.cs, который по умолчанию открыт в центральном окне.
-
Program.cs определяет точку входа в приложение
Запуск приложения
Чтобы запустить приложение в режиме отладки, нажмем на клавишу F5 или на зеленую стрелочку на панели Visual Studio.
После этого запустится пустая форма Form1 по умолчанию.
После запуска приложения студия компилирует его в файл с расширением exe. Найти данный файл можно, зайдя в папку проекта и далее в каталог
\bin\Debug\net7.0-windows
Рассмотрев вкратце создание проекта графического приложения, мы можем перейти к обзору основных компонентов и начнем мы с форм.