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

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

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

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

куски кода из старой версии. К счастью, вы занимаетесь разработкой не в Visual C++ и MFC, и не в Borland C++ и OWL. Вы имеете счастье использовать CBuilder.

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

В чем различие?

Первым важным аспектом, на который следует обратить внимание, переходя к многодокументным приложениям на CBuilder, является отсутствие принципиаль ной разницы между окном многодокументного приложения и обычным окном. Окна многодокументных приложений бывают двух типов родительского (parent) и дочернего (children). В каждом многодокументном приложении, как правило, существует одно родительское окно, которое является «рамкой», ограничивающей размер приложения на экране, в поле которой располагаются дочерние окна.

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

Рис. 2.6. Многодокументное приложение Scribble

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

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

Проводим преобразования

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

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

нормальной формы. Отличие находится в одном-единственном свойстве формы, а именно в свойстве Form Style (стиль формы). Для большинства форм в ваших приложениях свойство Form Style имеет значение fsNormal, которое обозначает, что форма является простым окном, которое мы можем рассматривать как некое нами же определенное диалоговое окно. Для форм

многодокументного приложения свойство Form Style устанавливается в fsMDIForm для родительской формы или в fsMDIChild для дочерних форм.

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

Первым шагом на пути преобразования нашего приложения Scribble в многодокументное станет добавление в приложение новой формы, которая будет единым родителем для всего приложения. Итак, добавьте новую форму и установите ее свойство Form Style в fsMDIForm. Добавьте в новую форму меню, а в него добавьте два новых пункта с названиями Файл и Вид. В пункт меню Файл добавьте два подпункта Новый и Выход. В пункт меню Вид добавьте один-единственный подпункт Обновить все.

Команда Файл - Новый будет использоваться для создания в приложении новых дочерних форм. Команда Файл - Выход предусмотрена для «чистого» выхода из приложения. И наконец, команда Вид - Обновить все будет перерисовывать все дочерние формы так, что у всех у них поля рисования будут отображать одну и ту же картинку.

Выберите форму для рисования из программы Scribble и измените ее стиль на fsMDIChild. Это будет основная форма просмотра в нашем приложении. Несмотря на то что нам придется сделать несколько изменений для корректного сосуществования нашей формы с остальными формами, основная часть кода для окна Scribble останется неизменной. Итак, все, что требуется для преобразования формы из независимой в дочернюю, — это изменить свойство Form Style.

Замечание

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

Следующим шагом надо убедиться, что главная форма создается первой в порядке автоматического создания форм, осуществляемом CBuilder. Для изменения порядка создания форм откройте окно Project Manager (View - Project Manager) и выберите в нем кнопку Options (опции). В списке автоматически создающихся форм щелкните на названии Form2 и, не отпуская кнопку мыши, перетащите ее на самый верх списка, перед названием Form1. Вот и все. Когда вы запустите программу, форма Form2 (главная форма многодокументного приложения) будет создана первой, а дочерние формы (Form1, форма рисования из примера Scribble) будут создаваться после нее, уже как дочерние формы единой родительской.

Каждая форма в системе должна знать об остальных формах, соответственно, нам надо сообщить им друг о друге. Для этого выберите главную форму (Form2) и команду File - Include Unit Hdr. Вы увидите список, в котором находится название формы Form1. Выберите его и щелкните кнопку OK. Теперь повторите все для формы Form1 и включите заголовок формы Form2.

Замечание

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

Когда вы включаете заголовок другой формы, вы автоматически получаете возможность использовать объекты, в нем определенные, в своей форме. Если вы взглянете в конец заголовочного файла для формы Form2 (Unit2.h), вы увидите строку следующего содержания:

extern TForm2 *Form2;

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

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

Формы до создания

Каким именно образом вы создаете новое окно? Это один из самых важных аспектов всей системы многодокументных приложений. Если вам доводилось работать с другими системами, вы, должно быть, привыкли доверять создание дочерних окон самой системе и знаете, что создание дочерних форм самостоятельно обычно весьма болезненное предприятие. Каркасы (frameworks) созданы для конкретного вида работы, и поэтому очень затруднительно бывает обходить их ограничения для того, чтобы сделать что-то по-своему. Поскольку система CBuilder основана на компонентах, она даже не пытается делать вещи по-своему, предоставляя вам возможность делать все так, как вам хочется.

