Разработка приложений в среде windows диалоговые окна меню

Работа по теме: лаб_раб_4. Глава: Разработка приложений в среде windows. Диалоговые окна. Меню. ВУЗ: СибГУ.

Лабораторная
работа 4

Цель работы:
получить практические навыки применения

основных компонентов библиотеки
FCL,
предназначенных
для
проектирования
Windows
интерфейса
приложения.

Задачи работы:

– изучить технологию
работы с диалоговыми окнами в приложениях
для Windows
в среде
разработки Visual
Studio
платформы .NET;

– изучить основные
способы создания многооконных приложений;

– выполнить
практическое задание по разработке
приложения на языке С#.

Краткие теоретические сведения

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

Задание по разработке
такой программы можно сформулировать
следующим образом:

1. Создать меню с
командами Ввод
данных
,
Изменение размеров
,
Выход
. Команда
Изменение
размеров
при
запуске приложения недоступна.

При выборе команды
Выход

приложение завершает работу. При выборе
команды Ввод
данных

открывается диалоговое окно, содержащее:

– два поля ввода
типа TextBox
с метками По
горизонтали
,
По вертикали;

– группу из двух
переключателей Уменьшение,
Увеличение

типа RadioButton;

– две кнопки типа
Button.

2. Обеспечить
возможность ввода значений в поля Размер
по горизонтали

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

После ввода значений
команда
Изменение размеров

становится доступной. При выборе этой
команды размеры главного окна увеличиваются
или уменьшаются на введенное в диалоговом
окне количество пикселей.

Вид
главного окна и диалогового окна
работающего приложения показан на рис.
4.1, а код программы приведен в листингах
4.1 и 4.2.

а

б

Рис. 4.1. Демонстрация
работы приложения:

а
– главное окно; б
– диалоговое окно

Листинг 4.1.
Код файла Form1.cs
главной формы приложения

namespace Диалоговые_окна

{

public partial class Form1 : Form

{

// hor – величина изменения размера
по горизонтали

// wer – величина изменения размера
по вертикали

int hor, wer;

// m – признак уменьшения размера

// b – признак увеличения размера

bool m, b;

public Form1( )

{

InitializeComponent( );

}

// Щелчок по
пункту меню
Изменение размеров

private void
изменениеРазмеровToolStripMenuItem_Click(object
sender,

EventArgs e)

{

if (b == true)

{

Height += wer;

Width += hor;

}

if (m == true)

{

Height -= wer;

Width -= hor;

}

this.изменениеРазмеровToolStripMenuItem.Enabled
= false;

}

// Щелчок по
пункту меню
Выход

private void
выходToolStripMenuItem_Click(object
sender, EventArgs e)

{

Close( );

}

// Щелчок по пункту меню Ввод данных

private void
вводДанныхToolStripMenuItem_Click(object
sender,

EventArgs e)

{

Form2 f = new Form2( );
// Создать обьект
второй формы

// Если вторая форма видима и пользователь
нажал закончил ввод данных

// в ней

if (f.ShowDialog(
) ==
DialogResult.OK)

{

// то получить из второй формы величину
изменения по горизонтали

hor = f.H;

// получить из второй формы величину
изменения по вертикали

wer = f.W;

// получить из второй формы направление
изменения «уменьшение»

m = f.R1;

// получить из второй формы направление
изменения «увеличение»

b = f.R2

// Сделать доступным пункт меню для
изменения размеров окна

this.изменениеРазмеровToolStripMenuItem.Enabled
= true;

}

}

}

}

Листинг 4.2. Код файла Form2.cs
диалогового окна

namespace Диалоговые_окна

{

public partial class Form2 : Form

{

int h, w;

public Form2( )

{

InitializeComponent( );

}

// Метод-свойство для получения размера
изменения по горизонтали

public int H

{

get

{

return h;

}

}

// Метод-свойство для получения размера
изменения по вертикали

public int W

{

get

{

return w;

}

}

// Метод-свойство для получения значения
состояния первой радиокнопки

public bool R1

{

get

{

return radioButton1.Checked;

}

}

// Метод-свойство для получения значения
состояния второй радиокнопки

public bool R2

{

get

{

return radioButton2.Checked;

}

}

// Щелчок по
кнопке Ввод

private void button1_Click(object
sender, EventArgs e)

{

try

{

h = Convert.ToInt32(textBox1.Text);

}

catch (FormatException)

{

MessageBox.Show(«Ошибка
ввода размера
по горизонтали!»);

return;

}

try

{

w = Convert.ToInt32(textBox2.Text);

}

catch (FormatException )

{

MessageBox.Show(«Ошибка
ввода размера
по вертикали!»);

return;

}

if (radioButton1.Checked == false
&& radioButton2.Checked ==

false)

{

MessageBox.Show(«Вы не задали
направление изменения!»);

return;

}

}

Соседние файлы в папке OOP

  • #
  • #
  • #
  • #
  • #
  • #

Свойства и функции системных часов

Чтобы получить от системных часов информацию о времени, можно использовать их различные свойства и функции. Информация о времени может потребоваться в программах при создании собственных календарей, часов или оповещений. В следующей таблице содержится перечень наиболее полезных функций системных часов. За дополнительной информацией обращайтесь к справочной системе Visual Studio.

Свойство или функция Описание
TimeString Возвращает от системных часов текущее время.
DateString Возвращает от системных часов текущую дату.
Now Возвращает закодированное значение, содержащее текущие дату и время. Наиболее полезно как аргумент для других функций системных часов.
Hour (time) Возвращает количество часов для указанного времени (от 0 до 24).
Minute (time) Возвращает количество минут для указанного времени (от 0 до 59).
Second (time) Возвращает количество секунд для указанного времени (от 0 до 59).
Day (date) Возвращает целое число, представляющее собой день месяца (от 1 до 31).
Month (date) Возвращает целое число, представляющее собой месяц (от 1 до 12).
Year (date) Возвращает год для указанной даты.
Weekday (date) Возвращает целое число, представляющее собой день недели (по американской системе: 1 — это воскресенье, 2 — это понедельник, и т.д.).

Использование элементов управления для диалоговых окон

В Visual Studio на закладке Windows Forms окна области элементов имеется семь стандартных элементов управления для диалоговых окон. Они предоставляются в готовом виде, так что вам вряд ли потребуется создавать собственные диалоговые окна для типичных задач, таких как открытие файлов, сохранение файлов или печать. Во многих случаях нужно написать код, который подключает эти диалоговые окна к программе, но пользовательский интерфейс уже сделан, и он соответствует стандартам для общих задач в приложениях Windows. Все семь имеющихся элементов управления для стандартных диалоговых окон перечислены в следующей таблице. Они во многом соответствуют объектам, которые в Visual Basic 6 предоставлялись в составе элемента управления CommonDialog, но имеют и некоторые отличия. Так, вы не найдете в таблице элемент управления PrintPreviewControl, но в случае необходимости его заменит элемент управления PrintPreviewDialog.

Название элемента управления Назначение
OpenFileDialog Получает названия диска, папки и файла для существующего файла.
SaveFile (Файл) Dialog Получает названия диска, папки и файла для нового файла.
FontDialog Позволяет выбрать новый шрифт и его стиль.
ColorDialog Позволяет выбрать цвет из палитры.
PrintDialog Позволяет задать параметры печати.
PrintPreviewDialog Отображает диалоговое окно предварительного просмотра материала для печати, так, как это делает Microsoft Word.
PageSetupDialog Позволяет управлять параметрами страницы: полями, размером бумаги и ее ориентацией.

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

Добавление элементов управления OpenFileDialog и ColorDialog

  1. Если вы не создавали проект Menu, в меню File (Файл) выберите Open (Открыть), затем Project (Проект), в папке c:vbnet03sbsГл.4menu выберите файл проекта Menu.vbproj, а затем нажмите кнопку Open (Открыть). Если форма не видна, дважды щелкните мышью в Solution Explorer (Обозревателе решений) на Form1.vb.

    Добавьте в область компонентов, в которой уже находится объект главного меню MainMenu1, два элемента управления диалоговых окон. Элемент управления OpenFileDialog потребуется, чтобы открывать файлы с точечными изображениями, а элемент управления ColorDialog позволит изменять цвет для показа даты. В процессе разработки элементы управления диалоговых окон помещаются в области компонентов, а не на поле формы, так как во время выполнения они на форме не появляются.

  2. В области элементов на закладке Windows Forms выберите элемент управления OpenFileDialog, а затем щелкните мышкой в области компонентов, где уже есть объект MainMenu1.

    Совет. Если вы не видите OpenFileDialog в области элементов, то он может быть за пределами видимости. Чтобы промотать список в области элементов, щелкните на нижней стрелке прокрутки, находящейся рядом с закладкой Clipboard Ring (Буфер обмена).

    В области компонентов появится объект диалогового окна для открытия файла.

  3. В области элементов на закладке Windows Forms выберите элемент управления ColorDialog, а затем щелкните на области компонентов, расположенной ниже поля формы. Теперь область компонентов выглядит так.

    Как и объект главного меню, объекты с диалогами открытия файла и выбора цвета можно настроить, задав их свойства.

Теперь с помощью элемента управления PictureBox создайте область для показа изображений. Этот объект показывает в поле формы содержимое из файлов изображений. На этот раз мы выведем на поле формы картинку, используя диалоговое окно для открытия файла.

Добавление объекта области показа изображения

  1. В области элементов выберите элемент управления PictureBox.
  2. В поле формы ниже надписи нарисуйте объект области показа изображений, и в окне Properties (Свойства) для свойства SizeMode этого объекта выберите значение StretchImage.

Теперь с помощью Menu Designer (Конструктора меню) добавьте в вашу программу меню File (Файл).

Добавление меню File (Файл)

  1. В поле формы щелкните на меню Дата, затем на ячейке Type Here (Прототип для текста), расположенной справа от этого меню. Теперь нужно добавить в программу меню Файл, в котором будут команды Открыть, Закрыть и Выход.
  2. Чтобы создать меню Файл с буквой «Ф» в качестве клавиши доступа, введите &Файл.
  3. Нажмите клавишу со стрелкой вниз, а затем, чтобы создать команду Открыть dots с буквой «O» в качестве клавиши доступа, введите &Открыть dots . Команда Открыть будет использоваться для загрузки точечных изображений. Так как эта команда должна будет открывать диалоговое окно, добавьте к ее имени многоточие.
  4. Нажмите клавишу со стрелкой вниз, а затем, чтобы создать команду Закрыть с буквой «З» в качестве клавиши доступа, введите &Закрыть. Команда Закрыть будет использоваться в программе для закрытия файла с изображением.
  5. Нажмите клавишу со стрелкой вниз, а затем, чтобы создать команду Выход с буквой «ы» в качестве клавиши доступа, введите В&ыход. Команда Выход будет использоваться для закрытия программы. Обратите внимание, что в этом случае в качестве клавиши доступа для команды Выход была использована третья буква, как это сделано во многих приложениях для Windows.
  6. Чтобы передвинуть меню Файл на первое место, просто перетащите его на меню Дата. В Menu Designer (Конструкторе меню) целые меню можно перемещать точно так же, как и отдельные команды внутри меню. Имеет смысл сделать меню Файл первым меню программы. Ваша форма должна выглядеть примерно так.

In this article, I’ll discuss how to use a dialog box as the main window for your program, step by step, from scratch. I’ll use Visual Studio 2008, but the steps should be similar for other Visual Studio versions, or even other IDEs.

1. Introduction

When writing pure Win32 programs, usually you see tutorials showing how to use “raw” windows, by filling a WNDCLASSEX structure, calling RegisterClassEx and then CreateWindowEx. This is explained in detail in Charles Petzold’s classic Programming Windows book – a must-have for any Win32 programmer, let me say.

But sometimes you don’t need to create a new window entirely from scratch, a simple dialog box would fit your needs.

In this article, I’ll discuss how to use a dialog box as the main window for your program, step by step, from scratch. A dialog box resource can be quickly created – with labels, editboxes, and buttons – using any resource editor. Here I’ll use Visual Studio 2008, but the steps should be similar for other Visual Studio versions, or even other IDEs.

I’ll use pure Win32 C code to keep things as simple as possible: no MFC, no ATL, no WTL, or whatever. I’ll also use the TCHAR functions (declared in tchar.h, more information here) to make the code portable with ANSI and Unicode, and only functions that are both x86 and x64 compatible.

1.1. Program Structure

Our program will be composed of three files:

  • C source file – the source code we’ll effectively write, and the central theme of this article;
  • RC resource script – describes the dialog box resources, easily created by Visual Studio or any resource editor, or even by hand, and compiled with a resource compiler; and
  • H resource header – simply the macro constants used in the RC file to identify the resources, usually created automatically together with the RC script.

2. The Dialog Box

Before writing the C source code, we’ll create an empty project and add a dialog box resource to it. When doing so, a resource script is created, containing the dialog box code. Let’s start a new project:

Image 1

Choose “Visual C++” and “Win32” from the tree in the left, then “Win32 project”, and give a name to it. Pay attention to the directory you are saving it. Then click OK:

Image 2

Now choose “Windows application” and “Empty project”. When creating an empty project, Visual Studio will create no files for us, and this is important because here we want to create a pure Win32 program, with no additional libraries. Then, click “Finish”:

Image 3

Now, let’s add the dialog box. In the Solution Explorer window – if you can’t see it, enable it in the “View” menu – right-click the project name and choose “Add”, “Resource”:

Image 4

Here you can see a couple of resource items whose script can be generated automatically by Visual Studio. We’ll use just the dialog box, so choose “Dialog” and click “New”:

Image 5

Once done, you should see your dialog in the resource editor, where you can add controls – like editboxes, buttons, and labels – by just using the mouse, positioning and arranging them really quick – much quicker than you would do with a “raw window” application, where you must deal with the code directly. My dialog looks like this:

Image 6

At this point, we have a resource script and a resource header, they can be seen in the Solution Explorer. Now it’s time to write the source code to bring this dialog box alive.

3. The Source Code

Let’s add an empty source file to our project. In the Solution Explorer, right-click the “Source Files” folder, then “Add”, “New Item”. Then give any name to the file, like “main.c”.

Image 7

In Visual Studio, by default, the source files will be compiled according to the file extension: C files compiled as plain C; and CPP, CXX (and some others) compiled as C++. Here we’ll write C code, but it can also be compiled as C++, so the file extension can be any of those cited. Particularly, I used the C extension, to make it clear it’s a plain C program.

Our C source will have only two functions:

  • WinMain – the program entry point, which will have the main program loop; and
  • DialogProc – the dialog box procedure, which will process the dialog messages.

Let’s start writing the code with the normal Win32 entry point function (the TCHAR version of it):

#include <Windows.h>
#include <tchar.h>


#include "resource.h"

int _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPCTSTR lpCmdLine, int nCmdShow)
{
  return 0;
}

4. Dialog Creation and Message Loop

The dialog will be created inside the WinMain function with the CreateDialogParam function (instead of CreateWindowEx), and there is no window class registration. Then, we make it visible with a call to ShowWindow:

HWND hDlg;
hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
ShowWindow(hDlg, nCmdShow);

IDD_DIALOG1 is the resource identifier to our dialog box, declared in resource.h. DialogProc is our dialog box procedure, which will handle all dialog messages – I’ll show it later on.

Then it follows the main program message loop. It’s the heart of any Win32 program – see it as the bridge between the operational system and your program. It also exists in common “raw window” programs, although slightly different from this. Here, the message loop is specifically to deal with a dialog box as the main window:

BOOL ret;
MSG msg;
while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
  if(ret == -1) 
    return -1;

  if(!IsDialogMessage(hDlg, &msg)) {
    TranslateMessage(&msg); 
    DispatchMessage(&msg); 
  }
}

The IsDialogMessage function immediately forwards the message to our dialog box procedure if it belongs to it. Otherwise, the message enters regular handling. More information about the message loop can be found here.

There is a possibility to bypass this program loop (not writing it), as explained by Iczelion in the 10th lesson of his wonderful Win32 Assembly article series. However, by doing so, we have less control: we cannot put any verification in the loop, like accelerator handling, for example. So, let’s keep the loop in our code.

4.1. Enabling Visual Styles

In order to get the common controls 6 visual styles, introduced with Windows XP, you must not only call InitCommonControls (declared in CommCtrl.h), but also embed a manifest XML file into your code. Fortunately, there is a handy trick you can use in the Visual C++ compiler, which I learned from Raymond Chen’s blog. Just add this to your code:

#pragma comment(linker, 
  ""/manifestdependency:type='Win32' "
  "name='Microsoft.Windows.Common-Controls' "
  "version='6.0.0.0' "
  "processorArchitecture='*' "
  "publicKeyToken='6595b64144ccf1df' "
  "language='*'"")

This will generate and embed the XML manifest file automatically, and you’ll never worry about it again.

To call InitCommonControls, you must statically link your program to ComCtl32.lib, and this can be accomplished with a #pragma comment directive as well:

#pragma comment(lib, "ComCtl32.lib")

4.2. Our WinMain

So far, this is our complete WinMain function (without the dialog box procedure yet):

#include <Windows.h>
#include <CommCtrl.h>
#include <tchar.h>
#include "resource.h"

#pragma comment(linker, 
  ""/manifestdependency:type='Win32' "
  "name='Microsoft.Windows.Common-Controls' "
  "version='6.0.0.0' "
  "processorArchitecture='*' "
  "publicKeyToken='6595b64144ccf1df' "
  "language='*'"")

