end;
   //Получение информации о том, установлен ли флажок
   //(установлен ли переключатель)
   function GetChecked(id: Integer):BOOL;
   begin
   if (SendDlgItemMessage(hParentWnd, id, BM_GETCHECK, 0, 0) = BST_CHECKED)
   then GetChecked:= True
   else GetChecked:= False;
   end;
 
   Функции и процедуры, описанные в листинге 2.17, предназначены для управления раскрывающимся списком (элементом ComboBox).
Листинг 2.17. Управление раскрывающимся списком
   //Добавление строки в список
   procedure AddToCombo(id: Integer; str: String);
   begin
   SendDlgItemMessage(hParentWnd, id, CB_ADDSTRING, 0,
   Integer(PAnsiChar(str)));
   end;
   //Удаление строки из списка
   procedure DeleteFromCombo(id: Integer; index: Integer);
   begin
   SendDlgItemMessage(hParentWnd, id, CB_DELETESTRING, index, 0);
   end;
   //Выделение строки с заданным номером
   procedure SetComboSel(id: Integer; index: Integer);
   begin
   SendDlgItemMessage(hParentWnd, id, CB_SETCURSEL, index, 0);
   end;
   //Получение номера выделенной строки (CB_ERR, если нет выделения)
   function GetComboSel(id: Integer): Integer;
   begin
   GetComboSel:= SendDlgItemMessage(hParentWnd, id, CB_GETCURSEL, 0, 0);
   end;
   //Получение количества строк
   function GetComboCount(id: Integer): Integer;
   begin
   GetComboCount:= SendDlgItemMessage(hParentWnd, id, CB_GETCOUNT, 0, 0);
   end;
   //Получение текста строки по ее индексу
   function GetComboItemText(id: Integer; index: Integer):String;
   var buffer: String;
   begin
   SetLength(buffer,
   SendDlgItemMessage(hParentWnd, id, CB_GETLBTEXTLEN, index, 0)
   );
   SendDlgItemMessage(hParentWnd, id, CB_GETLBTEXT, index,
   Integer(Addr(buffer)));
   GetComboItemText:= buffer;
   end;
 
   Сходные функции и процедуры, приведенные в листинге 2.18, предназначены для управления списком (элементом ListBox).
Листинг 2.18. Управление списком
   //Добавление строки в список
   procedure AddToList(id: Integer; str: String);
   begin
   SendDlgItemMessage(hParentWnd, id, LB_ADDSTRING, 0, Integer(PAnsiChar(str)));
   end;
   //Удаление строки из списка
   procedure DeleteFromList(id: Integer; index: Integer);
   begin
   SendDlgItemMessage(hParentWnd, id, LB_DELETESTRING, index, 0);
   end;
   //Выделение строки с заданным номером
   procedure SetListSel(id: Integer; index: Integer);
   begin
   SendDlgItemMessage(hParentWnd, id, LB_SETCURSEL, index, 0);
   end;
   //Получение номера выделенной строки (LB_ERR, если нет выделения)
   function GetListSel(id: Integer): Integer;
   begin
   GetListSel:= SendDlgItemMessage(hParentWnd, id, LB_GETCURSEL, 0, 0);
   end;
   //Получение количества строк
   function GetListCount(id: Integer): Integer;
   begin
   GetListCount:= SendDlgItemMessage(hParentWnd, id, LB_GETCOUNT, 0, 0);
   end;
   //Получение текста строки по ее индексу
   function GetListItemText(id: Integer; index: Integer):String;
   var buffer: String;
   begin
   SetLength(buffer,
   SendDlgItemMessage(hParentWnd, id, LB_GETTEXTLEN, index, 0)
   );
   SendDlgItemMessage(hParentWnd, id, LB_GETTEXT, index,
   Integer(Addr(buffer)));
   GetListItemText:= buffer;
   end;
 
   Функции и процедуры, приведенные в листинге 2.19, дают возможность управлять текстовыми полями (элементы Edit и Memo).