Для того чтобы обосновать вышесказанное, давайте обратимся к обработчику команды меню Файл - Новый. Этот обработчик должен создать и отобразить на экране новую дочернюю форму. Как правило, вам хотелось бы иметь возможность изменять название формы, чтобы оно несло смысловую нагрузку. В данном случае мы озаглавим форму «Scribble — дочернее». Для этого добавьте новый обработчик для пункта меню Файл - Новый и в него добавьте следующие строки:

void __fastcall TForm2::New1Click(TObject *Sender)

{

TForm1 *pForm1 = new TForm1(Application); pForm1->Caption = "Scribble _ дочернее";

}

Странным выглядит отсутствие в вышеуказанном коде каких-либо упоминаний о многодокументности (MDI). Новая форма создается как дочернее окно приложения. Свойство Form Style установлено в fsMDIChild, и поэтому при создании форма автоматически становится дочерним окном многодокументного приложения. Как вы видите, вся работа по созданию формы как дочерней для главной формы делается за вас компонентом как таковым.

Вторая строка кода просто присваивает свойству Caption формы значение «Scribble — дочернее»; это название и будет отображено на панели заголовка дочернего окна, когда оно появится на экране. После того как наберете указанные строки кода, скомпилируйте и запустите приложение. Выберите команду меню Файл - Новый, и, с красивым отступом от предыдущего, появится новое дочернее окно.

Замечание

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

Как вы, наверное, заметили, первое дочернее окно, которое появляется на экране сразу при запуске программы, не имеет заголовка «Scribble — дочернее» , поскольку оно было создано автоматически и не прошло через процесс, предусмотренный выбором команды Файл д Новый. Для разрешения этой проблемы есть два пути. Отмените автоматическое создание дочерней формы в своем приложении и начинайте работу программы с пустого родительского окна такой вариант широко распространен в программах Windows. Второе решение лежит в присвоении изначального значения свойству Caption уже во время проектирования приложения и присоединении к нему уникального значения переменной, соответствующего номеру дочернего окна, при создании окон во время исполнения. Стандартный в Windows вариант использование некоторого имени («Scribble — дочернее», например) и номера дочернего окна (по порядку создания). Так, например, первое окно будет иметь название «Scribble — дочернее 1», второе

«Scribble — дочернее и т. д.

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

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

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

//-------------------------------------------------------------------

#include <vcl\vcl.h> #pragma hdrstop #include "Unit1.h" #include "MainForm.h"

//-------------------------------------------------------------------

#pragma resource "*.dfm" TForm1 *Form1;

//-------------------------------------------------------------------

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

{

FbMouseDown = FALSE;

}

//-------------------------------------------------------------------

void __fastcall TForm1::OnMouseDown(TObject *Sender, TMouseButton Button,

TShiftState Shift, int X, int Y)

{

FbMouseDown = TRUE;

// Переместиться в начальную точку

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

Canvas->MoveTo(X,Y);

//Вводим в главную форму новые данные

//Заметьте: мы очищаем все существующие точки

Form2->ClearPoints;

Form2->AddPoint( X,Y );

}

//-------------------------------------------------------------------

void __fastcall TForm1::OnMouseMove(TObject *Sender, TShiftState Shift,

int X, int Y)

{

if ( FbMouseDown )

{

Canvas->LineTo( X,Y );

// Обновляем главную форму новыми данными

Form2->AddPoint( X,Y );

}

}

//------------------------------------------------------------------ -

void __fastcall TForm1::OnMouseUp(Toblect *Sender, TMouseButton Button,

TShiftState Shift, int X, int Y)

{

FbMouseDown = FALSE;

}

//-------------------------------------------------------------------

void __fastcall TForm1::OnPaint(TObject *Sender)

{

if ( Form2->NumberOfPoints() > 0 )

{

int X = 0, Y = 0;

//Получаем первую точку

Form2->GetPoint( 0,X,Y ); Canvas->MoveTo(X,Y);

//Проходим по каждой точке, получаем ее из

//главной формы и перерисовываем

for ( int i=1;i<Form2->NumberOfPoints();++i )

{

Form2->GetPoint( i,X,Y );

Canvas->Lineto( X,Y );

}

}

}

//-------------------------------------------------------------------

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

