Глава 4. Стандартные диалоговые окна

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

Все функции, переменные, записи для работы со стандартными диалоговыми окнами расположены в одном модуле, который называется commdlg. Поэтому вы должны подключить этот модуль, если собираетесь использовать окна типа «Open File». Порядок работы со стандартными диалогами следующий:

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

Диалоговое окно «Open File»

В Windows 2000 обычное окно открытия файла выглядит так:

Типичный вид стандартного диалогового окна открытия файла

Создается оно при помощи функции GetOpenFileName, а принимает она единственный параметр – запись типа TOpenFilename. Вот заголовок функции и параметры этой записи, а также их описание.
tagOFNA = packed record
    lStructSize: DWORD;
    hWndOwner: HWND;
    hInstance: HINST;
    lpstrFilter: PChar;
    lpstrCustomFilter: PChar;
    nMaxCustFilter: DWORD;
    nFilterIndex: DWORD;
    lpstrFile: PChar;
    nMaxFile: DWORD;
    lpstrFileTitle: PChar;
    nMaxFileTitle: DWORD;
    lpstrInitialDir: PChar;
    lpstrTitle: PChar;
    Flags: DWORD;
    nFileOffset: Word;
    nFileExtension: Word;
    lpstrDefExt: PChar;
    lCustData: LPARAM;
    lpfnHook: function(Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): UINT stdcall;
    lpTemplateName: PChar;
    pvReserved: Pointer;
    dwReserved: DWORD;
    FlagsEx: DWORD;
end;
TOpenFilenameA = tagOFNA;
TOpenFilename = TOpenFilenameA;

function GetOpenFileName(var OpenFile: TOpenFilename): Bool; stdcall;

