Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Теллес М. - Borland C++ Builder. Библиотека программиста - 1998

.pdf
Скачиваний:
764
Добавлен:
13.08.2013
Размер:
4.35 Mб
Скачать

Borland C++ Builder (+CD). Библиотека программиста 221

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

Построение строковой библиотеки

Мы решили, что создать библиотеку (DLL), содержащую наши строковые ресурсы, — задача нужная, так что пора этим заняться. Создание DLL в CBuilder не является сложным процессом, так же как и создание строк, входящих в эту библиотеку. Итак, поехали.

Создайте в CBuilder новый проект, выбрав команду File д New. На странице Projects страничного диалога выберите DLL из доступных типов объектов. Нажмите на кнопку OK, и CBuilder сгенерирует «скелет» DLL, включая весь предварительный код. Мы не собираемся работать с этим кодом в данном примере, так как эта DLL не будет содержать никакого дополнительного кода.

Создайте новый текстовый файл в CBuilder и добавьте в него следующие строки. Это будет наш строковый ресурс для DLL.

STRINGTABLE DISCARDABLE BEGIN

1001 "Hello"

1002 "Aloha"

1003 "Shalom"

1004 "Hola!" END

На случай, если вы не работали с таблицами строк раньше: принцип крайне прост. Ресурс «таблица строк» определяется выражением STRINGTABLE DISCARDABLE. Ключевое слово STRINGTABLE говорит компилятору ресурсов, что последующие строки описывают ресурс таблицы строк. Флажок DISCARDABLE указывает, что данный блок не обязательно хранить в фиксированной области памяти и что при необходимос ти его можно выгружать на диск. После определения блока идет выражение BEGIN, которое указывает на начало определения строк в таблице.

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

Сохраните этот файл как strings.rc в каталоге с вашим проектом в CBuilder. Добавьте ресурс к проекту, вызвав команду меню Project д Add to Project и выбрав файл string.rc. CBuilder знает, что делать с файлами описания ресурсов (resource script, RC), так что больше делать ничего не нужно. Закройте файл и соберите проект обычным образом, выбрав Project д Make или нажав F9. Файл ресурса скомпилируется, и, если не будет найдено ошибок, будет создан файл DLL. Вот и все о построении DLL в CBuilder.

Пример динамической загрузки строк

Следующим шагом в нашем примере является создание формы, которая бы использовала только что созданную нами «с таким трудом» DLL. В данном примере мы хотим, чтобы строки, содержащиеся в DLL, представляли собой представления статического текста в форме на разных языках. На самом деле, если вы посмотрите на данные, то увидите, что строки содержат слово

Borland C++ Builder (+CD). Библиотека программиста 222

«Hello» (привет) на разных языках (хотя в данном случае для них используется латинский алфавит).

На рис. 10.1 представлена форма, которую мы будем использовать для отображения данных. Как видите, на форме одна метка статического текста и четыре переключателя (radio buttons). Это все, что нам потребуется для реализации данного примера.

Рис. 10.1. Форма примера динамической загрузки строк

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

Первой функцией API, которую надо изучить, является функция LoadLibrary; она позволяет открывать и брать данные из динамической библиотеки (DLL) на диске. Для того чтобы мы могли использовать нашу DLL в последующем коде для формы, нам нужно открыть ее, когда форма загружается. Так что мы используем конструктор класса формы для инициализации ссылки (handle) на библиотеку. Добавьте следующий код в конструктор формы:

__fastcall TForm1::TForm1(TComponent *Owner) : TForm(Owner)

{

hLibHandle = LoadLibrary("project1.dll");

}

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

private: // User declarations

HINSTANCE hLibHandle;

Эти два изменения дают вам полный доступ к содержимому библиотеки project1.dll, которую мы построили в предыдущем разделе. Функция LoadLibrary дает вам ссылку на экземпляр DLL. Почему ссылку на экземпляр? Потому что библиотеки DLL могут быть загружены одновременно несколькими приложениями, работающими в Windows. Вот почему они полезны. Если десяток разных приложений использует код или ресурсы в DLL, то они могут все использовать совместно одну и ту же DLL, загруженную в память. Когда вы используете функцию API LoadLibrary, вы увеличиваете счетчик использования DLL. Библиотека DLL будет выгружена из памяти только