//-------------------------------------------------------------------

#ifndef Unit1H

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

#define Unit1H //-------------------------------------------------------------------

#include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp>

//-------------------------------------------------------------------

class TForm1 : public TForm

{

__published: // IDE-managed Components

void __fastcall OnMouseDown(TObject *Sender, TMouseButton Button,

TShiftState Shift, int X, int Y);

void __fastcall OnMouseMove(TObject *Sender, TShiftState Shift, int X, int Y);

void __fastcall OnMouseUp(TObject *Sender, TMouseButton Button,

TShiftState Shift, int X, int Y);

void __fastcall OnPaint(TObject *Sender); private: // User declarations

BOOL FbMouseDown; public: // User declarations

__fastcall TForm1(TComponent* Owner); };

//-------------------------------------------------------------------

extern TForm1 *Form1; //-------------------------------------------------------------------

#endif

Итак, мы убрали все эти замечательные строки кода из описания класса Form1 и переместили их, добавив странные вызовы методов в класс Form2. Какова же функция этих строк в классе Form2? Вы поймете, что же на самом деле происходит, если добавите нижеследующие строки в заголовочный файл Unit2.h:

const int MaxPoints = 100; class TForm2 : public TForm

{

__published: // IDE-managed components TMainMenu *MainMenu;

TMenuItem *File1;

TMenuItem *New1;

TMenuItem *Exit1; TMenuItem *Update1; TMenuItem *AllWindows1;

void __fastcall New1Click(TObject *Sender); private: // User declarations

int FnPoint;

int FPointX[MaxPoints+1]; int FPointY[MaxPoints+1]; public // User declarations

__fastcall TForm2(TComponent* Owner); void ClearPoint(void)

{

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

FnPoint = 0;

}

void AddPoint(int X, int Y)

{

if ( FnPpoint < MaxPOints )

{

FPointX[FnPoint] = X;

FPointY[FnPoint] = Y; FnPoint++;

}

}

int NumberOfPoints(void)

{

return FnPoint;

}

void GetPoint( int Index, int& X, int& Y )

{

if ( Index >= 0 && Index < FnPoint )

{

X = FPointX[Index];

Y = FPointY{index];

}

}

}; //-------------------------------------------------------------------

extern TForm2 *Form2; //-------------------------------------------------------------------

#endif

Не кажется ли вам приведенный код знакомым? Должен бы. Большая часть его просто скопирована из старой формы Scribble и упакована в методы этого объекта. Этот процесс называется инкапсуляцией (encapsulation) и является важным моментом в программировании на C++. Инкапсулируя доступ к изменению и восстановлению данных в методы объекта, мы защищаем данные от порчи, которую могут осуществить объекты. Кроме того, поскольку мы убрали данные из непосредственно изменяющихся блоков программы, мы оставили открытой возможность изменять способ хранения данных. Представьте, например, что данные больше не хранятся в виде простого статического массива, как сейчас. Представь те, что они хранятся в виде некоего динамического массива или даже хэш-таблицы (hash table). Возможно, наконец, что точки на самом деле хранятся на диске. Безотносительно того, как хранятся точки на самом деле,

подобное представление позволит нам спрятать настоящий формат данных от остальных объектов. Абсолютно не важно, как преобразуется объект Form2 с точки зрения данных, ведь пока сигнатура (параметры и типы возвращаемых значений функций) методов ClearPoints, AddPoint, NumberOfPoints и GetPoint не меняется, объекту Form1 незачем знать об этом. Это и есть «маскировка данных» в С++. Сами данные спрятаны в методах, которые используются для обращения к данным. Маскировка данных в чистом виде применяется в концепции свойств компонентов VCL, которые мы рассмотрим несколько позже. Я завел этот разговор только потому, что многие почему-то считают концепцию свойств не объектно-ориентированной и неудобной. Как мы увидим несколько дальше, это совсем не так.

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

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

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

Извечная проблема

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

Помните, некоторое время назад я просил вас добавить пункт меню, озаглавленный Обновить все, в меню Вид. Сейчас объясню, зачем. Метод, связанный с этой командой меню, призван решить рассматриваемую проблему, заставляя все дочерние окна обновиться.

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

void __fastcall TForm2::AllWindowsClick(TObject *Sender)

