Учебное пособие: Методические указания к курсу программирования для студентов физического факультета Сравнительное объектно-ориентированное проектирование
Название: Методические указания к курсу программирования для студентов физического факультета Сравнительное объектно-ориентированное проектирование Раздел: Остальные рефераты Тип: учебное пособие |
Министерство образования и науки Российской Федерации Федеральное агентство по образованию Государственное образовательное учреждение высшего профессионального образования «РОСТОВСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ» МЕТОДИЧЕСКИЕ УКАЗАНИЯ к курсу программирования для студентов физического факультета Сравнительное объектно-ориентированное проектирование 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 составить проект, тестирующий описанные классы спрайтов. |