#pragma comment(lib, "ComCtl32.lib")

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPTSTR lpCmdLine, int nCmdShow)
{
  HWND hDlg;
  MSG msg;
  BOOL ret;

  InitCommonControls();
  hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
  ShowWindow(hDlg, nCmdShow);

  while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
    if(ret == -1)
      return -1;

    if(!IsDialogMessage(hDlg, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  return 0;
}

5. Dialog Box Procedure

The dialog procedure is responsible for handling all program messages, responding to all events. It starts like this:

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  return FALSE;
}

Windows calls this function for every program message. If we return FALSE, it means Windows can carry out the default processing for the message, because we’re not interested in it; if we return TRUE, we tell Windows we’ve actually processed the message. This is slightly different from the “raw” window message handling through WndProc, where you return a call to the DefWindowProc function. Here, we must not call DefWindowProc, just return FALSE.

So, we’ll write only the handling of messages that are interesting to us. Among the most commonly used messages, we have WM_INITDIALOG, WM_COMMAND, and WM_SIZE. But to build a minimal functional program, we need only two:

switch(uMsg)
{
case WM_CLOSE: 
  return TRUE; 

case WM_DESTROY:
  return TRUE;
}

Notice we don’t need to handle the WM_PAINT message with dialog boxes.

5.1. The Minimal Message Handling

WM_CLOSE is called just prior to window closing. If you want to ask the user if he really wants to close the program, here is the place to put this check. To close the window, we call DestroyWindow – if we don’t call it, the window won’t be closed.

So here’s the message handling, also prompting the user. If you don’t need to prompt the user, just omit the MessageBox check and call DestroyWindow directly. And don’t forget to return TRUE here, whether you close the window or not:

case WM_CLOSE:
  if(MessageBox(hDlg,
    TEXT("Close the window?"), TEXT("Close"),
    MB_ICONQUESTION | MB_YESNO) == IDYES)
  {
    DestroyWindow(hDlg);
  }
  return TRUE;

Finally, we must handle the WM_DESTROY message, telling Windows we want to quit the main program thread. We do this by calling the PostQuitMessage function:

case WM_DESTROY:
  PostQuitMessage(0);
  return TRUE;

The WM_DESTROY message is also the best place to free resources that were allocated by the program and are still waiting to be deallocated – it’s final cleanup time. But don’t forget to do the cleanup before calling PostQuitMessage.

5.2. Closing on ESC

An interesting feature of the dialog boxes is that they can be easily programmed to be closed when the user hits the ESC key, and it can also be done when the dialog box is the main window as well. To do so, we must handle the WM_COMMAND message and wait for the IDCANCEL identifier, which comes in the low word of the WPARAM argument, and that’s what we do:

case WM_COMMAND:
  switch(LOWORD(wParam))
  {
  case IDCANCEL:
    SendMessage(hDlg, WM_CLOSE, 0, 0);
    return TRUE;
  }
  break;

The IDCANCEL identifier is declared in WinUser.h, which is included in Windows.h, so it’s always available.

Notice that when handling IDCANCEL, we send a WM_CLOSE message to our dialog window, which causes the dialog procedure to be called again with the WM_CLOSE message that we previously coded, so the user will be prompted if he wants to close the window (because that’s the way we coded it).

6. The Final Program

So here is our final program. It’s the C source code for a minimally functional Win32 dialog based program, with the message loop and visual styles properly enabled, just ready to go. You can keep this to use as the skeleton for any Win32 dialog based program.

#include <Windows.h>
#include <CommCtrl.h>
#include <tchar.h>
#include "resource.h"

#pragma comment(linker, 
  ""/manifestdependency:type='Win32' "
  "name='Microsoft.Windows.Common-Controls' "
  "version='6.0.0.0' "
  "processorArchitecture='*' "
  "publicKeyToken='6595b64144ccf1df' "
  "language='*'"")

#pragma comment(lib, "ComCtl32.lib")

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg)
  {
  case WM_COMMAND:
    switch(LOWORD(wParam))
    {
    case IDCANCEL:
      SendMessage(hDlg, WM_CLOSE, 0, 0);
      return TRUE;
    }
    break;

  case WM_CLOSE:
    if(MessageBox(hDlg, TEXT("Close the program?"), TEXT("Close"),
      MB_ICONQUESTION | MB_YESNO) == IDYES)
    {
      DestroyWindow(hDlg);
    }
    return TRUE;

  case WM_DESTROY:
    PostQuitMessage(0);
    return TRUE;
  }

  return FALSE;
}

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPTSTR lpCmdLine, int nCmdShow)
{
  HWND hDlg;
  MSG msg;
  BOOL ret;

  InitCommonControls();
  hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
  ShowWindow(hDlg, nCmdShow);

  while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
    if(ret == -1)
      return -1;

    if(!IsDialogMessage(hDlg, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  return 0;
}

7. Further Organization

Usually, when your program grows in complexity, you’ll end up with a huge DialogProc stuffed with code, and therefore very painful to maintain. A useful approach to this is the use of function calls to each message — the famous subroutines. It’s specially handy because it isolates the logic of the code, you can see clearly each part of the program, giving you the notion of responding to events. For example, our program could have two dedicated functions:

void onCancel(HWND hDlg)
{
  SendMessage(hDlg, WM_CLOSE, 0, 0);
}

void onClose(HWND hDlg)
{
  if(MessageBox(hDlg, TEXT("Close the program?"), TEXT("Close"),
    MB_ICONQUESTION | MB_YESNO) == IDYES)
  {
    DestroyWindow(hDlg);
  }
}

And it would allow us to rewrite our DialogProc like this:

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg) 
  {
  case WM_COMMAND:
    switch(LOWORD(wParam))
    {
    case IDCANCEL: onCancel(hDlg); return TRUE; 
    }
    break;

  case WM_CLOSE:   onClose(hDlg); return TRUE; 
  case WM_DESTROY: PostQuitMessage(0); return TRUE;
  }

  return FALSE;
}

History

  • 20th July, 2011: Initial version

This article, along with any associated source code and files, is licensed under A Public Domain dedication

This member has not yet provided a Biography. Assume it’s interesting and varied, and probably something to do with programming.

In this article, I’ll discuss how to use a dialog box as the main window for your program, step by step, from scratch. I’ll use Visual Studio 2008, but the steps should be similar for other Visual Studio versions, or even other IDEs.

1. Introduction

When writing pure Win32 programs, usually you see tutorials showing how to use “raw” windows, by filling a WNDCLASSEX structure, calling RegisterClassEx and then CreateWindowEx. This is explained in detail in Charles Petzold’s classic Programming Windows book – a must-have for any Win32 programmer, let me say.

But sometimes you don’t need to create a new window entirely from scratch, a simple dialog box would fit your needs.

In this article, I’ll discuss how to use a dialog box as the main window for your program, step by step, from scratch. A dialog box resource can be quickly created – with labels, editboxes, and buttons – using any resource editor. Here I’ll use Visual Studio 2008, but the steps should be similar for other Visual Studio versions, or even other IDEs.

I’ll use pure Win32 C code to keep things as simple as possible: no MFC, no ATL, no WTL, or whatever. I’ll also use the TCHAR functions (declared in tchar.h, more information here) to make the code portable with ANSI and Unicode, and only functions that are both x86 and x64 compatible.

1.1. Program Structure

Our program will be composed of three files:

  • C source file – the source code we’ll effectively write, and the central theme of this article;
  • RC resource script – describes the dialog box resources, easily created by Visual Studio or any resource editor, or even by hand, and compiled with a resource compiler; and
  • H resource header – simply the macro constants used in the RC file to identify the resources, usually created automatically together with the RC script.

2. The Dialog Box

Before writing the C source code, we’ll create an empty project and add a dialog box resource to it. When doing so, a resource script is created, containing the dialog box code. Let’s start a new project:

Image 1

Choose “Visual C++” and “Win32” from the tree in the left, then “Win32 project”, and give a name to it. Pay attention to the directory you are saving it. Then click OK:

Image 2

Now choose “Windows application” and “Empty project”. When creating an empty project, Visual Studio will create no files for us, and this is important because here we want to create a pure Win32 program, with no additional libraries. Then, click “Finish”:

Image 3

Now, let’s add the dialog box. In the Solution Explorer window – if you can’t see it, enable it in the “View” menu – right-click the project name and choose “Add”, “Resource”:

Image 4

Here you can see a couple of resource items whose script can be generated automatically by Visual Studio. We’ll use just the dialog box, so choose “Dialog” and click “New”:

Image 5

Once done, you should see your dialog in the resource editor, where you can add controls – like editboxes, buttons, and labels – by just using the mouse, positioning and arranging them really quick – much quicker than you would do with a “raw window” application, where you must deal with the code directly. My dialog looks like this:

Image 6

At this point, we have a resource script and a resource header, they can be seen in the Solution Explorer. Now it’s time to write the source code to bring this dialog box alive.

3. The Source Code

Let’s add an empty source file to our project. In the Solution Explorer, right-click the “Source Files” folder, then “Add”, “New Item”. Then give any name to the file, like “main.c”.

Image 7

In Visual Studio, by default, the source files will be compiled according to the file extension: C files compiled as plain C; and CPP, CXX (and some others) compiled as C++. Here we’ll write C code, but it can also be compiled as C++, so the file extension can be any of those cited. Particularly, I used the C extension, to make it clear it’s a plain C program.

Our C source will have only two functions:

  • WinMain – the program entry point, which will have the main program loop; and
  • DialogProc – the dialog box procedure, which will process the dialog messages.

Let’s start writing the code with the normal Win32 entry point function (the TCHAR version of it):

#include <Windows.h>
#include <tchar.h>


#include "resource.h"

int _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPCTSTR lpCmdLine, int nCmdShow)
{
  return 0;
}

4. Dialog Creation and Message Loop

The dialog will be created inside the WinMain function with the CreateDialogParam function (instead of CreateWindowEx), and there is no window class registration. Then, we make it visible with a call to ShowWindow:

HWND hDlg;
hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
ShowWindow(hDlg, nCmdShow);

IDD_DIALOG1 is the resource identifier to our dialog box, declared in resource.h. DialogProc is our dialog box procedure, which will handle all dialog messages – I’ll show it later on.

Then it follows the main program message loop. It’s the heart of any Win32 program – see it as the bridge between the operational system and your program. It also exists in common “raw window” programs, although slightly different from this. Here, the message loop is specifically to deal with a dialog box as the main window:

BOOL ret;
MSG msg;
while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
  if(ret == -1) 
    return -1;

  if(!IsDialogMessage(hDlg, &msg)) {
    TranslateMessage(&msg); 
    DispatchMessage(&msg); 
  }
}

The IsDialogMessage function immediately forwards the message to our dialog box procedure if it belongs to it. Otherwise, the message enters regular handling. More information about the message loop can be found here.

There is a possibility to bypass this program loop (not writing it), as explained by Iczelion in the 10th lesson of his wonderful Win32 Assembly article series. However, by doing so, we have less control: we cannot put any verification in the loop, like accelerator handling, for example. So, let’s keep the loop in our code.

4.1. Enabling Visual Styles

In order to get the common controls 6 visual styles, introduced with Windows XP, you must not only call InitCommonControls (declared in CommCtrl.h), but also embed a manifest XML file into your code. Fortunately, there is a handy trick you can use in the Visual C++ compiler, which I learned from Raymond Chen’s blog. Just add this to your code:

#pragma comment(linker, 
  ""/manifestdependency:type='Win32' "
  "name='Microsoft.Windows.Common-Controls' "
  "version='6.0.0.0' "
  "processorArchitecture='*' "
  "publicKeyToken='6595b64144ccf1df' "
  "language='*'"")

This will generate and embed the XML manifest file automatically, and you’ll never worry about it again.

To call InitCommonControls, you must statically link your program to ComCtl32.lib, and this can be accomplished with a #pragma comment directive as well:

#pragma comment(lib, "ComCtl32.lib")

4.2. Our WinMain

So far, this is our complete WinMain function (without the dialog box procedure yet):

#include <Windows.h>
#include <CommCtrl.h>
#include <tchar.h>
#include "resource.h"

#pragma comment(linker, 
  ""/manifestdependency:type='Win32' "
  "name='Microsoft.Windows.Common-Controls' "
  "version='6.0.0.0' "
  "processorArchitecture='*' "
  "publicKeyToken='6595b64144ccf1df' "
  "language='*'"")