lStructSize
Если приложение предназначено для работы в Windows95/98/NT то это поле должно быть равно 76. Если приложение предназначено для Windows 2000, то sizeof(TOPENFILENAME);
hwndOwner
описатель окна, которое будет владельцем данного диалога, может быть нулем, если владельца не будет;
hInstance
определяет различные параметры при изменении внешнего вида диалога, за подробностями обращайтесь в справку по winapi;
lpstrFilter
указатель на буфер, содержащий строку с фильтром, содержимое данного поля используется для построения выпадающего списка «Files of type » (см. рисунок выше). Буфер представляет собой пары описание_фильтра, фильтр, каждый элемент пары – строка с завершающим нулем, в конце всех фильтров стоит два символа с кодом нуль. описание_фильтра – это строка типа «Text files», фильтр – «*.txt;*.text;*.asc». Подробности ниже в примере.
lpstrCustomFilter
указатель на буфер в котором находится фильтр, используемый как стартовый, но только в том случае, когда поле nFilterIndex равно нулю;
nMaxCustFilter
размер буфера для поля lpstrCustomFilter, должен быть не меньше 40;
nFilterIndex
в этом поле номер фильтра, выбранный изначально. Номер соответствует номеру пары из поля lpstrFilter, нумерация начинается с единицы. Если нуль, то используется фильтр из поля lpstrCustomFilter;
lpstrFile
указатель на буфер, содержащий имя файла использующееся для инициализации диалога. Если пользователь выбрал какой-нибудь файл, то в буфере сохраняется полное имя этого файла. Если размер буфера недостаточен, то функция возвращает false, а вызов функции CommDlgExtendedError возвращает FNERR_BUFFERTOOSMALL;
nMaxFile
размер буфера, заданного в предыдущем параметре, должен быть не меньше 256;
lpstrFileTitle
указатель на буфер, который принимает только имя файла с расширением (без пути), может быть nil, тогда ничего не возвращается;
nMaxFileTitle
определяет размер буфера, заданного ы предыдущем параметре. Игнорируется, если lpstrFileTitle равен nil;
lpstrInitialDir
строка, определяющая начальный каталог. Если это поле равно nil, то поведение диалога зависит от версии операционной системы, подробности в справке по WinApi;
lpstrTitle
строка с заголовком диалога, если поле равно nil, то используется заголовок по умолчанию (Save As, например);
Flags
набор флагов, использующихся для инициализации диалога. После закрытия диалога здесь будут содержаться флаги, описывающие действия пользователя. Набор флагов очень большой, поэтому я опишу лишь самые важные.
OFN_EXPLORER
создается именно такое окно, как показано на рисунке выше, т.е. «современного» вида. В MSDN написано, что даже если не указывать этот флаг, все равно диалог будет отображаться в современном виде, однако у меня отсутствие этого флага приводит к тому, что диалоговое окно отображается так как в Win3.1;
OFN_CREATEPROMPT
вызывает появления запроса на создание файла, если файла, выбранного пользователем не существует;
OFN_FILEMUSTEXIST
определяет, что пользователь может выбрать только существующие файлы;
OFN_HIDEREADONLY
скрывает флажок с надписью «Read only»
OFN_NODEREFERENCELINKS
если этот флаг установлен, то при выборе файла ярлыка windows (с расширением .LNK), будет возвращено имя этого файла. Если флаг не установлен, то будет возвращено имя файла, на который ссылается ярлык;
OFN_OVERWRITEPROMPT
показать запрос на подтверждение перезаписи выбранного файла, если он уже существует, используется в диалоге «Save as»;
OFN_PATHMUSTEXIST
если этот флаг установлен, то пользователь может вводить только корректные имена файлов и каталогов;
OFN_READONLY
если этот флаг установлен перед вызовом функции, флажок «Read only» помечается включенным. Если этот флаг присутствует после вызова функции, значит пользователь включил флажок «Read only»;
OFN_SHOWHELP
если этот флаг определён, то в диалоге появляется кнопка Help, при нажатии на которую посылается сообщение HELPMSGSTRING окну, описатель которого занесён в поле hwndOwner. Чтобы получить численное значение сообщения, вы должны вызвать функцию RegisterWindowMessage(HELPMSGSTRING);
OFN_ALLOWMULTISELECT
если этот флаг определён, пользователь может выбрать несколько файлов. В этом случае имена файлов возвращаются в буфере из поля lpstrFile таким образом: сначала идет имя каталога, затем символ с кодом нуль, затем список имен выбранных файлов, также разделённых нулевым символом.
nFileOffset
определяет позицию (отсчет ведется с нуля), с которой начинается имя файла с расшрением, в буфере из поля lpstrFile. Например, для строки 'c:\dir1\dir2\file.ext', это поле будет иметь значение 13 – номер позиции, с которой начинается имя файла 'file.ext';
nFileExtension
определяет позицию (отсчет ведется с нуля), с которой начинается расширение выбранного пользователем файла. Если пользователь не указал расширения и поле lpstrDefExt есть nil, то позиция определяет завершающий нулевой символ. Если пользователь не указал расширение, но поставил точку в конце имени файла, то позиция равна нулю;
lpstrDefExt
определяет строку с расширением по умолчанию, которое добавляется, если пользователь не указал расширения. Строка может быть любой длины, но добавляются только первые три символа. Строка не может содержать символа «точка» (.);
lCustData
некоторые данные, которые передаются диалогу при создании;
lpfnHook
указатель на hook-функцию, которая управляет диалогом. Используется, когда вы хотите добавить в диалого дополнительные элементы управления, например;
lpTemplateName
определяет идентификатор ресурса диалога для изменения внешнего вида диалога;
pvReserved
зарезервировано
dwReserved
зарезервировано
FlagsEx
Только для Windows 2000/XP. Может содержать константу OFN_EX_NOPLACESBAR, которая прячет полосу в левой части окна с пиктограммами рабочего стола и т.п.

Нужно отметить, что вышеописанная запись используется ещё и в функции GetSaveFileName и разница между этими функциями небольшая – фактически только в надписи на кнопке «Open» или «Save», поэтому пример только для функции GetOpenfileName.

Как сделать

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

Во-первых, константа с файловым фильтром. В данном примере определяется константа с двумя фильтрами: Text files (*.txt)'#0'*.txt' и All files'#0'*.*'. Разделяются фильтры символом с кодом нуль (#0), завершается строка двумя нулевыми символами. Каждый фильтр состоит из текста фильтра, затем нулевой символ, затем сам фильтр – файловая маска.
const
    szFileFilter: string = 'Text files (*.txt)'#0'*.txt'#0'All files'#0'*.*'#0#0;