Borland C++ Builder (+CD). Библиотека программиста 223

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

Загружая библиотеку функцией LoadLibrary, не забывайте освободить ее, когда работа закончена. Если вы этого не сделаете, то она будет сидеть в памяти, пока Windows не догадается избавиться от нее. Создайте обработчик события формы OnClose и добавьте в метод FormClose следующий код:

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)

{

if ( hLibHandle != NULL )

{

FreeLibrary( hLibHandle );

}

}

Заметьте, что мы проверяем, не равна ли ссылка NULL. Если функция LoadLibrary не может найти файл или загрузить его в память по какой-либо причине, то она вернет ссылку, равную NULL. Нехорошо передавать пустые (NULL) ссылки функциям, которые ожидают корректные значения, так что мы делаем проверку ссылки на корректность перед тем, как освободить ее. То же самое мы делаем при каждом использовании ссылки, как вы вскоре увидите.

Итак, библиотека загружена и форма отображена. Следующий шаг получать строки при запросе пользователя. Давайте сначала обработаем первый переключатель (Английский ). Создайте обработчик для первого переключателя на форме и добавьте в него следующий код:

void __fastcall TForm1::RadioButton1Click(TObject *Sender)

{

if ( hLibHandle != NULL )

{

char szBuffer[ 256 ]; LoadString( hLibHandle, 1001,

szBuffer,

256 // размер буфера

);

Label1->Caption = szBuffer;

}

}

Обработчик сначала проверяет, была ли загружена библиотека, проверяя ссылку. Если она не равна NULL, то метод вызывает функцию API LoadString для загрузки данной строки из DLL. Функция API LoadString имеет следующий синтаксис:

int LoadString( HINSTANCE hInstance, int nResourceID,

LPSTR strBuffer, int nSizeOfBuffer);

В нашем случае ссылка на экземпляр (HINSTANCE) — это ссылка на библиотеку, которую мы загрузили функцией LoadLibrary. Идентификатор ресурса (nResourceID) — это тот же идентификатор строки, что мы описали в нашей таблице строк в файле ресурса. Функция LoadString будет искать строку с тем же ID (идентификатором), что и заданный. Так что,

Borland C++ Builder (+CD). Библиотека программиста 224

посмотрев в таблицу строк, описанную выше в данной главе, вы увидите, что строка с ID 1001 является английским словом «Hello».

Параметры szBuffer и 256 это буфер, в котором мы хотим хранить полученную строку, и, соответственно, размер этого буфера. Если функция LoadString найдет строку, более длинную, чем размер буфера, то она возвратит только первые 256 байтов строки и программа не «рухнет».

Для обработки остальных переключателей на форме мы просто меняем ID строки, которую хотим получить из DLL. Вот обработчики трех переключателей на форме:

void __fastcall TForm1::RadioButton2Click(TObject *Sender)

{

if ( hLibHandle != NULL )

{

char szBuffer[ 256 ]; LoadString( hLibHandle, 1002,

szBuffer,

256 // размер буфера

);

Label1->Caption = szBuffer;

}

}

//———————————————————————————————- void __fastcall TForm1::RadioButton3Click(TObject *Sender)

{

if ( hLibHandle != NULL )

{

char szBuffer[ 256 ]; LoadString( hLibHandle, 1003,

szBuffer,

256 // размер буфера

);

Label1->Caption = szBuffer;

}

}

//———————————————————————————————- void __fastcall TForm1::RadioButton4Click(TObject *Sender)

{

if ( hLibHandle != NULL )

{

char szBuffer[ 256 ]; LoadString( hLibHandle, 1004,

szBuffer,

256 // размер буфера

);

Label1->Caption = szBuffer;

}

}

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

Borland C++ Builder (+CD). Библиотека программиста 225

процесс:

void GetString( int nId, AnsiString &strLang )

