Глава 18. Более подробно о ресурсах


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

    Меню и курсоры - это два примера ресурсов программы Windows. Ресурсы это данные, хранимые в выполняемом (.EXE) файле программы, но они располагаются отдельно от обычного сегмента данных программы. Ресурсы разрабатываются и специфицируются вне кода программы, затем добавляются к скомпилированному коду программы для создания выполняемого файла программы.

    Следующие ресурсы вы будете создавать и использовать наиболее часто:

    Обычно при загрузке приложения в память Windows оставляет ресурсы на диске и загружает в случае необходимости отдельные ресурсы в процессе выполнения программы. За исключением побитовых отображений (графических образов), Windows удаляет ресурс из памяти после окончания его использования. Если вы хотите загрузить ресурс при загрузке программы или не хотите, чтобы Windows имела возможность удалить ресурс из памяти, вы можете сменить его атрибуты. Детальное описание создания и модификации ресурсов содержится в руководстве пользователя по пакету разработчика ресурсов.

Создание ресурсов


    Вы можете создавать ресурсы, используя редактор ресурсов или компилятор ресурсов. Паскаль поддерживает оба эти метода, поэтому вы сами можете выбрать, какой их них является более удобным. В большинстве случаев проще использовать редактор ресурсов и создавать ваши ресурсы визуально. Однако, иногда более удобно использовать компилятор ресурсов для компиляции файла описания ресурса, который может вам встретиться в книге или в журнале. Независимо от выбранного вами подхода вы обычно создаете файл ресурса (.RES) для каждого приложения. Этот файл ресурса будет содержать двоичную информацию о всех меню, диалогах, графических образах и других ресурсах, используемых вашим приложением.

    Двоичный файл ресурса (.RES) добавляется к вашему исполняемому файлу (.EXE) в процессе компиляции с использованием директивы компилятора $R, как это описано в данной главе. Вы также должны написать код, который будет загружать ресурсы в память. Это придаст вам дополнительную гибкость, поскольку ваша программа будет использовать память лишь для ресурсов, которые используются в данный момент. Загрузка ресурсов в память также рассматривается в данной главе.

Добавление ресурсов к выполняемой программе


    Ресурсы хранятся в двоичном формате в файле .RES, поэтому они должны быть добавлены к выполняемому файлу приложения (.EXE). Результатом будет файл, который наряду со скомпилированным кодом приложения будет содержать и его ресурсы.

    Есть три способа добавления ресурсов к выполняемому файлу:

    - Можно использовать редактор ресурсов для копирования ресурсов из файла .RES в уже скомпилированный файл программы .EXE. Инструкции по этой операции содержатся в руководстве пользователя по пакету разработчика ресурсов.

    - Можно задать директиву в исходном коде файла. Например, эта программа на языке Паскаль:

        program SampleProgram;
        {$r SAMPLE.RES}
          .
          .
          .

    добавит файл ресурсов SAMPLE.RES к выполняемому файлу. Каждая программа на языке Паскаль может иметь только один файл ресурсов (хотя этот файл ресурсов может включать другие файлы ресурсов). Все эти файлы должны быть файлами .RES и хранить ресурсы в двоичном формате. Директива компилятора $R позволяет вам задать отдельный файл .RES.

    - Использовать компилятор ресурсов.

Загрузка ресурсов в приложение


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

Загрузка меню


    Меню окна является одним из атрибутов его создания. Другими словами это характеристика окна, которая должна быть задана до создания соответствующего элемента меню (с помощью метода Create). Следовательно, меню может быть задано в типе конструктора Init или вскоре после конструирования. Ресурсы меню загружаются вызовом функции Windows LoadMenu со строкой идентификатора меню при конструировании нового объекта окна. Например:

     constructor SampleMainWindow.Init(AParent: PWindowsObject;
                                       ATitle: PChar);
     begin
       TWindow.Init(AParent, ATitle);
       Attr.Menu:=LoadMenu(HInstance, PChar(100));
       .
       .
     end;

    Код PChar(100) переводит целое значение 100 в тип PChar, совместимый с Windows тип строки. LoadMenu загружает ресурс меню с идентификатором 100 в новый объект окна. Ресурс может иметь символьное имя (строку), например, 'SampleMenu', а не числовой идентификатор. В этом случае предыдущий код будет выглядеть следующим образом:

     constructor SampleMainWindow.Init(AParent: PWindowsObject;
                                       ATitle: PChar);
     begin
       TWindow.Init(AParent, ATitle);
       Attr.Menu:=LoadMenu(HInstance, 'SampleMenu');
       .
       .
     end;

    Дополнительная информация по созданию объектов окна содержится в Главе 10, "Объекты окна".

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

     procedure HandleMenu101(var Msg: TMessage);
                                         virtual cm_First+101;

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

