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

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

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

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

Для воплощения игрового поля нам потребуются два различных типа объектов VCL (Visual Component Library): кнопки и изображения (image). Этот пример показывает, как используется управляющий элемент-изображение и как можно отразить его на экране. В данном случае нам ничего не придется делать самим — CBuilder все сделает за нас. После того как мы напишем всю игру целиком, включая рисование растровой картинки, организацию всех кнопок, проверку их на совпадение и их удаление в случае совпадения, и все в 25 строках кода (включая комментарии), вы увидите всю действительную мощь CBuilder.

Замечание

Вы найдете полный исходный текст программы Match Game на прилагаемом к книге компакт- диске.

Первое, что вам следует сделать, — это создать игровое поле. Положите на форму управляющий элемент изображение (TImage) и растяните его так, чтобы он занял все внутреннее пространство формы. Установите свойство Picture (картинка) на понравившуюся вам картинку. Мы вернемся к тому, как загрузить заданную картинку, чуть позже. Поверх изображения вам надо положить четыре строки, состоящие (каждая!) из четырех кнопок. Пока что сделайте свойство Caption каждой из них пустой строкой. На рис. 3.1 показано, как будет выглядеть законченное игровое поле.

Рис. 3.1. Форма CBuilder — поле игры Match Game

Совет

Вы можете подумать, что теперь вам придется выбирать каждую из 16 кнопок и для каждой устанавливать свойство Caption в пустую строку, но на самом деле это не так. Выберите сразу все кнопки, щелкнув мышью на каждой, держа нажатой клавишу Shift. Теперь перейдите на страницу Properties в Object Inspector. Там будут отображены только те свойства, которые могут быть установлены для множества объектов (очевидно, что, например, не Name (имя)). Выберите свойство Caption и удалите содержимое поля свойства. Нажмите Enter, и у всех объектов заголовки станут пустыми.

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

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

Устанавливаем заголовки кнопок

Перед тем как располагать заголовки на кнопках в специфическом порядке, нам надо создать список возможных заголовков. Поскольку я большой поклонник классического сериала «Star Trek», мы будем использовать имена персонажей из него. Измените, как показано ниже, исходный файл Unit.cpp, чтобы определить заголовки. После того как вы введете исходный текст, мы сможем поговорить о том, как работает конкретно эта функция.

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

char *strNames[] = { "Kirk",

"Spock",

"McCoy",

"Uhura",

"Sulu",

"Chekov",

"Scotty",

"Riley",

};

// А вот измененный метод FormCreate

void __fastcall TForm1::FormCreate(TObject *Sender)

{

int nStringIndex = 0;

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

{

TButton *pButton = dynamic_cast<Tbutton *>(Controls[i]); if ( pButton )

{

pButton->Caption = strNames[ nStringIndex ]; nStringIndex++;

if ( nStringIndex > 7 ) nStringIndex = 0;

}

}

}

Что же на самом деле происходит в этом методе? Первой загадкой является свойство ControlCount, которое используется в заголовке цикла. Свойство ControlCount (количество управляющих элементов) содержит количество дочерних управляющих элементов на форме. В нашем случае на форме располагает ся 16 кнопок и одно изображение, так что свойство ControlCount нашей формы имеет значение 17. Мы обходим все дочерние элементы, используя свойство формы Controls (управляющие элементы).

Свойство Controls класса Form содержит указатели на каждый управляющий элемент, находящийся на форме. Вы можете обращаться к любому из них совершенно одинаково, поскольку все эти управляющие элементы как-то связаны друг с другом в иерархии VCL. В свойстве Controls на самом деле хранятся объекты класса TControl, который является базовым классом для всех управляющих элементов в системе CBuilder.

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

После того как мы получили указатель на управляющий элемент, нам необходимо узнать, является ли этот элемент кнопкой или же каким-либо другим управляющим элементом. Если вам доводилось работать в других системах, то, скорее всего, вам доводилось использовать что-нибудь типа IsKindOf или даже проверять контрольные значения объекта. У CBuilder есть вариант получше функция dynamic_cast стандарта ANSI C++.

Функция dynamic_cast в C++ в общем случае имеет следующий синтаксис:

T* pObject = dynamic_cast<T *>(somepointer);

где T это тип, к которому вы хотите преобразовать указатель, а somepointer указатель на другой объект. Если somepointer не является указателем, то компилятор выдаст ошибку и программа не будет скомпилирована.