Определяем глобальные переменные, в основном буферы для хранения строк.
var
    lpCustFilter: array[0..255] of char = '';
    nFilterIndex: DWORD = 0;
    szFile: array[0..2048] of char = '';
    szFileTitle: array[0..255] of char;
    szCurrentDir: array[0..1024] of char = '';

Теперь пишем код для вызова функции GetOpenFileName. В данном случае код находится внутри оконной функции – это реакция на нажатие кнопки.
var
    ...
    ofn:        TOPENFILENAME;
    s:          string;
    cf:         PChar;
    errcode:    DWORD;
...

ofn.lStructSize := sizeof(TOPENFILENAME);
ofn.hWndOwner := wnd;
ofn.hInstance := hInstance;
ofn.lpstrFilter := @szFileFilter[1]; //преобразуем тип string к типу PChar
ofn.lpstrCustomFilter := lpCustFilter;
ofn.nMaxCustFilter := sizeof(lpCustFilter);
ofn.nFilterIndex := nFilterIndex;
ofn.lpstrFile := szFile;
ofn.nMaxFile := sizeof(szFile);
ofn.nFilterIndex := 0;

ofn.lpstrFileTitle := szFileTitle;
ofn.nMaxFileTitle := sizeof(szFileTitle);
ofn.lpstrInitialDir := szCurrentDir; // Глобальная переменная, где хранится адрес текущего каталога.
            Текущий каталог для ЭТОЙ функции, не для программы!
ofn.lpstrTitle := 'Заголовок диалога Open file';
ofn.Flags := OFN_EXPLORER or OFN_ALLOWMULTISELECT  or
            OFN_ENABLESIZING or OFN_SHOWHELP or OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST;
ofn.lpstrDefExt := 'txt';
ofn.lpfnHook := nil;
ofn.lpTemplateName := nil;
ofn.lCustData := 0;

if GetOpenFileName(ofn) then begin
    //т.е. пользователь выбрал файл(ы) и нажал OK

    nFilterIndex := ofn.nFilterIndex; // Запоминаем номер текущего фильтра
    if length(ofn.lpstrFile) < ofn.nFileOffset then begin
        // т.е. пользователь открыл несколько файлов
        cf := szFile;
        inc(cf, ofn.nFileOffset);

        s := 'Вы выбрали НЕСКОЛЬКО файлов в каталоге'#13#10;
        s := s + szFile + ''#13#10;
        repeat
            s := s + cf + #13#10;
            inc(cf, length(cf)+1);
        until length(cf)=0;
    end else begin
        // Т.е. пользователь открыл всего один файл
        s := 'Вы выбрали всего ОДИН файл: ' + szFile;
    end;
end else begin
    //либо пользователь нажал Cancel, либо произошла какая-то ошибка
    errcode := CommDlgExtendedError;
    case errcode of
        0: s := 'Вы нажали CANCEL';
    else
        s := 'Ошибка с кодом ' + IntToStr(errcode);
    end;
end;
say(wnd, s);

Используемая в примере функция CommDlgExtendedError возвращает код ошибки от выполнения самого последнего вызова функции из модуля commdlg.

Весь код программы вы можете скачать здесь.

Диалоговое окно «Save File»

Диалоговое окно «Save As», вызывается абсолютно точно так же, как и окно «Open File», только вместо GetOpenFileName вызывается GetSaveFileName. Ещё нужно добавить в список флагов флаг OFN_OVERWRITEPROMPT для того, чтобы появлялся запрос на перезапись существующего файла.

Диалоговое окно «Color»

Стандартное диалоговое окно выбора цвета обычно выглядит так:

стандартное диалоговое окно выбора цвета

Для его создания использутся функция ChooseColor и запись TChooseColor. Вот заголовок функции и все поля записи, а также их описание.
function ChooseColor(var CC: TChooseColor): Bool; stdcall;
ChooseColor = packed record
    lStructSize: DWORD;
    hWndOwner: HWND;
    hInstance: HWND;
    rgbResult: COLORREF;
    lpCustColors: ^COLORREF;
    Flags: DWORD;
    lCustData: LPARAM;
    lpfnHook: function(Wnd: HWND; Message: UINT; wParam: WPARAM; lParam: LPARAM): UINT stdcall;
    lpTemplateName: PAnsiChar;