#pragma comment(lib, "ComCtl32.lib")

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPTSTR lpCmdLine, int nCmdShow)
{
  HWND hDlg;
  MSG msg;
  BOOL ret;

  InitCommonControls();
  hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
  ShowWindow(hDlg, nCmdShow);

  while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
    if(ret == -1)
      return -1;

    if(!IsDialogMessage(hDlg, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  return 0;
}

5. Dialog Box Procedure

The dialog procedure is responsible for handling all program messages, responding to all events. It starts like this:

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  return FALSE;
}

Windows calls this function for every program message. If we return FALSE, it means Windows can carry out the default processing for the message, because we’re not interested in it; if we return TRUE, we tell Windows we’ve actually processed the message. This is slightly different from the “raw” window message handling through WndProc, where you return a call to the DefWindowProc function. Here, we must not call DefWindowProc, just return FALSE.

So, we’ll write only the handling of messages that are interesting to us. Among the most commonly used messages, we have WM_INITDIALOG, WM_COMMAND, and WM_SIZE. But to build a minimal functional program, we need only two:

switch(uMsg)
{
case WM_CLOSE: 
  return TRUE; 

case WM_DESTROY:
  return TRUE;
}

Notice we don’t need to handle the WM_PAINT message with dialog boxes.

5.1. The Minimal Message Handling

WM_CLOSE is called just prior to window closing. If you want to ask the user if he really wants to close the program, here is the place to put this check. To close the window, we call DestroyWindow – if we don’t call it, the window won’t be closed.

So here’s the message handling, also prompting the user. If you don’t need to prompt the user, just omit the MessageBox check and call DestroyWindow directly. And don’t forget to return TRUE here, whether you close the window or not:

case WM_CLOSE:
  if(MessageBox(hDlg,
    TEXT("Close the window?"), TEXT("Close"),
    MB_ICONQUESTION | MB_YESNO) == IDYES)
  {
    DestroyWindow(hDlg);
  }
  return TRUE;

Finally, we must handle the WM_DESTROY message, telling Windows we want to quit the main program thread. We do this by calling the PostQuitMessage function:

case WM_DESTROY:
  PostQuitMessage(0);
  return TRUE;

The WM_DESTROY message is also the best place to free resources that were allocated by the program and are still waiting to be deallocated – it’s final cleanup time. But don’t forget to do the cleanup before calling PostQuitMessage.

5.2. Closing on ESC

An interesting feature of the dialog boxes is that they can be easily programmed to be closed when the user hits the ESC key, and it can also be done when the dialog box is the main window as well. To do so, we must handle the WM_COMMAND message and wait for the IDCANCEL identifier, which comes in the low word of the WPARAM argument, and that’s what we do:

case WM_COMMAND:
  switch(LOWORD(wParam))
  {
  case IDCANCEL:
    SendMessage(hDlg, WM_CLOSE, 0, 0);
    return TRUE;
  }
  break;

The IDCANCEL identifier is declared in WinUser.h, which is included in Windows.h, so it’s always available.

Notice that when handling IDCANCEL, we send a WM_CLOSE message to our dialog window, which causes the dialog procedure to be called again with the WM_CLOSE message that we previously coded, so the user will be prompted if he wants to close the window (because that’s the way we coded it).

6. The Final Program

So here is our final program. It’s the C source code for a minimally functional Win32 dialog based program, with the message loop and visual styles properly enabled, just ready to go. You can keep this to use as the skeleton for any Win32 dialog based program.

#include <Windows.h>
#include <CommCtrl.h>
#include <tchar.h>
#include "resource.h"

#pragma comment(linker, 
  ""/manifestdependency:type='Win32' "
  "name='Microsoft.Windows.Common-Controls' "
  "version='6.0.0.0' "
  "processorArchitecture='*' "
  "publicKeyToken='6595b64144ccf1df' "
  "language='*'"")

#pragma comment(lib, "ComCtl32.lib")

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg)
  {
  case WM_COMMAND:
    switch(LOWORD(wParam))
    {
    case IDCANCEL:
      SendMessage(hDlg, WM_CLOSE, 0, 0);
      return TRUE;
    }
    break;

  case WM_CLOSE:
    if(MessageBox(hDlg, TEXT("Close the program?"), TEXT("Close"),
      MB_ICONQUESTION | MB_YESNO) == IDYES)
    {
      DestroyWindow(hDlg);
    }
    return TRUE;

  case WM_DESTROY:
    PostQuitMessage(0);
    return TRUE;
  }

  return FALSE;
}

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPTSTR lpCmdLine, int nCmdShow)
{
  HWND hDlg;
  MSG msg;
  BOOL ret;

  InitCommonControls();
  hDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0);
  ShowWindow(hDlg, nCmdShow);

  while((ret = GetMessage(&msg, 0, 0, 0)) != 0) {
    if(ret == -1)
      return -1;

    if(!IsDialogMessage(hDlg, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  return 0;
}

7. Further Organization

Usually, when your program grows in complexity, you’ll end up with a huge DialogProc stuffed with code, and therefore very painful to maintain. A useful approach to this is the use of function calls to each message — the famous subroutines. It’s specially handy because it isolates the logic of the code, you can see clearly each part of the program, giving you the notion of responding to events. For example, our program could have two dedicated functions:

void onCancel(HWND hDlg)
{
  SendMessage(hDlg, WM_CLOSE, 0, 0);
}

void onClose(HWND hDlg)
{
  if(MessageBox(hDlg, TEXT("Close the program?"), TEXT("Close"),
    MB_ICONQUESTION | MB_YESNO) == IDYES)
  {
    DestroyWindow(hDlg);
  }
}

And it would allow us to rewrite our DialogProc like this:

INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch(uMsg) 
  {
  case WM_COMMAND:
    switch(LOWORD(wParam))
    {
    case IDCANCEL: onCancel(hDlg); return TRUE; 
    }
    break;

  case WM_CLOSE:   onClose(hDlg); return TRUE; 
  case WM_DESTROY: PostQuitMessage(0); return TRUE;
  }

  return FALSE;
}

History

  • 20th July, 2011: Initial version

This article, along with any associated source code and files, is licensed under A Public Domain dedication

This member has not yet provided a Biography. Assume it’s interesting and varied, and probably something to do with programming.

Практическая
работа № 7.

Разработка
приложений Windows с использованием диалоговых окон.

Цель работы: получить навык работы с объектами в
среде
Visual C++.

Теоретическая часть

В терминах разработки .NET приложение с
графическим интерфейсом пользователя
Windows называется
приложением
Windows Forms (или Winforms). Разработка проекта Windows Forms с помощью Visual
C++ в общих чертах не отличается от разработки на других языках .NET, таких как Visual Basic или C#.

Приложения Windows Forms в Visual C++ используют классы .NET Framework и иные
функциональные возможности .
NET с новым синтаксисом Visual
C++.

Замечание: отобразите окно свойств для элементов (Вид→Другие окна→Окно свойств или Alt+Enter).

Замечание: знакомьтесь с назначением элементов формы,
просматривая всплывающие подсказки:

Стандартное
содержимое вашего проекта можно увидеть в Обозревателе решений:

 

Ранее вы уже выводили на экран окно с сообщением с помощью MassageBox. В данной практической работе  будет рассмотрен очень актуальный и
популярный вопрос – «Как связать две формы?».

Написано огромное количество программ, в которых после нажатия кнопки
появляется новая форма с новыми элементами.

Ход работы

Изучите теоретическую часть

Задание 1. (Создание многооконных приложений.)

В текстовое поле
вводится число, нажимается кнопка, введенное число возводится в квадрат, после
чего появляется «
Form2″,  на которой тоже есть
текстовое поле, где будет показан результат возведения числа в квадрат.

1.     
Создайте новый проект С++ (Файл→Создать→Проект→ВыбратьCLR+Приложение Windows Forms).
Появится уже знакомая вам форма
Form1.

2.     
Создайте вторую форму в этом же проекте:

       
пункт меню «Проект”→ «Добавить новый
элемент→»Форма Windows Forms»;

       
назовите элемент –»Form2«.

 

3.     
Расположите на форме  Form1:

       
текстовое поле: textBox1;

       
кнопку button1;

       
контейнер для изображения pictureBox1.

4.     
Расположите на форме  Form2

       
текстовое поле: textBox2;

       
контейнер для изображения pictureBox2.

5.      Задайте атрибуты BackColor, Text, Font и Image для Form,  textBox1и pictureBox1 соответственно так, чтобы  формы приняли следующий вид:

6.     
Важно понять – поскольку элемент «textBox1»
будет находится на обеих формах, то его нужно объявить как public в
верху кода формы «Form2«.

В «Form2″
просто поменяйте private на public у элемента textBox:

7.     
В коде формы «Form1» в самом верху
кода подключите библиотеку второй формы #include «Form2«.

 

8.     
В одном из событий нужно писать:

Form2^ gForm2 = gcnew Form2;

              gForm2->Show();

              gForm2->какой-то
элемент;

9.     
Добавьте код для кнопки: дважды кликните по ней,
в открывшемся окне пропишите содержимое фигурных скобок:

Примерный результат
работы вашей программы:

Задание 2.

Вспомните Практическую
работу  №7 “Вычисление факториала”, вам нужно изменить программу таким образом,
чтобы результат выводился в новом окне.

Замечание: если
ваш проект для П.р. №7 сохранен, можете просто дополнить его,  если нет –
выполните нижеследующие действия.

10.  Покажите выполненную работу преподавателю и получите
дополнительное задание.

11.  Ответьте на контрольные вопросы.

Дополнительное задание.

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

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 и все готово.

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

  1. Создать свой собственный класс через RegisterClassEx и в процедуре обработки класса схватывать WM_KEYDOWN, перенаправлять в процедуру обработки самого диалога. Да-да! Можно создавать диалоги со своим собственным классом, и встроенный в VS редактор даже позволяет задавать имя класса для диалога. Вот только кто об этом знает и этим пользуется?
    Минус очевиден: Нужно регистрировать еще один класс, иметь на 1 CALLBACK процедуру больше, суть которой будет всего-навсего в трансляции пары сообщений. Кроме того, мы не будем знать куда их транслировать, и придется городить костыли.
  2. Использовать встроенный механизм акселераторов. И нам даже не придется менять код диалоговой процедуры! Ну, разве что, добавить одну строчку в switch/case, но об этом ниже.

Tutorials?

Не побоюсь сказать, что все туториалы по созданию окон через WinAPI начинаются с такого незамысловатого кода, обозначая его, как «цикл обработки сообщений» (опущу детали по подготовке класса окна и прочую обвязку):


    while (GetMessage(&msg, nullptr, 0, 0))
    {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
    }

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

  1. GetMessage() выхватывает очередное сообщение из очереди, и ключевой момент: блокирует поток, если в очереди пусто.
  2. TranslateMessage() из WM_KEYDOWN/WM_KEYUP формирует сообщения WM_CHAR/WM_SYSCHAR (они нужны, если кто-то хочет сделать свой редактор текста).
  3. 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. Но теперь мы получили два недостатка:

  1. Этот код также будет хорошо работать только с одним (немодальным) окном;
  2. Получив все достоинства диалоговой навигации, мы лишаемся прелестей в виде сообщений 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.

  • Стартовая функция WinMain
  • Программа на Си для Windows, как и для любой другой платформы, должна
    обязательно содержать некоторую «стартовую» функцию, которой передается
    управление при запуске программы. Вообще говоря, имя такой «стартовой»
    функции может различаться в различных компиляторах, но исторически
    сложилось так (а, кроме того, имеются еще и стандарты ANSI и ISO,
    к которым, правда, производители коммерческих компиляторов типа Microsoft
    и Borland/Inprise относятся без особого трепета), что такой функцией является:

    int main()

    У этой функции может быть до трех параметров:

    int main(int argc, char *argv[], char *env[])
    • argc — количество параметров в командной строке (включая имя программы),
    • argv — массив строк-параметров (argv[0] — имя программы),
    • env — массив строк-переменных окружения.

    Многие компиляторы для Windows «понимают» такую стартовую функцию.
    Однако при этом они создают хотя и 32-битное, но консольное приложение.
    Пример 1 (example1.cpp):

    #include <stdio.h>
    int main() {
     printf("Hello, world!");
     getc(stdin);
     return 0;
    }

    Компилируем:

    bcc32 example1.cpp

    Запускаем:

    При использовании стандартных библиотек (stdio, stdlib и т. п.)
    вам не потребуется никаких лишних телодвижений по сравнению с обычными
    методами написания программ на Си. Если же ваша цель — 32-битное приложение
    с графическим интерфейсом, то черное консольное окошко будет вас раздражать.
    Пример (example2.cpp):

    #include <windows.h>
    int main() {
     MessageBox(NULL,"Hello, World!","Test",MB_OK);
     return 0;
    }

    В общем, чтобы получить нормальное приложение без каких-либо «довесков»
    типа консольного окошка, используется другая стартовая функция:

    int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hpi, LPSTR cmdline, int ss)
    • hInst — дескриптор для данного экземпляра программы,
    • hpi — в Win32 не используется (всегда NULL),
    • cmdline — командная строка,
    • ss — код состояния главного окна.

    Пример (example3.cpp):

    #include <windows.h>
    int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int) {
     MessageBox(NULL,"Hello, World!","Test",MB_OK);
     return 0;
    }

    Кроме того, компилятору и компоновщику нужно сообщить о том, что
    вы делаете графическое приложение. Для bcc32 это опция -tW:

    bcc32 -tW example3.cpp

    Если же вы соскучитесь по черному консольному окошку, его можно
    в любой момент создать при помощи вызова Win32 API

    BOOL AllocConsole(void)

  • О типах, о функциях
  • Как известно, в Си есть лишь три базовых типа
    (char, int, float/double)
    и еще несколько их вариаций с модификаторами signed/unsigned, short/long.
    Однако фирме Microsoft зачем-то понадобилось описывать функции Win32 API
    с помощью переопределенных типов:

    typedef unsigned char BYTE;
    typedef unsigned short WORD;
    typedef unsigned int UINT;
    typedef int INT;
    typedef long BOOL;
    #define FALSE 0
    #define TRUE 1
    typedef long LONG;
    typedef unsigned long DWORD;
    typedef void *LPVOID;
    typedef char CHAR;
    typedef CHAR *LPSTR;
    typedef const CHAR *LPCSTR;

    Кроме перечисленных простых типов, практически ни один вызов Win32 API
    не обходится без «штучек» с «ручками» — переменных типа handle («ручка»),
    которые идентифицируют некоторый объект («штучку»). Такие «ручки» принято
    называть дескрипторами. Реально такая переменная представляет собой всего
    лишь указатель на некоторую системную структуру или
    индекс в некоторой системной таблице.

    typedef void *HANDLE;    /* абстрактный дескриптор (например, файла) */
    typedef void *HMODULE;   /* дескриптор модуля */
    typedef void *HINSTANCE; /* дескриптор экземпляра программы */
    typedef void *HKEY;      /* дескриптор ключа в реестре */
    typedef void *HGDIOBJ;   /* дескриптор графического примитива (перо, шрифт, кисть, палитра,...) */
    typedef void *HWND;      /* дескриптор окна */
    typedef void *HMENU;     /* дескриптор меню */
    typedef void *HICON;     /* дескриптор иконки */
    typedef void *HBITMAP;   /* дескриптор картинки */
    typedef void *HFONT;     /* дескриптор шрифта */

    При заполнении различных структур часто требуется указать такую «ручку»
    от какой-нибудь «штучки». Очень часто вместо конкретного дескриптора допустимо
    передавать значение NULL, означающее, что вы еще не обзавелись
    такой «штучкой» или собираетесь использовать «штучку» по умолчанию.

    В стандартных версиях Си для функций используются два варианта соглашения
    о передаче параметров: соглашение языка Си (параметры функции помещаются в стек в порядке
    обратном их описанию, очистку стека производит вызывающая процедура) и
    соглашение языка Паскаль (параметры функции помещаются в стек в (прямом) порядке их
    описания, очистку стека производит вызываемая процедура). Для этих соглашений
    использовались, соответственно, модификаторы cdecl и pascal.
    При описании функций Win32 API используется модификатор WINAPI, а для
    описания пользовательских функций обратного вызова — модификатор CALLBACK.
    Оба этих модификатора являются переопределением специального модификатора _stdcall,
    соответствующего соглашению о передаче параметров, использующегося исключительно
    в Win32 API, — Standard Calling Convention (параметры функции помещаются в стек
    в порядке обратном их описанию, очистку стека производит вызываемая процедура).

  • Окна
  • Окно — это прямоугольная область экрана, в котором приложение отображает информацию и
    получает реакцию от пользователя. Одновременно на экране может отображаться несколько
    окон, в том числе, окон других приложений, однако лишь одно из них может получать
    реакцию от пользователя — активное окно. Пользователь использует клавиатуру, мышь
    и прочие устройства ввода для взаимодействия с приложением, которому принадлежит активное окно.
    Каждое 32-битное приложение создает, по крайней мере, одно окно, называемое главным окном,
    которое обеспечивает пользователя основным интерфейсом взаимодействия с приложением.

    Окно приложения может содержать строку заголовка title bar (1),
    строку меню menu bar (2), системное меню system menu (3),
    кнопку сворачивания окна minimize box (4), кнопку разворачивания окна maximize box (5),
    рамку изменения размеров sizing border (6), клиентскую область client area (7),
    горизонтальную и вертикальную полосы прокрутки scroll bars (8):

    Меню, строка заголовка с системными кнопками, системное меню, рамка изменения размеров
    и полосы прокрутки относятся к области окна, называемой неклиентской областью (non-client area).
    С неклиентской областью Windows справляется сама, а вот за содержимое и обслуживание
    клиентской области отвечает приложение.

    Кроме главного окна, приложение может использовать еще и другие типы окон:
    управляющие элементы (controls), диалоговые окна (dialog boxes),
    окна-сообщения (message boxes). Управляющий элемент — окно, непосредственно
    обеспечивающее тот или иной способ ввода информации пользователем. К управляющим
    элементам относятся: кнопки, поля ввода, списки, полосы прокрутки и т.п.
    Управляющие элементы обычно не болтаются сами по себе, а проживают в каком-либо
    диалоговом окне.
    Диалоговое окно — это временное окно, напичканное управляющими элементами,
    обычно использующееся для получения дополнительной информации от пользователя.
    Диалоговые окна бывают модальные (modal) и немодальные (modeless).
    Модальное диалоговое окно требует, чтобы пользователь обязательно ввел обозначенную
    в окне информацию и закрыл окно прежде, чем приложение продолжит работу.
    Немодальное диалоговое окно позволяет пользователю, не закрывая диалогового окна,
    переключаться на другие окна этого приложения.
    Окно-сообщение — это диалоговое окно предопределенного системой формата,
    предназначенное для вывода небольшого текстового сообщения с одной или несколькими
    кнопками. Пример такого окна показан в Примере 3.

    В отличие от традиционного программирования на основе линейных алгоритмов,
    программы для Windows строятся по принципам событийно-управляемого программирования
    (event-driven programming) — стиля программирования, при котором поведение
    компонента системы определяется набором возможных внешних событий и ответных реакций
    компонента на них. Такими компонентами в Windows являются окна. С каждым окном
    в Windows связана определенная функция обработки событий. События для окон
    называются сообщениями. Сообщение относится к тому или иному типу,
    идентифицируемому определенным кодом (32-битным целым числом), и сопровождается
    парой 32-битных параметров (WPARAM и LPARAM),
    интерпретация которых зависит от типа сообщения. В заголовочном файле windows.h
    для кодов сообщений определены константы с интуитивно понятными именами:

    #define WM_CREATE   0x0001  /* сообщение о создании окна */
    #define WM_DESTROY  0x0002  /* сообщение об уничтожении окна */
    #define WM_SIZE     0x0005  /* сообщение об изменении размеров окна */
    #define WM_COMMAND  0x0111  /* сообщение от команды меню или управляющего элемента */

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

    Для стандартных управляющих элементов (библиотека Common Controls Library — COMCTL32.DLL)
    в Windows имеются предопределенные обработчики событий, которые при наступлении
    интересных событий сообщают всяческую полезную информацию окну, содержащему этот
    управляющий элемент. Стандартная библиотека Common Dialog Box Library
    (COMDLG32.DLL) содержит несколько готовых весьма полезных диалоговых окон с
    обработчиками: диалоги выбора файла, настроек печати, выбора шрифта, выбора цвета и др.
    Кроме того, любая среда разработки (VisualBasic, Delphi, VisualC++ и т.п.) навязывает
    разработчику дополнительный набор готовых управляющих элементов и диалогов —
    иногда достаточно удобных, иногда не очень.

  • Структура программы
  • Программа для Win32 обычно состоит из следующих блоков:

    #include <windows.h>
    
    int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR cmdline,int ss) {
    

    /* Блок инициализации: создание класса главного окна, создание главного окна, загрузка ресурсов и т.п. */

    /* Цикл обработки событий: */ MSG msg; while (GetMessage(&msg,(HWND)NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); }

    return msg.wParam; } LRESULT CALLBACK MainWinProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) {

    /* Обработка сообщений главного окна */ switch (msg) { case WM_CREATE: /* ... */ return 0; case WM_COMMAND: /* ... */ return 0; case WM_DESTROY: /* ... */ PostQuitMessage(0); return 0; /* ... */ }

    return DefWindowProc(hw,msg,wp,lp); }

    Каждое окно принадлежит определенному классу окон. Окна одного класса
    имеют схожий вид, обслуживаются общей процедурой обработки событий,
    имеют одинаковые иконки и меню. Обычно каждое приложение создает
    для главного окна программы свой класс. Если приложению требуются
    дополнительные нестандартные окна, оно регистрирует другие классы.
    Стандартные диалоги и управляющие элементы принадлежат к предопределенным классам окон,
    для них не надо регистрировать новые классы. Чтобы определить новый класс окон,
    надо заполнить структуру WNDCLASS, содержащую следующие поля:

    • UINT style — стиль (поведение) класса окон,
    • WNDPROC lpfnWndProc — процедура обработки событий окна,
    • int cbClsExtra — размер дополнительной памяти в системной структуре класса для данных пользователя,
    • int cbWndExtra — размер дополнительной памяти в системной структуре окна для данных пользователя,
    • HINSTANCE hInstance — дескриптор модуля (экземпляра программы), в котором реализована процедура обработки,
    • HICON hIcon — дескриптор иконки окна,
    • HCURSOR hCursor — дескриптор курсора мыши для окна,
    • HBRUSH hbrBackground — дескриптор «кисточки» для закрашивания фона окна,
    • LPCSTR lpszMenuName — имя ресурса, содержащего меню окна,
    • LPCSTR lpszClassName — имя класса.

    Класс регистрируется при помощи функции:

    WORD WINAPI RegisterClass(const WNDCLASS *lpwc)

    При успешном завершении функция возвращает целочисленный код,
    соответствующий строке-имени класса в общесистемной таблице строк
    (такой код называется атомом). При ошибке возвращается 0.

    Для создания окна вызывается функция:

    HWND WINAPI CreateWindow(
      LPCSTR lpClassName,  /* имя класса */
      LPCSTR lpWindowName, /* имя окна (заголовок) */
      DWORD dwStyle,       /* стиль (поведение) окна */
      int x,               /* горизонтальная позиция окна на экране */
      int y,               /* вертикальная позиция окна на экране */
      int nWidth,          /* ширина окна */
      int nHeight,         /* высота окна */
      HWND hWndParent,     /* дескриптор родительского окна */
      HMENU hMenu,         /* дескриптор меню */
      HANDLE hInstance,    /* дескриптор экземпляра программы */
      LPVOID lpParam       /* указатель на какую-нибудь ерунду */
    )

    Вместо параметров x, y, nWindth, nHeight допустимо передавать
    константу CW_USEDEFAULT, позволяющую операционной системе задать эти числа
    по ее усмотрению.

    Интерпретация кода стиля определяется классом окна.
    Стиль определяет не только оформление окна, но и его поведение.
    Общие для всех классов константы стилей
    (при необходимости объединяются операцией побитовое ИЛИ):

    • WS_DISABLED — при создании окно заблокировано (не может получать реакцию от пользователя);
    • WS_VISIBLE — при создании окно сразу же отображается (не надо вызывать ShowWindow);
    • WS_CAPTION — у окна есть строка заголовка;
    • WS_SYSMENU — у окна есть системное меню;
    • WS_MAXIMIZEBOX — у окна есть кнопка разворачивания;
    • WS_MINIMIZEBOX — у окна есть кнопка сворачивания;
    • WS_SIZEBOX или WS_THICKFRAME — у окна есть рамка изменения размеров;
    • WS_BORDER — у окна есть рамка (не подразумевает изменение размеров);
    • WS_HSCROLL или WS_VSCROLL — у окна есть горизонтальная или вертикальная прокрутка;
    • WS_OVERLAPPED или WS_TILED — «перекрываемое» окно — обычное окно с рамкой и строкой заголовка;
    • WS_POPUP — «всплывающее» окно;
    • WS_OVERLAPPEDWINDOW — «перекрываемое» окно с системным меню, кнопками сворачивания/разворачивания,
      рамкой изменения размеров, короче, типичный стиль для главного окна приложения.

    Во время выполнения функции CreateWindow процедуре обработки событий
    окна посылается сообщение WM_CREATE. При успешном выполнении функции
    возвращается дескриптор созданного окна, при неудаче — NULL.

    После создания окна неплохо бы сделать его видимым (отобразить), если только
    оно не создано со стилем WS_VISIBLE:

    BOOL WINAPI ShowWindow(HWND hw, int ss)

    Второй параметр этой функции — код состояния отображения окна. В качестве этого кода
    можно взять значение четвертого параметра, с которым была запущена функция WinMain.
    Другие возможные значения этого параметра:

    • SW_SHOW — отобразить и активировать окно;
    • SW_HIDE — скрыть окно;
    • SW_MAXIMIZE — развернуть окно на весь экран;
    • SW_RESTORE — активировать окно и отобразить его в размерах по умолчанию;
    • SW_MINIMIZE — свернуть окно.

    Если перед вызовом этой функции окно было видимым, функция возвращает TRUE,
    если же окно было скрыто — FALSE.

    Если клиентская область главного окна приложения содержит объекты, прорисовываемые
    по сообщению WM_PAINT, имеет смысл прорисовать эти объекты сразу после
    отображения главного окна на экране. Функция UpdateWindow непосредственно
    вызывает процедуру обработки событий указанного окна с сообщением WM_PAINT
    (минуя очередь сообщений приложения):

    BOOL WINAPI UpdateWindow(HWND hw)

    Windows использует два способа доставки сообщений процедуре обработки событий окна:

    • непосредственный вызов процедуры обработки событий (внеочередные или
      неоткладываемые сообщенияnonqueued messages);
    • помещение сообщения в связанный с данным приложением буфер типа FIFO,
      называемый очередью сообщенийmessage queue
      (откладываемые сообщенияqueued messages).

    К внеочередным сообщениям относятся те сообщения, которые непосредственно
    влияют на окно, например, сообщение активации окна WM_ACTIVATE и т.п.
    Кроме того, вне очереди сообщений обрабатываются сообщения, сгенерированные
    различными вызовами Win32 API, такими как SetWindowPos,
    UpdateWindow, SendMessage,
    SendDlgItemMessage

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

    BOOL WINAPI GetMessage(
      MSG *lpmsg,          /* сюда попадает сообщение со всякими параметрами */
      HWND hw,             /* извлекать только сообщения для указанного окна (NULL - все) */
      UINT wMsgFilterMin,  /* фильтр сообщений (нам не надо - ставим 0) */
      UINT wMsgFilterMax   /* фильтр сообщений (нам не надо - ставим 0) */
    )

    Эта функция возвращает FALSE, если получено сообщение WM_QUIT,
    и TRUE в противном случае. Очевидно, что условием продолжения
    цикла обработки событий является результат этой функции. Если приложение хочет
    завершить свою работу, оно посылает само себе сообщение WM_QUIT
    при помощи функции

    void WINAPI PostQuitMessage(int nExitCode)

    Ее параметр — статус выхода приложения. Обычно эта функция вызывается в ответ на
    сообщение об уничтожении окна WM_DESTROY.

    После извлечения сообщения из очереди следует вызвать функцию
    TranslateMessage, переводящую сообщения от нажатых клавиш
    в удобоваримый вид, а затем DispatchMessage,
    которая определяет предназначенное этому сообщению окно и вызывает
    соответствующую процедуру обработки событий.

    BOOL WINAPI TranslateMessage(const MSG *lpmsg)
    LONG WINAPI DispatchMessage(const MSG *lpmsg)

    Результат возврата соответствует значению, которое вернула процедура обработки событий
    (обычно никому не нужен).

    Процедура обработки сообщений окна должна быть объявлена по следующему прототипу:

    LRESULT CALLBACK WindowProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp)

    Значения параметров: hw — дескриптор окна, которому предназначено сообщение,
    msg — код сообщения, wp и lp — 32-битные параметры
    сообщения, интерпретация которых зависит от кода сообщения. Зачастую старший/младший
    байт или старшее/младшее слово параметров сообщения несут независимый смысл, тогда
    удобно использовать определенные в windows.h макросы:

    #define LOBYTE(w)   ((BYTE) (w))
    #define HIBYTE(w)   ((BYTE) (((WORD) (w) >> 8) & 0xFF))
    #define LOWORD(l)   ((WORD) (l))
    #define HIWORD(l)   ((WORD) (((DWORD) (l) >> 16) & 0xFFFF))

    Например, сообщение WM_COMMAND посылается окну в трех случаях:

    1. пользователь выбрал какую-либо команду меню;
    2. пользователь нажал «горячую» клавишу (accelerator);
    3. в дочернем окне произошло определенное событие.

    При этом параметры сообщения интерпретируются следующим образом.
    Старшее слово параметра WPARAM содержит: 0 в первом случае, 1 во втором случае
    и код события в третьем случае. Младшее слово WPARAM содержит
    целочисленный идентификатор пункта меню, «горячей» клавиши или дочернего управляющего
    элемента. Параметр LPARAM в первых двух случаях содержит NULL,
    а в третьем случае — дескриптор окна управляющего элемента.

    Процедура обработки событий должна вернуть определенное 32-битное значение,
    интерпретация которого также зависит от типа сообщения. В большинстве случаев,
    если сообщение успешно обработано, процедура возвращает значение 0.

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

    LRESULT WINAPI DefWindowProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp)

    Все описанное в данном параграфе суммируется в примере 4 (example4.cpp):

    #include <windows.h>
    
    LRESULT CALLBACK MainWinProc(HWND,UINT,WPARAM,LPARAM);
    #define ID_MYBUTTON 1    /* идентификатор для кнопочки внутри главного окна */
    
    int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,int ss) {
     /* создаем и регистрируем класс главного окна */
     WNDCLASS wc;
     wc.style=0;
     wc.lpfnWndProc=MainWinProc;
     wc.cbClsExtra=wc.cbWndExtra=0;
     wc.hInstance=hInst;
     wc.hIcon=NULL;
     wc.hCursor=NULL;
     wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
     wc.lpszMenuName=NULL;
     wc.lpszClassName="Example 4 MainWnd Class";
     if (!RegisterClass(&wc)) return FALSE;
    
     /* создаем главное окно и отображаем его */
     HWND hMainWnd=CreateWindow("Example 4 MainWnd Class","EXAMPLE 4",WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInst,NULL);
     if (!hMainWnd) return FALSE;
     ShowWindow(hMainWnd,ss);
     UpdateWindow(hMainWnd);
    
     MSG msg; /* цикл обработки событий */
     while (GetMessage(&msg,NULL,0,0)) {
      TranslateMessage(&msg); 
      DispatchMessage(&msg); 
     } 
     return msg.wParam; 
    }
    
    /* процедура обработки сообщений для главного окна */
    LRESULT CALLBACK MainWinProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) {
     switch (msg) {
      case WM_CREATE:
       /* при создании окна внедряем в него кнопочку */
       CreateWindow("button","My button",WS_CHILD|BS_PUSHBUTTON|WS_VISIBLE,
        5,5,100,20,hw,(HMENU)ID_MYBUTTON,NULL,NULL);
       /* стиль WS_CHILD означает, что это дочернее окно и для него
        вместо дескриптора меню будет передан целочисленный идентификатор,
        который будет использоваться дочерним окном для оповещения 
        родительского окна через WM_COMMAND */
       return 0;
      case WM_COMMAND:
       /* нажата наша кнопочка? */
       if ((HIWORD(wp)==0) && (LOWORD(wp)==ID_MYBUTTON)) 
        MessageBox(hw,"You pressed my button","MessageBox",MB_OK|MB_ICONWARNING);
       return 0;
      case WM_DESTROY:
       /* пользователь закрыл окно, программа может завершаться */
       PostQuitMessage(0);
       return 0;
     }
     return DefWindowProc(hw,msg,wp,lp);
    }

    Приведенный пример создает окно с кнопкой «My button», при нажатии
    на которую вылезает окно-сообщение:

  • Ресурсы
  • Ресурсы — это бинарные данные, добавляемые в исполняемый файл
    при компоновке программы. К стандартным ресурсам относятся: иконки,
    курсоры, меню, диалоги, растровые изображения (BMP), векторные изображения (EMF),
    шрифты, таблицы горячих клавиш, таблицы строк, информация о версии программы или модуля.
    В процессе разработки программы ресурсы описывают в отдельном текстовом файле —
    файле описания ресурсов (расширение .rc), — а затем при помощи компилятора
    ресурсов переводят в бинарный вид и добавляют в исполняемый файл на этапе компоновки
    исполняемого файла. Использование ресурсов значительно облегчает работу программиста
    по визуализации графических примитивов интерфейса программы.

    Файл описания ресурсов состоит из операторов, объединяемых в блоки.
    Один оператор занимает одну строку файла. Допускается использовать
    комментарии, определяемые так же, как в программе на языке Си.
    Файл описания ресурсов перед компиляцией так же обрабатывается препроцессором,
    поэтому в нем можно использовать директивы препроцессора (#include,
    #define, …) и макроопределения. В сложных «блочных»
    описаниях ресурсов вместо ключевых слов BEGIN и END
    можно использовать { и }, соответственно.

    Иконки, картинки и курсоры мыши можно описать двумя способами
    (квадратные скобки не являются частью синтаксиса оператора и означают
    необязательный элемент):

    nameID RESOURCETYPE [load-option] [mem-option] filename

    Здесь nameID — численный или строковой идентификатор;
    RESOURCETYPE — ключевое слово, обозначающее тип ресурса:
    ICON, BITMAP или
    CURSOR;
    load-option и mem-option — всякие неинтересные
    в данный момент опции, которые можно спокойно пропустить;
    filename — имя файла, содержащее соответствующий ресурс.

    Примеры:

    disk1   BITMAP "disk.bmp"
    12      ICON   "myicon.ico"

    Эти ресурсы можно внедрить в виде шестнадцатеричных кодов
    непосредственно в файл ресурсов:

    nameID RESOURCETYPE
    BEGIN
     hex data
    END

    Пример:

    FltBmp BITMAP
    {
     '42 4D A2 00 00 00 00 00 00 00 3E 00 00 00 28 00'
     '00 00 19 00 00 00 19 00 00 00 01 00 01 00 00 00'
     '00 00 64 00 00 00 00 00 00 00 00 00 00 00 00 00'
     '00 00 00 00 00 00 00 00 00 00 FF FF FF 00 FF FF'
     'FF 80 FF FF FF 80 FF FF FF 80 FF FF FF 80 FF FF'
     'FF 80 FF FF FF 80 FF FF FF 80 C0 FF 81 80 FE FF'
     'BF 80 FE FF BF 80 FE FF BF 80 FE FF BF 80 FE FF'
     'BF 80 FE FF BF 80 FE FF BF 80 FE FF BF 80 FE FF'
     'BF 80 FE FF BF 80 FE 00 3F 80 FF FF FF 80 FF FF'
     'FF 80 FF FF FF 80 FF FF FF 80 FF FF FF 80 FF FF'
     'FF 80'
    }

    Следует отметить, что первая иконка в ресурсах
    будет использоваться «Проводником» как иконка
    исполняемого файла.

    Меню описывается следующим образом:

    nameID MENU [load-option] [mem-option]
    BEGIN 
        item-definitions
        ...
    END

    Здесь item-definitions — один из трех операторов:

    MENUITEM text, result [, optionlist]
            /* обычный пункт меню */
    MENUITEM SEPARATOR
            /* строка-сепаратор */
    POPUP text [, optionlist]
    BEGIN   /* подменю */
        item-definitions
        ...
    END

    Параметры операторов имеют следующий смысл:
    text — текст пункта меню или подменю
    (может содержать комбинации t — табуляция,
    a — выравнивание по правому краю, &
    следующий символ подчеркивается, обозначает «горячую» клавишу для
    указанного пункта меню);
    result — целочисленный идентификатор пункта меню,
    посылаемый окну-владельцу через сообщение WM_COMMAND
    при выборе этого пункта меню;
    optionlist — необязательный список опций, разделенных
    запятой или пробелом:

    • CHECKED — рядом с пунктом меню отображается галочка,
    • GRAYED — пункт меню неактивен (не может быть выбран)
      и отображается серым цветом и др.

    Доступ к ресурсам, скомпонованным с исполняемым файлом, можно получить
    при помощи следующих функций:

    HICON WINAPI LoadIcon(HINSTANCE hInst, LPCSTR lpIconName)
    HBITMAP WINAPI LoadBitmap(HINSTANCE hInst, LPCSTR lpBitmapName)
    HCURSOR WINAPI LoadCursor(HINSTANCE hInst, LPCSTR lpCursorName)
    HMENU WINAPI LoadMenu(HINSTANCE hInst, LPCSTR lpMenuName)

    Первый параметр этих функций — дескриптор экземпляра программы,
    второй — идентификатор соответствующего ресурса. Если ресурс идентифицируется
    не именем, а числом, то следует использовать макрос, объявленный в windows.h:

    #define MAKEINTRESOURCE(i)  (LPSTR) ((DWORD) ((WORD) (i)))

    Например:

    HMENU hMainMenu=LoadMenu(hInst,MAKEINTRESOURCE(10));

    Для закрепления полученных сведений, давайте добавим к примеру 4
    какую-нибудь иконку и такое меню:

    Для этого создаем файл ресурсов (example4a.rc):

    Ex4_Icon ICON "myicon.ico"
    
    Ex4_Menu MENU
    {
     POPUP "&File"
     {
      MENUITEM "&Open...tCtrl-O", 2
      MENUITEM "&Save", 3
      MENUITEM "Save &As...", 4
      MENUITEM SEPARATOR
      MENUITEM "&Hex view", 5, CHECKED GRAYED
      MENUITEM "&ExittAlt-F4", 6
     }
     POPUP "&Edit"
     {
      MENUITEM "&Copy", 7
      MENUITEM "&Paste", 8
      POPUP "Popup"
      {
       MENUITEM "1", 9
       MENUITEM "2", 10
       MENUITEM "3", 11
      }
      MENUITEM SEPARATOR
      MENUITEM "Search", 12
     }
     POPUP "&Help"
     {
      MENUITEM "&About...tF1", 13
     }
    }

    Для перевода файла описания ресурсов в бинарный вид используется
    компилятор ресурсов Borland Resource Compiler:

    brcc32 example4a.rc

    В результате получается файл example4a.res, который потребуется в процессе
    компоновки.

    В примере 4 надо изменить строки

     wc.hIcon=NULL;
     wc.lpszMenuName=NULL;

    на

     wc.hIcon=LoadIcon(hInst,"Ex4_Icon");
     wc.lpszMenuName="Ex4_Menu";

    Чтобы программа не была такой скучной, изменим обработчик сообщения WM_COMMAND:

      case WM_COMMAND:
       if (HIWORD(wp)==0) {
        char buf[256];
        switch (LOWORD(wp)) {
         case 6:  /* команда меню Exit */
          PostQuitMessage(0);
         default: /* все остальные команды */
          wsprintf(buf,"Command code: %d",LOWORD(wp));
          MessageBox(hw,buf,"MessageBox",MB_OK|MB_ICONINFORMATION);
        }
       }
       return 0;

    В результате при выборе того или иного пункта меню выводится окно-сообщение с кодом команды.

    Обратите внимание: среди команд меню не используется код 1,
    который отведен кнопке «My button». Это типичная практика назначать
    всем дочерним элементам окна и командам меню разные численные идентификаторы,
    что облегчает обработку сообщения WM_COMMAND.

  • Компиляция и компоновка сложных проектов
  • Теперь компоновка программы будет более сложной, поэтому bcc32
    с этой задачей не справится. В этом примере компилятор будет
    использоваться только для компилирования (запускаем с ключом ):

    bcc32 -c -tW example4a.cpp

    В результате получаем объектный файл example4a.obj.

    Чтобы собрать все части программы вместе, придется запускать
    компоновщик вручную (ilink32 или tlink32).
    В командной строке компоновщика указываются следующие параметры:

    ilink32 [options] objfiles,exefile,mapfile,libfiles,deffile,resfiles
    • options — о допустимых опциях можно узнать, запустив компоновщик без параметров.
      Нам потребуются:

      • -aa — тип приложения «графическое для Win32»
        (другие варианты: -ap — консольное приложение, -ad — драйвер);
      • -Tpe — формат выходного файла «.EXE» (другой вариант: -Tpd — «.DLL»);
      • -L путь — путь для поиска библиотек и объектных файлов
        (обычно: -Lc:bcc55lib).
    • objfiles — список объектных файлов, из которых составляется программа,
      разделенных пробелом или знаком «+». Этот список должен начинаться с борландовского
      инициализационного объектного файла: c0w32.obj — для графического приложения под
      Win32 или c0x32.obj — для консольного приложения.
    • exefile — имя исполняемого файла, который получится в результате компоновки.
    • mapfile — имя файла, который после компиляции будет содержать
      карту сегментов вашей программы (оно вам надо? если нет, здесь делаем «пусто»,
      а в опциях указываем -x, чтобы компоновщик не замусоривал рабочий каталог этой фигней).
    • libfiles — список библиотек, в которых надо искать не
      определенные в программе функции, разделенных пробелом или знаком «+».
      Как минимум, надо указать import32.lib, которая содержит код подключения
      к стандартным библиотекам Win32 API: kernel32.dll, user32.dll, gdi32.dll,
      advapi32.dll и др. Если вы используете какие-либо функции стандартных
      библиотек языка Си (stdlib, stdio, …), надо указать еще cw32.lib.
    • deffile — файл параметров модуля (module definition file).
      Это текстовый файл, в котором определяются различные настройки компилируемой
      программы типа: размеры сегментов стека, кода, данных, «заглушка»
      (что будет происходить при попытке запуска программы в DOS) и проч.
      Если не указывать этот файл, компоновщик выберет вполне приличные
      для большинства случаев параметры по умолчанию.
    • resfiles — файлы ресурсов (разделяются пробелом или знаком «+»).

    Компоновщик ilink32 умеет работать в пошаговом (инкрементирующем)
    режиме incremental linking, при этом он создает несколько файлов состояний
    (*.IL?). При последующих попытках компиляции он использует их, так что
    процесс компиляции занимает меньше времени. Чтобы отключить пошаговую
    компиляцию и не замусоривать рабочий каталог этими файлами, следует
    указать опцию -Gn. Например, если при отключенной пошаговой компиляции
    программа компилируется 8 секунд, то первая компиляция в пошаговом режиме
    займет 25 секунд, а все последующие — не более 2 секунд.

    Итак, компонуем модифицированный пример 4:

    ilink32 -aa -Tpe -Lc:bcc55lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res

    Если все сделано правильно, первое, что становится сразу заметным —
    у исполняемого файла появилась иконка:

    Эта же иконка отображается в строке заголовка главного окна программы. Под
    строкой заголовка отображается созданное нами меню. При выборе любой команды
    меню появляется окно-сообщение с кодом команды. При выборе команды «Exit»
    программа завершается.

    Если ваш проект состоит из множества файлов, то компилировать и
    компоновать их вручную становится затруднительно. В таких случаях
    используются сценарии компиляции, которые обрабатываются программой make.
    Сценарий компиляции — текстовый файл с именем Makefile, описывающий
    зависимости между различными файлами проекта, следующего формата:

    правило: зависимости
    	команды для выполнения

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

    Пример:

    example4a.exe: example4a.rc example4a.cpp myicon.ico
    	brcc32 example4a.rc
    	bcc32 -c -tW example4a.cpp
    	ilink32 -Gn -x -aa -Tpe -Lc:bcc55lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res

    Если не указывать в качестве файлов-зависимостей example4a.rc и example4a.cpp,
    то make не станет ничего делать, когда файл example4a.exe уже существует.
    Тем не менее, приведенный пример — не совсем удачный сценарий компиляции.
    Если мы изменим только файл ресурсов, make все равно будет перекомпилировать
    исходный текст. Если мы изменим только исходный текст, make будет
    перекомпилировать еще и ресурсы. С учетом этого замечания, более удачным
    будет следующий сценарий:

    example4a.exe: example4a.obj example4a.res
    	ilink32 -Gn -x -aa -Tpe -Lc:bcc55lib c0w32.obj+example4a.obj,example4a.exe,,import32.lib+cw32.lib,,example4a.res
    
    example4a.obj: example4a.cpp
    	bcc32 -c -tW example4a.cpp
    
    example4a.res: example4a.rc myicon.ico
    	brcc32 example4a.rc

    Если в командной строке make не указано иное, то make пытается выполнить
    первое правило из сценария. Именно поэтому первым правилом стоит example4a.exe
    — результат, который мы хотим получить после компиляции всего проекта.

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

    make

  • Диалоговые окна
  • Большинство приложений использует диалоговые окна для запроса у пользователя
    дополнительной информации для выполнения каких-либо команд. Например,
    команда открытия файла требует указания имени файла, так что приложение
    создает диалоговое окно, чтобы запросить у пользователя имя файла.
    Пока пользователь не укажет имя файла, команда не будет выполнена.
    После этого программа уничтожает это диалоговое окно. В этом случае
    используется модальное диалоговое окно. Другой пример: текстовый
    редактор может использовать немодальное диалоговое окно, для
    команды поиска. Пока редактор ищет введенную фразу, диалоговое окно
    остается на экране. Более того, пользователь может вернуться к редактированию
    текста, не закрывая диалог поиска. Либо пользователь может ввести
    другую фразу для поиска. Такое диалоговое окно остается открытым,
    пока приложение не завершится или пользователь непосредственно
    не выберет команду закрытия этого диалога.

    Чтобы создать диалоговое окно, приложение должно предоставить
    системе шаблон диалога, описывающий содержание и стиль диалога,
    и диалоговую процедуру. Диалоговая процедура выполняет примерно
    такие же задачи, что и процедура обработки событий окна.
    Диалоговые окна принадлежат к предопределенному классу окон.
    Windows использует этот класс и соответствующую процедуру
    обработки событий для модальных и немодальных диалогов.
    Эта процедура обрабатывает одни сообщения самостоятельно,
    а другие передает на обработку диалоговой процедуре приложения.
    У приложения нет непосредственного доступа к этому предопределенному
    классу и соответствующей ему процедуре обработки событий.
    Для изменения стиля и поведения диалога программа должна использовать
    шаблон диалогового окна и диалоговую процедуру.

    Для создания модального диалога используется функция DialogBox,
    а для создания немодального диалога — CreateDialog:

    int WINAPI DialogBox(HANDLE hInst, LPCSTR template, HWND parent, DLGPROC DlgFunc)
    HWND WINAPI CreateDialog(HANDLE hInst, LPCSTR template, HWND parent, DLGPROC DlgFunc)

    Параметры: hInst — дескриптор экземпляра программы
    (модуля, в котором находится шаблон); template — имя ресурса,
    описывающего диалог; parent — дескриптор родительского окна;
    DlgFunc — диалоговая функция следующего формата:

    BOOL CALLBACK DlgFunc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) 

    Параметры диалоговой функции такие же, как у обычной функции обработки
    событий. Отличие этой функции — она вызывается из предопределенной
    функции обработки событий для диалоговых окон. Она должна вернуть
    значение TRUE, если обработала переданное ей сообщение,
    или FALSE в противном случае. Она ни в коем случае
    не должна сама вызывать DefWindowProc.

    При создании диалогового окна диалоговая процедура получает
    сообщение WM_INITDIALOG. Если в ответ на это сообщение
    процедура возвращает FALSE, диалог не будет создан:
    функция DialogBox вернет значение -1,
    а CreateDialogNULL.

    Модальное диалоговое окно блокирует указанное в качестве родительского
    окно и появляется поверх него (вне зависимости от стиля WS_VISIBLE).
    Приложение закрывает модальное диалоговое окно при помощи функции

    BOOL WINAPI EndDialog(HWND hw, int result)

    Приложение должно вызвать эту функцию из диалоговой процедуры в ответ
    на сообщение от кнопок «OK», «Cancel» или команды «Close» из системного
    меню диалога. Параметр result передается программе как
    результат возврата из функции DialogBox.

    Немодальное диалоговое окно появляется поверх указанного в качестве
    родительского окна, но не блокирует его. Диалоговое окно остается
    поверх родительского окна, даже если оно неактивно. Программа сама
    отвечает за отображение/сокрытие окна (с помощью стиля WS_VISIBLE
    и функции ShowWindow). Сообщения для немодального
    диалогового окна оказываются в основной очереди сообщений программы.
    Чтобы эти сообщения были корректно обработаны, следует включить в цикл
    обработки сообщений вызов функции:

    BOOL WINAPI IsDialogMessage(HWND hwDlg, MSG *lpMsg)

    Если эта функция вернула TRUE, то сообщение обработано
    и его не следует передавать функциям TranslateMessage
    и DispatchMessage.

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

    BOOL WINAPI DestroyWindow(HWND hw)

    Шаблон диалогового окна в ресурсах задается следующим образом:

    nameID DIALOG [load-option] [mem-option] x, y, width, height
        [property-statements]
    BEGIN 
        control-statements
        ...
    END

    В начале блока описания диалога задается: nameID
    целочисленный или строковой идентификатор ресурса, x, y
    координаты диалога на экране (или относительно родительского окна),
    width, height — размер диалога.
    Координаты и размеры диалога и всех элементов внутри
    него задаются в диалоговых единицах (dialog units).
    Одна горизонтальная диалоговая единица соответствует 1/4 средней ширины
    символа в системном шрифте. Одна вертикальная диалоговая единица
    соответствует 1/8 средней высоты символа в системном шрифте.

    После заголовка блока идет ряд необязательных операторов-параметров диалога
    (property-statements) в любом порядке:

    STYLE style               /* стиль диалога */
    CAPTION captiontext       /* заголовок диалога */
    FONT pointsize, typeface  /* шрифт диалога (размер, название) */
    MENU menuname             /* меню диалога */

    В качестве стиля диалога можно применять все перечисленные для обычных окон
    стили. Обычно выбирают: WS_POPUP, WS_SYSMENU, WS_CAPTION,
    а также WS_BORDER для немодального диалога и DS_MODALFRAME
    для модального. Кроме того, можно использовать DS_SETFOREGROUND,
    чтобы при отображении диалога перевести его на передний план, даже если
    его родительское окно неактивно.

    В теле шаблона (control-statements) перечисляются
    составляющие его управляющие элементы. Один из возможных вариантов оператора:

    CONTROL text, id, class, style, x, y, width, height

    Здесь text — текст управляющего элемента (заголовок),
    id — целочисленный идентификатор элемента 0…65535
    (внутри одного диалога идентификаторы всех элементов должны различаться),
    class — имя класса, к которому принадлежит управляющий элемент,
    style — стиль управляющего элемента,
    x, y, width, height
    положение и размер диалогового элемента относительно клиентской
    области диалога в диалоговых единицах.

    Вот пример диалога, содержащего простое статическое текстовое поле и кнопку «OK»:

    Ex4_Dlg DIALOG 50,50,90,40
     STYLE WS_POPUP|WS_CAPTION|DS_MODALFRAME
     CAPTION "MyDlg"
     FONT 10, "Arial"
    {
     CONTROL "", 1, "STATIC", SS_LEFT, 5, 5, 80, 10
     CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 20, 80, 12
    }

    Добавим этот диалог к ресурсам примера 4.
    В текст программы добавим две глобальных переменных:

    char buf[256]=""; /* строка для текстового поля в диалоге */
    HINSTANCE h;      /* дескриптор экземпляра программы */

    Присвоим переменной h значение дескриптора экземпляра программы в самом начале
    функции WinMain. Это значение нам потребуется для вызова функции
    DialogBox.

    Изменим обработчик сообщения WM_COMMAND следующим образом:

      case WM_COMMAND:
        switch (LOWORD(wp)) {
         case 6:  /* команда меню Exit */
          PostQuitMessage(0);
         default: /* все остальные команды */
          wsprintf(buf,"Command code: %d",LOWORD(wp));
          DialogBox(h,"Ex4_Dlg",hw,DlgProc);
        }
        return 0;

    Теперь в текст программы необходимо добавить диалоговую процедуру:

    BOOL CALLBACK DlgProc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
     switch (msg) {
      case WM_INITDIALOG: /* сообщение о создании диалога */
       SetDlgItemText(hw,1,buf);
       return TRUE;
      case WM_COMMAND:    /* сообщение от управляющих элементов */
       if (LOWORD(wp)==2) EndDialog(hw,0);
     }
     return FALSE;
    }

    При создании диалога вызывается процедура SetDlgItemText,
    меняющая содержание текстового поля в диалоге (элемент с id=1).
    Для уничтожения диалога используется кнопка «OK», генерирующая
    сообщение WM_COMMAND с id=2.

    Функция DlgProc должна быть определена или описана
    до ссылки на нее в вызове DialogBox.

  • Управляющие элементы
  • Управляющие элементы, как и другие окна, принадлежат тому или
    иному классу окон. Windows предоставляет несколько предопределенных
    классов управляющих элементов. Программа может создавать управляющие
    элементы поштучно при помощи функции CreateWindow
    или оптом, загружая их вместе с шаблоном диалога из своих ресурсов.
    Управляющие элементы — это всегда дочерние окна. Управляющие элементы
    при возникновении некоторых событий, связанных с реакцией пользователя,
    посылают своему родительскому окну сообщения-оповещения
    (notification messages) WM_COMMAND или WM_NOTIFY.

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

    BOOL WINAPI EnableWindow(HWND hw,BOOL bEnable)

    В качестве второго параметра передается флаг TRUE (разблокировать)
    или FALSE (блокировать). Функция возвращает значение TRUE,
    если перед ее вызовом окно было заблокировано. Узнать текущий статус
    блокирования окна можно при помощи функции:

    BOOL WINAPI IsWindowEnabled(HWND hw),

    которая возвращает значение TRUE, если окно разблокировано.

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

    LRESULT WINAPI SendMessage(HWND hw, UINT msg, WPARAM wp, LPARAM lp)

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

    HWND WINAPI GetDlgItem(HWND hDlg, int idDlgItem)

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

    LRESULT WINAPI SendDlgItemMessage(
      HWND hwndDlg,	 /* дескриптор родительского диалога */
      int idControl, /* идентификатор управляющего элемента */
      UINT msg,      /* код сообщения */
      WPARAM wp,     /* параметр сообщения */
      LPARAM lp      /* параметр сообщения */
    )

    Для управляющих элементов внутри диалогов специальный смысл имеют
    стили WS_TABSTOP и WS_GROUP. Если в диалоге
    имеются управляющие элементы со стилем WS_TABSTOP, то при нажатии
    пользователем на клавишу [Tab] (или [Shift]+[Tab]), текущий активный элемент
    диалога будет терять фокус и передавать его следующему за ним (или предыдущему)
    ближайшему элементу со стилем WS_TABSTOP. С помощью стиля
    WS_GROUP элементы диалога можно объединять в группы. Группа
    элементов начинается с элемента со стилем WS_GROUP и заканчивается
    элементом, после которого идет элемент со стилем WS_GROUP, или
    последним элементом в диалоге. Внутри группы только первый элемент
    должен иметь стиль WS_GROUP. Windows допускает перемещение
    внутри группы при помощи клавиш-стрелок.

    Классы предопределенных управляющих элементов:

    «STATIC»
    Статический управляющий элемент представляет собой
    текстовую метку или прямоугольник. Не предоставляет пользователю
    ни средств ввода, ни вывода. Примеры:

    Соответствующее описание ресурсов:

     CONTROL "",-1, "STATIC", SS_BLACKFRAME,  5, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_GRAYFRAME,  30, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_WHITEFRAME, 55, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_BLACKRECT,  80, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_GRAYRECT,  105, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_WHITERECT, 130, 40, 20, 10
     CONTROL "",-1, "STATIC", SS_ETCHEDFRAME,155, 40, 20, 10
    
     /* Для статиков-иконок или картинок текстовое поле определяет имя ресурса */
     CONTROL "Ex4_Bmp",-1, "STATIC", SS_BITMAP, 5, 55, -1, -1
     CONTROL "Ex4_Icon",-1, "STATIC", SS_ICON, 65, 55, -1, -1
    
     CONTROL "text",-1, "STATIC", SS_LEFT,    105, 55, 20, 10
     CONTROL "text",-1, "STATIC", SS_CENTER,  130, 55, 20, 10
     CONTROL "text",-1, "STATIC", SS_RIGHT,   155, 55, 20, 10
    
     /* По умолчанию SS_LEFT, SS_RIGHT, SS_CENTER делают перенос по словам */
     CONTROL "This is long text example",-1, "STATIC", SS_SIMPLE|SS_SUNKEN, 65, 70, 55, 15
     CONTROL "This is long text example",-1, "STATIC", SS_LEFT|SS_SUNKEN,  125, 70, 50, 15

    Для текстовых статиков со стилями SS_LEFT, SS_RIGHT
    или SS_CENTER существуют более простые операторы объявления ресурсов:

     LTEXT "text",-1, 105, 55, 20, 10
     CTEXT "text",-1, 130, 55, 20, 10
     RTEXT "text",-1, 155, 55, 20, 10
    
     LTEXT "This is long text example",-1, 65, 70, 55, 15, SS_LEFTNOWORDWRAP|SS_SUNKEN
     LTEXT "This is long text example",-1,125, 70, 50, 15, SS_LEFT|SS_SUNKEN

    Чтобы поменять текст статика ему можно послать сообщение WM_SETTEXT
    (wp=0; lp=(LPARAM)(LPCSTR)lpsz — адрес строки) или
    использовать функции:

    BOOL WINAPI SetWindowText(HWND hw, LPCSTR lpsz)
    BOOL WINAPI SetDlgItemText(HWND hDlg, int idControl, LPCTSTR lpsz)

    Чтобы сменить иконку или картинку нетекстового статика, надо послать
    ему сообщение STM_SETIMAGE (wp=(WPARAM)fImageType
    тип изображения: IMAGE_BITMAP или IMAGE_ICON;
    lp=(LPARAM)(HANDLE)hImage — дескриптор иконки или картинки).

    «BUTTON»
    Кнопка — это небольшое прямоугольное дочернее окно, обычно
    имеющее два состояния: нажато/отпущено или включено/выключено.
    Пользователь меняет состояние этого элемента щелчком мыши.
    К этому классу относятся: кнопки-«давилки» (push buttons),
    кнопки-«галочки» (check boxes), «радио»-кнопки (radio buttons)
    и специальный тип групповых рамочек (group boxes). Примеры:

    Соответствующее описание ресурсов:

     /* DEFPUSHBUTTON - кнопка по умолчанию (нажимается по [Enter]) */
     CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 20, 50, 12
     CONTROL "text", 3, "BUTTON", BS_PUSHBUTTON, 60, 20, 50, 12
    
     CONTROL "GroupBox1", -1, "BUTTON", BS_GROUPBOX, 5, 35, 50, 50
    
     CONTROL "text", 4, "BUTTON", BS_CHECKBOX,     10, 45, 30, 10
     CONTROL "text", 5, "BUTTON", BS_AUTOCHECKBOX, 10, 57, 30, 10
     CONTROL "text", 6, "BUTTON", BS_AUTO3STATE,   10, 69, 30, 10
    
     CONTROL "GroupBox2", -1, "BUTTON", BS_GROUPBOX, 60, 35, 50, 50
    
     CONTROL "text", 7, "BUTTON", BS_AUTORADIOBUTTON|WS_GROUP, 65, 45, 30, 10
     CONTROL "text", 8, "BUTTON", BS_AUTORADIOBUTTON, 65, 57, 30, 10
     CONTROL "text", 9, "BUTTON", BS_AUTORADIOBUTTON, 65, 69, 30, 10

    Для кнопок существуют более простые операторы объявления ресурсов:

     DEFPUSHBUTTON text, id, x, y, width, height [, style]
     PUSHBUTTON text, id, x, y, width, height [, style]
     GROUPBOX text, id, x, y, width, height [, style]
     CHECKBOX text, id, x, y, width, height [, style]
     RADIOBUTTON text, id, x, y, width, height [, style]

    Разница между стилями BS_xxx и BS_AUTOxxx
    заключается в том, что при щелчке по AUTO-кнопкам Windows
    сама автоматически переключает их состояние. Для не AUTO-кнопок
    это надо делать вручную в диалоговой процедуре, послав сообщение
    BM_SETCHECK (wp=(WPARAM)fCheck — флаг:
    BST_UNCHECKED, BST_CHECKED или BST_INDETERMINATE
    (для BS_3STATE-кнопок); lp=0) или при помощи функций:

    BOOL WINAPI CheckDlgButton(HWND hDlg, int idButton, UINT fCheck)
    BOOL WINAPI CheckRadioButton(
        HWND hDlg,         /* дескриптор родительского диалога */
        int idFirstButton, /* id первой радио-кнопки в группе */
        int idLastButton,  /* id последней радио-кнопки в группе */
        int idCheckButton  /* id отмечаемой радио-кнопки */
    )

    Автоматические радио-кнопки должны быть объединены в группу
    при помощи стиля WS_GROUP, чтобы Windows корректно их
    обрабатывала.
    Проверить состояние кнопки можно, послав ей сообщение BM_GETCHECK
    (wp=0; lp=0) или вызовом функции:

    UINT WINAPI IsDlgButtonChecked(HWND hDlg, int idButton)

    При щелчке мыши по кнопке она присылает родительскому диалогу сообщение-оповещение
    WM_COMMAND (HIWORD(wp)=BN_CLICKED; LOWORD(wp)=(int)idButton;
    lp=(HWND)hwndButton
    ).

    «EDIT»
    Поле редактирования предназначено для ввода пользователем текста
    с клавиатуры. Щелчком мыши внутри элемента пользователь передает
    этому элементу фокус ввода (input focus).
    При этом внутри элемента появляется текстовый курсор — мигающая
    каретка. Пользователь может использовать мышь для перемещения
    каретки по полю редактирования и выделению текста в этом поле. Примеры:

    Соответствующее описание ресурсов:

     /* По умолчанию эти элементы создаются вообще без рамки, поэтому добавлено WS_BORDER */
     CONTROL "" 4, "EDIT", ES_MULTILINE|ES_WANTRETURN|WS_BORDER 5, 45, 60, 35
    
     CONTROL "text", 5, "EDIT", ES_LEFT|WS_BORDER, 70, 45, 30, 10
     CONTROL "text", 6, "EDIT", ES_CENTER|ES_PASSWORD|WS_BORDER, 70, 57, 30, 10
     CONTROL "text", 7, "EDIT", ES_RIGHT|ES_READONLY|WS_BORDER, 70, 69, 30, 10

    Стиль ES_WANTRETURN означает, что кнопка [Enter] будет
    обрабатываться самим элементом, а не передаваться диалогу. Благодаря
    этому стилю оказался возможен переход на новую строчку для предложения
    «Она съела кусок…» (на картинке).
    По умолчанию текстовые поля позволяют вводить столько текста,
    сколько может отобразиться в рамках поля. Чтобы предоставить
    пользователю возможность ввести больше текста, надо использовать
    стиль ES_AUTOHSCROLLES_AUTOVSCROLL
    для многострочных полей).
    Для текстовых полей существует более простой оператор объявления ресурсов:

     EDITTEXT id, x, y, width, height [, style]

    Чтобы поменять содержимое текстового поля, программа вызывает
    функцию SetDlgItemText. Чтобы получить текущее
    содержимое текстового поля, используется функция:

    UINT WINAPI GetDlgItemText(
        HWND hDlg,       /* дескриптор родительского диалога */
        int idControl,   /* идентификатор поля */
        LPSTR lpString,  /* буфер под текст */
        int nMaxCount    /* размер буфера */
    )

    Чтобы узнать размер строки в текстовом поле, надо послать элементу
    сообщение WM_GETTEXTLENGTH (wp=0; lp=0).
    Текстовое поле посылает родительскому диалогу следующие сообщения-оповещения
    WM_COMMAND (LOWORD(wp)=(int)idContol; lp=(HWND)hwndEditCtrl):

    • HIWORD(wp)=EN_KILLFOCUS — текстовое поле потеряло фокус
      (фокус передан другому элементу диалога);
    • HIWORD(wp)=EN_SETFOCUS — текстовое поле получило фокус;
    • HIWORD(wp)=EN_CHANGE — пользователь изменил текст в поле;
    • HIWORD(wp)=EN_ERRSPACE — закончилось место, отведенное под
      текстовый буфер управляющего элемента.
    «LISTBOX»
    Окно-список используется для отображения списка имен
    (например, имен файлов). Пользователь может, просматривая список,
    выделить один или несколько элементов щелчком мыши. При выделении
    того или иного элемента списка, он подсвечивается, а родительскому
    окну посылается сообщение-оповещение. Для очень больших списков
    могут использоваться полосы прокрутки. Примеры:

    Соответствующее описание ресурсов:

     CONTROL "" 3, "LISTBOX", LBS_MULTIPLESEL|WS_BORDER|WS_VSCROLL, 5, 45, 60, 35
     CONTROL "" 4, "LISTBOX", LBS_MULTICOLUMN|WS_BORDER|WS_HSCROLL, 70, 45, 60, 35
     CONTROL "" 5, "LISTBOX", LBS_SORT|LBS_NOSEL|WS_BORDER, 135, 45, 60, 35

    или в короткой форме:

     LISTBOX 3, 5, 45, 60, 35, LBS_MULTIPLESEL|WS_BORDER|WS_VSCROLL
     LISTBOX 4, 70, 45, 60, 35, LBS_MULTICOLUMN|WS_BORDER|WS_HSCROLL
     LISTBOX 5, 135, 45, 60, 35, LBS_SORT|LBS_NOSEL|WS_BORDER

    Для добавления элемента к списку следует послать ему сообщение
    LB_ADDSTRING (wp=0; lp=(LPARAM)(LPCSTR)lpsz
    строка для добавления). Для того, чтобы заполнить один из списков,
    показанных на рисунке, в обработчик сообщения WM_INITDIALOG
    в диалоговую процедуру был вставлен такой фрагмент:

       char *a[12]={
        "jan","feb","mar","apr","may","jun",
        "jul","aug","sep","oct","nov","dec"};
       for (int i=0; i<12; i++) {
        SendDlgItemMessage(hw,3,LB_ADDSTRING,0,(LPARAM)a[i]);
       }

    Кроме этого, список «понимает» следующие сообщения:

    • LB_DELETESTRING (wp=(WPARAM)index; lp=0) —
      удалить элемент с указанным номером;
    • LB_INSERTSTRING (wp=(WPARAM)index; lp=(LPARAM)(LPCSTR)lpsz) —
      вставить указанную строку в список как элемент с индексом index;
    • LB_FINDSTRING (wp=(WPARAM)indexStart; lp=(LPARAM)(LPTSTR)lpszFind) —
      найти элемент, содержащий указанную строку (поиск ведется, начиная
      с элемента indexStart), результат сообщения — номер элемента,
      удовлетворяющего критерию, или LB_ERR;
    • LB_GETCOUNT (wp=0; lp=0) — количество элементов в списке;
    • LB_GETCURSEL (wp=0; lp=0) — выделенный элемент в списке;
    • LB_RESETCONTENT (wp=0; lp=0) — удалить все элементы из списка.

    Окно-список посылает родительскому диалогу следующие сообщения-оповещения
    WM_COMMAND (LOWORD(wp)=(int)idContol; lp=(HWND)hwndListBox):

    • HIWORD(wp)=LBN_DBLCLK — пользователь дважды щелкнул мышью по списку;
    • HIWORD(wp)=LBN_SELCHANGE — пользователь выделил другой элемент
      в списке (или отменил выделение).
    «COMBOBOX»
    Комбобокс — это помесь поля редактирования с окном-списком.
    Этот элемент содержит поле редактирование и список, который может
    отображаться все время либо «выпадать» при нажатии на кнопку рядом
    с полем редактирования. Есть три основных типа комбобоксов:

    • «выпадающий» комбобокс (CBS_DROPDOWN) содержит поле
      редактирования и «выпадающий» список;
    • «выпадающий» список (CBS_DROPDOWNLIST) не содержит
      поля для изменения текста;
    • простой комбобокс (CBS_SIMPLE) содержит поле
      редактирования и обычный список.


    Обратите внимание, что в ресурсах значение высоты элемента определяет
    размер поля редактирования вместе с «выпавшим» списком по вертикали.

     CONTROL "" 3, "COMBOBOX", CBS_DROPDOWN|CBS_AUTOHSCROLL|WS_VSCROLL, 5, 45, 60, 70
     CONTROL "" 4, "COMBOBOX", CBS_DROPDOWNLIST|WS_VSCROLL, 70, 45, 60, 70
     CONTROL "" 5, "COMBOBOX", CBS_SIMPLE|CBS_SORT, 135, 45, 60, 70

    Короткий вариант этого же объявления ресурсов:

     COMBOBOX 3, 5, 45, 60, 70, CBS_DROPDOWN|CBS_AUTOHSCROLL|WS_VSCROLL
     COMBOBOX 4, 70, 45, 60, 70, CBS_DROPDOWNLIST|WS_VSCROLL
     COMBOBOX 5, 135, 45, 60, 70, CBS_SIMPLE|CBS_SORT

    Для работы с комбобоксами существуют сообщения, аналогичные списковым:
    CB_ADDSTRING, CB_DELETESTRING, CB_INSERTSTRING,
    CB_FINDSTRING, CB_GETCOUNT, CB_GETCURSEL,
    CB_RESETCONTENT.
    Комбобокс посылает родительскому диалогу сообщение оповещение WM_COMMAND
    со следующими кодами оповещения:

    • CBN_SELCHANGE, когда пользователь выделяет другую строку
      в комбобоксе (бывает полезным для простых комбобоксов);
    • CBN_SELENDOK, когда пользователь выбрал элемент в выпадающем
      списке и щелкнул мышкой по выделению (подтвердил выделение), для
      простых комбобоксов посылается перед каждым CBN_SELCHANGE;
    • CBN_SELENDCANCEL, когда пользователь закрыл выпадающий список,
      так и не выбрав никакой элемент;
    • CBN_DROPDOWN, когда открывается выпадающий список;
    • CBN_CLOSEUP, когда выпадающий список был закрыт по той или
      иной причине.

    Кроме предопределенных управляющих элементов, Windows предоставляет еще
    набор стандартных управляющих элементов посредством библиотеки
    Common Controls Library (COMCTL32.DLL). Чтобы воспользоваться ей,
    в тест программы надо включить заголовочный файл commctrl.h
    и добавить в блок инициализации программы вызов функции:

    void WINAPI InitCommonControls(void)

    Управляющие элементы из этой библиотеки, как правило, посылают
    сообщения-оповещения родительскому диалогу через сообщение WM_NOTIFY
    (wp=(int)idControl; lp=(LPARAM)(NMHDR*)pmnh — указатель на структуру
    со специльными параметрами сообщения-оповещения).

    Классы управляющих элементов из Common Controls Library:

    List View Controls (WC_LISTVIEW)
    Элемент просмотра списков — это окно отображающее совокупность
    элементов. Каждый элемент может быть представлен текстовой меткой и
    (необязательно) иконкой. Типичный пример использования этого элемента —
    программа «Проводник». Содержимое того или иного каталога представляется
    в виде элемента просмотра списков. Есть четыре основных стиля для этого
    элемента:

    • крупные иконки — стиль LVS_ICON;
    • мелкие иконки — стиль LVS_SMALLICON;
    • список — стиль LVS_LIST;
    • таблица — стиль LVS_REPORT.

     CONTROL "",3,WC_LISTVIEW,LVS_REPORT|WS_BORDER, 5, 45, 60, 70
     CONTROL "",4,WC_LISTVIEW,LVS_LIST|WS_BORDER, 70, 45, 120, 70
     CONTROL "",5,WC_LISTVIEW,LVS_ICON|WS_BORDER, 200, 45, 120, 70

    Приведенные в примере списки заполнялись в диалоговой
    процедуре при инициализации диалога (WM_INITDIALOG):

       /* Создание колонок для 1го списка */
       LV_COLUMN lc; lc.mask=LVCF_FMT|LVCF_TEXT|LVCF_SUBITEM|LVCF_WIDTH;
       lc.fmt=LVCFMT_LEFT;
       lc.pszText="Col1"; lc.iSubItem=0; lc.cx=40;
       SendDlgItemMessage(hw,3,LVM_INSERTCOLUMN,0,(LPARAM)&lc);
       lc.pszText="Col2"; lc.iSubItem=1; lc.cx=40;
       SendDlgItemMessage(hw,3,LVM_INSERTCOLUMN,1,(LPARAM)&lc);
    
       /* Создание списка иконок для 2го и 3го списков */
       HIMAGELIST himl1,himl2;
       himl1=ImageList_Create(16,16,ILC_MASK,1,0);   /* список маленьких иконок */
       ImageList_AddIcon(himl1,LoadIcon(h,"Ex4_Icon"));
       himl2=ImageList_Create(32,32,ILC_MASK,1,0);   /* список больших иконок */
       ImageList_AddIcon(himl2,LoadIcon(h,"Ex4_Icon"));
       SendDlgItemMessage(hw,4,LVM_SETIMAGELIST,LVSIL_SMALL,(LPARAM)himl1);
       SendDlgItemMessage(hw,5,LVM_SETIMAGELIST,LVSIL_NORMAL,(LPARAM)himl2);
    
       /* Заполнение списков */
       LV_ITEM li; li.mask=LVIF_TEXT|LVIF_IMAGE; 
       li.iImage=0; /* номер иконки в списке */
       for (int i=0; i<12; i++) {
        li.iItem=i;
        li.iSubItem=0; li.pszText=a[i];
        SendDlgItemMessage(hw,3,LVM_INSERTITEM,0,(LPARAM)&li);
        SendDlgItemMessage(hw,4,LVM_INSERTITEM,0,(LPARAM)&li);
        SendDlgItemMessage(hw,5,LVM_INSERTITEM,0,(LPARAM)&li);
        wsprintf(str,"%d",i); /* вторая колонка для 1го списка */
        li.iSubItem=1; li.pszText=str;
        SendDlgItemMessage(hw,3,LVM_SETITEM,0,(LPARAM)&li);
       }
    
    Status Windows (STATUSCLASSNAME)
    Поле статуса — это горизонтальное окно в нижней части
    родительского окна, которое программа обычно использует для отображения
    каких-либо характеристик, параметров или небольших текстовых сообщений.

    В примере 4б можно заменить статическое текстовое поле
    на поле статуса:

     Ex4_Dlg DIALOG 50,50,70,40
      STYLE WS_POPUP|WS_CAPTION|WS_BORDER
      CAPTION "MyDlg"
      FONT 10, "Arial"
     {
      CONTROL "OK", 2, "BUTTON", BS_DEFPUSHBUTTON, 5, 10, 60, 12
      CONTROL "Status text",1,STATUSCLASSNAME, 0, 0, 0, 0, 0
     }

    При создании поля статуса не требуется указывать ни размер, ни позицию
    окна, Windows сама выберет эти параметры подходящим образом.
    Для создания поля статуса можно использовать специальную функцию:

      HWND WINAPI CreateStatusWindow(
        LONG style,       /* стиль: обязательно указываем WS_CHILD|WS_VISIBLE */
        LPCTSTR lpszText, /* текст поля статуса */
        HWND hwndParent,  /* родительское окно */
        UINT wID          /* числовой id поля статуса */
      )

    С помощью сообщения SB_SETPARTS поле статуса можно разбить
    на части (wp=(int)nParts — количество частей;
    lp=(LPARAM)(int*)widths — указатель на массив размеров частей).
    В таком случае текст для каждой части поля статуса задается сообщением
    SB_SETTEXT (wp=(int)iPart — номер части;
    lp=(LPARAM)(LPSTR)lpszText — текстовая строка). Пример:

     CreateStatusWindow(WS_CHILD|WS_VISIBLE|SBARS_SIZEGRIP,"text",hw,100);
     int widths[5]={100,150,200,-1};
     SendDlgItemMessage(hw,100,SB_SETPARTS,4,(LPARAM)widths);
     SendDlgItemMessage(hw,100,SB_SETTEXT,2,(LPARAM)"part 2");
     SendDlgItemMessage(hw,100,SB_SETTEXT,3,(LPARAM)"last part");

    Up-Down Controls (UPDOWN_CLASS)
    Управляющий элемент «up-down» представляет собой пару небольших
    кнопок-стрелок, нажимая которые, пользователь увеличивает или уменьшает
    значение. Этот элемент, как правило, связывается с элементом-компаньоном
    (buddy window), обычно реализованным в виде поля редактирования.
    Для пользователя элемент «up-down» и его компаньон представляются единым
    управляющим элементом.

     CONTROL "0", 5, "EDIT", ES_LEFT|WS_BORDER, 5, 30, 30, 10
     CONTROL "", 6, UPDOWN_CLASS, UDS_AUTOBUDDY|UDS_SETBUDDYINT, 35, 30, 10, 10

    Если при создании элемента «up-down» указать стиль UDS_AUTOBUDDY,
    то компаньоном будет назначен предыдущий управляющий элемент диалога.
    Программа может также передать дескриптор окна-компаньона при помощи
    сообщения UDM_SETBUDDY (wp=(WPARAM)(HWND)hwndBuddy
    дескриптор окна-компаньона; lp=0). Если элементу «up-down»
    назначить стиль UDS_SETBUDDYINT, то он будет автоматически
    менять текст окна-компаньона, представляющий числовое значение.
    Другой способ создать элемент «up-down» — использовать функцию

    HWND WINAPI CreateUpDownControl(
      DWORD dwStyle,   /* стиль элемента */
      int x, int y,    /* позиция */	
      int cx, int cy,  /* размеры */
      HWND hParent,    /* дескриптор родительского окна */
      int ID,          /* id элемента */
      HINSTANCE hInst, /* дескриптор экземпляра программы */
      HWND hBuddy,     /* дескриптор окна-компаньона */
      int nUpper,      /* максимальное значение */
      int nLower,      /* минимальное значение */
      int nPos         /* текущее значение */
    )
    Progress Bars (PROGRESS_CLASS)
    Полоса прогресса — это окно, которое программа может использовать
    для индикации состояния выполнения какой-либо длительной операции.
    Окно представляет собой прямоугольник, заполняемый системным цветом
    слева направо.

       CONTROL "",3,PROGRESS_CLASS,WS_BORDER, 5, 45, 100, 10

    Каждый раз, когда приложение посылает этому окну сообщение
    PBM_STEPIT (wp=0; lp=0), заполнение полосы прогресса
    продвигается дальше вправо на некоторое значение.

    Tooltip Controls (TOOLTIPS_CLASS)
    Окно-подсказка — всплывающее окно, содержащее строку описательной
    информации о том или ином элементе интерфейса программы. Таким элементом
    интерфейса может быть конкретное окно (управляющий элемент) или прямоугольный
    участок клиентской области какого-либо окна. Большую часть времени
    подсказка скрыта. Она появляется, когда пользователь задерживат курсор
    мыши над тем или иным элементом интерфейса программы более, чем на полсекунды.
    Подсказка скрывается, когда пользователь кликает мышью или уводит курсор
    с этого элемента. Одно окно-подсказка может обслуживать любое количество
    элементов интерфейса. Чтобы назначить тому или иному элементу интерфейса
    программы подсказку, надо окну-подсказке послать сообщение TTM_ADDTOOL
    (wp=0; lp=(LPARAM)(TOOLINFO*)lpti — указатель на структуру,
    содержащую информацию об элементе). Пример:

      TOOLINFO ti;
      HWND hwTooltip=CreateWindow(TOOLTIPS_CLASS,"",TTS_ALWAYSTIP,
       CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL,NULL,h,NULL);
      ti.cbSize=sizeof(TOOLINFO);
      ti.uFlags=TTF_SUBCLASS|TTF_IDISHWND;
      ti.hwnd=hMainWnd;
      ti.uId=(UINT)GetDlgItem(hMainWnd,ID_MYBUTTON);
      ti.hinst=h;
      ti.lpszText="Tooltip for my button";
      SendMessage(hwTooltip,TTM_ADDTOOL,0,(LPARAM)&ti);

    Property Sheets & Tab Controls
    Элементы вкладки свойств и переключатели вкладок
    обычно используются совместно. Пример использования вкладок:

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

     /* Шаблон вкладки "Общие" */
     ODTab_General DIALOG 0,0,180,150
      CAPTION "Общие"
      STYLE WS_CHILD|WS_VISIBLE
      FONT 8,"Arial"
     {
      GROUPBOX "Интерфейс",-1,5,5,170,40
       PUSHBUTTON "Шрифт температуры",IDC_FONT,20,15,140,12
       AUTOCHECKBOX "На полный экран",IDC_FULLSCR,20,30,140,10,WS_DISABLED
      GROUPBOX "Алгоритм",-1,5,50,170,80
       LTEXT "Приоритет:",-1,15,65,50,10
       CTEXT "priority",IDC_PRIOTEXT,70,56,85,8
       CONTROL "",IDC_PRIOTRACK,TRACKBAR_CLASS,TBS_TOP|TBS_AUTOTICKS,70,64,85,17
       LTEXT "Период опроса датчика:",-1,15,85,110,10
       EDITTEXT IDC_TRATE,125,85,25,10,ES_RIGHT
       LTEXT "мс",-1,152,85,10,10
       LTEXT "Период опроса формирователя:",-1,15,100,110,10
       EDITTEXT IDC_FRATE,125,100,25,10,ES_RIGHT
       LTEXT "мс",-1,152,100,10,10
       LTEXT "Тайм-аут:",-1,15,115,110,10
       EDITTEXT IDC_TIMEOUT,125,115,25,10,ES_RIGHT
       LTEXT "мс",-1,152,115,10,10
     }
     /* Шаблон вкладки "Датчик" */
     ODTab_Sensor DIALOG 0,0,180,150
      CAPTION "Датчик"
      STYLE WS_CHILD|WS_VISIBLE
      FONT 8,"Arial"
     {
      /* ... */
     }
     /* Шаблон вкладки "Порты" */
     ODTab_Ports DIALOG 0,0,200,150
      CAPTION "Порты"
      STYLE WS_CHILD|WS_VISIBLE
      FONT 8,"Arial"
     {
      /* ... */
     }

    Для создания диалога с вкладками используется функция PropertySheet,
    перед вызовом которой надо заполнить соответствующие системные структуры:

     char *TabTemplts[NumTabs]={"ODTab_General","ODTab_Sensor","ODTab_Ports"};
     PROPSHEETPAGE psp[NumTabs]; /* заполняется для каждой вкладки */
     for (i=0; i<NumTabs; i++) {
      psp[i].dwSize=sizeof(PROPSHEETPAGE);
      psp[i].dwFlags=PSP_DEFAULT;
      psp[i].hInstance=hThisInstance;
      psp[i].pszTemplate=TabTemplts[i];
      psp[i].pfnDlgProc=(DLGPROC)GlobDlgProc;
      psp[i].pfnCallback=NULL;
     }
     PROPSHEETHEADER psh;        /* описывает весь диалог */
     psh.dwSize=sizeof(PROPSHEETHEADER);
     psh.dwFlags=PSH_NOAPPLYNOW|PSH_PROPSHEETPAGE;
     psh.hwndParent=hWnd;
     psh.hInstance=hThisInstance;
     psh.pszCaption="Настройки";
     psh.nPages=NumTabs;
     psh.nStartPage=0;
     psh.ppsp=(LPCPROPSHEETPAGE)&psp;
     psh.pfnCallback=NULL;
     PropertySheet(&psh);

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

    Trackbars (TRACKBAR_CLASS)
    Ползунок (бегунок) используется, если от пользователя
    требуется получить дискретное значение из определенного диапазона.
    Маркер ползунка пермещается на заданное программой значение.
    Пример ползунка показан на первой вкладке предыдущего примера.
    Ползунки бывают горизонтальные (TBS_HORZ) или вертикальные
    (TBS_VERT). Диапазон значений ползунка задается сообщением
    TBM_SETRANGE (wp=(BOOL)fRedraw — перерисовать маркер
    после изменения диапазона; lp=MAKELONG(lMinimum,lMaximum)
    диапазон значений: младшее слово — минимальное значение,
    старшее слово — максимальное значение). Переместить ползунок можно
    при помощи сообщения TBM_SETPOS (wp=TRUE;
    lp=(LONG)position — новая позиция ползунка). Чтобы получить
    текущее значение ползунка, следует послать ему сообщение TBM_GETPOS
    (wp=0; lp=0). Ползунок оповещает родительское окно о событиях
    через сообщение WM_HSCROLL (LOWORD(wp)=ScrollCode
    код события; HIWORD(wp)=posistion — позиция маркера ползунка;
    lp=(HWND)hwndTrackBar — дескриптор элемента).

    Toolbars (TOOLBARCLASSNAME)
    Панель инструментов — это окно, содержащее набор кнопок,
    посылающих командное сообщение родительскому окну, когда пользователь
    щелкает по ним. Как правило, кнопки на панели инструментов соответствуют
    часто используемым командам меню приложения. Панель инструментов
    располагается ниже строки меню.

    Информация о кнопках передается панели инструментов через структуру
    TBBUTTON, а для создания окна панели инструментов удобно
    использовать функцию CreateToolbarEx.

       TBBUTTON tbb[nButtons];
       /* 1я кнопка */
       tbb[0].iBitmap=STD_PROPERTIES;   /*id иконки*/
       tbb[0].idCommand=ID_OPTION;      /*id команды*/
       tbb[0].fsState=TBSTATE_ENABLED;  /*состояние*/
       tbb[0].fsStyle=TBSTYLE_BUTTON;   /*стиль*/
       tbb[0].iString=0;                /*подпись под кнопкой*/
       /* 2я кнопка */
       tbb[1].iBitmap=STD_REDOW;
       tbb[1].idCommand=ID_CALCOUT;
       tbb[1].fsState=TBSTATE_ENABLED;
       tbb[1].fsStyle=TBSTYLE_BUTTON; 
       tbb[1].iString=0;
       /* 3я кнопка */
       tbb[2].iBitmap=STD_UNDO;
       tbb[2].idCommand=ID_CALCIN;
       tbb[2].fsState=TBSTATE_ENABLED;
       tbb[2].fsStyle=TBSTYLE_BUTTON;
       tbb[2].iString=0;
       /* разделитель */
       tbb[3].fsStyle=TBSTYLE_SEP;
       /* и т.д. */
       HWND htb=CreateToolbarEx(
        (HWND) hWndMain,                /*дескриптор родительского окна*/
        (DWORD) WS_CHILD|WS_VISIBLE|TBSTYLE_TOOLTIPS, /*стиль*/
        (UINT) ID_TOOLBAR,              /*id окна*/
        (int) 15,                       /*количество иконок в указанном ресурсе*/
        (HINSTANCE) HINST_COMMCTRL,     /*дескриптор модуля, из которого берется ресурс с иконками*/
        (UINT) IDB_STD_SMALL_COLOR,     /*id ресурса с иконками*/
        (TBBUTTON*) tbb,                /*указатель на массив с информацией о кнопках*/
        (int) nButtons,                 /*количество кнопок на панели*/
        16,16,                          /*размеры кнопок*/
        15,15,                          /*размеры иконок для кнопок*/
        sizeof(TBBUTTON)                /*размер структуры TBBUTTON*/
       );
    Rich Edit Controls (RICHEDIT_CLASS)
    Продвинутое поле редактирования является развитием
    класса «EDIT» стандартных управляющих элементов. Элементы управления
    этого класса поддерживают форматирование текста (по отдельным символам и
    по отдельным абзацам) и позволяют внедрять OLE-объекты.

    Tree View Controls (WC_TREEVIEW)
    Элемент просмотра дерева позволяет представлять информацию
    об иерархии некоторых объектов (содержание документа, дерево каталогов
    файловой системы и т.п.) Каждый объект может быть представлен текстовой
    меткой и иконкой. Объект может иметь иерархию дочерних объектов,
    которая раскрывается по щелчку на этом элементе.

  • Стандартные диалоги
  • Windows предоставляет набор готовых стандартных диалогов посредством
    библиотеки Common Dialog Boxes Library (COMDLG32.DLL): диалог открытия
    и сохранения файла, диалог печати документа, диалог выбора цвета, шрифта и т.п.
    Чтобы создать один из перечисленных диалогов, надо заполнить определенную
    структуру и вызвать соответствующую функцию из этой библиотеки:

    • BOOL WINAPI ChooseColor(CHOOSECOLOR* lpcc)
      — создает диалог, отображающий палитру цветов и позволяющий
      пользователю выбрать тот или иной цвет или создать свой.
    • BOOL WINAPI ChooseFont(CHOOSEFONT* lpcf)
      — создает диалог, отображающий имена установленных в системе шрифтов,
      их кегль, стиль начертания и т.п.
    • BOOL WINAPI GetOpenFileName(OPENFILENAME* lpofn)
      и BOOL WINAPI GetSaveFileNAme(OPENFILENAME* lpofn)
      — создают диалог, отображающий содержимое того или иного каталога,
      и позвояющий пользователю выбрать уникальное имя файла для открытия или
      сохранения.
    • BOOL WINAPI PrintDlg(PRINTDLG* lppd)
      — создает диалог, позволяющий пользователю установить различные
      опции печати, например, диапазон страниц, количество копий и др.
    • BOOL WINAPI PageStupDlg(PAGESETUPDLG* lppsd)
      — создает диалог, позволяющий пользователю выбрать различные
      параметры страницы: ориентацию, поля, размер бумаги и т.п.
    • HWND WINAPI FindText(FINDREPLACE* lpfr)
      — создает диалог, позволяющий пользователю ввести строку для поиска
      и такие опции, как направление поиска.
    • HWND WINAPI ReplaceText(FINDREPLACE* lpfr)
      — создает диалог, позволяющий пользователю ввести строку для поиска,
      строку для замены и опции замены (направление поиска, область поиска).

    Все перечисленные диалоги, кроме последних двух, — модальные,
    т.е. указанная функция не вернет значение, пока пользователь тем или
    иным способом не закроет диалог. Значение TRUE означает,
    что пользователь закрыл диалог, нажав на «ОК», а соответствующая структура
    заполнена новыми значениями. Значение FALSE означает,
    что пользователь нажал на [Esc], выбрал команду системного меню «Закрыть»
    или нажал кнопку «Отмена», а соответствующая структура осталась неизменной.
    Пример использования диалога открытия файла:

      char filename[MAX_PATH]="";             /*буфер под имя файла*/
      OPENFILENAME of;
      of.lStructSize=OPENFILENAME_SIZE_VERSION_400A; /*размер структуры OPENFILENAME*/
      of.hwndOwner=hw;                        /*дескриптор родительского окна*/
      of.hInstance=h;                         /*дескриптор экземпляра программы*/
      of.lpstrFilter="All files (*.*)*.*";/*фильтр файлов (тип)*/
      of.lpstrCustomFilter=NULL;              /*еще один фильтр: нам не надо*/
      of.nMaxCustFilter=0;                    /*нам не надо*/
      of.nFilterIndex=1;                      /*количество заданных нами фильтров*/
      of.lpstrFile=filename;                  /*адрес буфера под имя файла*/
      of.nMaxFile=MAX_PATH;                   /*размер буфера под имя файла*/
      of.lpstrFileTitle=NULL;                 /*буфер под рекомендуемый заголовок: нам не надо*/
      of.nMaxFileTitle=0;                     /*нам не надо*/
      of.lpstrInitialDir=NULL;                /*стартовый каталог: текущий*/
      of.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY; /*разные флаги*/
      if (GetOpenFileName(&of)) {
       /* действия в случае успешного выбора файла */
      }
    

  • Немодальный диалог в качестве главного окна
  • Ресурсы значительно облегчают работу программиста по созданию интерфейса
    программы, поэтому весьма привлекательно использовать ресурсы для описания
    главного окна. Это возможно в случае, если главное окно реализовано в виде
    немодального диалога.

    Пример 5 демонстрирует использование немодального диалога
    в приложении типа «блокнот».

    Файл example5.h содержит константы-идентификаторы команд меню
    и элементов диалога.

    #define IDC_OPEN	10
    #define IDC_SAVE	11
    #define IDC_SAVEAS	12
    #define IDC_EXIT	13
    #define IDC_ABOUT	14
    
    #define ID_EDIT		20
    #define ID_STATUS	21

    Файл example5.rc описывает ресурсы программы: иконку, меню и шаблон диалога.

    #include "example5.h"
    
    Ex5_Icon ICON "myicon.ico"
    
    Ex5_Menu MENU
    {
     POPUP "&File"
     {
      MENUITEM "&Open...", IDC_OPEN
      MENUITEM "&Save", IDC_SAVE
      MENUITEM "Save &As...", IDC_SAVEAS
      MENUITEM SEPARATOR
      MENUITEM "&ExittAlt-F4", IDC_EXIT
     }
     POPUP "&Help"
     {
      MENUITEM "&About...", IDC_ABOUT
     }
    }
    
    Ex5_Dlg DIALOG 50,50,300,200
     STYLE WS_OVERLAPPED|WS_CAPTION|WS_BORDER|WS_SYSMENU|WS_VISIBLE
     MENU "Ex5_Menu"
     CAPTION "Example 5"
     FONT 10, "Arial"
    {
     EDITTEXT ID_EDIT, 5, 5, 290, 180, ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL
     CONTROL "", ID_STATUS, STATUSCLASSNAME, 0, 0, 0, 0, 0
    }

    Файл example5.cpp — текст программы.

    #include <windows.h>
    #include <commctrl.h>
    #include <stdlib.h>
    #include "example5.h"
    
    BOOL CALLBACK MainProc(HWND,UINT,WPARAM,LPARAM);
    
    HINSTANCE hThisInstance;
    char filename[MAX_PATH]=""; /*буфер имени файла*/ 
    
    int WINAPI WinMain(HINSTANCE hInst,HINSTANCE,LPSTR,int) {
     hThisInstance=hInst;
     InitCommonControls();
     HWND hMainWnd=CreateDialog(hInst,"Ex5_Dlg",NULL,(DLGPROC)MainProc);
     if (!hMainWnd) return FALSE;
     MSG msg;                   /*цикл обработки событий*/
     while (GetMessage(&msg,NULL,0,0)) {
      if (!IsDialogMessage(hMainWnd,&msg)) {
       TranslateMessage(&msg); 
       DispatchMessage(&msg); 
      }
     }
     return msg.wParam; 
    }
    
    /* диалоговая процедура */
    BOOL CALLBACK MainProc(HWND hw,UINT msg,WPARAM wp,LPARAM lp) {
     static DWORD OldIcon=0;    /* id старой иконки диалога */
     static OPENFILENAME of;
     char* buf;
     HANDLE hf; 
     DWORD len,len1;
     switch (msg) {
      case WM_INITDIALOG:       /* меняем иконку диалога */
       OldIcon=SetClassLong(hw,GCL_HICON,(long)LoadIcon(hThisInstance,"Ex5_Icon"));
       return TRUE;
      case WM_COMMAND:
       switch (LOWORD(wp)) {
        case IDCANCEL:          /* посылается при закрытии диалога по [Esc]*/
        case IDC_EXIT:          /* команда меню "Exit" */
         DestroyWindow(hw);
         break;
        case IDC_OPEN:          /* команда меню "Open" */
         of.lStructSize=OPENFILENAME_SIZE_VERSION_400A;
         of.hwndOwner=hw;
         of.lpstrFilter="All files (*.*)*.*";
         of.lpstrCustomFilter=NULL; of.nMaxCustFilter=0;
         of.nFilterIndex=1;
         of.lpstrFile=filename; of.nMaxFile=MAX_PATH;
         of.lpstrFileTitle=NULL; of.nMaxFileTitle=0;
         of.lpstrInitialDir=NULL;
         of.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY;
         if (!GetOpenFileName(&of)) break;
         SetDlgItemText(hw,ID_STATUS,filename);
         /* открываем файл */
         hf=CreateFile(filename,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
         if (hf==INVALID_HANDLE_VALUE) {
          MessageBox(hw,"Open failed","Error",MB_ICONHAND|MB_OK);
          break;
         }
         len=GetFileSize(hf,NULL);
         buf=(char*)malloc(len+1); /* доп. байт под символ-терминатор (0) */
         if (!buf) {
          MessageBox(hw,"Mem alloc failed","Error",MB_ICONHAND|MB_OK);
          break;
         }
         ReadFile(hf,buf,len,&len1,NULL);
         buf[len1]=0;
         CloseHandle(hf);
         SetDlgItemText(hw,ID_EDIT,buf);
         free(buf);
         break;
        case IDC_SAVEAS:        /* команда меню "Save As" */
         of.lStructSize=OPENFILENAME_SIZE_VERSION_400A;
         of.hwndOwner=hw;
         of.lpstrFilter="All files (*.*)*.*";
         of.lpstrCustomFilter=NULL; of.nMaxCustFilter=0;
         of.nFilterIndex=1;
         of.lpstrFile=filename; of.nMaxFile=MAX_PATH;
         of.lpstrFileTitle=NULL; of.nMaxFileTitle=0;
         of.lpstrInitialDir=NULL;
         of.Flags=OFN_PATHMUSTEXIST|OFN_OVERWRITEPROMPT|OFN_HIDEREADONLY;
         if (!GetSaveFileName(&of)) break;
        case IDC_SAVE:          /* команда меню "Save" */
         if (lstrlen(filename)==0) {
          /* для нового файла - вызываем диалог "Save As" */
          PostMessage(hw,WM_COMMAND,IDC_SAVEAS,lp);
          break;
         }
         SetDlgItemText(hw,ID_STATUS,filename);
         /* сохраняем файл */
         hf=CreateFile(filename,GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
         if (hf==INVALID_HANDLE_VALUE) {
          MessageBox(hw,"Save failed","Error",MB_ICONHAND|MB_OK);
          break;
         }
         len=SendDlgItemMessage(hw,ID_EDIT,WM_GETTEXTLENGTH,0,0);
         buf=(char*)malloc(len+1); /* доп. байт под символ-терминатор (0) */
         GetDlgItemText(hw,ID_EDIT,buf,len+1);
         if (!buf) {
          MessageBox(hw,"Mem alloc failed","Error",MB_ICONHAND|MB_OK);
          break;
         }
         WriteFile(hf,buf,len,&len1,NULL);
         CloseHandle(hf);
         free(buf);
         break;
        case IDC_ABOUT:         /* команда меню "About" */
         MessageBox(hw,"Example N5 from http://dims.karelia.ru/win32","About",MB_OK|MB_ICONINFORMATION);
         break;
       }
       return TRUE;
      case WM_DESTROY:          /* при закрытии окна восстанавливаем старую иконку */
       SetClassLong(hw,GCL_HICON,(long)OldIcon);
       PostQuitMessage(0);
       return TRUE;
     }
     return FALSE;
    }

    Целью данной части главы будет закрепление навыков работы с оконными приложениями. Мы рассмотрим на примере, как создаются элементы меню, панели инструментов, увидим, как создаются и вызываются дополнительные диалоговые окна, как работать с окнами выбора файлов. Также мы познакомимся еще с несколькими элементами управления и научимся динамически загружать изображения в форму.

    Важные темы, рассматриваемые в данной части главы:

    1. Как создать меню, добавить в него новые элементы, C#.NET.
    2. Как создать панель управления в C# .NET.
    3. Как вызвать окно выбора файла в C# .NET.
    4. Как загрузить изображение в C# .NET.
    5. Как создать и вызвать дополнительную форму

    Цели обозначены, так что приступим.

    Первоначально, создайте новый проект, указав в качестве шаблона Windows Form Application и назовите second_application. Затем нажмите OK.

    Добавление меню в приложение C# .NET

    Здесь все предельно просто: чтобы добавить меню в приложение, сначала откройте окно Toolbox (Панель элементов).
    Нам потребуется разворот Menus & Toolbars (Меню и панели инструментов). В нем нас интересует элемент MenuStrip (рис. 1).
    Уроки OpenGL + C#: Выбор элемента для созданию меню Рисунок 1. Выбор элемента для созданию меню.

    Зажмите его левой клавишей мыши и перетащите на форму. На нашей форме отразиться элемент меню, как показано на рисунке 2.
    Уроки OpenGL + C#: Создание элементов меню Рисунок 2. Создание элементов меню.

    Теперь, щелкнув на строке «введите здесь» вы можете ввести название меню: назовем его «File». При этом добавятся 2 дополнительных элемента – один снизу, он будет доступен при открытии меню, второй справа — для создания новых разделов меню (рис. 3).
    Уроки OpenGL + C#: Процесс добавления элементов меню Рисунок 3. Процесс добавления элементов меню.

    Дайте название подэлементу меню FileВыход”, как показано на рисунке 4. После этого назовите еще один раздел меню – «загрузить». В меню загрузить добавьте подэлемент «загрузить изображение» и в раскрывающемся меню назначьте ему 2 подэлемента: «В формате JPG» и «В формате PNG». Выглядеть меню будет следующим образом (рис. 4):

    Уроки OpenGL + C#: Заполнение разделов создаваемого меню Рисунок 4. Заполнение разделов создаваемого меню.

    Теперь рассмотрим то, как просто добавить обработчик события меню. Для этого перейдите к меню «Выход». Теперь сделайте двойной щелчок левой клавишей мыши – MS Visual Studio автоматически создаст код функции обработчика и настроит событие обработки.

    Перед нами откроется код функции обработчика:

    /*http://esate.ru, Anvi*/
    
    private void выйтиToolStripMenuItem_Click(object sender, EventArgs e) 
    { 
    
    } 
    
    
    

    Так как этот элемент меню отвечает за выход из приложения, добавим в него код, который будет генерировать MessageBox с вопросом о подтверждении выхода из приложения. Если пользователь подтвердит выход – приложение будет завершено.

    Новый код функции с комментариями:

    /*http://esate.ru, Anvi*/
    
    private void выйтиToolStripMenuItem_Click(object sender, EventArgs e) 
    { 
    
      // создаем переменную rsl, которая будет хранить результат вывода окна с вопросом 
      // (пользователь нажал одну из клавиш на окне - это и есть результат) 
      // MessageBox будет содержать вопрос, а также кнопки Yes, No и иконку Question (Вопрос) 
      DialogResult rsl = MessageBox.Show ("Вы действительно хотите выйти из приложения?", "Внимание!", MessageBoxButtons.YesNo, MessageBoxIcon.Question); 
    
      // если пользователь нажал кнопку да 
      if (rsl == DialogResult.Yes) 
      { // выходим из приложения 
        Application.Exit();
    
      }
    
    } 
    
    
    

    Теперь можно откомпилировать приложение и проверить работоспособность кнопки.

    Часто панель управления (Toolbar) дублирует элементы меню для быстрого к ним доступа.

    Создание панели управления (Toolbar) в C# .NET

    Нам снова нужно перейти к окну Toolbox (Панель инструментов) и развороту Menus & Toolbars. В этот раз мы выберем элемент ToolStrip (рис. 5).
    Уроки OpenGL + C#: Окно Toolbox Рисунок 5. Окно Toolbox.

    Перетащите элемент управления на окно и вы увидите, как вдоль его верхней границы разместиться панель ToolBar (рис. 6).
    Уроки OpenGL + C#: Добавленный элемент Toolbar Рисунок 6. Добавленный элемент Toolbar.
    Мы изменим положение привязки нашего ToolBar’a. Для этого щелкнем по нему правой кнопкой и в открывшемся контекстном меню выберем пункт Свойства. Откроется окно свойств: здесь мы изменим привязку на левую часть окна, внеся изменения в параметр Dock, как показано на рисунке 7.
    Уроки OpenGL + C#: Привязка панели инструментов к левой стороне Рисунок 7. Привязка панели инструментов к левой стороне.
    Теперь увеличим размеры кнопок на Toolbar‘e. Для этого сначала необходимо в его свойствах установить параметр AutoSize равным false. Теперь мы можем изменить размеры самих кнопок: установим параметры SizeWidth равным 44. Поле станет шире (рис. 8).
    Уроки OpenGL + C#: Установка размеров изображений, размещаемых на кнопках создаваемого Toolbar Рисунок 8. Установка размеров изображений, размещаемых на кнопках создаваемого Toolbar.

    Теперь добавим 3 кнопки на наш ToolBar. Для этого щелкните по нему и в раскрывающемся списке элементов, которые мы можем добавить, выберите элемент button (рис. 9).
    Уроки OpenGL + C#: Добавление кнопок на панель элементов (Toolbar) Рисунок 9. Добавление кнопок на панель элементов (Toolbar).

    Повторите операцию, чтобы кнопок на панели стало две. Теперь поочередно выберите каждую кнопку и в ее свойствах установите AutoSize равный false. После это перейдите к полю Size и установите высоту равную 42. Теперь кнопки примут квадратный вид.

    Таким образом, на панели разместятся 3 кнопки, как показано на рисунке 10.
    Уроки OpenGL + C#: Пример добавленных кнопок с установленными размерами Рисунок 10. Пример добавленных кнопок с установленными размерами.
    Теперь назначим изображения для данных картинок. В качестве изображений можно использовать все современные форматы, в том числе и png24 с поддержкой прозрачности.

    Мы будем использовать 3 следующих изображения:

    Первое

    Изображение для кнопки 1: будет назначено кнопке, отвечающей за открытие дополнительного диалогового окна.

    Два следующих изображения будут назначены кнопкам, которые будут отвечать за загрузку файлов jpg и png.

    Второе

    Изображение для кнопки 2.

    Третье

    Изображение для кнопки 3.

    (!!!) Обратите внимание, что у данных изображений

    прозрачный фон

    .

    2 кнопки будут дублировать меню с функциями загрузки изображений, 1-я кнопка будет предназначена для вызова окна, с отображением картинки, которую мы загрузили.

    Теперь для установки изображений необходимо перейти в свойства картинки, после чего мы установим значение параметра ImageScaling равным none, чтобы изображение не масштабировалось. Теперь в параметре Image мы можем выбрать изображение для загрузки, как показано на рисунке 11.

    Уроки OpenGL + C#: Установка изображений на кнопки Рисунок 11. Установка изображений на кнопки.
    В процессе выбора откроется окно, показанное на рисунке 12.
    Уроки OpenGL + C#: Импорт рисунка для установки на кнопке Рисунок 12. Импорт рисунка для установки на кнопке.

    Теперь щелкните на кнопке Import и выберите необходимый рисунок. Аналогично повторите с другими рисунками. В результате вы получите 3 красивые кнопки, как показано на рисунке 13.
    Уроки OpenGL + C#: Пример созданной панели инструментов Рисунок 13. Пример созданной панели инструментов.

    Для того чтобы создать обработчики нажатий на кнопки этого ToolBox‘а, достаточно совершить двойной щелчок мыши на каждом из них – MS Visual Studio автоматически сгенерирует код обработчик события и заготовки функций.

    В будущем мы добавим вызов необходимых нам функций из этого обработчика.

    Теперь мы разместим на форме элемент PictureBox и настроим размеры окна, чтобы оно выглядело следующем образом (рис. 14).
    Уроки OpenGL + C#: Расположение элемента PictureBox в окне программы Рисунок 14. Расположение элемента PictureBox в окне программы.

    В свойствах добавленного элемента PictureBox установите параметр SizeMode, равный StretchImage. Теперь, когда мы реализуем загрузку изображения, оно будет масштабироваться под размеры нашего элемента PictureBox.

    Создание окна выбора файла в C# .NET

    Чтобы пользователь мог выбирать файл для загрузки через стандартное в Windows окно загрузки файлов, мы выполним следующие действия.

    Перейдите к окну ToolBox (Панель элементов).

    Теперь перетащите элемент управления OpenFileDialog (рис. 15) на форму.
    Уроки OpenGL + C#: Добавление элемента OpenFileDialog Рисунок 15. Добавление элемента OpenFileDialog.

    Местоположение, куда вы перетащите элемент, неважно: он добавится в поле под окном, к другим специфическим объектам (рис. 16).
    Уроки OpenGL + C#: Элемент OpenFileDialog, расположенный под редактируемой формой Рисунок 16. Элемент OpenFileDialog, расположенный под редактируемой формой.

    Как видно из рисунка 16, к дополнительным элементам, уже установленным на наше окно (меню и ToolBox), добавился еще и элемент OpenFileDialog1.

    Теперь мы реализуем код открытия окна выбора файла, с необходимыми нам параметрами.

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

    Код сгенерированных функций выглядит следующим образом:

    /*http://esate.ru, Anvi*/
    
    // обработка кнопки меню "загрузка - в формате jpg" 
    private void вФорматеJPGToolStripMenuItem_Click(object sender, EventArgs e) 
    {
    
    } 
    
    // обработка кнопки меню "загрузка - в формате png" 
    private void вФорматеPNGToolStripMenuItem_Click(object sender, EventArgs e) 
    {
    
    } 
    
    // обработка кнопки №2 на панели 
    private void toolStripButton2_Click(object sender, EventArgs e) 
    { 
    
    } 
    
    // обработка кнопки №3 на панели 
    private void toolStripButton3_Click(object sender, EventArgs e) 
    { 
    
    } 
    
    
    

    Теперь напишем следующую функцию LoadImage.


    Открытие окна выбора файла и загрузка изображения в C# .NET

    /*http://esate.ru, Anvi*/
    
    Image MemForImage; 
      
      // функция загрузки изображения 
      private void LoadImage(bool jpg) 
      { 
      
      // директория, которая будет выбрана как начальная в окне для выбора файла 
      openFileDialog1.InitialDirectory = "c:"; 
      
      // если будем выбирать jpg файлы 
      if(jpg) 
      { 
      // устанавливаем формат файлов для загрузки - jpg 
      openFileDialog1.Filter = "image (JPEG) files (*.jpg)|*.jpg|All files (*.*)|*.*";
      } 
      else 
      { 
      // устанавливаем формат файлов для загрузки - png 
      openFileDialog1.Filter = "image (PNG) files (*.png)|*.png|All files (*.*)|*.*";
      } 
      
      // если открытие окна выбора файла завершилось выбором файла и нажатием кнопки ОК 
      if(openFileDialog1.ShowDialog() == DialogResult.OK) 
      { 
      
      try // безопасная попытка 
      { 
      // пытаемся загрузить файл с именем openFileDialog1.FileName - выбранный пользователем файл. 
      MemForImage = Image.FromFile(openFileDialog1.FileName); 
      // устанавливаем картинку в поле элемента PictureBox 
      pictureBox1.Image = MemForImage;
      } 
      catch (Exception ex) // если попытка загрузки не удалась 
      {
      // выводим сообщение с причиной ошибки 
      MessageBox.Show( "Не удалось загрузить файл: " + ex.Message);
      }
      
      }
      
      } 
      
    

    Функция LoadImage в качестве входного параметра будет получать флаг о том, какой фильтр для выбора файлов необходимо выбрать. Далее вызывается окно выбора файла и если оно при закрытии возвращает результат DialogResult.OK, то мы пытаемся загрузить и установить выбранную картинку в поле PictureBox. Конструкция try catch необходима нам здесь по следующей причине: если загрузка прошла неудачно, то мы получим сообщение об ошибке, но на выполнение программы это не повлияет, и мы сможем продолжить ее выполнение.

    Далее функции обработчики нажатий пунктов меню и кнопок на панели управления реализуют вызов функции загрузки с необходимым флагом.

    /*http://esate.ru, Anvi*/
    
    
    // обработка кнопки меню "загрузка - в формате jpg" 
    private void вФорматеJPGToolStripMenuItem_Click(object sender, EventArgs e) 
    { 
    
    // загружаем jpg-файлы 
    LoadImage(true);
    
    } 
    
    // обработка кнопки меню "загрузка - в формате png" 
    private void вФорматеPNGToolStripMenuItem_Click(object sender, EventArgs e) 
    { // загружаем png файлы 
    LoadImage(false);
    
    } 
    
    // обработка кнопки №2 на панели 
    private void toolStripButton2_Click(object sender, EventArgs e) 
    { // загружаем jpg фалйы 
    LoadImage(true);
    
    } 
    
    // обработка кнопки №3 на панели 
    private void toolStripButton3_Click(object sender, EventArgs e) 
    { // загружаем png файлы 
    LoadImage(false);
    
    } 
    
    

    Теперь, если откомпилировать приложение (F5), то можно попробовать загрузить изображение. Обратите внимание, что загрузка png24 с альфа-каналом тоже работает (рис. 17).

    Уроки OpenGL + C#: Пример загруженного изображения Рисунок 17. Пример загруженного изображения.
    Как видите, наше изображение масштабируется под размер элемента PictureBox. Поэтому сейчас мы добавим в проект еще одну форму, на которой мы будем отображать изображение в его истинном размере.

    Добавление и вызов дополнительного диалогового окна

    Для этого перейдите к окну Solution Explorer (Обозреватель решений), после чего щелкните на названии проекта правой кнопкой мыши и в открывшемся контекстном меню выберите Add->Form, как показано на рисунке 18.
    Уроки OpenGL + C#: Добавление в проект нового диалогового окна Рисунок 18. Добавление в проект нового диалогового окна.

    В открывшемся окне введите названия класса, который будет отвечать за генерацию нашего вспомогательного окна – Preview.cs (рис. 19).
    Уроки OpenGL + C#: Установка названия создаваемой формы для нового диалогового окна Рисунок 19. Установка названия создаваемой формы для нового диалогового окна.

    Теперь измените размер окна так, как показано на рисунке 20. Затем на форме разместите элемент panel. Внутри элемента panel разместите элемент pictureBox, как показано на рисунке 20.
    Уроки OpenGL + C#: Размещение элементов panel и picturebox на созданном диалоговом окне Рисунок 20. Размещение элементов panel и picturebox на созданном диалоговом окне.

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

    Перейдите к свойствам элемента panel и установите значение параметра AutoScroll равное true. Теперь, в том случае, если размер изображения будет превышать размер элемента panel1, будут появляться вертикальная и горизонтальная полоса прокрутки, с помощью которых можно будет просмотреть изображение.

    Теперь рассмотрим исходный код, необходимый для реализации данных возможностей.

    Первым делом назначим обработчик кнопки с изображением стрелки направленной вправо на нашем главном окне. Щелкните по ней дважды, после чего вы перейдете к редактированию кода функции, которая будет вызвана при щелчке по данной кнопке.

    Он будет выглядеть следующим образом:

    /*http://esate.ru, Anvi*/
    
    // кнопка активации дополнительного диалогового окна 
    private void toolStripButton1_Click(object sender, EventArgs e) 
    { 
    
      // создаем новый экземпляр класса Preview, 
      // отвечающего за работу с нашей дополнительной формой 
      // в качестве параметра мы передаем наше загруженное изображение 
      Form PreView = new Preview(MemForImage); 
      // затем мы вызываем диалоговое окно 
      PreView.ShowDialog();
    
    } 
    
    

    Как видно из исходного кода, класс Preview получает в качестве параметра наше загруженное изображение. Для правильной работы программы мы должны изменить конструктор класса Preview, а также реализовать остальную функциональность программы.

    Перейдите к окну Preview, после чего сделайте двойной щелчок левой клавшей мыши на нем (НЕ на размещенных на нем объектах).

    Откроется для редактирования функция:

    /*http://esate.ru, Anvi*/
    
    private void Preview_Load(object sender, EventArgs e) 
    
    

    Но мы сначала изменим код конструктора класса, теперь он будет выглядеть следующим образом:

    /*http://esate.ru, Anvi*/
    
    public partial class Preview : Form 
    { 
    
      // объект Image для хранения изображения 
      Image ToView; 
      // модифицируем конструктор окна таким образом, чтобы он получал 
      // в качестве параметра изображение для отображения 
      
      public Preview(Image view) 
      { 
      // получаем изображение 
      ToView = view; 
      InitializeComponent();
    
      }
    
    
    

    Теперь отредактируем код функции Preview_Load. Он будет выглядеть следующим образом:

    /*http://esate.ru, Anvi*/
    
    // эта функция выполнится при загрузке окна 
    private void Preview_Load(object sender, EventArgs e) 
    { 
      // если объект, хранящий изображение не равен null 
      if (ToView != null) 
      { 
        // устанавливаем новые размеры элемента pictureBox1, 
        // равные ширине (ToView.Width) и высоте (ToView.Height) загружаемого изображения. 
        pictureBox1.Size = new Size(ToView.Width, ToView.Height); 
        // устанавливаем изображение для отображения в элементе pictureBox1 
        pictureBox1.Image = ToView;
      }
    } 
    
    

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

    /*http://esate.ru, Anvi*/
    
    // обработчик кнопки закрыть 
    private void button1_Click(object sender, EventArgs e) 
    { 
      // закрываем диалоговое окно 
      Close();
    } 
    
    

    Вот и все. Теперь, если будет загружено большое изображение, его отображение в дополнительном окне будет снабжено полосами прокрутки (рис. 21).
    Уроки OpenGL + C#: Изображение с полосами прокрутки Рисунок 21. Изображение с полосами прокрутки.

    Откомпилируйте приложение (F5), чтобы проверить его.

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

    Понравилась статья? Поделить с друзьями:
  • Разрешить использование веб камеры windows 10
  • Разработка клиентских windows приложений на платформе microsoft net framework
  • Разрешить rdp в брандмауэре windows 10
  • Разрешить индексировать содержимое файлов на этом диске windows 10 нужно ли
  • Разработка windows приложений на языке c 2005 скачать