Загрузка оперативных клавиш


    Оперативные клавиши - это активные клавиши или комбинации клавиш, которые используются для задания команд приложения. Обычно оперативные клавиши определяются как эквиваленты выбора пунктов меню. Например, клавиша Del - это стандартная оперативная клавиша, которую можно использовать как альтернативу выбора пункта Delete в меню Edit. Однако, оперативные клавиши могут реализовывать команды, которые не соответствуют элементам меню.

    Ресурсы оперативных клавиш хранятся в таблице оперативных клавиш. Для загрузки таблицы оперативных клавиш используется функция Windows LoadAccelerators, которая просто возвращает описатель таблицы. В отличие от ресурса меню, который связан с конкретным окном, ресурс оперативной клавиши принадлежит всему приложению. Каждое приложение может иметь только один такой ресурс. Объекты приложения резервируют одно поле объекта, HAccTable, для хранения описателя ресурса оперативных клавиш. Обычно вы будете загружать ресурс оперативных клавиш в методе объекта приложения InitInstance:

     procedure SampleApplication.InitInstance;
     begin
        TApplication.InitInstance;
        HAccTable := LoadAccelerators(HInstance,
                                      'SampleAccelerators');
     end;

    Часто вы будете определять оперативные клавиши для быстрого выбора вариантов меню. Например, Shift+Ins обычно используется для быстрого выбора команды Paste. Оперативные клавиши генерируют основанные на команде сообщения, которые идентичны сообщениям, генерируемым выбором пункта меню. Для привязки метода реакции на выбор в меню с соответствующей оперативной клавишей нужно убедиться в том, что определенное в ресурсе значение оперативной клавиши идентично идентификатору элемента меню.

Загрузка блоков диалога


    Блоки диалога являются единственным типом ресурса, который непосредственно соответствует типу объекта ObjectWindows. TDialog и его производные типы, включая TDlgWindow, определяют объекты интерфейса, которые используют ресурсы блока диалога. Каждый объект блока диалога обычно связан с одним ресурсом блока диалога, который задает его размер, местоположение и ассортимент управляющих элементов (таких, как командные кнопки и блоки списка).

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

     Adlg := New(PSampleDialog, Init(@Self, 'AboutBox'));
или

     
     Adlg := New(PSampleDialog, Init(@Self, PChar(120)));

    Дополнительная информация по созданию объектов диалога содержится в Главе 11, "Объекты диалоговых блоков".

Загрузка курсоров и пиктограмм


Каждый тип объекта окна имеет специальные атрибуты, называемые атрибутами регистрации. Среди этих атрибутов есть курсор окна и пиктограмма. Для установки этих атрибутов для типа окна вы должны определить метод GetWindowClass (как и GetClassName).

    Например, вы создаете курсор для выбора элементов в блоке списка. Курсор имеет вид указательного пальца и хранится в ресурсе курсора с именем 'Finger'. Кроме того, вы создаете ресурс пиктограммы с именем 'SampleIcon', который выглядит как улыбающееся лицо. Вы должны написать метод GetWindowClass следующим образом:

     procedure SampleWindow.GetWindowClass(var AWndClass:
                                           TWndClass);
     begin
        TWindow.GetWindowClass(AWndClass);
        AWndClass.hCursor:=LoadCursor(HInstance, 'Finger');
        AWndClass.hIcon:=LoadIcon(HInstance, 'SampleIcon');
     end;

    Однако, между курсором и пиктограммой имеется одно отличие. Оно состоит в том, что курсор задается для одного окна, а пиктограмма представляет все приложение. Следовательно, пиктограмма устанавливается в типе объекта только для основного окна. У этого правила имеется одно исключение: для приложений, которые следуют правилам многодокументального интерфейса (MDI), каждое дочернее окно MDI имеет свою собственную пиктограмму. Для использования одного из уже имеющихся курсоров или пиктограмм Windows, передайте 0 в HInstance и используйте значение idc_ (например, idc_IBeam) для курсора и значение idi_ (например, idi_Hand) для пиктограммы. Например:

     procedure SampleWindow.GetWindowClass(var AWndClass:
                                           TWndClass);
     begin
      TWindow.GetWindowClass(AWndClass);
      AWndClass.hCursor := LoadCursor(HInstance, idc_IBeam);
      AWndClass.hIcon := LoadIcon(HInstance, idi_Hand);
     end;

    Дополнительную информацию по регистрационным атрибутам окна можно найти в Главе 10, "Объекты окна".