{

// Сначала загрузить DLL

HINSTANCE hLibHandle = LoadLibrary("project1.dll"); if ( hLibHandle )

{

char szBuffer[ 256 ];

LoadString( hLibHandle, nId, szBuffer, 256 ); strLang = szBuffer;

FreeLibrary( hLibHandle );

}

}

Тогда весь предыдущий код мог бы использовать одну функцию GetString для получения нужных данных. Заметьте также, что мы использовали объект AnsiString для получения данных, который лучше работает со строками. Строка такого типа (AnsiString) также может быть напрямую присвоена свойству Caption (заголовок) метки статического текста.

Теперь вы овладели загрузкой строк, зависящих от языка, из DLL. Больше нечего сказать об этом, разве что можно заметить, что такой же процесс применим и к растровым рисункам (bitmaps), и к значкам (icons) в ресурсах. Если у вас на форме есть объект «растровый рисунок» (TBitmap), то вы можете присвоить ему рисунок из ресурса, получив ссылку типа HBITMAP из файла ресурса и затем присвоив ее свойству Handle (ссылка) объекта TBitmap. Это работает замечательно со всем, кроме меню. Давайте разберемся, почему это так.

Загрузка динамических меню

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

MainMenu1->Handle = LoadMenu( HInstance,

MAKEINTRESOURCE(ID_MY_MENU));

Вы могли бы так подумать; однако суть в том, что это не работает. Меню обрабатываются Windows API по-другому, нежели растровые рисунки, значки или строки, так как меню является частью окна, в то время как все остальное отдельные элементы, не связанные с чем-либо. Я был, по правде говоря, слегка удивлен, что компания Borland не оформила функциональность меню так же, как оформлено все остальное, но, в конце концов, не надо обольщаться, что кто-то сделает за вас всю работу.

Почему вы не можете написать приведенный только что код? Ответ прост. Свойство Handle объекта TMainMenu является частным (private) членом класса, так что вы не можете обращаться к нему напрямую из вашей формы. Вместо этого вы можете обращаться к свойствам меню косвенно, добавляя или удаляя элементы из меню.

Однако выходит, что мы можем эмулировать динамическую загрузку элементов меню, делая это вручную. Для этого поместите на форму компонент TMainMenu (главное меню) без элементов. Форма, которую мы используем, приведена на рис. 10.2. Как видите, на ней нет ничего, кроме

Borland C++ Builder (+CD). Библиотека программиста 226

главного меню без элементов.

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

1001 MENU PRELOAD DISCARDABLE BEGIN

POPUP "&File" BEGIN

MENUITEM "&New\tCtrl+N", 101 MENUITEM "&Open...\tCtrl+O", 102 MENUITEM "&P&rint setup...", 103 MENUITEM "&Recent File", 104 MENUITEM "E&xit", 105

END

POPUP "&View" BEGIN

MENUITEM "&Toolbar", 106 MENUITEM "&Status bar", 107 END

POPUP "&Help" BEGIN

MENUITEM "&About", 108 END

END

Рис. 10.2. Форма примера динамического меню

Добавление файла ресурсов в проект дело нехитрое. Выберите команду меню Project д Add to project и выберите файл ресурса из списка, задав маску файлов *.rc (описания ресурсов). CBuilder знает о файлах ресурсов достаточно, так что он автоматически их скомпилирует и свяжет с исполняемым файлом во время сборки приложения.

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

Borland C++ Builder (+CD). Библиотека программиста 227

посмотрим на сам код, который загружает меню из ресурса и встраивает его в форму:

void __fastcall TForm1::FormCreate(TObject *Sender)