Вы, наверное, помните метод static_cast, о котором мы говорили в прошлой главе, который ближе к нормальному варианту работы в C++ по преобразованию объектов. В случае, если аргумент somepointer равен NULL или некорректен (не является указателем), static_cast не выполнится, и только. В свою очередь, dynamic_cast сработает корректно, только если объект, который вы хотите преобразовать, относится к нужному типу. Для кода приведенной выше функции это значит, что каждый элемент массива Controls будет приведен к указателю на объект класса TButton, только если этот управляющий элемент действительно является объектом класса

TButton.

Замечание

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

После того как мы получили указатель на объект кнопку, остальное просто. Все, что мы делаем,

это перемещаемся по именам в массиве strNames. Когда мы доходим до его конца, мы просто переустанавливаем счетчик индекса на начало.

Проверка на совпадение

Следующим шагом после того, как всем кнопкам были присвоены некоторые значения, будет проверка на совпадение. Здесь нам очень поможет возможность VCL ставить в соответствие нескольким объектам один и тот же обработчик события. Выберите все кнопки на форме, щелкнув по каждой мышью при нажатой клавише Shift. Перейдите на страницу Events в Object Inspector и добавьте обработчик для события OnClick. В поле ввода в правой части сетки введите имя HandleButtonClick. Этот метод будет создан и ассоциирован с каждой кнопкой формы. Вот код для метода HandleButtonClick:

void __fastcall TForm1::HandleButtonClick(TObject *Sender)

{

TButton *pButton = static_cast<TButton *>(Sender); if ( m_nClick == 1) // Второй щелчок

{

// Сбрасываем номер щелчка m_nClick = 0;

if ( m_pPrevButton == NULL )

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

return;

// Сравниваем заголовки двух кнопок

if ( m_pPrevButton->Caption == pButton->Caption )

{

m_pPrevButton->Hide(); pButton->Hide();

}

else

{

MessageBeep(MB_ICONEXCLAMATION);

}

}

else

{

//Первый щелчок m_nClick = 1;

//Сохраняем кнопку

m_pPrevButton = pButton;

}

}

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

< Предшествующий код опущен для экономии места> void __fastcall FormCreate(Tobject *Sender);

private // User declarations TButton *m_pPrevButton; int m_nClick;

public // User declarations

__fastcall TForm1(TComponent* Owner); };

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

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

{

m_nClick = 0; m_pPrevButton = NULL;

}

Как это все работает?

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

Когда пользователь нажимает кнопку в первый раз, вызывается метод HandleButtonClick. В этот момент переменная m_nClick равна 0 и указатель на предыдущую кнопку (m_pPrevButton) равен NULL. Метод HandleButtonClick начинает выполняться с секции else, где указатель на предыдущую кнопку устанавливается на нажатую кнопку. Кроме того, переменной m_nClick присваивается значение 1, что означает для формы, что кнопка была нажата и теперь будет предпринята попытка найти кнопку, ей соответствующую.

В следующий раз, когда нажимается кнопка, метод обращается к первой части секции if, которая выглядит так:

if ( m_nClick == 1 ) // Второй щелчок

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

запрашиваем у предыдущей кнопки ее свойство Caption и сравниваем его со свойством Caption кнопки, которую мы только что выбрали. Если они одинаковы, мы «прячем» обе кнопки, вызвав метод Hide (спрятать) для каждой из них. Это открывает находящийся под ними управляющий элемент Image и показывает нам кусочек изображения, выглядывающий из глубин игрового поля. Если две кнопки не совпадают, то (при помощи функции Windows API MessageBeep) система порождает сигнал спикера.

Замечание

Хотя мы и использовали метод Hide в коде нашего примера, вы могли бы столь же легко справиться с задачей, устанавливая в коде программы свойство Visible (видимый) каждой кнопки в false. Код, осуществляющий это, выглядит так:

m_pPrevButton->Visible = false;

pButton->Visible = false;

На рис. 3.2 показано окно частично завершенной игры Match Game, на котором некоторые кнопки уже убраны и на игровом поле между оставшимися кнопками проглядывает изображение.

Рис. 3.2. Частично завершенная игра Match Game

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

Совершенствуем игру Match Game

Несмотря на то что первый образец программы Match Game, несомненно, работает, он требует определенной эстетической доработки. Во-первых, пользователю приходится довольствоваться тем изображением, которое мы положили в управляющий элемент Image, когда проектировали форму. Было бы куда лучше, если бы родители, например, могли загрузить любую, на свой вкус, картинку, перед тем как давать своему ребенку поиграть в нашу игру. Также было бы неплохо, если бы кнопки могли появляться при каждом новом запуске программы в новом порядке. Во второй версии Match Game мы попробуем расправиться с этими проблемами.