Листинг 2.19. Управление текстовыми полями
   //Получение позиции первого выделенного символа (нумерация с нуля)
   function GetSelStart(id: Integer): Integer;
   var selStart, selEnd: Integer;
   begin
   SendDlgItemMessage(hParentWnd, id, EM_GETSEL, Integer(Addr(selStart)),
   Integer(Addr(selEnd)));
   GetSelStart:= selStart;
   end;
   //Получение длины выделенного фрагмента текста
   function GetSelLength(id: Integer): Integer;
   var selStart, selEnd: Integer;
   begin
   SendDlgItemMessage(hParentWnd, id, EM_GETSEL, Integer(Addr(selStart)),
   Integer(Addr(selEnd)));
   GetSelLength:= selEnd – selStart;
   end;
   //Выделение фрагмента текста (позиция первого символа с нуля)
   procedure SetSel(id: Integer; start, length: Integer);
   begin
   SendDlgItemMessage(hParentWnd, id, EM_SETSEL, start, start + length);
   end;
   //Получение выделенного фрагмента текста
   function GetSelText(id: Integer): String;
   var allText: String;
   begin
   allText:= GetText(id);
   GetSelText:= Copy(allText, GetSelStart(id)+1,GetSelLength(id));
   end;
   //Замена выделенного текста
   procedure ReplaceSelText(id: Integer; newText: String);
   begin
   SendDlgItemMessage(hParentWnd, id, EM_REPLACESEL,
   0, Integer(PAnsiChar(newText)));
   end;
 
   В листинге 2.20 приведены функции и процедуры, которые можно применять ко всем элементам управления с одинаковым успехом.
Листинг 2.20. Общие функции и процедуры
   //Установка текста окна
   procedure SetText(id: Integer; str: String);
   begin
   SetWindowText(GetDlgItem(hParentWnd, id), PAnsiChar(str));
   end;
   //Получение текста окна
   function GetText(id: Integer): String;
   var buffer: String;
   begin
   SetLength(buffer, GetWindowTextLength(hParentWnd));
   GetWindowText(hParentWnd, PAnsiChar(buffer), Length(buffer));
   GetText:= buffer;
   end;
   //Активизация/деактивизация окна
   procedure SetEnabled(id: Integer; fEnabled: BOOL);
   begin
   EnableWindow(GetDlgItem(hParentWnd, id), fEnabled);
   end;

Реакция на сообщения элементов управления

   При возникновении какого-либо предусмотренного для элемента управления события родительскому окну посылается сообщение WM_COMMAND.
   Примечание
   Сообщение WM_COMMAND приходит также при перерисовке так называемых «самоперерисовывающихся» (Owner Draw) элементов управления. Однако ввиду специфики данного вопроса и ограниченности объема главы оно рассматриваться не будет.
   Таким образом, когда родительское окно получает сообщение WM_COMMAND, то из двух прилагающихся параметров (lParam и wParam) можно извлечь следующие сведения:
   • старшие 16 бит wParam представляют собой целочисленный код уведомления, позволяющий определить, что именно произошло с элементом управления;
   • младшие 16 бит wParam представляют собой идентификатор элемента управления, состояние которого изменилось (именно этот идентификатор передавался вместо дескриптора меню при создании элементов управления);
   • lParam содержит дескриптор (HWND) окна элемента управления, состояние которого изменилось.
   Для выделения старших 16 бит из 32-битного значения можно использовать функцию HiWord. Для получения младших 16 бит можно использовать функцию с именем LoWord. Обе функции объявлены в модуле Windows.
   В качестве примеров можно привести следующие коды уведомлений:
   • BN_CLICKED – нажата кнопка;
   • EN CHANGE – изменен текст в текстовом поле;
   • LBN_SELCHANGE – изменилось выделение в списке;
   • CBN_SELCHANGE – изменилось выделение в раскрывающемся списке.
   Эти и все остальные константы уведомлений стандартных элементов управления объявлены в модуле Messages.
   Примечание
   Коды и описания уведомлений рассматриваемых в этой главе элементов управления приведены в приложении 3.

Пример приложения

   Рассмотрим небольшой пример, иллюстрирующий принцип работы с элементами управления, помещенными на форму описанным ранее способом. Проект этого приложения называется ControlsDemo.
   Не буду заострять внимание на регистрации класса главного окна приложения, так как она аналогична приведенной в листинге 2.4. Рассмотрим создание окна с элементами управления в нем (листинг 2.21).