end;

lStructSize
размер данного типа, должен быть равен sizeof(TCHOOSECOLOR);
hwndOwner
описатель родительского окна для данного диалога, если это поле равно нулю, то диалог не имеет родителя и отображается на панели задач;
rgbResult
если установлен флаг CC_RGBINIT, то это поле определяет начальный цвет, выбранный в диалоге, точнее ближайший из доступных в системе. Если флаг не установлен, то начальный цвет – черный. Если пользователь щелкнул по кнопке ОК, то это поле после завершения работы диалога содержит выбранный цвет. Цвет в формате RGB.
lpCustColors
указатель на массив из 16 значений типа COLORREF, которые содержат цветовые значения в поле Custom colors (см. рисунок выше);
Flags
набор флагов для инициализации диалога и получения информации, когда он завершается. Должен быть комбинацией следующих констант, соединённых оператором or:
CC_FULLOPEN
окно появляется изначально с возможностью выбоа любого цвета и не надо нажимать кнопку «Define custom colors»;
CC_PREVENTFULLOPEN
кнопка «Define Custom Colors » недоступна;
CC_RGBINIT
если этот флаг определён, то диалог использует значение из поля rgbResult для задания текущего (выбранного) цвета;
CC_SHOWHELP
если этот флаг определен, то на диалоге появляется кнопка «Help»;
CC_SOLIDCOLOR
если этот флаг определён, то в поле всех цветов отображаются только сплошные, но не смешанные цвета.
lCustData
некоторое число, которое передается в оконную процедуру диалога. Задается, если вы используете hook-функцию для этого диалога;

Как сделать

Для начала определим глобальные переменные, чтобы информация, полученная в какой-нибудь момент, была доступна позже.
var
    custColors: array[0..15] of COLORREF;      // массив с преопределенными цветами
    crCurColor: COLORREF = $FFFFFF; // Белый цвет - начальный

А вот сам код, создающий этот диалог.
var
    cc: TChooseColor;
    hbrMain: HBRUSH;
...

cc.lStructSize := sizeof(TCHOOSECOLOR);
cc.hWndOwner := wnd;
cc.Flags :=  CC_RGBINIT;
cc.lpCustColors := @custcolors[0];
cc.rgbResult := crCurColor;

if ChooseColor(cc) then begin
    // т.е. пользователь нажал но ОК

    // создаем кисть выбранного цвета
    hbrMain := CreateSolidBrush(cc.rgbResult);

    // меняем цвет кисть фона окна на нашу
    SetClassLong(wnd, GCL_HBRBACKGROUND,hbrMain);

    // перерисовываем окно
    InvalidateRect(wnd, nil, true);
end;

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

Диалоговое окно «Font»

Стандартное диалоговое окно выбора шрифта выглядит так:

стандартное диалоговое окно выбора шрифта

Для его создания используется функция ChooseFont и запись TChooseFont.
function ChooseFont(var ChooseFont: TChooseFont): Bool; stdcall;
TChooseFont = packed record
    lStructSize: DWORD;
    hWndOwner: HWND;
    hDC: HDC;
    lpLogFont: PLogFontW;
    iPointSize: Integer;
    Flags: DWORD;
    rgbColors: COLORREF;
    lCustData: LPARAM;
    lpfnHook: function(Wnd: HWND; Message: UINT; wParam: WPARAM; lParam: LPARAM): UINT stdcall;
    lpTemplateName: PWideChar;
    hInstance: HINST;
    lpszStyle: PWideChar;
    nFontType: Word;
    wReserved: Word;
    nSizeMin: Integer;
    nSizeMax: Integer;
end;