{

// Загрузить ресурс меню из файла ресурсов

HMENU hMenu = LoadMenu((HINSTANCE) HInstance, MAKEINTRESOURCE(1001));

//Получить количество подменю в этом меню int nCount = GetMenuItemCount(hMenu);

//Пройтись по всем пунктам,

//получая нужную информацию

for ( int i=0; i<nCount; ++i )

{

// Получить подменю

HMENU hSubMenu = GetSubMenu( hMenu, i );

//Сколько подпунктов в этом меню? char szBuffer[80];

int nSubCount = GetMenuItemCount( hSubMenu ); GetMenuString( hMenu,i,szBuffer,80,MF_BYPOSITION); TMenuItem *pMenuItem = new TMenuItem(MainMenu1); pMenuItem->Caption = szBuffer; MainMenu1->Items->Add( pMenuItem );

//Получить информацию о подменю

for ( int nSubPos = 0; nSubPos<bSubCount; ++nSubPos)

{

GetMenuString( hSubMenu,nSubPos,szBuffer, 80,MF_BYPOSITION );

TMenuItem *pSubMenuItem = new TMenuItem(pMenuItem); pSubMenuItem->Caption = szBuffer;

pMenuItem->Add( pSubMenuItem );

}

}

}

Динамическая загрузка меню

Для того чтобы загрузить ресурс меню из ресурсов, находящихся в исполняемом файле (или любой другой тип ресурсов, в данном случае), вам нужно знать идентификатор ресурса. Это тот ID, который мы написали в выражении MENU в файле ресурса. Этот идентификатор передается функции API LoadMenu для получения ссылки на ресурс меню. Вот где начинаются чудеса.

Когда мы загрузили меню, нам нужно знать, сколько пунктов главного меню (верхняя полоса) будет в данном меню. Это получается вызовом функции API GetMenuItemCount, которая имеет один параметр ссылка1 на меню, с которым мы работаем. Это работает с главными меню или же подменю. В данном случае мы передаем ссылку на главное меню и получаем количество подменю в нем.

Для добавления пунктов меню мы пробегаем по всем подменю для данного ресурса меню. Каждое подменю добавляется в главное: создается объект типа TMenuItem для подменю, и его свойство Caption устанавливается в нужное имя подменю. Внутри подменю мы повторяем весь процесс. Мы получаем количество пунктов подменю и затем создаем каждый из них.

Важной функцией, используемой нами, является функция API GetMenuString. Эта функция

Borland C++ Builder (+CD). Библиотека программиста 228

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

ЗАМЕЧАНИЕ

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

Единственное, чего этот пример не показывает, — корректная обработка разделителей. В принципе, нетрудно получить эту информацию из ресурса меню, прочитанного из файла ресурса (используя функцию API GetMenuState и проверяя флаги на предмет, является ли пункт меню разделителем). Я оставляю это за рамками примера, чтобы его было проще понять.

Удаление пунктов меню

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

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

1 Здесь и далее в основном под ссылкой понимается не элемент языка C++, а термин, по-английски звучащий как handle, который используется для идентификации разнообразных объектов в системе Windows. — Примеч. перев.

Во-первых, измените форму: добавьте две кнопки с названиями Удалить все и Добавить пункты. Новая форма показана на рис. 10.3. Первая кнопка будет использоваться для удаления всех существующих пунктов из меню, а вторая будет делать то, что мы уже делали при создании формы динамически добавлять пункты меню.

Рис. 10.3. Измененная форма с динамическим меню

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

Borland C++ Builder (+CD). Библиотека программиста 229

void __fastcall TForm1::FormCreate(TObject *Sender)

{

AddMenuItems();

}

Следующим шагом является удаление пунктов меню. Это просто, как вы и могли подумать. В конце концов, пункты меню хранятся в свойстве Items объекта TMainMenu. Свойство Items (элементы), как и все свойства Items в VCL, имеет набор функций для установки и удаления элементов. В данном случае, однако, вместо метода Remove (который является стандартным способом удаления элементов из списка) мы используем метод Delete. Этот метод удалит меню и все подменю из списка, а также освободит память, связанную с пунктами меню. Метод Remove не освобождает память, так что часть памяти была бы потеряна в вашем приложении. Вот код обработчика нажатия на кнопку Удалить все:

void __fastcall TForm1::Butoon1Click(TObject *Sender)

{

while ( MainMenu1->Items->Count > 0 ) MainMenu1->Items->Delete( 0 );

}