Загрузка строковых ресурсов


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

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

     LoadString(HInstance, StringID, @TextItem,SizeOf(TextItem));

    LoadString возвращает число скопированных в буфер символов, или ноль, если ресурс не существует.

    Вы можете использовать ресурс строки для вывода текста в блоке сообщения. Например, вы можете вывести сообщение об ошибке. В данном примере вы определяете строку 'Program unavailable' в таблице строк и определяете константу ids_NoProgrm в качестве идентификатора строки. Для использования этого ресурса строки в блоке сообщения об ошибке, вы можете написать следующую процедуру:

     procedure TestDialog.RunErrorBox(ErrorNumber:
                                      Integer); virtual;
     var
       TextItem: array[0..255] of Char;
     begin
       LoadString(HInstance, ids_NoPrgrm, @TextItem, 20);
       MessageBox(HWindow, @TextItem, 'Error', mb_OK or
                  mb_IconExclamation);
     end;

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

    Другое использование ресурса строк применяется для элементов меню, которые добавляются в меню вашего исходного кода. В этом случае сначала получается ресурс строки с помощью LoadString. Затем эта строка передается как параметр в вызовы функций Window CreateMenu и AppendMenu. Например:

     procedure SampleWindow.Init(AParent: PWindpwsObject; ATitle:
                                 PChar);
     var
      TextItem: array[0..255] of Char;
     begin
        TWindow.Init(AParent, ATitle);
        Attr.Menu := LoadMenu(HInstance, PChar(100));
        LoadString(HInstance, 301, @TextItem, 20);
        AppendMenu(Attr.Menu, mf_String ormf_Enabled, 501,
                   @TextItem);
     end;

Загрузка графических изображений


    Функция Windows LoadBitmap загружает ресурсы графических изображений (битовых отображений). LoadBitmap загружает побитовое распределение в память и возвращает его описатель. Например:

     HMyBit:=LoadBitmap(HInstance, PChar(501));

загружает ресурс побитового отображения с идентификатором 501 и записывает его описатель в переменную HMyBit. После загрузки побитового отображения оно останется в памяти до его явного удаления вами. В отличие от других ресурсов, оно остается в памяти даже после закрытия пользователем вашего приложения.

    В Windows имеется ряд заранее определенных графических изображений, которые используются как часть графического интерфейса Windows. Ваше приложение может загружать эти изображения (например, obm_DnArrow, obm_Close и obm_Zoom). Как и предопределенные пиктограммы и курсоры, предопределенные графические изображения могут быть загружены, если в вызове LoadBitmap вместо HInstance задать ноль:

     HMyBit:=LoadBitmap(0,  PChar(obm_Close));

    После загрузки графического образа ваше приложение может использовать его разными способами:

    Дополнительная информация относительно использования графики с побитовым отображением содержится в Главе 17.

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

     if DeleteObject(HMyBit) then         { успешно };

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

Использование побитовых отображений для создания кистей


    Вы можете использовать графические образы для создания кистей, которые могут закрашивать области экрана. Область может быть закрашена сплошным цветом или в виде заданного образца. Минимальный размер используемого в кисти графического образа составляет 8 на 8 элементов изображения. Если вы применяете большее графическое изображение, то в кисти используется только его левый верхний угол 8 на 8. Предположим, что вы хотите заполнить область полосками, как это показано на Рис. 18.1.


Рис. 18.1. Заполнение области экрана полосками.

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


Рис. 18.2. Ресурс графического изображения для создания кисти по образцу Рис. 18.1.

    Следующий код помещает образец графического образа в кисть:

     procedure SampleWindow.MakeBrush;
     var
       MyLogBrush: TLogBrush;
     begin
       HMyBit := LoadBitmap(HInstance, PChar(502));
       MyLogBrush.lbStyle := bs_Pattern;
       MyLogBrush.lbHatch := HMyBit;

       TheBrush := CreateBrushInderect(@MyLogBrush);
     end;

    Для проверки образца, отобразим его в прямоугольнике:

     procedure MyWindow.Paint(PaintDC: HDC;
                              var PaintInfo: TPaintStruct);
     begin
       SelectObject(PaintDC, TheBrush);
       Rectangle(PaintDC, 20, 20, 200, 200);
     end;

    После использования кисти вы должны удалить и кисть, и графическое изображение:

     DeleteObject(HMyBit);
     DeleteObject(TheBrush);

Отображение графических изображений в меню


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

     type
      MyLong = record
       case Integer of
        0: (TheLong: Longint);
        1: (Lo: Word;
            Hi: Word);
      end;

     constructor SampleWindow.Init(AParent: PWindowsObject;
                                   ATitle: PChar);
     var
        ALong: MyLong;
     begin
        TWindow.Init(AParent, ATitle);
        Attr.Menu := LoadMenu(HInstance, PChar(100));
        ALong.Lo := LoadBitmap(HInstance, PChar(503));
        ModifyMenu(Attr.Menu, 111, mf_ByCommand or mf_Bitmap, 211,
                   PChar(ALong.TheLong));
          .
          .
          .
     end;

    В приведенном выше коде 111 - это идентификатор команды варианта меню, который изменяется, а 211 это его новый идентификатор. Однако, вы можете использовать один и тот же идентификатор в обоих случаях.


Рис. 18.3. Меню, где в качестве одного из пунктов выбора использовано графическое изображение.