lStructSize
размер данной записи, нужно использовать sizeof(TChooseFont);
hwndOwner
описатель владельца данного диалога, может быть нулем, если владельца быть не должно;
hDC
описатель графического контекста принтера, шрифты которого нужно включить в список. Это поле используется только если установлены флаги CF_PRINTERFONTS или CF_BOTH;
lpLogFont
указатель на запись типа TLogFont. Если установлен флаг CF_INITTOLOGFONTSTRUCT, то данные из этого поля используются для инициализации диалога. Если пользователь нажал на ОК в диалоге, то в это поле помещается результат о выбранном шрифте;
iPointSize
определяет размер выбранного шрифта (в 1/10 пункта). После закрытия диалога содержит размер шрифта, выбранного пользователем;
Flags
набор битовых флагов для установки параметров диалогового окна. Вот некоторое из них:
CF_APPLY
на диалоге появляется кнопка «Apply», для обработки нажатия на эту кнопку нужно использовать hook-функцию для этого диалога;
CF_BOTH
этот флаг является комбинацией флагов CF_SCREENFONTS и CF_PRINTERFONTS, в диалоге отображаются как экранные, так и принтерные шрифты. Принтер определяется своим контекстом, переданным в поле hDC
CF_TTONLY
определяет, что в диалоге будут показаны только шрифты TrueType;
CF_EFFECTS
этот флаг позволяет устанавливать в диалоге такие параметры шрифта как цвет, подчеркивание, зачеркивание, вы можете использовать поле rgbColors для задания начального цвета шрифта;
CF_FIXEDPITCHONLY
если этот флаг установлен, то отображаются только моноширинные шрифты;
CF_FORCEFONTEXIST
не позволяет пользователю выбрать некорректные или несуществующие шрифты;
CF_INITTOLOGFONTSTRUCT
определяет, что нужно использовать поле pLogFont для инициализации диалога;
CF_LIMITSIZE
определяет, что отображаются шрифты с размером только из диапазона, определяемого полями nSizeMin и nSizeMax;
CF_NOFACESEL
если установлен этот флаг, то поле выбора имени шрифта не инициализируется;
CF_NOSCRIPTSEL
этот флаг делает недоступным выпадающий список «Script» в диалоге, возвращаемый тип набора символов равен DEFAULT_CHARSET;
CF_NOSTYLESEL
если установлен этот флаг, то поле выбора стиля шрифта (список «Font style») не инициализируется;
CF_NOSIZESEL
если установлен этот флаг, то поле выбора размера шрифта не инициализируется;
CF_NOSIMULATIONS
если установлен этот флаг, от отображаются только реальные шрифты;
CF_NOVECTORFONTS
если установлен этот флаг, то векторные шрифты не отображаются;
CF_NOVERTFONTS
если установлен этот флаг, то отображаются только горизонтально ориентированные шрифты;
CF_PRINTERFONTS
отображаются только шрифты принтера, определяемого контекстом в поле hDC;
CF_SCALABLEONLY
отображаются только масштабируемые шрифты;
CF_SCREENFONTS
отображаются только экранные шрифты;
CF_SELECTSCRIPT
если установлен этот флаг, то отобоажаются только шрифты с набором символов, установленным в поле pLogFont (точнее, в поле lfCharSet записи TLogFont, на которую указывает поле pLogFont);
CF_SHOWHELP
на диалоге отображается кнопка «Help»;
CF_USESTYLE
если установлен этот флаг, то поле lpszStyle будет использоваться для инициализации стиля шрифта;
CF_WYSIWYG
если установлен это флаг, то отображаются только шрифты, доступные как на экране, так и на принтере одновременно.
rgbColors
если установлен флаг CF_EFFECTS, это поле используется для задания начального цвета шрифта. После закрытия окна содержит цвет шрифта, выбранный пользователем;
lCustData
содержит значение, передаваемое в hook-процедуру при инициализации диалога;
lpfnHook
указатель на hook-функцию для этого диалога;
lpTemplateName
строка с именем ресурса диалогового окна для переопределения текущего вида окна;
hInstance
либо описатель модуля с шаблоном диалогового окна, либо описатель блока памяти с этим шаблоном4
lpszStyle
указатель на буфер, сожержащий стиль шрифта. Если установлен флаг CF_USESTYLE, это поле используется для инициализации. После закрытия окна содержит стиль, установленный пользователем;
nFontType
определяет тип возвращенного шрифта, является набором следующих флагов:
BOLD_FONTTYPE
возвращен полужирный шрифт;
ITALIC_FONTTYPE
возвращен наклонный шрифт;
PRINTER_FONTTYPE
возвращен принтерный шрифт;
REGULAR_FONTTYPE
возвращен нормальный шрифт (нормальной жирности);
SCREEN_FONTTYPE
возвращен экранный шрифт;
SIMULATED_FONTTYPE
возвращен шрифт, сгенерированный GDI;
wReserved
зарезервировано в целях выравнивания;
nSizeMin
определяет минимальный размер шрифта, который может выбрать пользователь, срабатывает, только если установлен флаг CF_LIMITSIZE;
nSizeMax
определяет максимальный размер шрифта, который может выбрать пользователь, срабатывает, только если установлен флаг CF_LIMITSIZE;