{

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

{

Form1 *pForm = static_cast<TForm1 *>(MDIChildren[i]); if ( pForm )

pForm->Invalidate();

}

}

Что же здесь происходит? У формы есть свойство MDIChildCount, которое равно текущему количеству открытых дочерних окон. Это свойство есть на самом деле у всех форм, но его значение равно 0, если стиль формы не является fsMDIForm. Кроме количества дочерних окон, у формы есть свойство, называемое MDIChildren, которое является динамическим массивом указателей на формы. Мы проходим по всем дочерним формам нашей родительской формы и обновляем их, используя метод Invalidate.

Если программирование под Windows не является для вас привычным, то модель рисования окна покажется вам немного странной. Рисование сопряжено с отменой действия (invalidation) частей форм. Отмена действия означает, что операционной системе посылается сообщение о том, что часть окна (или окно целиком) надо перерисовать. Кто угодно может сообщить окну, что оно должно быть перерисовано, просто вызвав метод Invalidate этого окна (или формы).

Последнее, о чем стоит упомянуть в связи с нашим небольшим кусочком кода, — это оператор static_cast. Функция static_cast является новой для C++, она появилась только в последней версии стандарта ANSI C++. Функция static_cast пытается привести объект к заданному типу. Общий вид записи оператора static_cast выглядит следующим образом:

T *pT = static_cast<T *>(someobjpointer);

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

где T это тип (как TForm1 в примере выше), который определен в системе, а someobjpointer заданный указатель. Обратите внимание на то, чтобы этот параметр был указателем, иначе вы получите ошибку.

Если преобразование прошло успешно, возвращаемое значение это корректный указатель на объект типа T. Если преобразование не было успешным, возвращается значение NULL. Поэтому проверьте возвращенное значение на равенство NULL перед тем, как использовать метод Invalidate для этого объекта. Это основная смысловая проверка, которая доступна вам в вашем приложении. Если вы работаете с указателями, перед обращением проверяйте их значения на равенство NULL.

Последний шаг

Осталась последняя вещь, которую мы должны сделать, чтобы завершить приложение Scribble. Тем более впечатляющую, что для ее осуществления нам потребуется единственная строка кода.

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

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

void __fastcall TForm2::ExitClick(Tobjest *Sender)

{

Application->Terminate();

}

Объект Application (приложение) — это глобальный интерфейс с задачей в операционной системе, которая соответствует нашему приложению. Метод приложения Terminate освобождает все существующие формы, указатели и управляющие элементы и затем закрывает все окна приложения. Application->Terminate() это наиболее предпочтительный метод для завершения работы приложения, и он может безопасно вызываться почти что из любого места в программе. Не надо просто закрывать окна, надеясь, что все хорошо; используйте метод Terminate.

Что мы узнали в этой главе?

В этой главе мы сделали очень много.

Форма может быть создана без заголовка. Что более важно, начальные параметры формы могут быть изменены программно до того, как форма будет отображена на экране. Это осуществляется при помощи метода формы CreateParams (создать параметры).

Формы обладают множеством свойств. Например, свойство Caption (заголовок) управляет текстом, отображенным на панели заголовка формы. Свойство Canvas (холст) используется для рисования в клиентской области формы.

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

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

опосредованно, ассоциировав их с событием. Это позволяет динамически изменять обработчики событий во время исполнения.

Файлы описания формы (Form Definition Files, DFM) можно просмотреть в редакторе как обычные текстовые файлы.

Копирование проекта должно производиться отдельно от сохранения проекта под новым именем при использовании команды File д Save Project As (сохранить проект как).

В CBuilder легко и просто использовать растровые рисунки. Они могут быть загружены посредством метода LoadFromFile (загрузить из файла) и нарисованы с помощью метода Draw (нарисовать) объекта Canvas.

Формы многодокументных приложений (Multiple Document Interface, MDI) — это, в принципе, обычные формы со специфическим значением свойства Form Style (стиль формы) — fsMDIForm. Дочерние окна точно такие же, но у них значение свойства Form Style установлено в fsMDIChild.

При помощи метода Application->Terminate() рекомендуется заканчивать работу приложения.

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

Глава 3. Работа с изображениями

Отображение рисунков в формах

Динамическое создание изображений

Динамическое изменение управляющих элементов

Работа с картинками

Создание растровых изображений

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

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

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

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

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