:: MVP ::
|
|
:: RSS ::
|
|
|
Программировать интерфейс – это конечно здорово, если разрабатываемое приложение будет иметь мало элементов управления
(такое, как например keygen!). В противном случае может оказаться так, что львиную долю кода мы напишем для создания
интерфейса (расположение контролов, их выравнивание и т.д.), и лишь несколько строк будут составлять функциональное
наполнение программы. В таких случаях целесообразнее использовать ресурсы, о чем и пойдет речь в данной статье.
Использование ресурсов несколько увеличит размер исполняемого файла, но я не думаю что сейчас это столь критично,
чтобы отказываться от использования ресурсов в пользу программирования интерфейса.
Начнем с простого, создадим форму с несколькими элементами управления, и далее, по ходу дела будем ее дорабатывать и
наделять функционалом. Как работать с ресурсами в Delphi я уже писал и повторяться не буду, так что кому интересно,
читаем статью "Хранение ресурсов внутри исполняемого файла и их
использование".
С чего же начать? Конечно же, с создания ресурса, из которого мы и получим окно. Разберем структуру этого ресурса.
#define IDC_EDT1 1001
#define IDC_STC1 1002
#define IDC_BTN1 1003
DLG_WINDOW DIALOGEX 10,10,216,135
CAPTION "Пример использования ресурсов"
FONT 8,"MS Sans Serif",0,0
STYLE WS_VISIBLE|WS_CAPTION|WS_SYSMENU
EXSTYLE WS_EX_DLGMODALFRAME|WS_EX_APPWINDOW|WS_EX_CLIENTEDGE
{
CONTROL "",IDC_EDT1,"Edit",WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP,6,15,183,15,WS_EX_CLIENTEDGE
CONTROL "Введите любой текст",IDC_STC1,"Static",WS_CHILDWINDOW|WS_VISIBLE,6,3,75,9
CONTROL "...",IDC_BTN1,"Button",WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP,195,15,15,15
}
|
Объявляется окно с идентификатором DLG_WINDOW, указываются размеры и положение окна, заголовок, параметры шрифта и стили.
Внутри фигурных скобок перечисляются элементы управления, после ключевого слова CONTROL указываем необходимые параметры:
текст, идентификатор, класс, стили, позицию и размеры, при необходимости указывается расширенный стиль. Ничего сложного нет,
суть очень напоминает программный способ создания элементов управления, разница лишь в синтаксисе. Вместо фигурных скобок
допускается использование ключевых слов BEGIN и END.
В качестве идентификаторов не обязательно использовать константы, но мне это представляется более удобным, так что приведенный
выше код мог бы выглядеть и так:
DLG_WINDOW DIALOGEX 10,10,216,135
CAPTION "Пример использования ресурсов"
FONT 8,"MS Sans Serif",0,0
STYLE WS_VISIBLE|WS_CAPTION|WS_SYSMENU
EXSTYLE WS_EX_DLGMODALFRAME|WS_EX_APPWINDOW|WS_EX_CLIENTEDGE
BEGIN
CONTROL "",1001,"Edit",WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP,6,15,183,15,WS_EX_CLIENTEDGE
CONTROL "Введите свое имя",1002,"Static",WS_CHILDWINDOW|WS_VISIBLE,6,3,75,9
CONTROL "...",1003,"Button",WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP,195,15,15,15
END
|
Половина дела сделана, осталось создать приложение на основе этого ресурса, для чего компилируем его, переходим в Delphi и пишем:
program Project1;
uses
Windows, Messages;
{$R Dialog.res}
(* Оконная процедура *)
function DlgProc( Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM ): bool; stdcall;
begin
case Msg of
WM_DESTROY, WM_CLOSE: begin
Result := true;
PostQuitMessage( 0 );
Exit;
end;
end;
end;
begin
DialogBox( hInstance, 'DLG_WINDOW', 0, @DlgProc );
end.
|
Сложно представить себе что то проще, диалоговое окно со всеми элементами управления создается одной стройкой! Все что нужно –
указать идентификатор окна и назначить ему оконную процедуру, в которой пока что обрабатывается только сообщение о закрытии программы.
Сейчас программе явно не хватает индивидуальности, которую ей могла бы придать иконка. Для этого создадим еще один файл ресурсов
(вообще то можно все писать и в одном файле, но в примере я буду делать отдельные файлы, полагая что это упростит освоение материала).
1 ICON "icon1.ico"
2 ICON "icon2.ico"
3 ICON "icon3.ico"
4 ICON "icon4.ico"
5 ICON "icon5.ico"
|
Сначала идет идентификатор ресурса, потом его тип, и в конце путь к файлу с иконкой. Кстати, иконка не обязательно должна быть одна
(в данном примере их пять), можете предоставить пользователю возможность самому выбрать ту, которая ему больше понравится.
Назначить иконку окну можно в любой момент, но все же лучше сделать это в момент запуска программы, для чего нужно обработать
сообщение WM_INITDIALOG:
{$R Icon.res}
{...}
(* Оконная процедура *)
function DlgProc( Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM ): bool; stdcall;
begin
case Msg of
Result := true;
WM_INITDIALOG: SendMessage( Wnd, WM_SETICON, 1, LoadIcon( HInstance, MAKEINTRESOURCE( 1 ) ) );
{...}
end;
end;
|
Смотрим дальше, и вот незадача, на компе уже давно тусуется Windows 7 (ну или в худшем случае XP), а контролы выглядят как в
Windows 2000. Кто ж не такую программу позарится, надо срочно исправлять. Но как? Да проще некуда, добавим в ресурсы манифест,
содержание которого будет примерно следующим:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="X86"
name="Company.Product.Name"
type="win32"
/>
<description></description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="X86"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
|
Создаем файл ресурса:
Суть содержимого файла ресурса должна быть уже понятна – идентификатор ресурса, его тип и путь к файлу манифеста.
Компилируем, и просто подключаем к программе.
Разговор об интерфейсе закончим созданием главного меню. Не будем делать его навороченным, нам ведь главное понять принцип.
#define IDR_MENU1 10000
#define IDM_FILE 10001
#define IDM_EXIT 10002
#define IDM_HELP 10003
#define IDM_PROPERTY 10004
IDR_MENU1 MENU
BEGIN
POPUP "Файл"
BEGIN
MENUITEM "Выход\tCtrl+Q",IDM_EXIT
END
POPUP "Помощь"
BEGIN
MENUITEM "Свойства файла",IDM_PROPERTY
END
END
|
Описание меню смеет иерархическую структуру, в которой пункты главного меню обозначаются ключевым словом POPUP,
а элементы выпадающего меню ключевым словом MENUITEM. Давайте подключим это меню к программе.
{$R MainMenu.res}
var
Menu: HMenu;
(* Оконная процедура *)
function DlgProc( Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM ): bool; stdcall;
begin
Result := false;
case Msg of
WM_INITDIALOG: begin
{...}
Menu := LoadMenu( hInstance, MAKEINTRESOURCE( IDR_MENU1 ) );
SetMenu( Wnd, Menu );
end;
{...}
end;
end;
|
Теперь поговорим о функционале, и о том, как работать с элементами пользовательского интерфейса. Нажатие на кнопку
или пункт системного меню генерирует сообщение WM_COMMAND, в младшем слове параметра wParam которого содержится
идентификатор элемента управления, инициировавшего событие. Попробуем наделить функционалом пункт меню “Выход”.
(* Оконная процедура *)
function DlgProc( Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM ): bool; stdcall;
begin
case Msg of
{...}
WM_COMMAND: begin
case LoWord( wParam ) of
IDM_EXIT: EndDialog( Wnd, 0 );
end;
end;
end;
end;
|
Вы заметили, что рядом с пунктом меню "Выход" указан акселератор (или, говоря иначе, комбинация горячих клавиш)?
Однако пока это всего лишь часть заголовка меню, надо это исправить. К сожалению, хотя горячие клавиши и можно
поместить в ресурсы, подключить их у меня не получилось, а дело здесь в следующем. Если бы мы создавали форму не
из ресурса, мы сами бы написали для нее цикл обработки сообщений. А так как нажатие "горячих" клавиш не нужно
транслировать в символьные сообщения, цикл выглядел бы следующим образом:
var
hAccelTable: HACCEL;
{...}
hAccelTable := LoadAccelerators( hInstance, MAKEINTRESOURCE( IDR_ACCEL1 ) ) ;
while GetMessage( Msg, 0, 0, 0 ) do
begin
if TranslateAccelerator( Msg.hwnd, hAccelTable, Msg ) = 0 then
begin
TranslateMessage( Msg );
DispatchMessage( Msg );
end;
end;
|
Создавая форму из ресурса мы назначаем ей оконную процедуру, сообщения в которую приходят уже транслированные,
и в этом вся загвоздка. Однако ситуация не безвыходная, решение существует, и заключается оно в обработке
сообщения WM_MENUCHAR. Получив это сообщение мы проверяем, какие клавиши нажаты в данный момент на клавиатуре,
и если нажата нужная нам комбинация, выполняем соответствующее действие. На практике это выглядит так:
(* Оконная процедура *)
function DlgProc( Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM ): bool; stdcall;
var
{...}
State: TKeyboardState;
begin
Result := false;
case Msg of
{...}
WM_MENUCHAR: begin
GetKeyboardState( State );
if ( ( State[Ord( 'Q' )] and 128 ) <> 0 ) and ( ( State[Ord( VK_CONTROL )] and 128 ) <> 0 ) then
//if ( GetAsyncKeyState( Ord( 'Q' ) ) <> 0 ) and ( ( State[Ord( VK_CONTROL )] and 128 ) <> 0 ) then
begin
DlgProc( Wnd, WM_COMMAND, IDM_EXIT, 0 );
Result := true;
end;
end;
{...}
end;
end;
|
Для того чтобы диалоговая форма начала получать сообщение WM_MENUCHAR, меню должно быть раскрыто, или
хотя бы активировано, что достигается нажатием клавиши Alt.
Теперь переходим к работе с контролами. Для получения и изменения текста нужно воспользоваться функциями
GetDlgItemText и SetDlgItemText. Попробуем получать текст из элемента “Edit” по мере его заполнения и
выводить в элемент “Static”.
(* Оконная процедура *)
function DlgProc( Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM ): bool; stdcall;
var
p: PChar;
n: integer;
begin
Result := false;
case Msg of
WM_INITDIALOG: begin
{...}
SetDlgItemText( Wnd, IDC_STC2, PWideChar( 'Здравствуйте, ' ) );
end;
{...}
WM_COMMAND: begin
case LoWord( wParam ) of
{...}
IDC_EDT1: begin
case HiWord( wParam ) of
EN_CHANGE: begin
Result := true;
n := GetWindowTextLength( lParam ) + 1;
GetMem( p, n );
GetDlgItemText( Wnd, IDC_EDT1, p, n );
SetDlgItemText( Wnd, IDC_STC2, PWideChar( 'Здравствуйте, ' + p ) );
FreeMem( p, n );
end;
end;
end;
end;
end;
end;
end;
|
Так как я делаю пример на Delphi XE, то привожу строку к типу PWideChar, если же у вас версия Delphi ниже 2009,
то приводить (здесь и в дальнейшем) следует к типу PChar.
В приведенном примере мы объединяем введенный пользователем текст со строкой, а почему бы эту строку не поместить в ресурс?
STRINGTABLE DISCARDABLE
LANGUAGE LANG_RUSSIAN, 0x1
{
2000 "Здравствуйте, %s."
}
|
Приветственное сообщение будем показывать после нажатия на кнопку, код в этом случае будет несколько отличаться от приведенного выше.
(* Оконная процедура *)
function DlgProc( Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM ): bool; stdcall;
var
p: PChar;
n: integer;
begin
Result := false;
case Msg of
{...}
WM_COMMAND: begin
case LoWord( wParam ) of
{...}
IDC_BTN1: begin
Result := true;
n := GetWindowTextLength( GetDlgItem( Wnd, IDC_EDT1 ) ) + 1;
GetMem( p, n );
GetDlgItemText( Wnd, IDC_EDT1, p, n );
MessageBox( Wnd, PWideChar( Format( LoadStr( 2000 ), [p] ) ), 'Приветствие!', 0 );
FreeMem( p, n );
end;
end;
end;
end;
end;
|
Если при обработке сообщения от “Edit” мы получали Handle элемента управления в параметре lParam, то теперь нам нжно получить его
самостоятельно, для чего нужно воспользоваться функцией GetDlgItem. Строку загружаем из ресурса, используя функцию LoadStr,
передав в нее идентификатор строки.
И вот, когда программа уже готова, не плохо бы включить в ресурсы краткую информацию о ней е ее разработчике.
1 VERSIONINFO
FILEVERSION 1,0,0,0 // Версия файла
PRODUCTVERSION 1,0,0,0 // Версия продукта
FILEOS VOS__WINDOWS32 // Операционная система
FILETYPE VFT_APP // Тип файла
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "041904E3" // Блок с информацией на русском языке
BEGIN
VALUE "FileVersion", "1.0.0.0\0"
VALUE "ProductVersion", "1.0.0.0\0"
VALUE "InternalName", "Project1.exe\0" // Внутреннее имя
VALUE "OriginalFilename", "PROJECT1.EXE\0" // Оригинальное имя файла
VALUE "FileDescription", "Пример работы с ресурсами\0" // Продукт
VALUE "ProductName", "Project1\0"
VALUE "CompanyName", "Decoding™\0"
VALUE "LegalCopyright", "©Decoding. Все права защищены.\0" // Авторское право
VALUE "LegalTrademarks", "Торговая марка\0"
VALUE "Comments", "Комментарий\0"
VALUE "CustomValue1", "Дополнительное значение 1\0"
VALUE "CustomValue2", "Дополнительное значение 2\0"
END
BLOCK "041904E3" // Блок с информацией на английском языке
BEGIN
VALUE "FileVersion", "1.0.0.0\0"
VALUE "ProductVersion", "1.0.0.0\0"
VALUE "InternalName", "Project1.exe\0" // Внутреннее имя
VALUE "OriginalFilename", "PROJECT1.EXE\0" // Оригинальное имя файла
VALUE "FileDescription", "An example of working with resources\0" // Продукт
VALUE "ProductName", "Project1\0"
VALUE "CompanyName", "Decoding™\0"
VALUE "LegalCopyright", "©Decoding. All Rights Reserved.\0" // Авторское право
VALUE "LegalTrademarks", "Trademark\0"
VALUE "Comments", "Comments\0"
VALUE "CustomValue1", "Additional value 1\0"
VALUE "CustomValue2", "Additional value 2\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0419, 0x04E3, 0x0409, 0x04B0
END
END
|
Первым идет идентификатор информационного ресурса, это значение должно равняться 1. Далее следует информация о версии файла,
его типе и операционной системе, для которой он предназначен (за подробностями отправляемся в MSDN (ищите по словосочетанию
“VERSIONINFO Resource”) – найдете что то вроде
этого, этого или
этого).
Далее следует блок “StringFileInfo”, который, в свою очередь, включает в себя блоки с информацией о файле. Количество блоков может
быть произвольным, в зависимости от того, на какой рынок ориентирована ваша программа. В данном примере присутствует два блока – первый
для русифицированных операционных систем, второй – для англоязычных. Имя блока формируется следующим образом: язык + кодовая страница:
041904E3 = 0x0419 (1049, Russian) + 0x04E3 (1251, Cyrillic)
040904B0 = 0x0409 (1033, U.S. English) + 0x04B0 (1200, Unicode)
Далее, в блоке VarFileInfo, мы указываем, поддержку каких языков мы включили в ресурс.
Теперь, если мы откроем свойства файла, мы увидим нашу информацию. Правда не всю, Windows покажет нам только те предопределенные
значения, о которых она знает, а именно: Comments, CompanyName, FileDescription, FileVersion, InternalName, LegalCopyright, LegalTrademarks,
OriginalFilename, PrivateBuild, ProductName, ProductVersion, SpecialBuild.
Научим нашу программу показывать эту информацию.
function FileVersion( Info: string ): string;
var
szName: array[0..255] of Char;
P: Pointer;
Value: Pointer;
Len: UINT;
GetTranslationString: string;
FFileName: PChar;
FValid: boolean;
FSize: DWORD;
FHandle: DWORD;
FBuffer: PChar;
begin
try
FFileName := StrPCopy( StrAlloc( Length( ParamStr( 0 ) ) + 1 ), ParamStr( 0 ) );
FValid := False;
FSize := GetFileVersionInfoSize( FFileName, FHandle );
if FSize > 0 then
try
GetMem( FBuffer, FSize );
FValid := GetFileVersionInfo( FFileName, FHandle, FSize, FBuffer );
except
FValid := False;
raise;
end;
Result := '';
if FValid then
VerQueryValue( FBuffer, '\VarFileInfo\Translation', P, Len )
else
P := nil;
if P <> nil then
GetTranslationString := IntToHex( MakeLong( HiWord( Longint( P^ ) ),
LoWord( Longint( P^ ) ) ), 8 );
if FValid then
begin
StrPCopy( szName, '\StringFileInfo\' + GetTranslationString + '\' + Info );
if VerQueryValue( FBuffer, szName, Value, Len ) then
Result := StrPas( PChar( Value ) );
end;
finally
if FBuffer <> nil then
FreeMem( FBuffer, FSize );
StrDispose( FFileName );
end;
end;
(* Оконная процедура *)
function DlgProc( Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM ): bool; stdcall;
var
{...}
StrMsg: string;
begin
Result := false;
case Msg of
{...}
WM_COMMAND: begin
case LoWord( wParam ) of
{...}
IDM_PROPERTY: begin
StrMsg := 'Компания:' + #9#9#9 + FileVersion( 'CompanyName' ) + #13 +
'Описание файла:' + #9#9#9 + FileVersion( 'FileDescription' ) + #13 +
'Версия файла:' + #9#9#9 + FileVersion( 'FileVersion' ) + #13 +
'Внутреннее имя:' + #9#9#9 + FileVersion( 'InternalName ') + #13 +
'Авторское право:' + #9#9#9 + FileVersion( 'LegalCopyright' ) + #13 +
'Торговая марка:' + #9#9#9 + FileVersion( 'LegalTrademarks' ) + #13 +
'Оригинальное имя файла:' + #9#9 + FileVersion( 'OriginalFilename' ) + #13 +
'Название продукта:' + #9#9#9 + FileVersion( 'ProductName' ) + #13 +
'Версия продукта:' + #9#9#9 + FileVersion( 'ProductVersion' ) + #13 +
'Комментарий:' + #9#9#9+ FileVersion( 'Comments' ) + #13 +
'Дополнительная информация:' + #9 + FileVersion( 'CustomValue1' ) + #13 +
'Дополнительная информация:' + #9 + FileVersion( 'CustomValue2' );
MessageBox( Wnd, PWideChar( StrMsg ), 'Информация о файле', 0 );
end;
{...}
end;
end;
end;
end;
|
Пара слов о неприятных моментах. Компилируя ресурс утилитой brcc32.exe, входящей в состов BDS 2006 и выше (а возможно и BDS 2005, не проверял),
русские символы будут отображаться абракадаброй. В интернете можно найти информацию о том, что в библиотеки rw32core.dll, идущей в комплекте с
этими студиями, напрочь отсутствует VERSIONINFO. Звучит это несколько странно, если учесть тот факт, что сама студия без каких либо проблем
вставляет в файл этот ресурс, но все же поверим на слово. Решения тут 2 – либо все писать на английском, либо заменить эту библиотеку аналогичной
из D6 или D7 (другие не проверял).
Ну, вот и все, рассказал практически все, что хотел, осталось сказать несколько слов о создании файлов с ресурсами. Писать все это в ручную –
дело неблагодарное, отнимающее значительную часть времени, мало чем отличающееся от ручного программирования интерфейса. Процесс создания
файлов с ресурсами можно и нужно автоматизировать, и я хочу порекомендовать вам программу “Resource editor”. Благодаря визуальному редактору
проектировать интерфейс так же просто, как и в Delphi, да и создание ресурсов других типов не потребует от вас много сил и времени, так что
пользуйтесь, не стесняйтесь! И не забудьте в настройках на вкладке "Behaviour" установить
флажок "Borland compatible".
А я на этом прощаюсь, удачи в программировании.
.: Пример к данной статье :.
|
При использовании материала - ссылка на сайт обязательна
|
|