Министерство образования и науки Российской Федерации
Федеральное агентство по образованию
Государственное образовательное учреждение
высшего профессионального образования
«РОСТОВСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ»
МЕТОДИЧЕСКИЕ УКАЗАНИЯ
к курсу программирования
для студентов физического факультета
Сравнительное объектно-ориентированное проектирование
Delphi vs C++ vs C#
Часть 2
Ростов-на-Дону
2006
Методические указания разработаны кандидатом физико-математических наук, доцентом кафедры теоретической и вычислительной физики Г.В. Фоминым.
Ответственный редактор доктор физ.-мат. наук, профессор В.П. Саченко
Компьютерный набор и верстка Г.В. Фомин
Печатается в соответствии с решением кафедры теоретической и вычислительной физики физического факультета РГУ, протокол №1 от 17 января 2006 г.
Сравнительное объектно-ориентированное проектирование
Delphi vs C++ vs C#
Часть 2
Содержание настоящего пособия является продолжением его 1-ой части «Сравнительное объектно-ориентированное проектирование Delphi vs C++ vs C#».
Как и в первой части, здесь публикуются прокомментированные коды нескольких классов, написанных на трех языках. Слушателю предлагается создать приложения, тестирующие эти классы, предварительно разобравшись в структуре самих классов и их возможностях.
Спрайты
Это классы, реализующие алгоритм воспроизведения коллекции графических объектов, упорядоченных в третьем измерении (так называемое z
-упорядочение). Каждый спрайт занимает свой «слой» в измерении, перпендикулярном экрану (z
-направление), как отдельное окно. Однако в отличие от окна спрайт принадлежит коллекции, связанной лишь с одним окном.
Delphi
В Delphi код приложения разбивается на отдельные модули. Каждый модуль состоит из интерфейсной секции
, секции реализации
и, возможно, секции инициализации. В интерфейсной секции размещаются описания типов, переменных, заголовков процедур и функций, доступных тем частям приложения, которые ссылаются на данный модуль. В секции реализации размещается код, реализующий объявленные в интерфейсе методы классов, процедуры и функции, а также локальные типы, переменные, процедуры и функции, доступные только коду самого модуля.
Интерфейсная секция классов спрайтов
unit uSprite;
{В модуле описаны классы TSpriteList, TSprite и их наследники,
предназначенные для Z-упорядочения графических изображений
на любой канве (например канве объекта типа TPaintBox).
Конструктор класса TSpriteList имеет один параметр -
канву, на которой производится отрисовка.
Конструктор класса TSprite имеет два параметра, определяющие
прямоугольник спрайта и список, которому спрайт принадлежит.}
interface
//Модули VCL, в которых описаны используемые в интерфейсе типы
uses Controls,Graphics,Classes,Types;
type
// Предварительное объявление класса TSprite
TSprite=class;
// Тип переменных, содержащих ссылки на классы типа TSprite
TSpriteClass=class of TSprite;
// Список спрайтов
TSpriteList=class
private
// Поля
// Хранит канву ("контекст устройства"), используемую для отображения спрайтов списка
FCanvas:Controls.TControlCanvas;
// Хранит режим отображения графического объекта при его копировании на канву
FCanvasCopyMode:Graphics.TCopyMode;
// Хранит прямоугольник, ограничивающий область отображения спрайтов списка
FClientRect:Types.TRect;
// Хранит список указателей на спрайты
FList:Classes.TList;
// Хранит текущее число спрайтов в списке
FCount:integer;
// Метод
// Возвращает спрайт списка под номером aZ
function GetSprite(aZ:integer):TSprite;
public
// Свойства
// Возвращает спрайт из списка как элемент массива
property Sprites[aZ:integer]:TSprite read GetSprite;default;
// Возвращает текущее число спрайтов в списке
property Count:integer read FCount;
// Возвращает ссылку на список указателей спрайтов
property List:Classes.TList read FList;
// Возвращает ссылку на канву, с которой связаны спрайты списка
property Canvas:Controls.TControlCanvas read FCanvas;
// Возвращает прямоугольник, ограничивающий область изображения спрайтов списка
property ClientRect:Types.TRect read FClientRect;
// Конструктор
// Создает и инициализирует экземпляр списка спрайтов, связанного с данной канвой
constructor Create(const aCanvas:Controls.TControlCanvas);
// Методы
// Реализует действия перед освобождением объекта
procedure BeforeDestruction;override;
// Создает и добавляет в список объект класса aSpriteClass,
// занимающего прямоугольник SpriteRect
function AddSprite(const aSpriteClass:TSpriteClass;
const SpriteRect:Types.TRect):TSprite;
// Перемещает спрайт внутри списка в z-направлении (с одного слоя в другой)
procedure MoveSprite(const fromZ,toZ:integer);
// Удаляет спрайт с индексом aZ (слой) из списка
procedure DeleteSprite(const aZ:integer);virtual;
// Очищает список от указателей на спрайты
procedure Clear;virtual;
end;
// Тип обработчика события, наступающего перед смещением спрайта
OnMoveEvent=function(Sender:TSprite;var NewLocation:Types.TPoint):
Boolean of object;
// Абстрактный класс спрайта регулирует изображение и перемещение спрайта.
// Изображению спрайта на канве предшествует сохранение в памяти фона,
// который перекрывается изображением.
// Требуемый участок фона сохраняется в объекте типа TBitmap.
// Изображение спрайта исчезает в момент восстановления фона –
// обратного копирования на канву сохраненного участка.
TSprite=class(TObject)
private
// Поля
// Хранит состояние видимости спрайта
FVisible: boolean;
// Хранит номер слоя, занимаемого спрайтом
FZ: integer;
// Хранит маску - наличие пересечений с одним из выше лежащих спрайтов
FMask: boolean;
// Хранит ссылку на список, которому принадлежит спрайт
FSpriteList: TSpriteList;
// Хранит Bitmap, содержащий фон спрайта
FImage: Graphics.TBitmap;
// Хранит координаты левого верхнего угла спрайта
FLocation: Types.TPoint;
// Хранит размеры спрайта
FSize: Types.TSize;
// Хранит ссылку на обработчик смещения спрайта
FOnMove: OnMoveEvent;
// Методы
// Готовит спрайт к изображению
procedure BeginPaint;
// Завершает процесс изображения спрайта
procedure EndPaint;
// Устанавливает маску для спрайта из слоя aZ
procedure SetMask(const aZ:integer);
// Определяет факт перекрытия спрайтов из слоев First и Second
function Intersect(const First,Second:integer):boolean;
// Устанавливает состояние видимости спрайта
procedure SetVisible(const aVisible: Boolean);
// Возвращает прямоугольник спрайта
function GetSpriteRect:Types.TRect;
// Конструктор
// Создает и инициализирует спрайт, принадлежащий списку Sprites
// с прямоугольником SpriteRect
constructor Create(const SpriteRect: Types.TRect;const Sprites: TSpriteList);
protected
// Методы
// Восстанавливает изображение фона спрайта
procedure Restore;virtual;
// Изображает спрайт
procedure Paint;virtual;
// Формирует реальное изображение спрайта (в этом классе метод абстрактный)
procedure PaintPicture;virtual;abstract;
public
// Свойства
// Возвращает слой спрайта
property Z:integer read FZ;
// Устанавливает и возвращает обработчик при перемещении спрайта
property OnMove:OnMoveEvent read FOnMove write FOnMove;
// Устанавливает и возвращает состояние видимости спрайта
property Visible:Boolean read FVisible write SetVisible;
// Возвращает положение левого верхнего угла спрайта
property Location:Types.TPoint read FLocation;
// Возвращает размеры спрайта
property SpriteSize:Types.TSize read FSize;
// Возвращает прямоугольник спрайта
property SpriteRect:Types.TRect read GetSpriteRect;
// Возвращает ссылку на список, которому спрайт принадлежит
property SpriteList:TSpriteList read FSpriteList;
// Методы
// Выполняет инициализирующие действия сразу после создания спрайта
procedure AfterConstruction;override;
// Выполняет действия непосредственно перед освобождением спрайта
procedure BeforeDestruction;override;
// Перемещает спрайт на вектор drift
function Move(const drift: Types.TSize): boolean;virtual;
// Перемещает спрайт в новое положение NewLocation
function MoveTo(const NewLocation: Types.TPoint): boolean;virtual;
end;
// Тип массива, хранящего карту следов (пикселей) спрайтов на канве
TTraceMap=Array of array of Boolean;
// Список спрайтов, оставляющих след на канве
TTracedSpriteList=class(TSpriteList)
private
// Поле
// Хранит карту следов на канве
FTraceMap:TTraceMap;
public
//Возвращает карту следов на канве
property TraceMap:TTraceMap read FTraceMap;
// Методы
// Выполняет инициализирующие действия сразу после создания списка
procedure AfterConstruction;override;
// Выполняет действия непосредственно перед освобождением списка
procedure BeforeDestruction;override;
// Удаляет спрайт с индексом aZ (слой) из списка
procedure DeleteSprite(const aZ:integer);override;
// Очищает список от указателей на спрайты
procedure Clear;override;
end;
// Тип массива точек следа спрайта
TTracePoints=array of Types.TPoint;
// Класс, спрайты которого оставляют след перемещения
// по канве списка типа TTracedSpriteList
TTracedSprite=class(TSprite)
private
// Поля
// Хранит указание, оставляет ли спрайт след
FTraced:Boolean;
// Хранит точки со следом
FTracePoints:TTracePoints;
// Хранит указание, имеет ли след определенный цвет
FTraceColored:Boolean;
// Хранит цвет следа
FTraceColor:Graphics.TColor;
// Хранит центр спрайта
FCenter:Types.TPoint;
// Метод
// Устанавливает цвет спрайта
procedure SetTraceColor(const aTraceColor:Graphics.TColor);
public
// Свойства
// Возвращает и устанавливает указание на наличия следа
property Traced:Boolean read FTraced write FTraced;
// Возвращает и устанавливает указатель на точки следа
property TracePoints:TTracePoints read FTracePoints;
// Возвращает и устанавливает указание, имеет ли след определенный цвет
property TraceColored:Boolean read FTraceColored write FTraceColored;
// Возвращает и устанавливает цвет следа
property TraceColor:Graphics.TColor read FTraceColor write SetTraceColor;
// Возвращает центр спрайта
property Center:Types.TPoint read FCenter;
// Методы
// Выполняет инициализирующие действия сразу после создания спрайта
procedure AfterConstruction;override;
// Выполняет действия непосредственно перед освобождением спрайта
procedure BeforeDestruction;override;
// Перемещает спрайт на вектор drift
function Move(const drift:Types.TSize):boolean;override;
// Воспроизводит след
procedure PutTrace;
end;
const DefaultColor=$ffffff;//Цвет эллипса по умолчанию
type
// Класс, изображающий спрайт в форме сплошного эллипса
TEllipseSprite=class(TTracedSprite)
private
// Поле
// Хранит цвет эллипса
FColor:Graphics.TColor;
protected
// Методы
// Изображает эллипс
procedure PaintPicture;override;
// Устанавливает цвет эллипса
procedure SetColor(const aColor:Graphics.TColor);
public
// Свойство
// Возвращает и устанавливает цвет эллипса
property Color:Graphics.TColor read FColor write SetColor;
// Метод
// Выполняет инициализирующие действия сразу после создания спрайта
procedure AfterConstruction;override;
end;
Вспомним правила описания в Delphi в контексте приведенного выше интерфейса модуля uSprite. С этой целью рассмотрим фрагмент начала модуля
uses Controls,Graphics,Classes,Types;
type
// Предварительное объявление класса TSprite
TSprite=class;
// Тип переменных, содержащих ссылки на классы типа TSprite
TSpriteClass=class of TSprite;
// Список спрайтов
TSpriteList=class
// Описание членов класса
…
end;
· Директива uses означает, что в коде настоящего модуля используются типы, переменные, процедуры, функции или константы (короче – имена), описанные в интерфейсах модулей Controls, Graphics, Classes, Types. Все перечисленные модули принадлежат в данном случае библиотеке среды Delphi.
· Служебное слово type означает, что ниже следует описание типов
. Тип – это формат переменных. Существуют стандартные типы такие как , , и другие. Их формат задан средой. Другие типы, которые оказываются необходимыми в конкретном приложении или модуле, требуют специального описания.
· Краткое описание TSprite=class; типа TSprite означает, что класс TSprite будет описан ниже, но упоминание о нем необходимо уже здесь. Дело в том, что описанный ниже класс TSpriteList использует в своем описании TSprite. В то же время полное описание класса TSprite в свою очередь содержит ссылку на класс TSpriteList. Эта взаимозависимость описаний двух классов не позволяет предпочесть в порядке описания один класс другому. Выход – дать краткое (пустое) описание одного из классов перед полным описанием другого.
· Тип TSpriteClass=class of TSprite описывает переменные, которые содержат в себе ссылки на таблицы виртуальных методов класса TSprite и его наследников. Такие переменные могут быть использованы, например, при создании экземпляра объекта, о котором во время программирования известно лишь то, что он принадлежит к семейству спрайтов, то есть является наследником класса TSprite. Так одним из параметров метода AddSprite(const aSpriteClass: TSpriteClass; const SpriteRect: Types.TRect) класса TSpriteList является переменная типа TSpriteClass, указывающая, экземпляр какого класса спрайтов следует добавить в список.
Строка TSpriteList=class открывает описание класса
, которое содержит в себе поля
, свойства
и методы
класса TSpriteList вплоть до служебного слова end, завершающего перечисление членов класса
. Все поля объекта инициализируются при явном вызове конструктора в коде приложения. По умолчанию, если в теле конструктора не указаны другие значения, все поля будут инициализированы нулями.
Каждый член класса TSpriteList имеет определенный уровень доступа
. Так в описании класса TSpriteList имеется две секции, выделенные модификаторами доступа private и public.
Рассмотрим фрагмент кода, описывающий класс TSpriteList:
TSpriteList=class
private
// Поля
// Хранит канву ("контекст устройства"),используемую для отображения спрайтов списка
FCanvas:Controls.TControlCanvas;
…
// Метод
// Возвращает спрайт списка под номером aZ
function GetSprite(aZ:integer):TSprite;
public
// Свойства
// Возвращает ссылку на канву, с которой связаны спрайты списка
property Canvas:Controls.TControlCanvas read FCanvas;
…
// Возвращает спрайт из списка как элемент массива
property Sprites[aZ:integer]:TSprite read GetSprite;default;
// Конструктор
// Создает и инициализирует экземпляр списка спрайтов, связанного с данной канвой
constructor Create(const aCanvas:Controls.TControlCanvas);
…
end;
В Delphi модификатор доступа private применяется к членам класса, которые доступны лишь тому же модулю
, в котором описан сам класс, но недоступны другим модулям программы. Обычно поля класса имеют уровень доступа
private. Члены класса с уровнем доступа public доступны любой части программы
. Свойства класса обычно имеют уровень доступа public. Так поле FCanvas (идентификаторы полей в Delphi принято начинать буквой F от field – поле) имеет уровень доступа private, но свойство Canvas открыто для доступа. Через свойство Canvas можно прочесть поле FCanvas, но нельзя изменить его значение. Так свойства могут регулировать доступ к полям.
Что касается методов, то их разделение по уровням доступа зависит от логики класса. Так, метод GetSprite(aZ:integer):TSprite класса TSpriteList «спрятан» от внешнего доступа под модификатором private. Его роль ограничивается обеспечением доступного свойства Sprites[aZ:integer] возвращаемым значением – спрайтом с индексом aZ из списка. Другие методы класса TSpriteList имеют открытый доступ. Среди них конструктор класса Create, создающий экземпляр объекта и инициализирующий его поля. Параметром конструктора является объект типа TControlCanvas из библиотечного модуля Controls. Объекты этого типа предоставляют спрайтам область изображения - прямоугольник с известными границами в окне приложения и инструменты изображения – кисть и карандаш с цветовой палитрой.
Модификатор const, указанный в описании параметра конструктора и многих других методов, не является обязательным. Он указывает лишь на то, что метод обязуется внутри не изменять значения параметра, передаваемого ему с этим модификатором.
Модификатор default в свойстве Sprites указывает на то, что доступ к объектам класса TSpriteList может осуществляться через свойство Sprites как к элементам массива – в индексном виде.
В коде настоящего модуля имена, описанные в других модулях, специально записаны в расширенном формате с тем, чтобы явно указать их принадлежность. Например, имя типа TControlCanvas, описанного в модуле Controls, записано в расширенном виде Controls.TControlCanvas. Вообще говоря, расширенное имя можно сократить, убрав имя модуля, если отсутствует конфликт имен.
Метод
procedure BeforeDestruction; override;
имеет модификатор override. Это означает, что метод BeforeDestruction является виртуальным и унаследован от предка класса TSpriteList, где он описан как виртуальный (virtual). Предком класса TSpriteList является класс TObject.
Другие методы
procedure DeleteSprite(const aZ:integer); virtual;
procedure Clear; virtual;
описаны как виртуальные в самом классе TSpriteList. У наследника TTracedSpriteList, эти же методы преобретают модификатор override.
Рассмотрим еще один фрагмент кода, относящийся к описанию Tsprite и следующий за описанием класса TSpriteList.
// Тип обработчика события, наступающего перед смещением спрайта
OnMoveEvent=function(Sender:TSprite;var NewLocation:Types.TPoint):Boolean of object;
// Абстрактный класс спрайта, регулирующий изображение и перемещение спрайта
TSprite=class(TObject)
private
…
// Конструктор
// Создает и инициализирует спрайт, принадлежащий списку Sprites
// с прямоугольником SpriteRect
constructor Create(const SpriteRect:Types.TRect;const Sprites:TSpriteList);
protected
…
// Формирует реальное изображение спрайта (в этом классе метод абстрактный)
procedure PaintPicture;virtual;abstract;
public
…
end;
Здесь
· Тип функции OnMoveEvent, описанный с модификатором of object, означает, что это тип метода класса
, а не просто тип какой-то отдельной функции. Разница в том, что метод класса обязательно имеет один скрытый параметр Self - экземпляр класса, который его вызывает. У обычных процедур и функций такого параметра нет. Обработчики событий
в Delphi обычно имеют тип метода
. Тогда в них можно подставить ссылку на метод либо формы приложения, либо другого класса, использующего объявленное событие в своих целях.
· В заголовке описания класса TSprite в скобках указан предок TObject, хотя такое указание отсутствует в описании класса TSpriteList. В Delphi отсутствие предка по умолчанию означает, что предком является класс TObject. Так что в описании класса TSprite ссылку на TObject можно также опустить.
· Конструктор класса TSprite помещен в раздел private. Это делает невозможным создание экземпляров отдельных спрайтов из кода, написанного вне модуля uSprite. Логика классов TSprite и TSpriteList предполагает, что созданием спрайтов занимается только метод Add класса TSpriteList, который только и вызывает конструктор экземпляров класса TSprite.
· В описании класса TSprite присутствуют методы с уровнем доступа protected. Эти методы и вообще члены класса с доступом protected доступны любому предку класса TSprite, даже если они описаны в других модулях, но не доступны коду других классов, описанных в других модулях.
· Среди методов класса TSprite, защищенных модификатором protected есть абстрактный метод procedure PaintPicture; virtual; abstract. Он отмечен модификатором abstract. Абстрактный метод PaintPicture не имеет реализации в классе TSprite. Его реализация будет предложена наследниками. Наличие абстрактного метода делает сам класс TSprite абстрактным в том смысле, что его экземпляры не могут быть созданы.
После описания класса TSprite описаны один тип динамического массива
// Тип массива, хранящего карту следов (пикселей) спрайтов на канве
TTraceMap=Array of array of Boolean;
Тип TTraceMap описывает двумерный массив логических значений.
Динамичность массива в том, что его размер не фиксируется как постоянная величина в процессе разработки класса (design time), а определяется лишь в ходе счета (run time). Конкретные переменные, например, размеры области изображения спрайтов, приобретают реальные значения при создании экземпляра класса TTracedSpriteList=class(TSpriteList). Это происходит в методе AfterConstruction класса TTracedSpriteList, выполняющемся сразу вслед за созданием экземпляра объекта этого класса.
За описанием класса TTracedSpriteList и перед описанием класса TtracedSprite есть описание другого типа динамического массива
// Тип массива точек следа спрайта
TTracePoints=array of Types.TPoint;
Это уже одномерный массив точек - записей типа TPoint, описанных в стандартном модуле Types.
Вслед за этим описан класс
TTracedSprite=class(TSprite)
наследник класса TSprite.
Обратите внимание, что класс TTracedSprite, как и его предок TSprite, является абстрактным классом, так как не реализует абстрактный метод PaintPicture.
Вслед за описанием класса TTracedSprite расположен текст
const DefaultColor=$ffffff; //Цвет эллипса по умолчанию
type
// Класс, изображающий спрайт в форме сплошного эллипса
TEllipseSprite=class(TTracedSprite)
Здесь
· Служебное слово const указывает на то, что DefaultColor является постоянной величиной. Значение DefaultColor записано в 16-ной системе счисления, которая удобна при записи цветов. (В данном случае $ffffff означает максимальное число, содержащееся в трех байтах; в десятичной системе это число равно 224
– 1 = 1677215.) Дело в том, что информация о цвете в Delphi представляется четырехбайтовым целым числом. Старший байт используется для системных цветов, а в трех младших байтах находятся стандартные цвета – в младшем красный, в среднем зеленый и в старшем байте - синий. Другими словами чисто зеленый цвет, к примеру, отвечает числу $ff00. В 16-ричной записи видна структура байтов. Каждому байту отводится по две 16-ричные цифры. В данном случае число $ffffff означает, что все составляющие цвета входят одинаково и с полной интенсивностью – это белый цвет.
· Вслед за описанием постоянной идет описание класса TEllipseSprite, поэтому набирается служебное слово type, действие которого было отменено const.
· Класс TEllipseSprite является наследником класса TTracedSprite. В классе TEllipseSprite уже реализован абстрактный метод PaintPicture, поэтому можно создавать его экземпляры – сплошные эллипсовидые спрайты заданного цвета.
Секция реализации
В этой секции модуля находится код методов пяти классов, описанных выше
implementation uses SysUtils;
//Определяет, находится ли прямоугольник source внутри прямоугольника dest
function Contains(const source,dest:Types.TRect):Boolean;
begin
with dest do
Result:=(source.Left>=Left) and (source.Top>=Top)
and (source.Right<=Right) and (source.Bottom<=Bottom);
end {Contains};
//Реализация методов класса TSpriteList
constructor TSpriteList.Create(const aCanvas:Controls.TControlCanvas);
begin
inherited Create;
if Assigned(aCanvas) then FCanvas:=aCanvas else
raise SysUtils.Exception.Create('Конструктору класса TSpriteList не передана канва!');
FClientRect:=FCanvas.Control.ClientRect;
FCanvasCopyMode:=FCanvas.CopyMode;
FList:=Classes.TList.Create;
end {TSpriteList.Create};
procedure TSpriteList.BeforeDestruction;
begin
Clear;
FCanvas.CopyMode:=FCanvasCopyMode;
FList.Free;
FCount:=0;
inherited
end {TSpriteList.BeforeDestruction};
function TSpriteList.GetSprite(aZ:integer):TSprite;
begin
Result:=TSprite(FList[aZ]);
end {GetSprite};
function TSpriteList.AddSprite(const aSpriteClass:TSpriteClass;
const SpriteRect:Types.TRect):TSprite;
var aSprite:TSprite;
begin
Result:=nil;
if Assigned(aSpriteClass) and (SpriteRect.Right- SpriteRect.Left>0) and
(SpriteRect.Bottom-SpriteRect.Top>0) and Contains(SpriteRect,ClientRect) then
begin
aSprite:=aSpriteClass.Create(SpriteRect,Self);
aSprite.FZ:=FList.Add(aSprite);
FCount:=FList.Count;
Result:=aSprite;
end
end {AddSprite};
procedure TSpriteList.MoveSprite(const fromZ,toZ:integer);
var i,minZ:integer;
begin
if (fromZ<>toZ) and (fromZ>-1) and (fromZ<FCount) and
(toZ>-1) and (toZ<FCount) then
begin
if fromZ<toZ then minZ:=fromZ else minZ:=toZ;
for i:=FCount-1 downto minZ do
if Self[i].FVisible then Self[i].Restore;
FList.Move(fromZ,toZ);
for i:=minZ to FCount-1 do
begin
Self[i].FZ:=i;
if Self[i].FVisible then Self[i].Paint
end
end
end {MoveSprite};
procedure TSpriteList.DeleteSprite(const aZ:integer);
var i:integer;
begin
if (aZ>-1) and (aZ<FCount) then
begin
for i:= FCount-1 downto aZ do
with Self[i] do
if Visible then Restore;
Self[aZ].Free;
FList[aZ]:=nil;
FList.Delete(aZ);
FCount:=FList.Count;
for i:= aZ to FCount-1 do
with Self[i] do
begin
Dec(FZ);
if Visible then Paint;
end
end
end {TSpriteList.DeleteSprite};
procedure TSpriteList.Clear;
var i:integer;
begin
if Assigned(FList) then
for i:= FCount - 1 downto 0 do DeleteSprite(i);
end {TSpriteList.Clear};
//Реализация методов класса TSprite
constructor TSprite.Create(const SpriteRect:Types.TRect;const Sprites:TSpriteList);
begin
inherited Create;
FZ:=-1;
FSpriteList:=Sprites;
FLocation:=SpriteRect.TopLeft;
with FSize,SpriteRect do
begin
cx:=Right-Left;cy:=Bottom-Top
end;
end {TSprite.Create};
procedure TSprite.AfterConstruction;
begin
inherited;
FImage:=Graphics.TBitmap.Create;
FImage.Height:=FSize.cy;
FImage.Width:=FSize.cx;
end {TSprite.AfterConstruction};
procedure TSprite.BeforeDestruction;
begin
FImage.Free;
inherited
end {TSprite.BeforeDestruction};
procedure TSprite.SetVisible(const aVisible:Boolean);
begin
if aVisible<>FVisible then
begin
if aVisible then
begin
BeginPaint;
Paint;
EndPaint;
end else
begin
BeginPaint;
Restore;
EndPaint;
end;
FVisible:=aVisible
end
end {SetVisible};
function TSprite.Move(const drift:Types.TSize):boolean;
var NewPos:Types.TPoint;VisState:Boolean;
begin
Result:=true
;
NewPos:=Types.Point(FLocation.X+drift.cx,FLocation.Y+drift.cy);
if Assigned(FOnMove) then Result:=FOnMove(Self,NewPos);
Result:=Result and Contains(
Types.Rect(NewPos.X,NewPos.Y,NewPos.X+FSize.cx,NewPos.Y+FSize.cy),
FSpriteList.FClientRect);
if Result then
begin
VisState:=FVisible;
Visible:=false
;
FLocation:=NewPos;
Visible:=VisState
end
end {TSprite.Move};
function TSprite.MoveTo(const NewLocation:Types.TPoint):boolean;
begin
Result:=Move(Types.TSize(
Types.Point(NewLocation.X-FLocation.X,NewLocation.Y-FLocation.Y)))
end {MoveTo};
procedure TSprite.BeginPaint;
var i:integer;
begin
SetMask(FZ);
for i:=FSpriteList.FCount-1 downto FZ+1 do
with FSpriteList[i] do
if FMask and FVisible then Restore;
end {BeginPaint};
procedure TSprite.SetMask(const aZ:integer);
var i:integer;
begin
for i:=aZ+1 to FSpriteList.FCount-1 do
begin
with FSpriteList[i] do
FMask:= Intersect(aZ,i) or FMask;
if FMask then SetMask(i)
end
end {SetMask};
procedure TSprite.EndPaint;
var i:integer;
begin
for i:=FZ+1 to FSpriteList.FCount-1 do
with FSpriteList[i] do
if FMask then
begin
if FVisible then Paint;
FMask:=false
end
end {EndPaint};
procedure TSprite.Paint;
begin
with FSpriteList do
begin
FCanvas.CopyMode:=cmSrcCopy;
with FImage do
Canvas.CopyRect(Types.Rect(0,0,Width,Height),FCanvas,SpriteRect);
end;
PaintPicture
end {Paint};
procedure TSprite.Restore;
begin
with FSpriteList.FCanvas do
begin
CopyMode:= cmSrcCopy;
with FImage do CopyRect(SpriteRect,Canvas,Types.Rect(0,0,Width,Height));
end
end {Restore};
function TSprite.GetSpriteRect:Types.TRect;
begin
with FLocation,FSize do Result:=Types.Rect(X, Y, X+cx,Y+cy)
end {GetSpriteRect};
function TSprite.Intersect(const First,Second:integer):boolean;
var rect:Types.TRect;
begin
with FSpriteList[First] do
Result:=IntersectRect(rect,SpriteRect,FSpriteList[Second].SpriteRect);
end {Intersect};
//Реализация методов класса TTracedSpriteList
procedure TTracedSpriteList.AfterConstruction;
begin
inherited;
with ClientRect do SetLength(FTraceMap,Right-Left+1,Bottom-Top+1);
end {TTracedSpriteList.AfterConstruction};
procedure TTracedSpriteList.BeforeDestruction;
begin
inherited;
FTraceMap:=nil;
end {TTracedSpriteList.BeforeDestruction};
procedure TTracedSpriteList.DeleteSprite(const aZ:integer);
begin
if (aZ > -1) and (aZ < Count) then
begin
TTracedSprite(Self[aZ]).FTracePoints:=nil;
inherited DeleteSprite(aZ);
end
end {TTracedSpriteList.DeleteSprite};
procedure TTracedSpriteList.Clear;
var i,j:integer;
begin
for i:= Low(FTraceMap) to High(FTraceMap) do
for j:= Low(FTraceMap[i]) to High(FTraceMap[i]) do
FTraceMap[i,j]:= false
;
inherited Clear;
end {TTracedSpriteList.Clear};
//Реализация методов класса TTracedSprite
procedure TTracedSprite.AfterConstruction;
begin
inherited;
FCenter:=Types.CenterPoint(SpriteRect);
end {TTracedSprite.AfterConstruction};
procedure TTracedSprite.BeforeDestruction;
begin
FTracePoints:=nil;
inherited
end {TTracedSprite.BeforeDestruction};
procedure TTracedSprite.SetTraceColor(const aTraceColor:Graphics.TColor);
begin
FTraceColor:=aTraceColor;
FTraceColored:=true
end {SetTraceColor};
function TTracedSprite.Move(const drift:Types.TSize):Boolean;
begin
if FVisible and FTraced then PutTrace;
Result:=inherited Move(drift);
if Result then
FCenter:=Types.CenterPoint(SpriteRect)
end {TTracedSprite.Move};
procedure TTracedSprite.PutTrace;
var i:integer;
begin
with FCenter do
begin
for i:=FSpriteList.FCount-1 downto 0 do
begin
with FSpriteList[i] do
if FVisible and Types.PtInRect(SpriteRect,Self.FCenter)
then Restore;
end;
with TTracedSpriteList(FSpriteList),FClientRect do
if not TraceMap[x-Left,y-Top] then
begin
with FCanvas do
if FTraceColored then Pixels[x,y]:=FTraceColor else
Pixels[x,y]:=$ffffff xor Pixels[x,y];
TraceMap[x-Left,y-Top]:=true
;
SetLength(FTracePoints,High(FTracePoints)+2);
FTracePoints[High(FTracePoints)].X:=x;FTracePoints[High(FTracePoints)].Y:=y;
end;
for i:=0 to FSpriteList.FCount-1 do
begin
with FSpriteList[i] do
if FVisible and Types.PtInRect(SpriteRect,Self.FCenter)
then Paint;
end
end
end {PutTrace};
//Реализация методов класса TEllipseSprite
procedure TEllipseSprite.AfterConstruction;
begin
inherited;
FColor:=DefaultColor;
end {TEllipseSprite.AfterConstruction};
procedure TEllipseSprite.SetColor(const aColor: Graphics.TColor);
var VisState:Boolean;
begin
if FColor<>aColor then
begin
VisState:=FVisible;
Visible:=false
;
FColor:=aColor;
if VisState then Visible:=true
end
end {SetColor};
procedure TEllipseSprite.PaintPicture;
begin
with FSpriteList.FCanvas do
begin
Brush.Style:=bsSolid;
Brush.Color:=Color;
Pen.Color:=color;
Ellipse(SpriteRect);
end;
end {PaintPicture};
end {uSprite}.
Следует отметить, что в Delphi в разделе реализации можно указывать лишь имена методов, не повторяя список параметров и тип функции. Например, вместо строки кода
function TTracedSprite.Move(const drift: Types.TSize): Boolean;
можно было бы записать ее краткий вариант
function TTracedSprite.Move;
Здесь мы этим не пользовались, чтобы не затруднять чтение кода.
Предлагается составить оконное приложение, тестирующее представленные классы спрайтов. Для этого следует разместить на форме объект типа TPaintBox с произвольным фоном (в виде рисунка). Создать на нем произвольный список эллиптических спрайтов, имеющих разные атрибуты (цвет, размер) и перемещающихся в границах прямоугольника с произвольными скоростями, зеркально отражаясь от границ и оставляя след.
C++
Теперь рассмотрим версию тех же классов спрайтов, написанную на языке C++ в среде C++ Builder (6-ая версия) фирмы Borland.
Структура программного модуля в C++ несколько отличается от структуры модуля, написанного на Object Pascal в Delphi. В некотором смысле интерфейсной секции дельфийского модуля соответствует отдельный физический файл программного модуля на C++, именуемый «хэдер», или файл заголовков. Хэдер имеет расширение .h. Хэдер все же отличается от дельфийской секции interface тем, что в него можно помещать содержательную часть кода, а не только заголовки. Смотрите, к примеру, функцию Contains, описанную в хэдере.
Другой файл, имеющий расширение .cpp и то же имя, что хэдер, содержит реализацию кода, как в секции реализации дельфийского модуля. Оба файла образуют пару, соответствующую одному программному модулю типа unit в Delphi.
Хэдер
В начале рассмотрим подробнее содержание хэдера классов спрайтов модуля uSprite.
#ifndef uSpriteH
#define uSpriteH
//---------------------------------------------------------------------------
/*Модуль, в котором описаны классы TSpriteList и TSprite
для Z-упорядочения графических изображений
на любой канве (например, канве объекта типа TPaintBox).
Конструктор класса TSpriteList имеет один параметр - канву,
на которой производится отрисовка.
Конструктор класса TSprite имеет также один параметр - прямоугольник спрайта.
Объекты типа TSprite помещаются в список
методом AddSprite класса TSpriteList*/
class
TSprite;
//TSpriteList
class
TSpriteList
{
private
:
// Поля
int
count;
TControlCanvas* canvas;
TRect clientRect;
TList* list;
TCopyMode canvasCopyMode;
// Метод
TSprite* __fastcall
GetItems(int
);
public
:
// Свойства
__property
int
Count={read=count};
__property
TControlCanvas* Canvas={read=canvas};
__property
TRect ClientRect={read=clientRect};
__property
TList* List={read=list};
__property
TSprite* Items[int
Index]={read=GetItems};
// Конструктор
__fastcall
TSpriteList(TControlCanvas* const
);
// Деструктор
__fastcall
virtual
~TSpriteList();
// Методы
TSprite* __fastcall
AddSprite(TSprite* const
);
void
__fastcall
MoveSprite(int
const
, int
const
);
void
__fastcall
virtual
DeleteSprite(int
const
);
void
__fastcall
virtual
Clear();
};
// Тип массива следов спрайтов на канве
typedef
DynamicArray< DynamicArray < bool
> > TTraceMap;
//TTracedSpriteList
class
TTracedSpriteList:public
TSpriteList
{
private
:
// Поле
TTraceMap traceMap;
public
:
// Свойство
__property
TTraceMap TraceMap = {read=traceMap};
// Конструктор
__fastcall
TTracedSpriteList(TControlCanvas* const
);
// Деструктор
__fastcall
~TTracedSpriteList();
// Методы
void
__fastcall
virtual
DeleteSprite(int
const
);
void
__fastcall
virtual
Clear();
};
typedef
bool
__fastcall
(__closure
*OnMoveEvent)(TSprite* ,TPoint&);
//TSprite
class
TSprite:public
TObject
{
// Класс TSpriteList, объявленный friend
, получает доступ
// к private
и protected
членам класса TSprite
friend
class
TSpriteList;
private
:
// Поля
bool
visible;
int
z;
TSpriteList* spriteList;
OnMoveEvent onMove;
TSize size;
TPoint location;
Graphics::TBitmap* image;
bool
mask;
// Методы
void
__fastcall
SetVisible(bool
const
);
TRect __fastcall
GetSpriteRect();
void
__fastcall
BeginPaint();
void
__fastcall
EndPaint();
void
__fastcall
SetMask(int
const
);
bool
__fastcall
Intersect(int
const
,int
const
);
protected
:
// Методы
void
__fastcall
virtual
PaintPicture()=0;
void
__fastcall
virtual
Restore();
void
__fastcall
virtual
Paint();
public
:
// Свойства
__property
bool
Visible={read=visible,write=SetVisible};
__property
int
Z={read=z};
__property
TSpriteList* SpriteList={read=spriteList};
__property
OnMoveEvent OnMove={read=onMove,write=onMove};
__property
TSize Size={read=size};
__property
TPoint Location={read=location};
__property
TRect SpriteRect={read=GetSpriteRect};
// Конструктор
__fastcall
TSprite(TRect const
);
// Деструктор
__fastcall
virtual
~TSprite();
// Методы
bool
__fastcall
virtual
Move(TSize const
);
bool
__fastcall
virtual
MoveTo(TPoint const
);
};
// Тип динамического массива точек со следами спрайта
typedef
DynamicArray <TPoint> TTracePoints;
//TTracedSprite
class
TTracedSprite:public
TSprite
{
private
:
// Поля
TTracePoints trPoints;
bool
traced;
bool
traceColored;
TColor traceColor;
TPoint center;
// Метод
void
__fastcall
SetTraceColor(TColor const
);
public
:
// Свойство
__property
TTracePoints TrPoints={read=trPoints};
__property
bool
Traced={read=traced,write=traced};
__property
TColor TraceColor={read=traceColor,write=SetTraceColor};
__property
bool
TraceColored={read=traceColored,write=traceColored};
__property
TPoint Center={read=center};
// Конструктор
__fastcall
TTracedSprite(TRect const
);
// Деструктор
__fastcall
~TTracedSprite();
// Методы
bool
__fastcall
virtual
Move(TSize const
);
void
__fastcall
PutTrace();
};
const
TColor DefaultColor=0xffffff;
//TEllipseSprite
class
TEllipseSprite:public
TTracedSprite
{
private
:
// Поле
TColor color;
protected
:
// Методы
void
__fastcall
virtual
PaintPicture();
void
__fastcall
SetColor(TColor const
);
public
:
// Свойство
__property
TColor Color={read=color, write=SetColor};
// Конструктор
__fastcall
TEllipseSprite(TRect const
);
};
bool
Contains(TRect const
source,TRect const
dest)
{
return
source.Left>=dest.Left && source.Top>=dest.Top &&
source.Right<=dest.Right && source.Bottom<=dest.Bottom;
}
#endif
Весь код хэдера заключен «в скобки» защитного блокиратора
вида
#ifndef uSpriteH
#define uSpriteH
…
#endif
Это директивы компилятору
, которые переводятся так
#ifndef uSpriteH – если не определен символ uSpriteH
#define uSpriteH – определи символ uSpriteH
#endif – заверши область действия директивы «если».
Таким образом, если перед началом компиляции модуля символ uSpriteH определен
, то все, что находится дальше вплоть до директивы #endif , то есть все операторы модуля, компилироваться не будут
. Символ uSpriteH определяется при первой компиляции, когда он еще не
определен, поэтому все повторные компиляции модуля блокируются
.
Рассмотрим отдельные фрагменты кода.
class
TSprite;
//TSpriteList
class
TSpriteList
{
private
:
// Поля
int
count;
TControlCanvas* canvas;
…
void
__fastcall
SetVisible(bool
const
);
TRect __fastcall
GetSpriteRect();
…
__property
int
Count = {read=count};
…
__property
TSprite* Items[int
Index]={read=GetItems};
// Конструктор
__fastcall
TSpriteList(TControlCanvas* const
);
// Деструктор
__fastcall
virtual
~TSpriteList();
TSprite* __fastcall
AddSprite(TSprite* const
);
…
}
Здесь
· В описании типов и переменных на языке C в начале указывается идентификатор типа или тип, а затем имя типа или переменной: class
TSpriteList или int
count.
· Описание членов класса заключается в фигурные скобки. Эти скобки в C играют также роль ограничителей begin, end в Delphi.
· В описании TControlCanvas* canvas; стоит звездочка *. Это описание в языке С означает, что поле canvas является ссылкой на объект
класса TControlCanvas, т.е. просто целым числом, содержащим адрес объекта в памяти. Если звездочку опустить, то canvas будет описана как объект типа TControlCanvas «по значению
», т.е. содержать в себе все поля объекта типа TControlCanvas. В языке C описание объекта по значению приводит к тому, что в месте описания происходит создание реального экземпляра объекта – вызывается его «конструктор по умолчанию» и все поля инициализируются.
· В языке C нет процедур, как в Delphi, - только функции. Те функции, которые не возвращают значений, имеют тип void
. Они являются аналогами процедур в Delphi.
· В C++ Builder в описании всех методов классов участвует модификатор __fastcall
. Его смысл - обеспечить компиляцию в наиболее быстрый способ вызова метода при выполнении кода.
· В языке C даже, если функция не имеет параметров, в ее описании должны стоять скобки как в GetSpriteRect().
· В отличие от Delphi транслятор с языка C различает прописные и строчные буквы. Поэтому принято давать одинаковые имена полям и соответствующим свойствам, но начинать имена полей со строчной буквы, а свойств – с прописной буквы. Сравните, к примеру, описания поля count и свойства Count.
· Обратите внимание на синтаксис описания свойств в C++ Builder.
· Конструктор в C++ отличается от других методов тем, что его имя совпадает с именем класса и что он не возвращает никакой тип, даже void
.
· Имя деструктора также совпадает с именем класса, но перед именем дается знак отрицания ~. Как и констуктор, деструктор не возвращает какой-либо тип. Кроме того, деструктор не должен иметь параметров. Деструктор часто объявляется виртуальным. В этом случае деструкторы всех наследников автоматически становятся виртуальными.
· В C++ модификатор virtual
у виртуальных методов не заменяется у наследников на override
, а остается virtual
.
· В реализации на C++ у метода AddSprite есть только один параметр – ссылка на объект класса TSprite. Поэтому при обращении к методу AddSprite объект спрайта должен быть уже создан. В C++ нет возможности вызвать конструктор объекта, тип класса которого является переменной, как это делается в Delphi.
· При описании заголовков метода в хэдере языка C можно не указывать явно идентификаторы параметров – достаточно только типы. Так, в заголовке метода AddSprite указан только тип
единственного параметра TSprite* const
. Модификатор const
играет ту же роль, что и в Delphi – параметр, объявленный как const
, - не меняет своего значения внутри функции.
Прокомментируем другой фрагмент кода.
…
// Тип массива следов спрайтов на канве
typedef
DynamicArray< DynamicArray < bool
> > TTraceMap;
//TTracedSpriteList
class
TTracedSpriteList:public
TSpriteList
{
…
};
typedef
bool
__fastcall
(__closure
*OnMoveEvent)(TSprite* ,TPoint&);
//TSprite
class
TSprite:public
TObject
{
// Класс TSpriteList, объявленный friend
, получает доступ
// к private
и protected
членам класса TSprite
friend
class
TSpriteList;
…
protected
:
// Методы
void
__fastcall
virtual
PaintPicture()=0;
…
};
Здесь
· Служебное слово typedef
указывает на описание типа (подобно type в Delphi).
· Типом динамического массива, названного TTraceMap, является выражение DynamicArray< DynamicArray < bool
> >. Оно имеет смысл двумерного массива («массива массивов») переменных логического типа. Имя DynamicArray является именем стандартного шаблона (template), находящегося в библиотеке C++Builder. Это параметризованные
, или полиморфные
(generic) функции. В Delphi нет аналогов шаблонам. Аргументом шаблона является тип. В данном случае аргументом внутреннего шаблона DynamicArray является тип bool
, а аргументом внешнего – сам возвращаемый тип внутреннего шаблона DynamicArray< bool
>.
· Класс TTracedSpriteList является наследником класса TSpriteList. В заголовке описания класса TTracedSpriteList присутствует ссылка на наследник TSpriteList с модификатором public
. Модификатор public
в данном контексте означает, что все члены, наследуемые от TSpriteList, сохраняют свою, заданную предком, доступность и в наследнике (public
остается public
и т.д.). Если бы модификатором был protected
, то все наследуемые члены класса, объявленные в предке с модификаторами public
и protected
, приобрели бы в наследнике модификатор protected
.
· В описании
typedef
bool
__fastcall
(__closure
*OnMoveEvent)(TSprite* ,TPoint&); именем описываемого типа является OnMoveEvent. Сам тип является методом класса
с двумя параметрами типа TSprite* и TPoint&, который возвращает тип bool
. То, что OnMoveEvent именно метод класса, а не просто функция, отмечено модификатором __closure
. Тип TPoint является стандартным и описан в библиотеке C++Builder. Знак & служит для описания «параметра по ссылке» – аналог служебного слова var в Delphi.
· Модификаторы доступа к членам класса в C имеют слегка иной смысл, нежели в Delphi. Все члены с модификатором private
доступны только
методам этого же
класса вне зависимости от того, в каком модуле класс описан. Члены класса с модификатором protected
– только методам своего класса и классов-наследников. В Delphi члены с модификаторами private
и protected
доступны всему коду того модуля, в котором описан класс. Однако в C++ существует способ сделать доступными защищенные (private
и protected
) члены класса другому классу. Для этого класс, методам которого разрешается доступ к защищенным членам, описывается как friend
. Примером является декларация из описываемого кода friend
class
TSpriteList. Она говорит, что классу TSpriteList разрешается доступ ко всем без исключения членам класса TSprite.
· Обратите внимание на синтаксис описания абстрактного метода
в C++ void
__fastcall
virtual
PaintPicture()=0;
Реализация классов спрайтов
Ниже приведен полный код реализации классов спрайтов, описанных в хэдере. Комментарий к коду приводится непосредственно в тексте кода.
#include <vcl.h> //Модуль, несущий определения библиотеки VCL
/*Директива #pragma hdrstop означает окончание списка хэдеров,
компилируемых предварительно для использования в нескольких
файлах-исходниках одного проекта. В данном случае в этом списке
есть только файл vcl.h.
Директива #pragma hdrstop автоматически добавляется средой.*/
#pragma hdrstop
#include "uSprite.h" //хэдер нашего исходника
/*Директива #pragma package(smart_init) служит для «разумной»
последовательности в инициализации модулей при формировании
кода проекта. Она также автоматически добавляется средой
при создании нового модуля.*/
#pragma package(smart_init)
/*Далее располагается собственно авторский код.
Любой метод класса должен иметь в заголовке
имя класса, отделенного от имени самого метода
двойным двоеточием. В Delphi это была точка.*/
// Здесь реализуются методы класса TSpriteList.
// Конструктор инициализирует поля класса
__fastcall
TSpriteList::TSpriteList(TControlCanvas* const
canvas)
{
if
(canvas) //Условие оператора if всегда пишется в скобках.
/* Проверку наличия не нулевого указателя можно проводить,
используя просто сам указатель, как в коде.
Это равносильно записи условия в виде (canvas!=NULL) –
указатель canvas не равен
NULL*/
{
// служебное слово this
в C имеет смысл self в Delphi – указатель на вызывающий объект
// вызов члена объекта, если объект задан своим указателем, происходит оператором ->
// оператор присвоения в С имеет вид =, а для сравнения используется двойной знак ==
this
->canvas=canvas;
clientRect=canvas->Control->ClientRect;
canvasCopyMode=canvas->CopyMode;
list=new
TList(); // Так создается экземпляр объекта. Здесь TList() – конструктор.
} else
/*Служебное слово throw
используется для создания исключительной ситуации.
После этого нормальный ход про
Управление передается на ближайший блок catch
.*/
throw
Exception("Канва не задана!");
}
// Деструктор очищает список от спрайтов, восстанавливает свойства канвы
// и убирает сам экземпляр списка list
__fastcall
TSpriteList::~TSpriteList()
{
Clear();
canvas->CopyMode=canvasCopyMode;
delete
list; // Так вызывается деструктор объекта.
}
// Возвращает элемент списка спрайтов, отвечающий слою aZ,
// как указатель на объект типа TSprite
TSprite* __fastcall
TSpriteList::GetItems(int
aZ)
{
// служебное слово return
вызывает выход из метода и возвращение значения функции
// выражение (TSprite*) означает преобразование типа
указателя, полученного после
// вызова свойства list->Items[aZ], в указатель на TSprite
return
(TSprite*)list->Items[aZ];
}
// Добавляет в список объект типа TSprite и возвращает указатель на добавленный объект
TSprite* __fastcall
TSpriteList::AddSprite(TSprite* const
sprite)
{
// двойной знак && есть операция логического умножения
if
(sprite && Contains(sprite->SpriteRect,ClientRect))
{
sprite->spriteList=this
;
sprite->z =list->Add(sprite);
count=list->Count;
return
sprite;
} else
return
NULL;
}
// Перемещает спрайт с одной плоскости в другую (в смысле z-упорядочения)
void
__fastcall
TSpriteList::MoveSprite(int
const
fromZ, int
const
toZ)
{
if
(fromZ != toZ && fromZ > -1 && fromZ < count &&
toZ > -1 && toZ < count)
{
//В языке C локальные переменные (как minZ здесь)
// могут быть описаны в любой точке кода
// Выражение вида a = b?c:d называется условным выражением.
// В нем переменной a присваивается значение c, если выполняется условие b,
// и значение d, если оно не выполняется
// int
minZ = fromZ < toZ ? fromZ : toZ;
// В операторе цикла значение i в начале инициализируется,
// затем проверяется условие окончания цикла,
// выполняется оператор внутри цикла (если условие соблюдено),
// затем меняется значение параметра i.
// В данном случае оператор i-- означает уменьшение i на 1.
for
(int
i = count - 1; i >= minZ; i--)
if
(Items[i]->Visible) Items[i]->Restore();
list->Move(fromZ,toZ);
for
(int
i = minZ; i < count; i++)
{
Items[i]->z = i;
if
(Items[i]->Visible) Items[i]->Paint();
}
}
}
// Освобождает экземпляр объекта типа TSprite,
// находящийся в списке под номером aZ, и убирает указатель из списка
void
__fastcall
TSpriteList::DeleteSprite(int
const
aZ)
{
if
(aZ<count && aZ>-1)
{
for
(int
i= count-1;i>=aZ;i--)
if
(Items[i]->Visible) Items[i]->Restore();
delete
Items[aZ];
list->Items[aZ]=NULL;
list->Delete(aZ);
count=list->Count;
for
(int
i=aZ;i<count;i++)
{
Items[i]->z--;
if
(Items[i]->Visible) Items[i]->Paint();
}
}
}
// Очищает список от всех спрайтов
void
__fastcall
TSpriteList::Clear()
{
if
(list && count > 0)
for
(int
i = count - 1; i > -1; i--) DeleteSprite(i);
};
// Реализация методов класса списка спрайтов со следом TTracedSpriteList
// Конструктор вызывает конструктор предка и инициализирует поле traceMap
// После имени конструктора через двоеточие вызывается конструктор предка TSpriteList.
__fastcall
TTracedSpriteList::TTracedSpriteList(TControlCanvas* const
canvas):
TSpriteList(canvas) // Вызов конструктора предка
{
traceMap.Length=ClientRect.Right-ClientRect.Left+1;
for
(int
i=0;i<=traceMap.High;i++)
traceMap[i].Length=ClientRect.Bottom-ClientRect.Top+1;
}
// Деструктор вызывает очистку списка от спрайтов и вызывает деструктор предка
__fastcall
TTracedSpriteList::~TTracedSpriteList()
{
Clear();
}
// Удаляет спрайт слоя aZ из списка и удаляет сам спрайт
void
__fastcall
TTracedSpriteList::DeleteSprite(int
const
aZ)
{
((TTracedSprite*)Items[aZ])->TrPoints.Length=0;
TSpriteList::DeleteSprite(aZ); // Вызывается метод предка
}
// Очищает следы спрайтов и вызывает унаследованный метод очистки
void
__fastcall
TTracedSpriteList::Clear()
{
for
(int
i=traceMap.Low;i<= traceMap.High;i++)
for
(int
j=traceMap[i].Low;j<traceMap[i].High;j++)
traceMap[i][j]=false
;
TSpriteList::Clear(); // Вызывается метод предка
}
// Реализация методов класса спрайт TSprite
// Конструктор инициализирует поля класса
__fastcall
TSprite::TSprite(TRect const
rect)
{
location=Point(rect.Left,rect.Top);
size.cx=rect.Width(); size.cy=rect.Height();
image=new
Graphics::TBitmap();
image->Height=rect.Height();
image->Width =rect.Width();
z=-1;
}
// Деструктор уничтожает поле image
__fastcall
TSprite::~TSprite()
{
delete
image;
}
// Устанавливает новое значение поля visible и изображает или убирает спрайт с экрана
void
__fastcall
TSprite::SetVisible(bool
const
value)
{
if
(value!=visible)
{
if
(value)
{
BeginPaint();
Paint();
EndPaint();
} else
{
BeginPaint();
Restore();
EndPaint();
}
visible=value;
}
}
// Директива компилятору #define в данном случае вводит имя sprite
// для выражения ((TSprite*)(spriteList->Items[i])).
// Это укорачивает имя кода последующих методов
#define sprite ((TSprite*)(spriteList->Items[i]))
// Перемещает спрайт на вектор drift в плоскости изображения
bool
__fastcall
TSprite::Move(TSize const
drift)
{
TPoint newPos=Point(location.x+drift.cx,location.y+drift.cy);
bool
result=true
;
// В этом месте вызывается обработчик события onMove, если он задан
if
(onMove) result=onMove(this
,newPos);
// Здесь используется то, что оператор присвоения в C возвращает присвоенное значение
// Переменная result приобретает новое значение и одновременно возвращает его как
// условие оператора if
if
(result=result &&
Contains(Rect(newPos.x,newPos.y,newPos.x+size.cx,newPos.y+size.cy),
spriteList->ClientRect))
{
bool
VisState=visible;
Visible=false
;
location=newPos;
Visible=VisState;
}
return
result;
}
// Перемещает спрайт в точку newPos
bool
__fastcall
TSprite::MoveTo(TPoint const
newPos)
{
TSize s;
s.cx=newPos.x-location.x;s.cy=newPos.y-location.y;
return
Move(s);
}
// Готовит изображение спрайта
void
__fastcall
TSprite::BeginPaint()
{
SetMask(Z);
for
(int
i=spriteList->Count-1;i>=Z+1;i--)
if
(sprite->mask && sprite->visible) sprite->Restore();
}
// Устанавливает маску для спрайта с индексом anID (слой)
void
__fastcall
TSprite::SetMask(int
const
anID)
{
for
(int
i=anID+1;i<spriteList->Count;i++)
{
sprite->mask= sprite->Intersect(anID,i) || sprite->mask;
if
(mask) SetMask(i);
}
}
// Завершает изображение спрайта
void
__fastcall
TSprite::EndPaint()
{
for
(int
i=Z+1;i<spriteList->Count;i++)
if
(sprite->mask)
{
if
(sprite->visible) sprite->Paint();
sprite->mask=false
;
}
}
// Директива компилятору #undef отказывается от обозначения sprite
#undef sprite
// Директива компилятору #define в данном случае вводит имя canvas
#define canvas spriteList->Canvas
// Изображает спрайт на канве
void
__fastcall
TSprite::Paint()
{
canvas->CopyMode=cmSrcCopy;
image->Canvas->CopyRect(Rect(0,0,image->Width,image->Height),
canvas, SpriteRect);
PaintPicture();
}
// Убирает изображение спрайта с канвы, восстанавливая фон
void
__fastcall
TSprite::Restore()
{
canvas->CopyMode=cmSrcCopy;
canvas->CopyRect(SpriteRect, image->Canvas,
Rect(0,0,image->Width,image->Height));
}
// Директива компилятору #undef отказывается от обозначения canvas
#undef canvas
// Возвращает прямоугольник спрайта
TRect __fastcall
TSprite::GetSpriteRect()
{
return
Rect(location,Point(location.x+size.cx,location.y+size.cy));
}
// Определяет факт пересечения прямоугольников спрайтов,
// находящихся в слоях First и Second
bool
__fastcall
TSprite::Intersect(int
const
First,int
const
Second)
{
TRect rect;
return
IntersectRect(rect,
((TSprite*)(spriteList->Items[First]))->SpriteRect,
((TSprite*)(spriteList->Items[Second]))->SpriteRect);
}
// Реализация методов класса спрайт со следом TtracedSprite
// Констуктор класса вызывает конструктор предка и инициализирует поле center
__fastcall
TTracedSprite::TTracedSprite(TRect const
rect):TSprite(rect)
{
center = CenterPoint(SpriteRect);
}
// Деструктор освобождает массив точек следа и вызывает деструктор предка
__fastcall
TTracedSprite::~TTracedSprite()
{
trPoints.Length=0;
}
// Устанавливает цвет следа и, одновременно, делает след цветным
void
__fastcall
TTracedSprite::SetTraceColor(TColor const
value)
{
traceColor=value;
traceColored=true
;
}
// Перемещает спрайт на вектор drift
bool
__fastcall
TTracedSprite::Move(TSize const
drift)
{
if
(Visible && Traced) PutTrace();
bool
result=TSprite::Move(drift); // Так вызывается метод наследника
if
(result) center =CenterPoint(SpriteRect);
return
result;
}
#define sprite ((TTracedSprite*)(SpriteList->Items[i]))
#define sprList ((TTracedSpriteList*)SpriteList)
// Помещает пиксел следа на канву
void
__fastcall
TTracedSprite::PutTrace()
{
for
(int
i=SpriteList->Count-1;i>=0;i--)
if
(sprite->Visible && PtInRect(sprite->SpriteRect,Center))
sprite->Restore();
// Знак ! означает оператор логического отрицания в C
if
(!sprList->TraceMap[Center.x-sprList->ClientRect.Left]
[Center.y-sprList->ClientRect.Top])
{
SpriteList->Canvas->Pixels[Center.x][Center.y]=traceColored?traceColor:
// Знак ^ означает оператор логической симметрической разности.
(TColor)(0xffffff ^ SpriteList->Canvas->Pixels[Center.x][Center.y]);
sprList->TraceMap[Center.x-sprList->ClientRect.Left]
[Center.y-sprList->ClientRect.Top]=true
;
trPoints.Length++;
trPoints[trPoints.High].x=Center.x;
trPoints[trPoints.High].y=Center.y;
}
for
(int
i=0;i<SpriteList->Count;i++)
if
(sprite->Visible && PtInRect(sprite->SpriteRect,Center))
sprite->Paint();
}
#undef sprite
#undef sprList
// Реализация методов класса эллиптического спрайта TEllipseSprite
// Констуктор вызывает конструктор предка и инициализирует поле color
__fastcall
TEllipseSprite::TEllipseSprite(TRect const
rect):
TTracedSprite(rect)
{
color=DefaultColor;
}
// Устанавливает цвет спрайта, меняя его изображение на экране
void
__fastcall
TEllipseSprite::SetColor(const
TColor value)
{
if
(color!=value)
{
bool
VisState=Visible;
Visible=false
;
color=value;
if
(VisState) Visible=true
;
}
}
#define canvas SpriteList->Canvas
// Создает изображение эллиптического спрайта на канве
void
__fastcall
TEllipseSprite::PaintPicture()
{
canvas->Brush->Style=bsSolid;
canvas->Brush->Color=color;
canvas->Pen->Color=color;
canvas->Ellipse(SpriteRect);
};
#undef canvas
Предлагается создать оконное приложение, тестирующее описанные классы спрайтов, в среде C++ Builder.
C#
В языке C# компилируемый модуль
является отдельным файлом и содержит в себе сразу и описание, и реализацию методов класса. Хэдеры отсутствуют. Последовательность описания членов класса не имеет значения. Более того, такой модуль легко скомпилировать в форме отдельного исполняемого модуля
с расширением .dll (dynamic link library). В отличие от exe-файла динамически загружаемая библиотека не имеет точки входа и не может выполняться независимо от вызывающего приложения.
В языке C# все типы являются классами – наследниками одного общего для всех класса Object. Это относится даже к простым типам int
, double и т.д. Такие типы являются типами
-значениями
. К типам-значениям относится также перечислимый тип enum. Объекты типов-значений передаются целиком со всеми своими полями. Обычно это небольшие по объему структуры
(struct). Другие типы классов передаются по ссылке (указателю, или адресу) и называются ссылочными типами
. К ним относятся многие библиотечные и пользовательские классы (class
).
В C# cуществует специфический тип классов, обозначаемый служебным словом delegate
. Тип delegate
позволяет описывать указатели на любой метод класса, которые, в частности, могут служить обработчиками событий.
В нашей реализации спрайтов код всех классов помещается в отдельный компилируемый модуль, который компилируется в отдельный исполняемый модуль типа библиотеки – модуль с расширением .dll.
Весь код в C# разбит на пространства имен
(namespace
). Часто отдельный компилируемый модуль относится к одному пространству имен, которое указывается в заголовке модуля (в нашем случае это namespace
spritesdll). Но это не правило.
В общем случае
· один исполняемый модуль (.dll или .exe) может собираться из нескольких компилируемых модулей, образуя «сборку» (assembly);
· один компилируемый модуль может состоять из нескольких пространств имен;
· одно пространство имен может охватывать несколько компилируемых модулей;
· описание одного класса может охватывать несколько компилируемых модулей, но при этом каждый отдельный класс может принадлежать только одному пространству имен.
Далее весь комментарий находится в тексте.
/* В начале модуля обычно находится список используемых пространств имен.
Каждое из имен в списке предваряется служебным словом using
.
Если имя пространства имен (например, в нашем случае, имя System.Collections)
присутствует в списке, то в коде модуля имя любого идентификатора из пространства
имен System.Collections (в нашем случае имя типа ArrayList) может быть записано
сокращенно (ArrayList) – без указания имени пространства имен
(т.е., не в виде System.Collections.ArrayList).*/
using
System;
using
System.Collections;
using
System.Drawing;
using
System.Drawing.Drawing2D;
using
System.Windows.Forms;
namespace
spritesdll
{
// Следующий ниже и далее в тексте комментарий, выделенный тройным слэшом ///,
// используется средой для поддержки справочной системы, описывающей элементы кода
/// <summary>
/// Поддерживает список спрайтов, используя объект типа ArrayList.
/// </summary>
/// <remarks>
/// Спрайт - плоский графический объект, занимающий прямоугольную область экрана.
/// Каждый спрайт списка принадлежит как-бы отдельной
/// изображающей плоскости экрана - z-слою.
/// Каждый спрайт списка имеет свое значение z - индекс спрайта в списке.
/// Ось z направлена перпендикулярно экрану по направлению к наблюдателю.
/// Единственным параметром конструктора класса SpriteList является
/// объект типа Control.
/// Объект Control ограничивает область перемещения спрайтов списка и
/// создает объект класса Graphics для изображения спрайта.
/// При перерисовке объекта Control или его уничтожении список очищается.
/// Каждый спрайт списка создается методом Add, параметрами которого являются
/// тип класса спрайта и занимаемый спрайтом прямоугольник.
/// Метод Add возвращает экземпляр созданного спрайта.
/// Прямоугольник спрайта должен полностью принадлежать прямоугольнику
/// объекта Control.
/// Списку могут принадлежать спрайты разного типа -
/// наследники абстрактного класса Sprite.
/// Метод RemoveSpriteAt удаляет из списка спрайт, принадлежащий конкретному слою.
/// Метод Clear удаляет все спрайты из списка.
/// Метод MoveSprite перемещает спрайт из одного слоя в другой.
/// Элементы списка доступны через индексы с нулевой базой.
/// </remarks>
public
class
SpriteList
{
/// <summary>
/// Хранит ссылку на объект типа Graphics для изображения спрайтов.
/// </summary>
Graphics canvas;
/// <summary>
/// Возвращает ссылку на объект типа Graphics для изображения спрайтов.
/// </summary>
// Так описываются свойства в C#. Модификатор доступа internal
ограничивает
// доступ к члену класса тем исполняемым модулем, в котором этот член описан.
internal
Graphics Canvas { get
{ return
canvas; } }
/// <summary>
/// Хранит ссылку на Control, с которым связан список спрайтов.
/// </summary>
Control parent;
/// <summary>
/// Возвращает ссылку на Control, ограничивающий спрайты списка.
/// </summary>
internal
Control Parent { get
{ return
parent; } }
/// <summary>
/// Хранит ссылку на клиентский прямоугольник объекта Control.
/// </summary>
Rectangle clientRect;
/// <summary>
/// Возвращает ссылку на клиентский прямоугольник объекта Control.
/// </summary>
public
Rectangle ClientRect { get
{ return
clientRect; } }
/// <summary>
/// Хранит ссылку на список ссылок на спрайты.
/// </summary>
// ArrayList – стандартный класс, описанный в одной из библиотек .net.
ArrayList list = new
ArrayList();
/// <summary>
/// Возвращает ссылку на список ссылок на спрайты.
/// </summary>
internal
ArrayList List { get
{ return
list; } }
/// <summary>
/// Возвращает спрайт - элемент списка из данного слоя.
/// </summary>
/// <param name="z">
/// Слой-индекс спрайта в списке.
/// </param>
/// <returns>
/// Спрайт из слоя z.
/// </returns>
// Так описывается свойство, индексирующее объекты класса – так называемый индексатор
public
Sprite this
[int
z] { get
{ return
(Sprite)list[z]; } }
/// <summary>
/// Хранит текущее число спрайтов в списке.
/// </summary>
int
count;
/// <summary>
/// Возвращает число спрайтов в списке.
/// </summary>
public
int
Count { get
{ return
count; } }
/// <summary>
/// Инициализирует новый экземпляр объекта класса типа SpriteList.
/// </summary>
/// <param name="control">
/// Объект типа Control, на прямоугольнике которого предполагается размещать
/// спрайты - элементы списка SpriteList.
/// </param>
/// <remarks>
/// Конструктор списка создает объект типа Graphics для изображения спрайтов
/// и добавляет к событиям перерисовки и уничтожения объекта Control
/// вызов метода Clear
/// </remarks>
public
SpriteList(Control control)
{
if
(control == null
) throw
(
new
ArgumentNullException("Аргумент конструктора SpriteList не определен!"));
parent = control;
canvas = parent.CreateGraphics();
clientRect = parent.ClientRectangle;
parent.HandleDestroyed += delegate
{ Clear(); };
parent.Invalidated += delegate
{ Clear(); };
}
/// <summary>
/// Возвращает перечислитель, позволяющий перемещаться по списку.
/// </summary>
/// <returns>
/// Ссылка на объект типа IEnumerator для списка SpriteList.
/// </returns>
/// <remarks>
/// Функция GetEnumerator позволяет использовать оператор foreach
/// для членов списка (спрайтов).
/// </remarks>
public
IEnumerator GetEnumerator() { return
list.GetEnumerator(); }
/// <summary>
/// Очищает список и освобождает объект типа Graphics,
/// используемый для изображения спрайтов.
/// </summary>
~SpriteList()
{
Clear();
if
(canvas != null
) canvas.Dispose();
}
/// <summary>
/// Создает новый экземпляр спрайта и добавляет его к списку.
/// </summary>
/// <param name="SpriteType">
/// Имя класса добавляемого спрайта.
/// </param>
/// <param name="SpriteRect">
/// Прямоугольник спрайта.
/// </param>
/// <returns>
/// Созданный и добавленный в список спрайт.
/// </returns>
/// <remarks>
/// Метод Add возвращает null
, если прямоугольник спрайта не
/// вписывается в прямоугольник объекта Control.
/// </remarks>
public
Sprite AddSprite(Type SpriteType, Rectangle SpriteRect)
{
if
(SpriteType != null
&& SpriteRect != null
&& SpriteRect.Height > 0 && SpriteRect.Width > 0 &&
clientRect.Contains(SpriteRect))
{
Sprite sprite;
try
{
sprite = (Sprite)Activator.CreateInstance(SpriteType,
new
object
[2] { SpriteRect, this
});
}
catch
(Exception e)
{
throw
(e is
System.Reflection.TargetInvocationException ?
e.InnerException : e);
}
sprite.Z = list.Add(sprite);
count = list.Count;
return
sprite;
}
return
null
;
}
/// <summary>
/// Меняет z-слой положения спрайта.
/// </summary>
/// <param name="fromZ">
/// Исходный слой.
/// </param>
/// <param name="toZ">
/// Конечный слой.
/// </param>
public
void
MoveSprite(int
fromZ, int
toZ)
{
if
(fromZ != toZ &&
fromZ > -1 && fromZ < count &&
toZ > -1 && toZ < count)
{
Sprite tempSprite;
int
minZ = fromZ < toZ ? fromZ : toZ;
for
(int
i = count - 1; i >= minZ; i--)
if
(this
[i].Visible) this
[i].Restore();
tempSprite = this
[fromZ];
list.RemoveAt(fromZ);
list.Insert(toZ, tempSprite);
for
(int
i = minZ; i < count; i++)
{
this
[i].Z = i;
if
(this
[i].Visible) this
[i].Paint();
}
}
}
/// <summary>
/// Удаляет спрайт заданного слоя из списка.
/// </summary>
/// <param name="z">
/// Слой удаляемого спрайта.
/// </param>
public
virtual
void
RemoveSpriteAt(int
z)
{
if
(z > -1 && z < count)
{
for
(int
i = count - 1; i >= z; i--)
if
(this
[i].Visible) this
[i].Restore();
list.RemoveAt(z);
count = list.Count;
for
(int
i = z; i < count; i++)
{
this
[i].Z--;
if
(this
[i].Visible) this
[i].Paint();
}
}
}
/// <summary>
/// Очищает список от спрайтов.
/// </summary>
public
virtual
void
Clear()
{
if
(list != null
&& count > 0)
for
(int
i = count - 1; i > -1; i--) RemoveSpriteAt(i);
}
}
/// <summary>
/// Тип делегата, предназначенного для обработки события,
/// наступающего в методе Move перед перемещением спрайта.
/// </summary>
/// <param name="sender">
/// Экземпляр наследника класса Sprite, вызывающий обработчик.
/// <param name="newLocation">
/// Новое положение левой верхней вершины спрайта,
/// которое может быть изменено обработчиком.
/// </param>
/// <returns>
/// true
, если перемещение в новое положение разрешено, и false
в противном случае.
/// </returns>
public
delegate
bool
BeforeMoveEventHandler(Sprite sender, ref
Point newLocation);
/// <summary>
/// Абстрактный класс спрайтов.
/// </summary>
/// <remarks>
/// Спрайт - это графический объект, ограниченный прямоугольной областью.
/// Объекты наследников класса Sprite создаются методом AddSprite класса SpriteList.
/// Изображения спрайтов могут независимо перемещаться на экране,
/// как бы занимая каждый свой слой (z-упорядочение).
/// Для перемещения спрайтов служат методы Move и MoveTo.
/// Свойство Visible определяет присутствие спрайта на экране.
/// </remarks>
public
abstract
class
Sprite : Object
{
/// <summary>
/// Инициализирует экземпляр объекта класса Sprite.
/// Вызывается в методе AddSprite класса SpriteList.
/// </summary>
/// <param name="SpriteRect">
/// Прямоугольник спрайта.
/// <param name="sprites">
/// Список спрайтов, которому принадлежит создаваемый экземпляр.
/// </param>
/// <remarks>
/// Конструктор инициализирует поля объекта.
/// </remarks>
internal
Sprite(Rectangle SpriteRect, SpriteList sprites)
{
spriteSize = SpriteRect.Size;
location = SpriteRect.Location;
image = new
Bitmap(spriteSize.Width, spriteSize.Height);
bmpCanvas = Graphics.FromImage(image);
this
.sprites = sprites;
}
/// <summary>
/// Деструктор. Освобождает объект image.
/// </summary>
~Sprite()
{
if
(image != null
) image.Dispose();
}
/// <summary>
/// Хранит текущий индекс-слой спрайта.
/// </summary>
int
z = -1;
/// <summary>
/// Возвращает и устанавливает значение индекса-слоя спрайта.
/// </summary>
public
int
Z { get
{ return
z; } internal
set
{ z = value
; } }
/// <summary>
/// Хранит текущее значение маски, используемой при определении фона спрайта.
/// </summary>
bool
mask;
/// <summary>
/// Устанавливает маску спрайта.
/// </summary>
/// <param name="layer">
/// Индекс (слой) спрайта.
/// </param>
void
SetMask(int
layer)
{
for
(int
i = layer + 1; i < sprites.Count; i++)
{
sprites[i].mask = sprites[i].Intersect(layer, i) || sprites[i].mask;
if
(mask) SetMask(i);
}
}
/// <summary>
/// Хранит ссылку на объект класса Bitmap,
/// временно хранящего фон спрайта.
/// </summary>
Bitmap image;
/// <summary>
/// Хранит ссылку на объект класса Graphics на Bitmap, содержащий фон спрайта.
/// </summary>
Graphics bmpCanvas;
/// <summary>
/// Хранит ссылку на список типа SpriteList, которому принадлежит спрайт.
/// </summary>
SpriteList sprites;
/// <summary>
/// Устанавливает и возвращает ссылку на SpriteList, которому принадлежит спрайт.
/// </summary>
public
SpriteList Sprites
{
internal
set
{ sprites = value
; }
get
{ return
sprites; }
}
/// <summary>
/// Хранит текущее состояние видимости спрайта на экране.
/// </summary>
bool
visible;
/// <summary>
/// Устанавливает и возвращает состояние видимости спрайта на экране.
/// </summary>
public
bool
Visible
{
set
{
if
(value
!= visible)
{
BeginPaint();
if
(value
) Paint(); else
Restore();
EndPaint();
visible = value
;
}
}
get
{ return
visible; }
}
/// <summary>
/// Полиморфный метод установки значений полей класса.
/// </summary>
/// <typeparam name="T">
/// Тип устанавливаемого поля.
/// </typeparam>
/// <param name="outValue">
/// Результирующее значение поля.
/// </param>
/// <param name="inValue">
/// Устанавливаемое значение поле.
/// </param>
/// <remarks>
/// Метод Set убирает спрайт с экрана на время изменения его поля типа T.
/// </remarks>
protected
void
Set<T>(ref
T outValue, T inValue)
{
if
(!outValue.Equals(inValue))
{
bool
VisState = visible;
Visible = false
;
outValue = inValue;
Visible = VisState;
}
}
/// <summary>
/// Хранит положение верхнего левого угла спрайта.
/// </summary>
Point location;
/// <summary>
/// Устанавливает и возвращает положение верхнего левого угла спрайта.
/// </summary>
public
Point Location { get
{ return
location; } }
/// <summary>
/// Хранит размер спрайта.
/// </summary>
Size spriteSize;
/// <summary>
/// Возвращает размер спрайта.
/// </summary>
public
Size SpriteSize { get
{ return
spriteSize; } }
/// <summary>
/// Возвращает прямоугольник спрайта
/// </summary>
public
Rectangle SpriteRect { get
{ return
new
Rectangle(location, spriteSize); } }
/// <summary>
/// Хранит обработчик движения спрайта.
/// </summary>
BeforeMoveEventHandler onBeforeMove;
/// <summary>
/// Устанавливает и возвращает обработчик движения спрайта.
/// </summary>
public
BeforeMoveEventHandler OnBeforeMove
{
set
{ onBeforeMove = value
; }
get
{ return
onBeforeMove; }
}
/// <summary>
/// Готовит изображение спрайта.
/// </summary>
void
BeginPaint()
{
SetMask(z);
for
(int
i = sprites.Count - 1; i >= z + 1; i--)
if
(sprites[i].mask && sprites[i].Visible) sprites[i].Restore();
}
/// <summary>
/// Завершает изображение спрайта.
/// </summary>
void
EndPaint()
{
for
(int
i = z + 1; i < sprites.Count; i++)
if
(sprites[i].mask)
{
if
(sprites[i].Visible) sprites[i].Paint();
sprites[i].mask = false
;
}
}
/// <summary>
/// Определяет факт пересечения прямоугольников двух спрайтов.
/// </summary>
/// <param name="First">
/// Индекс (слой) первого спрайта.
/// </param>
/// <param name="Second">
/// Индекс (слой) второго спрайта.
/// </param>
/// <returns>
/// true
, если спрайты пересекаются, и false
в противном случае.
/// </returns>
bool
Intersect(int
First, int
Second)
{
return
sprites[First].SpriteRect.IntersectsWith
(sprites[Second].SpriteRect);
}
/// <summary>
/// Создает конкретное изображение спрайта.
/// </summary>
/// <remarks>
/// Метод PaintPicture является абстрактным в этом классе и должен быть
/// перекрыт наследниками, формирующими изображение с помощью этого метода.
/// </remarks>
protected
abstract
void
PaintPicture();
/// <summary>
/// Убирает спрайт с экрана.
/// </summary>
protected
internal
virtual
void
Restore()
{
sprites.Canvas.DrawImage(image, location);
}
/// <summary>
/// Помещает спрайт на экран.
/// </summary>
protected
internal
virtual
void
Paint()
{
bmpCanvas.CopyFromScreen(sprites.Parent.RectangleToScreen
(SpriteRect).Location, new
Point(), image.Size);
PaintPicture();
}
/// <summary>
/// Смещает положение спрайта на плоскости XY.
/// </summary>
/// <param name="drift">
/// Вектор смещения.
/// </param>
/// <returns>
/// true
, если смещение произошло, и false
, если нет.
/// </returns>
public
virtual
bool
Move(Size drift)
{
Point newPos = location + drift;
bool
result = true
;
if
(onBeforeMove != null
)
result = onBeforeMove(this
, ref
newPos);
if
(result = result &&
sprites.ClientRect.Contains(new
Rectangle(newPos, spriteSize)))
Set<Point>(ref
location, newPos);
return
result;
}
/// <summary>
/// Перемещает сайт в новое положение на плоскости XY.
/// </summary>
/// <param name="newLocation">
/// Новое положение левого верхнего угла спрайта.
/// </param>
/// <returns>
/// true
, если перемещение произошло, false
, если нет.
/// </returns>
public
virtual
bool
MoveTo(Point newLocation)
{
return
Move((Size)newLocation - (Size)location);
}
}
/// <summary>
/// Собирает и хранит информацию о следах спрайтов, формирующих список.
/// </summary>
/// <remarks>
/// Объекты класса TracedSpriteList в добавление к свойствам своего предка
/// SpriteList создают и поддерживают битовый массив, хранящий информацию о
/// каждом пикселе клиентской области. Если пиксел является следом спрайта,
/// то соответствующий элемент массива имеет значение true
, если нет, то false
.
/// Класс TracedSpriteList перекрывает методы RemoveSpriteAt и Clear, уничтожая
/// информацию о следе удаляемого спрайта.
/// </remarks>
public
class
TracedSpriteList : SpriteList
{
/// <summary>
/// Хранит двумерный битовый массив, отображающий состояние пикселей
/// прямоугольника объекта Control - принадлежит ли пиксел следу спрайта, или фону.
/// </summary>
BitArray[] traceMap;
/// <summary>
/// Возвращает ссылку на битовый массив состояния следов спрайтов.
/// </summary>
internal
BitArray[] TraceMap { get
{ return
traceMap; } }
/// <summary>
/// Инициализирует экземпляр объекта класса TracedSpriteList.
/// </summary>
/// <param name="control">
/// Объект, на котором изображаются спрайты.
/// </param>
public
TracedSpriteList(Control control) : base
(control)
{
traceMap = new
BitArray[ClientRect.Width];
for
(int
i = 0; i < traceMap.Length; i++)
traceMap[i] = new
BitArray(ClientRect.Height);
}
/// <summary>
/// Убирает спрайт из списка.
/// </summary>
/// <param name="z">
/// Индекс-слой устраняемого спрайта.
/// </param>
public
override
void
RemoveSpriteAt(int
z)
{
if
(z > -1 && z < Count)
{
((TracedSprite)this
[z]).TracePoints.Clear();
base
.RemoveSpriteAt(z);
}
}
/// <summary>
/// Очищает список от спрайтов.
/// </summary>
public
override
void
Clear()
{
for
(int
i = 0; i < traceMap.Length; i++)
for
(int
j = 0; j < traceMap[i].Count; j++)
traceMap[i][j] = false
;
base
.Clear();
}
}
/// <summary>
/// Спрайт, оставляющий след.
/// </summary>
/// <remarks>
/// Класс TracedSprite как и его предок является абстрактным.
/// Наследники класса TracedSprite получают возможность оставлять
/// след на клиентской области в форме отдельного пикселя на месте
/// положения своего центра в момент, предшествующий смене положения.
/// Порождать объекты класса TracedSprite должен метод Add, вызванный классом
/// TracedSpriteList. В противном случае будет сгенерирована
/// исключительная ситуация типа ArgumentException.
/// </remarks>
public
abstract
class
TracedSprite : Sprite
{
/// <summary>
/// Хранит true
, если спрайт оставляет след, и false
, если нет.
/// </summary>
bool
traced;
/// <summary>
/// Устанавливает и возвращает значение поля traced.
/// </summary>
public
bool
Traced { set
{ traced = value
; } get
{ return
traced; } }
/// <summary>
/// Хранит true
, если пиксели следа
/// имеют специальный цвет, и false
, если нет.
/// </summary>
/// <remarks>
/// Если пиксели следа не имеют специального цвета, то их цвет определяется
/// как дополнительный до белого от цвета пикселя фона.
/// </remarks>
bool
traceColored;
/// <summary>
/// Устанавливает и возвращает значение поля traceColored.
/// </summary>
public
bool
TraceColored
{
set
{ traceColored = value
; }
get
{ return
traceColored; }
}
/// <summary>
/// Хранит цвет следа.
/// </summary>
Color traceColor = Color.White;
/// <summary>
/// Устанавливает и возвращает цвет следа.
/// </summary>
public
Color TraceColor
{
set
{ traceColored = true
; traceColor = value
; }
get
{ return
traceColor; }
}
/// <summary>
/// Хранит координаты точек следа.
/// </summary>
ArrayList tracePoints = new
ArrayList();
/// <summary>
/// Возвращает ссылку на список координат точек следа.
/// </summary>
public
ArrayList TracePoints { get
{ return
tracePoints; } }
/// <summary>
/// Хранит относительное положение центра спрайта.
/// </summary>
Size centerOffset;
/// <summary>
/// Хранит абсолютное положение центра спрайта.
/// </summary>
Point center;
/// <summary>
/// Возвращает положение центра спрайта.
/// </summary>
public
Point Center { get
{ return
center; } }
/// <summary>
/// Инициализирует экземпляр объекта класса TracedSprite.
/// </summary>
/// <param name="SpriteRect">
/// Прямоугольник спрайта.
/// <param name="sprites">
/// Список спрайтов, которому принадлежит создаваемый экземпляр.
/// </param>
internal
TracedSprite(Rectangle SpriteRect, SpriteList sprites) : base
(SpriteRect, sprites)
{
if
(!(Sprites is
TracedSpriteList))
throw
(new
ArgumentException("Спрайт со следом может быть" +
" только членом списка - наследника TracedSpriteList!"));
centerOffset = new
Size(SpriteSize.Width / 2, SpriteSize.Height / 2);
center = Location + centerOffset;
}
/// <summary>
/// Перемещает спрайт на плоскости XY.
/// </summary>
/// <param name="drift">
/// Вектор смещения.
/// </param>
/// <returns>
/// true
, если перемещение произошло, и false
, если нет.
/// </returns>
public
override
bool
Move(Size drift)
{
if
(Visible && Traced) PutTrace();
bool
result = base
.Move(drift);
if
(result) center = Location + centerOffset;
return
result;
}
/// <summary>
/// Изображает след спрайта.
/// </summary>
/// <remarks>
/// След спрайта изображается в виде пикселя измененного цвета в точке,
/// где находился центр спрайта на момент его перемещения.
/// </remarks>
public
void
PutTrace()
{
for
(int
i = Sprites.Count - 1; i >= 0; i--)
if
(Sprites[i].Visible &&
Sprites[i].SpriteRect.Contains(center))
Sprites[i].Restore();
if
(!((TracedSpriteList)Sprites).TraceMap[center.X - Sprites.ClientRect.Left]
[center.Y - Sprites.ClientRect.Top])
{
if
(!traceColored)
using
(Bitmap bitmap = new
Bitmap(1, 1))
using
(Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(Sprites.Parent.RectangleToScreen(
new
Rectangle(center.X, center.Y, 1, 1)).Location,
new
Point(), bitmap.Size);
Color clr = bitmap.GetPixel(0, 0);
using
(Brush brush = new
SolidBrush(
Color.FromArgb(0xff ^ clr.R, 0xff ^ clr.G, 0xff ^ clr.B)))
Sprites.Canvas.FillRectangle(brush, center.X, center.Y, 1, 1);
}
else
using
(Brush brush = new
SolidBrush(traceColor))
Sprites.Canvas.FillRectangle(brush, center.X, center.Y, 1, 1);
((TracedSpriteList)Sprites).TraceMap[center.X - Sprites.ClientRect.Left]
[center.Y - Sprites.ClientRect.Top] = true
;
tracePoints.Add(new
Point(center.X, center.Y));
}
foreach
(TracedSprite sprite in
Sprites)
if
(sprite.Visible && sprite.SpriteRect.Contains(center))
sprite.Paint();
}
/// <summary>
/// Очищает коллекцию точек следа спрайта.
/// </summary>
~TracedSprite()
{
if
(TracePoints != null
&& TracePoints.Count > 0) TracePoints.Clear();
}
}
/// <summary>
/// Спрайт в форме заполненного эллипса, заданного цвета и градиента.
/// </summary>
public
class
FillEllipseSprite : TracedSprite
{
/// <summary>
/// Хранит цвет спрайта.
/// </summary>
Color color = System.Drawing.Color.Gray;
/// <summary>
/// Возвращает и устанавливает цвет спрайта.
/// </summary>
public
Color Color
{
set
{ Set<Color>(ref
color, value
); }
get
{ return
color; }
}
/// <summary>
/// Хранит указание на то, является ли заполнение эллипса градиентным.
/// </summary>
bool
isGradient = true
;
/// <summary>
/// Устанавливает и возвращает поле isGradient.
/// </summary>
public
bool
IsGradient
{
set
{ Set<bool
>(ref
isGradient, value
); }
get
{ return
isGradient; }
}
/// <summary>
/// Хранит цвета границы градиентного заполнения.
/// </summary>
Color[] colors = { Color.FromArgb(0, 0, 0) };
/// <summary>
/// Устанавливает и возвращает цвета границы градиентного заполнения.
/// </summary>
public
Color[] Colors
{
set
{ Set<Color[]>(ref
colors, value
); }
get
{ return
colors; }
}
/// <summary>
/// Инициализирует экземпляр объекта класса FillEllipseSprite.
/// </summary>
/// <param name="SpriteRect">
/// Прямоугольник эллипса.
/// <param name="sprites">
/// Список спрайтов, которому принадлежит создаваемый экземпляр.
/// </param>
public
FillEllipseSprite(Rectangle SpriteRect, SpriteList sprites)
: base
(SpriteRect, sprites) { }
/// <summary>
/// Изображает спрайт в форме заполненного эллипса.
/// </summary>
protected
override
void
PaintPicture()
{
if
(!isGradient)
using
(Brush brush = new
SolidBrush(color))
Sprites.Canvas.FillEllipse(brush, SpriteRect);
else
using
(GraphicsPath path = new
GraphicsPath())
{
path.AddEllipse(SpriteRect);
using
(PathGradientBrush pthGrBrush = new
PathGradientBrush(path))
{
pthGrBrush.CenterColor = color;
pthGrBrush.SurroundColors = colors;
Sprites.Canvas.FillEllipse(pthGrBrush, SpriteRect);
}
}
}
}
}
Предлагается в среде MS Visual Studio 2005 составить проект, тестирующий описанные классы спрайтов.