Листинг 2.21. Создание главного окна приложения (с элементами управления)
   program ControlsDemo;
   uses
   Windows, Messages,
   Controls in 'Controls.pas';
   {$R *.res}
   var
   hMainWnd: HWND;
   hInst: Cardinal;
   mess: MSG;
   //Функция обработки сообщений
   ...
   //Создание окна и цикл обработки сообщений
   begin
   hInst:= GetModuleHandle(nil);
   //Регистрация и создание главного окна
   if not RegisterWindow() then Exit;
   hMainWnd:= CreateWindow(
   'MyWindowClass', //Имя класса окна
   'Главное окно', //Заголовок окна
   WS_OVERLAPPEDWINDOW,
   CW_USEDEFAULT, //Координата X по умолчанию
   CW_USEDEFAULT, //Координата Y по умолчанию
   CW_USEDEFAULT, //Ширина по умолчанию
   CW_USEDEFAULT, //Высота по умолчанию
   HWND(nil), //Нет родит ельского окна
   HMENU(nil), //Нетменю
   hInst,
   nil);
   if (hMainWnd = HWND(nil)) then Exit;
   //Инициализация модуля Controls для работы с главным окном
   Controls.hParentWnd:= hMainWnd;
   Controls.hAppInst:= hInst;
   //Создание элементов управления
   CreateFrame(10, 80, 170, 70, –1, 'Кнопки');
   CreateButton(2 0, 100, 70, 30, 1001, 'Кнопка 1');
   CreateButton(100, 100, 70, 30, 1002,'Кнопка 2');
   CreateFrame(2 00, 10, 200, 180, –1, 'Флажки и переключатели');
   CreateCheck(2 10, 30, 180, 20, 2001, 'Флажок 1');
   CreateCheck(2 10, 60, 180, 20, 2002, 'Флажок 2', True);
   CreateOption(2 10, 100, 180, 20, 3001, 'Переключатель 1', True);
   CreateOption(2 10, 130, 180, 20, 3002, 'Переключатель 2', False, True);
   CreateOption(2 10, 160, 180, 20, 3003, 'Переключатель 3', True);
   CreateFrame(420, 10, 300, 180, –1, 'Списки и статические надписи');
   CreateLabel(430, 30, 70, 20, -1, 'Надпись');
   CreateCombo(510, 30, 200, 100, 4001);
   CreateList(430, 60, 280, 120, 5001);
   CreateFrame(2 00, 200, 200, 240, –1, 'Текстовые поля');
   CreateEdit(2 10, 22 0, 180, 20, 6001, 'Текст в текстовом поле');
   CreateMemo(2 10, 25 0, 180, 180, 6002, 'Текст в многострочном'+ #13
   + #10 + 'текстовом поле');
   //Добавление строк в списки
   AddToCombo(4001, 'Строка 1');
   AddToCombo(4001, 'Строка 2');
   AddToCombo(4001, 'Строка 3');
   AddToList(5 001, 'Строка 1');
   AddToList(5 001, 'Строка 2');
   AddToList(5 001, 'Строка 3');
   ShowWindow(hMainWnd, SW_NORMAL);
   //Запуск цикла обработки сообщений
   while (Longint(GetMessage(mess, 0, 0, 0)) <> 0)
   do begin
   TranslateMessage(mess);
   DispatchMessage(mess);
   end;
   end.
 
   Код листинга 2.21 заодно демонстрирует использование некоторых из приведенных ранее функций работы с элементами управления. Выглядит созданное окно так, как показано на рис. 2.3.
   Рис. 2.3. Окно с элементами управления
 
   Принцип построения функции обработки сообщений для этого окна описан в листинге 2.22.
Листинг 2.22. Функция обработки сообщений
   //Функция обработки сообщений
   function WindowFunc(hWnd:HWND; msg:UINT;
   wParam:WPARAM; lParam:LPARAM):LRESULT; stdcall;
   var
   ps: PAINTSTRUCT;
   begin
   case msg of
   WM_PAINT:
   begin
   //Перерисовка содержимого окна
   BeginPaint (hWnd, ps);
   Text Out (ps.hdc, 10, 10, 'Текст в окне', 12);
   EndPaint(hWnd, ps);
   end;
   WM_CLOSE:
   if (hWnd = hMainWnd) then
   PostQuit Message(0); //При закрытии этого окна завершается
   //приложение
   WM_COMMAND:
   begin
   case LOWORD(wParam) of
   //нажата "Кнопка 1"
   1001: if HIWORD(wParam) = BN_CLICKED then;
   //нажата "Кнопка 2"
   1002: if HIWORD(wParam) = BN_CLICKED then;
   //установлен "Флажок 1"
   2001: if HIWORD(wParam) = BN_CLICKED then;
   //установлен "Флажок 2"
   2002: if HIWORD(wParam) = BN_CLICKED then;
   //установлен "Переключатель 1"
   3001: if HIWORD(wParam) = BN_CLICKED then;
   //установлен "Переключатель 2"
   3002: if HIWORD(wParam) = BN_CLICKED then;
   //установлен "Переключатель 3"
   3003: if HIWORD(wParam) = BN_CLICKED then;
   //выделение в ComboBox
   4001: if HIWORD(wParam) = CBN_SELCHANGE then;
   //выделение в ListBox
   5001: if HIWORD(wParam) = LBN_SELCHANGE then;
   //изменен текст в Edit
   6001: if HIWORD(wParam) = EN_CHANGE then;
   //изменен текст в Memo
   6002: if HIWORD(wParam) = EN_CHANGE then;
   end;
   end;
   else
   begin
   //Обработка по умолчанию
   WindowFunc:= DefWindowProc(hWnd, msg, wParam, lParam);
   Exit;
   end;
   end;
   WindowFunc:= S_OK; //Сообщение обработано
   end;
 
   Приведенная в листинге 2.22 функция не претендует на то, чтобы быть эталоном в порядке классификации сообщений от элементов управления. Иногда бывает полезно сразу классифицировать сообщения не по элементам управления, которые их прислали, а по типу самих сообщений. К тому же в ряде случаев можно предусмотреть один обработчик сообщений сразу для нескольких элементов управления, например, для группы переключателей. В таком случае полезным окажется параметр lParam сообщения WM COMMAND.
   Кстати, размер исполняемого файла этого приложения равен всего 19 Кбайт.

Стандартные окна Windows

   Теперь рассмотрим, как можно с помощью только функций Windows API вызывать некоторые распространенные окна. Чтобы использовать API-функции и структуры с информацией для этих окон, необходимо подключить следующие модули:
   • CommDlg – для окон открытия и сохранения файла, выбора цвета и шрифта, поиска и замены текста;
   • ShlObj и ActiveX – для окна выбора папки (второй модуль нужен для доступа к интерфейсу IMalloc, зачем – будет рассказано далее);
   • Windows – помимо объявления основных структур и API-функций, этот модуль содержит объявления функций для работы с окнами подключения и отключения от сетевого ресурса (сетевого диска);
   • ShellAPI – для системного окна О программе.
   Вариант использования рассматриваемых в этом разделе окон приведен в подразделе «Демонстрационное приложение» данной главы (стр. 81).
   Примечание
   В приведенных далее примерах вызова окон можно увидеть не объявленные, но используемые в программах переменные hAppInst и hParentWnd. Подразумевается, что это глобальные переменные, которые инициализируются вне процедур и функций, приведенных в примерах. Для инициализации этих переменных можно также написать специальную процедуру, например, с именем Init, в которую и передавать значения для hParentWnd и hAppInst.

Окно открытия и сохранения файла

   Чтобы использовать возможности окна открытия файла, достаточно воспользоваться функцией, представленной в листинге 2.23.
Листинг 2.23. Окно открытия файла
   function ShowOpen(strFilter: string; nFilterIndex: Integer= 0;
   strInitFileName: string = '';
   var
   ofn: OPENFILENAME;
   begin
   ZeroMemory(Addr(ofn), SizeOf(ofn));
   //Формирование буфера (260 символов)
   SetLength(strInitFileName, MAX_PATH);
   PrepareFilterString(strFilter);
   //Заполнение структуры для окна
   ofn.lStructSize:= SizeOf(ofn);
   ofn.hWndOwner:= hParentWnd;
   ofn.hInstance:= hAppInst;
   ofn.lpstrFilter:= PAnsiChar(strFilter);
   ofn.nFilterIndex:= nFilterIndex;
   ofn.lpstrFile:= PAnsiChar(strInitFileName);
   ofn.nMaxFile:= MAX_PATH;
   ofn.lpstrTitle:= pAnsiChar(strTitle);
   ofn.Flags:= OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_HIDEREADONLY;
   //Отображение окна и обработка результата
   if(GetOpenFileName(ofn) = True) then
   ShowOpen:= ofn.lpstrFile;
   end;
 
   Приведенная в листинге 2.23 функция возвращает непустую строку – полный путь файла в случае, если пользователь выбрал или ввел имя файла. Здесь главной трудностью является заполнение довольно большой структуры OPENFILENAME. В данном примере используются только базовые возможности окна открытия файла и лишь некоторые из поддерживаемых им флагов (поле Flags):
   • OFN_FILEMUSTEXIST – при этом установленном флаге, если окно успешно завершило свою работу, можно быть уверенным, что результирующий путь является путем существующего файла;
   • OFN_PATHMUSTEXIST – данный флаг не дает ввести имя файла в несуществующей папке (например, при вводе c: \docs\mydoc1.doc, если папки docs не существует, будет выдано соответствующее сообщение);
   • OFN_HIDEREADONLY – позволяет скрыть флажок Только для чтения.
   Теперь отдельно рассмотрим, зачем в приведенном примере вызывается дополнительная функция PrepareFilterString (листинг 2.24).
Листинг 2.24. Преобразование строки фильтра
   procedure PrepareFilterString(var strFilter: string);
   var
   i: Integer;
   begin
   for i:= 1 to length(strFilter) do
   if (strFilter[i] = '|') then strFilter[i]:= #0;
   end;
 
   Дело в том, что при задании фильтров (поле lpstrFile) требуется, чтобы каждое название и обозначение фильтров были отделены символом #0, а за последним фильтром шла последовательность из двух нулевых символов. На практике задавать строку из нескольких фильтров в следующем виде недостаточно удобно:
 
   'Текстовые файлы'+ #0 + '*.txt'+ #0 + 'Все файлы'+ '*.*'+ #0 + #0
 
   Поэтому часто применяются другие разделители, которые впоследствии преобразуются в символы #0. В данном случае в качестве разделителя используется символ |, благодаря чему приведенная выше строка фильтра может быть записана так:
 
   'Текстовые файлы|*.txt |Все файлы|*.*||'
 
   Согласитесь, что получилось более кратко и понятно.
   Теперь обратимся к окну сохранения файла. Для его вызова достаточно переделать код из листинга 2.23 следующим образом (листинг 2.25).
Листинг 2.25. Окно сохранения файла
   function ShowSave(strFilter: string; nFilterIndex: Integer = 0;
   strInitFileName: string = '';
   strTitle: string = 'Сохранение файла'):string;
   var
   ofn: OPENFILENAME;
   begin
   ZeroMemory(Addr(ofn), SizeOf(ofn));
   //Формирование буфера (260 символов)
   SetLength(strInitFileName, MAX_PATH);
   PrepareFilterString(strFilter);
   //Заполнение структуры для окна
   ofn.lStructSize:= SizeOf(ofn);
   ofn.hWndOwner:= hParentWnd;
   ofn.hInstance:= hAppInst;
   ofn.lpstrFilter:= PAnsiChar(strFilter);
   ofn.nFilterIndex:= nFilterIndex;
   ofn.lpstrFile:= PAnsiChar(strInitFileName);
   ofn.nMaxFile:= MAX_PATH;
   ofn.lpstrTitle:= pAnsiChar(strTitle);
   ofn.Flags:= OFN_PATHMUSTEXIST or OFN_OVERWRITEPROMPT;
   //Отображение окна и обработка результата
   if (GetSaveFileName(ofn) = True) then
   ShowSave:= ofn.lpstrFile;
   end;
 
   Здесь дополнительно к упомянутому ранее флагу OFN_PATHMUSTEXIST добавлен флаг OFN_OVE RWRITEPROMPT для того, чтобы при указании имени уже существующего файла был задан вопрос, желает ли пользователь заменить этот файл.

Окно для выбора цвета

   Код вызова следующего окна – окна для выбора цвета – приведен в листинге 2.26.
Листинг 2.26. Окно для выбора цвета
   function ShowChooseColor(lastColor: COLORREF = 0):COLORREF;
   var
   choose: TChooseColor;
   begin
   ZeroMemory(Addr(choose), SizeOf(choose));
   //Заполнение структуры для окна
   choose.lStructSize:= SizeOf(choose);
   choose.hWndOwner:= hParentWnd;
   choose.hInstance:= hAppInst;
   choose.rgbResult:= lastColor;
   choose.lpCustColors:= Addr(colors);
   choose.Flags:= CC_RGBINIT or CC_ANYCOLOR or CC_FULLOPEN;
   //Отображение окна и обработка результата
   if (ChooseColor(choose) = True) then ShowChooseColor:= choose.rgbResult
   else ShowChooseColor:= lastColor;
   end;
 
   Здесь также заполняется специальная структура. Для этого используются следующие флаги:
   • CC_RGBINIT – использовать значение поля rgbResult в качестве предустановленного значения цвета (по умолчанию как ранее выбранного);
   • CC_ANYCOLOR – отображать все доступные предопределенные цвета (левая половина, рис. 2.4);
   Рис. 2.4. Окно для выбора цвета
 
   • CC_FULLOPEN – раскрывать панель подбора цвета (правая половина окна, рис. 2.4).
   Стоит пояснить, что за переменная, а точнее, адрес, сохраняется в поле lpCustColors – это массив из 16 значений типа COLORREF:
 
   colors: array [1..16] of COLORREF;
 
   Обратите внимание на 16 квадратов в левой нижней области окна (рис. 2.4) – это места для определенных пользователем цветов. Для заполнения этой области окна и используются значения из массива colors. Массив может быть как локальным, так и глобальным (что иногда удобнее, так как значения определенных пользователем цветов в этом случае сохраняются между вызовами окна выбора цвета).

Окно для выбора шрифта

   Для вывода окна для выбора шрифта подойдет функция, приведенная в листинге 2.27.
Листинг 2.27. Окно для выбора шрифта
   function ShowChooseFont(var font: LOGFONT):BOOL;
   var
   choose: TChooseFont;
   begin
   ZeroMemory(Addr(choose), SizeOf(choose));
   //Заполнение структуры для окна
   choose.lStructSize:= SizeOf(choose);
   choose.hWndOwner:= hParentWnd;
   choose.hInstance:= hAppInst;
   choose.lpLogFont:= Addr(font);
   choose.Flags:= CF_BOTH or CF_INITTOLOGFONTSTRUCT;
   //Отображение окна и обработка результата
   if (ChooseFont (choose) = True) then
   begin
   CopyMemory(Addr(font), choose.lpLogFont, SizeOf(font));
   ShowChooseFont:= True;
   end
   else ShowChooseFont:= False;
   end;
 
   Здесь используются флаги окна, имеющие следующие значения:
   • CF_BOTH – позволяет отображать экранные и принтерные шрифты (для отображения либо экранных, либо принтерных шрифтов можно использовать флаги CF_SCREENFONTS и CF_PRINTERFONTS соответственно);
   • CF_INITTOLOGFONTSTRUCT – позволяют выбрать в окне шрифт, соответствующий (или максимально похожий) шрифту, описываемому структурой LOGFONT, указатель на которую сохраняется в поле lpLogFont.

Окно для выбора папки

   Чтобы иметь возможность пользоваться окном Обзор папок, можно использовать функцию, представленную в листинге 2.28.
Листинг 2.28. Окно для выбора папки
   function ShowChooseFolder(strTitle: string):string;
   var
   choose: BROWSEINFO;
   buffer: string;
   pidl: PItemIDList;
   begin
   ZeroMemory(Addr(choose), SizeOf(choose));
   SetLength(buffer, MAX_PATH);
   //Заполнение структуры для окна
   choose.hwndOwner:= hParentWnd;
   choose.pi dlRoot:= nil; //Корень – папка Рабочего стола
   choose.pszDisplayName:= PAnsiChar(buffer);
   choose.lpszTitle:= PAnsiChar(strTitle);
   choose.ulFlags:= 0;
   //Вывод окна и обработка результата
   pidl:= SHBrowseForFolder(choose);
   if (pidl <> nil) then
   begin
   //Получение полного пути выбранной папки
   SHGetPathFromIDList(pidl, PAnsiChar(buffer));
   ShowChooseFolder:= buffer;
   DeletePIDL(pidl);
   end
   else
   ShowChooseFolder:= '';
   end;
 
   Представленная в листинге 2.28 функция ShowChooseFolder возвращает полный путь указанной папки, если она выбрана, и пустую строку в противном случае. Само окно Обзор папок показано на рис. 2.5.
   Рис. 2.5. Окно для выбора папки
 
   Особенностью использованной в данном примере функции SHBrowseForFolder является то, что она возвращает не путь выбранной папки, а указатель на структуру ItemlDList (что-то вроде внутреннего представления путей). Для извлечения построения пути по содержимому этой структуры используется функция SHGetPathFromIDList. После этого структура становится больше не нужна, и ее следует удалить (с использованием специального интерфейса IMalloc). Для этого используется процедура DeletePIDL, реализованная в листинге 2.29.
Листинг 2.29. Удаление структуры ItemlDList
   procedure DeletePIDL(pidl: PItemIDList);
   var
   pMalloc: IMalloc;
   begin
   SHGetMalloc(pMalloc);
   if (pMalloc <> nil) then
   begin
   pMalloc.Free(pidl);
   pMalloc._Release();
   end;
   end;
 
   Функцию SHBrowseForFolder (листинг 2.28) можно использовать и для указания принтеров или компьютеров. Для этого достаточно установить флаги BIF_ BROWSEFORCOMPUTER и BIF_BROWSEFORPRINTER соответственно:
 
   choose.ulFlags:= BIF_BROWSEFORCOMPUTER;
 
   и
 
   choose.ulFlags:= BIF_BROWSEFORPRINTER;
 
   Чтобы в окне отображались еще и значки файлов, нужно установить флаг BIF_ BROWSEINCLUDEFILES.

Окна подключения и отключения сетевого ресурса

   Часто бывает удобно осуществлять доступ к сетевым папкам так же, как и к локальным дискам компьютера (с использованием того же принципа построения пути). Окна подключения и отключения сетевого ресурса дают пользователю возможность выбрать, какие папки считать сетевыми дисками и какие сетевые диски можно отключить.
   Вид окна подключения сетевого ресурса в Windows XP показан на рис. 2.6.
   Рис. 2.6. Окно подключения сетевого диска
 
   Для вызова окна подключения сетевого ресурса можно использовать функцию, приведенную в листинге 2.30.
Листинг 2.30. Окно подключения сетевого ресурса
   function ShowConnection(): BOOL;
   begin
   ShowConnection:=
   WNetConnectionDialog(hParentWnd, RESOURCETYPE_DISK) = NO_ERROR;
   end;
 
   Функция ShowConnection возвращает значение True в случае удачного подключения и False – в противном случае.
   Окно отключения сетевого диска показано на рис. 2.7.
   Рис. 2.7. Отключение сетевого ресурса
 
   Функция, показывающая окно отключения сетевого диска, приведена в листинге 2.31.
Листинг 2.31. Окно отключения сетевого ресурса
   function ShowDisconnect(): BOOL;
   begin
   ShowDisconnect:=
   WNetDisconnectDialog(hParentWnd, RESOURCETYPE_DISK) = NO_ERROR;
   end;
 
   Аналогично ShowConnection функция ShowDisconnect возвращает значение True, если отсоединен хотя бы один диск, и False – в противном случае.

Системное окно О программе

   Этот последний и довольно специфический пример приведен на тот случай, если возникнет желание или необходимость использовать окно О программе, которое выводится для самой операционной системы Windows и ее компонентов. Процедура, выводящая это окно, приведена в листинге 2.32.
Листинг 2.32. Окно О программе
   procedure ShowAbout(strAppName: string; strInfo: string);
   begin
   ShellAbout(hParentWnd, PAnsiChar(strAppName), PAnsiChar(strInfo),
   LoadIcon(0, IDI_ASTERISK));
   end;
 
   Правда, в окне О программе Windows XP на информацию о приложении отведено всего две строки (и место для значка). Все остальное место занимают информация о регистрации операционной системы и фирменная эмблема Microsoft Windows XP.

Демонстрационное приложение

   Теперь пришла очередь рассмотреть небольшое приложение, использующее описанные выше окна (проект StandartWindows). Окно этого приложения показано на рис. 2.8.
   Рис. 2.8. Окно демонстрационного приложения
 
   Размер EXE-файла приложения равен 22 Кбайт.
   В листинге 2.33 приведены объявления используемых глобальных переменных, код, реализующий создание окна и элементов управления в нем, а также цикл обработки сообщений (файл StandartWindows.dpr). Функции работы с рассмотренными выше окнами вынесены в отдельный модуль StdWindows (файл StdWindows.pas).
   В листингах 2.33-2.34 используются уже знакомые вам функции из модуля Controls.
Листинг 2.33. Глобальные переменные, код создания окна и цикл обработки сообщений
   program StandartWindows;
   {$R *.res}
   uses
   Windows, Messages, CommDlg,
   Controls in 'Controls.pas',
   StdWindows in 'StdWindows.pas';
   var
   hMainWnd: HWND;
   hInst: Cardinal;
   mess: MSG;
   curColor: COLORREF;
   font: LOGFONT;
   hCurFont: HFONT;
   ...
   function RegisterWindow():Boolean;
   ...
   begin
   hInst:= GetModuleHandle(nil);
   //Регистрация и создание главного окна
   if not RegisterWindow() then Exit;
   hMainWnd:= CreateWindow(
   'MyWindowClass', //Имя класса окна
   'Стандартные окна Windows', //Заголовок окна
   WS_CAPTION or WS_SYSMENU or WS_CLIPCHILDREN or WS_CLIPSIBLINGS,
   CW_USEDEFAULT, //Координата X по умолчанию
   CW_USEDEFAULT, //Координата Y по умолчанию
   470, 420,
   HWND(nil), //Нет родительского окна
   HMENU(nil), //Нетменю
   hInst,
   nil);
   if (hMainWnd = HWND(nil)) then Exit;
   //Инициализация модуля Controls для работы с главным окном приложения
   Controls.hParentWnd:= hMainWnd;
   Controls.hAppInst:= hInst;
   //Инициализация модуля StdWindows для работы с главным окном приложения
   StdWindows.hParentWnd:= hMainWnd;
   StdWindows.hAppInst:= hInst;
   //Создание кнопок для открытия окон
   CreateButton(2 0, 20, 200, 30, 1001, 'Открытие файла');
   CreateButton(2 0, 60, 200, 30, 1002, 'Сохранение файла');
   CreateButton(2 0, 100, 200, 30, 1003, 'Выбор цвета');
   CreateButton(2 0, 140, 200, 30, 1004, 'Выбора шрифта');
   CreateButton(2 0, 180, 200, 30, 1005, 'Окно поиска текста');
   CreateButton(2 0, 22 0, 200, 30, 1006, 'Окно поиска и замены');
   CreateButton(23 0, 20, 22 0, 30, 1010, 'Выбор папки');
   CreateButton(23 0, 60, 22 0, 30, 1011, 'Подключение сетевого ресурса');
   CreateButton(23 0, 100, 22 0, 30, 1012, 'Отключение сетевого ресурса');
   CreateButton(23 0, 140, 22 0, 30, 1013, 'Системное окно "О программе"');
   //Текстовое поле для результата
   CreateMemo (20, 270, 430, 100, 2001);
   ShowWindow(hMainWnd, SW_NORMAL);
   //Запуск цикла обработки сообщений
   while (Longint(GetMessage(mess, 0, 0, 0)) <> 0) do
   begin
   if (IsDialogMessage(hMainWnd, mess) = False) then
   begin
   TranslateMessage(mess);
   DispatchMessage(mess);
   end;
   end;
   end.
 
   Код функции RegisterWindow опущен, поскольку он аналогичен приведенному в листинге 2.4. Функции работы с рассмотренными ранее окнами вынесены в модуль StdWindows(файл StdWindows.pas).
   Особенностью цикла обработки сообщений в этом примере является использование API-функции IsDialogMessage, которая позволяет реагировать на некоторые действия пользователя так, как это делается в рассмотренных выше окнах. Примером может служить перемещение фокуса между окнами при нажатии клавиши Tab.
   Перед функцией RegisterWindow (на месте многоточия перед ее объявлением в листинге 2.33) находится функция обработки сообщений, имеющая следующий вид (листинг 2.34).
Листинг 2.34. Функция обработки сообщений
   function WindowFunc(hWnd:HWND; msg:UINT; wParam:WPARAM; lParam:LPARAM):LRESULT; stdcall;