Как сделать

Порядок работы такой же: заполнить поля записи, вызвать функцию ChooseFont с этой записью в качестве параметра, проанализировать результат. Демонстрационная программа представляет собой окно с кнопкой и дочерним окном типа static с надписью. При нажатии на кнопку появляется диалог выбора шрифта. Выбранный шрифт используется как новый шрифт для окна типа static (тестовое окно).

Обратите внимание, что функция ChooseFont не создает сам шрифт, а лишь его описание, расположенное в записи типа TLogFont. Чтобы создать шрифт, нужно вызвать функцию CreateFontIndirect с этой записью в качестве параметра.

Сначала определяем глобальные переменные.
var
    lfFont: TLogFont;          // данные о текущем шрифте
    fntFont: HFONT = 0;        // описатель текущего шрифта
    crTextColor: COLORREF = 0; // текущий цвет
    hbrBrush: HBRUSH;          // текущая кисть для закраски фона тестового окна

В оконной функции определяем обработчик нажатия кнопки:
var
    cf: TCHOOSEFONT;  // запись с параметрами вызова функции
    fnt: HFONT;

...

IDOK : begin
    cf.lStructSize := sizeof(TCHOOSEFONT);
    cf.hWndOwner := wnd; // описатель главного окна
    
    // разрешаем использование эффектов, инициализируем диалог данными о текущем шрифте, 
    // разрешаем только экранные шрифты
    cf.Flags := CF_EFFECTS or CF_INITTOLOGFONTSTRUCT or CF_NOSIMULATIONS or CF_SCREENFONTS;
    cf.lpLogFont := @lfFont;
    // устанавливаем начальный шрифт в диалоге
    cf.rgbColors := crTextColor;
    // создаем диалог при помощи вызова функции ChooseFont
    if ChooseFont(cf) then begin
        // если пользователь нажал ОК
        // запоминаем выбранный цвет
        crTextColor := cf.rgbColors;
        // создаем шрифт по его описанию, полученному из  диалога
        fnt := CreateFontIndirect(lfFont);
        // устанавливаем новый шрифт в тестовом окне
        SendMessage(wndStatic, WM_SETFONT, fnt, 1);
        // удаляем старый (предыдущий) шрифт
        DeleteObject(fntFont);
        fntFont := fnt;
    end;
end;

К сожалению, цвет шрифта тестового окна так просто не изменить, поэтому ставим обработчик сообщения WM_CTLCOLORSTATIC, которое посылается, если нужно перерисовать дочерние элементы управления типа static. В параметре wParam этого сообщения передаётся описатель контекста элемента, а в lParam его описатель как окна.
WM_CTLCOLORSTATIC: begin
    if lParam=wndStatic then begin
        // устанавливаем новый цвет шрифта
        SetTextColor(wParam, crTextColor);
        // устанавливаем фон текста
        SetBkColor(wParam, GetSysColor(COLOR_BTNFACE));
        // возвращаемое значение должно быть описателем кисти, которой
        // будет закрашен фон элемента управления
        result := hbrBrush;
    end;
end;

Значение переменной hbrBrush нужно определить перед созданием тестового окна:
// создаем однородную кисть такого же цвета, как цвет всего окна
hbrBrush := CreateSolidBrush(GetSyscolor(COLOR_BTNFACE));
Назад|Содержание|Вперед

Хостинг от uCoz