Главная » C++, C++ Builder » Работа с изображениями C++ Builder

0

Windows — это графическая операционная система, CBuilder — графическая среда разработки для C++, WWW переполнен графикой. Все эти вещи имеют отношение к изображениям и обработке изображений, и в этой главе мы рассмотрим обработку изображений в CBuilder.

Графика — это слишком увлекательный предмет для того, чтобы изучать ее на основе сухих казенных примеров, и поэтому мы чуть-чуть порезвимся, изучая графические аспекты CBuilder. С помощью CBuilder мы создадим две простенькие игры — игра совпадений (Match Game) и крестики-нолики. Обе предоставят возможность слегка развлечься, изучая систему. Несмотря на то что эти игры вряд ли смогут осчастливить кого-нибудь не умнее менеджера дольше, чем на несколько минут, они по крайней мере смогут хоть ненадолго занять того трехлетнего лоботряса, что обитает у вас дома.

Пример номер один: игра Match Game

Для первого примера обработки изображений давайте создадим простую игру Match типа той, в которую вы  наверняка играли в детстве, — где надо найти парные одинаковые картинки. Вы, может быть, знакомы также с версиями этой игры типа Match или Concentration. А вот как собираемся воспроизвести эту игру мы. Игровое поле состоит из 16 кнопок,  расположенных в сетке 4ґ4, и на каждой кнопке написано ее название. Если вы выберете две кнопки с одинаковыми названиями, они исчезнут и вам откроется кусочек картинки, расположенной под кнопками.

Для воплощения игрового поля нам потребуются два различных типа объектов 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 (создание формы), который вызывается, когда форма впервые создается в приложении.

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

Перед тем как располагать  заголовки на кнопках в специфическом порядке, нам  надо  создать список возможных заголовков. Поскольку я большой поклонник классического  сериала  «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.

После того как мы получили указатель на управляющий элемент, нам необходимо узнать, является ли этот элемент кнопкой  или  же каким-либо другим управляющим элементом. Если  вам доводилось работать в других системах, то, скорее всего, вам доводилось использовать что-нибудь типа 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 )

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;

}

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

Когда пользователь нажимает кнопку в первый раз, вызывается метод 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. Библиотека программиста – 1998

По теме:

  • Комментарии