Замечание

Исходный текст второй версии программы Match Game можно найти на прилагаемом компакт- диске.

Давайте решать проблемы по мере их поступления сначала позаботимся, чтобы пользователь мог динамически загружать картинки в элемент Image во время исполнения. Добавьте в форму главное меню (main menu), положив его на форму, открытую в редакторе форм. Добавьте в главное меню пункт Файл с двумя подпунктами Выбрать картинку и Выход. В завершение перейдите на вкладку Dialogs в палитре компонентов (Component Palette) и перетащите оттуда на форму компонент Open Dialog (окно диалога открытия файла). Это тот самый диалог, который мы собираемся использовать, чтобы позволить пользователю выбирать файл с растровым рисунком для отображения в игровом поле.

Добавьте обработчик для команды меню Выбрать картинку и добавьте в него следующие строки:

void __fastcall TForm1::SelectImageClick(TObject *Sender)

{

OpenDialog->Filter =

" Файлы растровых рисунков (*.bmp)|*.bmp"; if ( OpenDialog->Execute() )

{

TPicture *pPicture = new TPicture; pPicture->LoadFromFile( OpenDialog->FileName ); Image1->Picture = pPicture;

}

}

В описании этого нехитрого, в общем, метода, присутствуют тем не менее две интересные вещи. Во-первых, вы увидели, как ограничивать файлы заданным расширением или типом. Установив свойство Filter объекта OpenDialog в тип, который вы хотите использовать, вы ограничиваете список файлов, который отображается в окне диалога, файлами с соответствующим расширением. Формат строки имеет следующий общий синтаксис:

Name|Type

где Name это тот текст, который увидит пользователь в выпадающем списке фильтров. Свойство Name обычно имеет структуру Описание (*.ext), где ext — это расширение нужных файлов. В нашем случае описание будет выглядеть, как «Файлы растровых рисунков», а расширение — bmp.