Обработчик нажатия на кнопку Добавить пункты, конечно, представляет собой просто вызов функции AddMenuItems:

void __fastcall TForm1::Button2Click(TObject *Sender)

{

AddMenuItems();

}

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

Кое-что о файлах ресурсов

Хотя вы обычно будете создавать ресурс с помощью какой-нибудь утилиты, например Resource Workshop в Borland C++ или Resource Editor в Visual C++, вы, разумеется , можете создать файл ресурса самостоятельно. Есть элементы, разрешенные в файле ресурсов, которые имеют значение для приложения в CBuilder, и несколько элементов, которые почти не имеют никакого значения. Давайте сделаем небольшой обзор тех вещей, которые вы можете поместить в файл ресурсов, а также тех, которые вы вряд ли будете когда-либо использовать.

Таблица строк первый элемент, который бывает в файлах ресурсов. Как мы видели, ресурс таблицы строк хорошо подходит для разноязычных строк и для изменения текста в форме «на ходу». У таблицы строк следующий синтаксис:

STRINGTABLE DISCARDABLE BEGIN

id string END

где id идентификатор, который вы хотите сопоставить строке. Строка (string) в данном листинге представляет собой ту самую строку, которую вы хотите добавить в таблицу.

Borland C++ Builder (+CD). Библиотека программиста 230

Другим, очень популярным типом ресурса является ресурс «растровый рисунок» (bitmap). Этот ресурс позволяет вам определить рисунок, который может быть загружен программой во время выполнения, но хранится он вместе с приложением. Ресурсы bitmap имеют следующий синтаксис:

id BITMAP DISCARDABLE "bitmapfile"

где id число, сопоставленное рисунку, а bitmapfile имя файла, в котором в системе Windows лежит этот рисунок. Например, если вы хотите добавить рисунок

«clouds» (облака), который находится в каталоге Windows, то вам нужно написать следующее:

102 BITMAP DISCARDABLE "c:\windows\clouds.bmp"

Флажок DISCARDABLE говорит системе Windows о том, что при необходимости (если не хватает физической памяти для чего-нибудь) этот рисунок можно временно выгрузить на диск. Обычно нет причин не ставить этот флажок для рисунков.

Ресурс меню, который мы коротко рассмотрели в предыдущем примере, также можно использовать в файлах ресурсов вашего приложения. Основной синтаксис ресурса меню таков:

menu-id MENU PRELOAD DISCARDABLE BEGIN

POPUP main-menu-name BEGIN

MENUITEM sub-menu-string, sub-menu-id [,GRAYED] MENUITEM SEPARATOR

END

END

где параметр menu-id представляет собой идентификатор, который используется при загрузке этого ресурса меню в приложение. Main-menu-name представляет строку, которая будет отображаться как название пункта главного меню. Хорошим примером этого служит меню File, которое встречается почти во всех приложениях Windows. Любое название пункта (имя меню) может содержать необязательный символ & (амперсанд), что сделает следующий символ в имени подчеркнутым при отображении меню (этот пункт будет доступен при нажатии подчеркнутой буквы на клавиатуре).

Параметр sub-menu-string является названием пункта меню, отображаемого, когда главное меню «выпадает» вниз. Например, команда Exit в меню File. Используя стандартное обозначение команд меню, в последовательности File|Exit часть File представляет параметр main-menu-name, а часть Exit — параметр sub-menu-name. Соответственно, sub-menu-id является идентификатором команды, связанной с этим пунктом меню. Это команда, которая вызывается выбором данного пункта меню. Необязательный параметр GRAYED указывает, что данный пункт меню будет вначале отображен как недоступный (бледный).

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

Вы вряд ли будете использовать ресурс DIALOG (диалог). Этот тип ресурса представляет собой шаблон окна диалога. Вам не нужно определять эти шаблоны для использования диалогов в вашем приложении, так как CBuilder строит для вас окна диалога в виде форм в вашем проекте.

Последний тип ресурса, который вы можете определить в вашем приложении пользовательский

Соседние файлы в предмете Программирование на C++