Type это то действительное расширение, которое вы хотите ввести. Символ `|' (трубопровод) между этими двумя частями необходим, его обязательно надо ввести.

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

Следующая интересная вещь касается использования объекта TPicture. Мы еще не сталкивались с TPicture, так что стоит познакомиться с ним поближе. Объект TPicture используется для хранения растровых рисунков, иконок и других графических образов. TPicture может быть отображен так же, как мы отображали растровые рисунки во второй главе. Кроме того, вы можете загрузить объект TPicture непосредственно с диска, используя метод LoadFromFile, а также загрузить его из файла ресурсов, о чем мы до сих пор не говорили.

После того как вы получили объект TPicture с загруженной из файла, выбранного пользователем, картинкой, просто присвойте эту картинку свойству Picture управляющего элемента Image. Вот, собственно, и все. Управляющий элемент сам отобразит картинку в своем поле.

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

FormCreate:

void __fastcall TForm1::FormCreate(TObject *Sender)

{

char strings_1[MaxStringCount*2][ 20 ]; char strings_2[MaxStringCount*2][ 20 ];

//Сначала копируем все строки в массив for ( int i=0; i<MaxStringCount*2-1; ++i )

{

strcpy ( strings_1[i], strNames[i]);

strcpy ( strings_1[i+MaxStrCount], strNames[i]);

}

//Теперь запомним их в произвольном порядке for ( int i=MaxStringCount*2-1; i>=0; --i )

{

int nInd = random( i);

//Кладем эту строку во второй массив

strcpy ( strings_2[i], strings_1[nInd] );

// Копируем последнюю строку в эту позицию strcpy ( strings_1[nInd], strings_1[i] );

}

int nStringIndex = 0;

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

{

TButton *pButton = dynamic_cast<TButton *>(Controls[i]); if ( pButton )

{

pButton->Caption = strings_2[ nStringIndex ]; if ( nStringIndex < MaxStringCount*2-1 ) nStringIndex ++;

}

}

}

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

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

const MaxStringCount = 8;

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

Как это все работает?

Процесс размещения заголовков в произвольном порядке состоит из трех этапов. Сначала мы копируем все строки в одномерный массив, размер которого равен удвоенному количеству строк, с которыми мы имеем дело. Кстати, здесь проявляется еще одно преимущество, связанное с хранением количества строк в виде константы. Если мы когда-нибудь решим изменить количество кнопок на форме и количество строк, которые мы используем для их заполнения, все остальное само по себе изменится соответствующим образом.

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

Заключительная часть это уже знакомое нам по прошлой версии программы присвоение каждой кнопке нового заголовка. Если вы теперь запустите программу Match Game 2, вы увидите нечто похожее на окно, изображенное на рис. 3.3.

Итак, теперь у вас есть вполне работоспособная игра, в которую ваши дети могут играть до умопомрачения. Правда, если вы, конечно, не хотите вырастить из детей фанатов шоу Star Trek, вам лучше изменить текст заголовков на что-нибудь более подходящее им по возрасту.

Несмотря на то что мы неплохо развлеклись, а попутно и узнали кое-что о TPicture и TImageControls, вы пока не много узнали о графике, встроенной в CBuilder. В следующем небольшом примере вы кое-что узнаете о некоторых особенностях внутреннего устройства системы.

Пример номер два: крестики-нолики

Рис. 3.3. Окно обновленной программы Match Game

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

Замечание

Хотя в примере мы и говорили все время об использовании TButton, в примере, находящемся на прилагаемом компакт-диске, я, на самом деле, использовал TBitBtn. TBitBtn — класс, наследующий от TButton, который может отображать (нести на кнопке) не только текст, но и изображение. Если у вас дети настолько малы, что не умеют читать, можете попробовать приложить руку к изменению программы Match Game 2, так чтобы использовать рисунки вместо текста. Все будет написано на самом деле практически так же, только вместо свойства Caption (заголовок) вам придется использовать свойство TBitBtn, которое называется Glyph (характерный рисунок, «иконка» кнопки). Кроме того, вам придется создать объекты TPicture для каждого изображения, которое собираетесь использовать (так же, как мы это делали в процедуре загрузки картинки для игрового поля). Вместе с CBuilder поставляются неплохие картинки, которые вы можете найти в директории CBuilder\Images вашего дерева каталогов.

Пример номер два: крестики-нолики

Крестики-нолики одна из старейших игр, известных человеку. Она не сложна, и я думаю, что большинство людей знакомы с ее правилами. В нашей версии крестиков-ноликов (программа будет называться Tic-Tac-Toeтаково английское на звание игры) мы изучим подробнее внутренние особенности графики в CBuilder, процесс пользовательского ввода/вывода и возможности формы в рисовании и отображении объектов.

На рис. 3.4 показана законченная форма с расположенными на ней компонентами VCL, необходимыми для успешного функционирования приложения. Ну да, она пуста. Вся работа по прорисовке формы будет сделана самим приложением.

Рис. 3.4. Форма программы Tic-Tac-Toe (крестики-нолики)

Замечание

Исходный текст программы Tic-Tac-Toe (крестики-нолики) находится на прилагаемом компакт- диске.

Шаг первый: создаем изображения

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

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

class TForm1 : public TForm

{

__published: // IDE-managed Components void __fastcall FormPaint(TObject *Sender;

void __fastcall FormMouseDown(TObject *Sender,

Пример номер два: крестики-нолики

TMouseButton Button, TShiftState Shift, int X, int Y); private: // User declarations

Graphics::TBitmap *FpXBitmap; Graphics::TBitmap *FpYBitmap; int FnGameBoard[3][3];

int FnWhichPlayer; public: // User declarations

__fastcall TForm1(TComponent* Owner); };

Два объекта TBitmap будут использованы для внутренней отрисовки растровых рисунков и их отображения на форме в соответствии с ходами игроков. Массив FnGameBoard используется для хранения текущих выбранных клеток игрового поля и принадлежности этих клеток. И наконец, переменная класса FnWhichPlayer используется для отслеживания, чей ход.

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

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

{

//Присваиваем право хода первому игроку

FnWhichPlayer = 1;

//Инициализируем игровое поле

FpXBitmap = new Graphics::TBitmap; FpXBitmap->Width = ClientWidth/3 - 2;

FpXBitmap->Height = ClientHeight/3 - 2; // Заливаем рисунок цветом формы

FpXBitmap->Canvas->Brush->Color = Color; Trect r;

r.Left = 0; r.Top = 0;

r.Right = FpXBitmap->Width; r.Bottom = FpXBitmap->height; FpXBitmap->Canvas->FillRect( r); FpYBitmap = new Graphics::TBitmap;

FpYBitmap->Width = ClientWidth/3 - 2; FpYBitmap->Height = ClientHeight/3 - 2;

//Заливаем рисунок цветом формы

FpYBitmap->Canvas->Brush->Color = Color; FpYBitmap->Canvas->FillRect( r);

//Отображаем рисунок X (крестик) FpXBitmap->Canvas->MoveTo(0,0); FpXBitmap->Canvas->LineTo(FpXBitmap->Width, FpXBitmap->Height); FpXBitmap->Canvas->MoveTo(FpXBitmap->Width,0);

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