Юрий Спектор дата публикации 17-05-2010 22:16 Библиотека EasyCAD library, или контейнер визуальных объектов для практикующих
Появление данного материала является чем-то вроде логического продолжения публикации "Контейнер визуальных объектов". Несмотря на то, что идея последней была лишь показать на каком-либо примере, как проектируются объектно-ориентированные системы, много вопросов поступило именно по прикладной части, т.е. по реализации конкретного поведения объектов или контейнера. Заинтересовавшись этой темой, автор предлагает вашему вниманию библиотеку "EasyCAD Library", в которой была предпринята попытка решить эти и многие другие вопросы.
По своей сути, библиотека представляет собой набор классов, как законченных (для использования), так и базовых (для наследования), предназначенных для разработки приложений работающих с векторной графикой, таких как редакторы диаграмм и различные системы автоматизированного проектирования (САПР или CAD, англ. Computer-Aided Design). Эта задача решается на абстрактном уровне. Предполагается, что программист, который будет использовать эту библиотеку, сможет максимально сосредоточиться на решении своей конкретной прикладной задачи, не тратя времени на разработку абстрактного "движка". Кроме предоставления готовой архитектуры, решается несколько типовых практических задач, таких как визуализация схемы (двумерное векторное представление), ее редактирование пользователем, сохранение и загрузка документов, вывод на печать, взаимодействие с буфером обмена (Window Clipboard) и другие.
Сразу оговорим, что библиотека коммерческая, более подробную информацию по ее приобретению и использованию вы найдете на сайте проекта — http://easycad-lib.com. Есть и хорошая новость : Существует бесплатная тестовая версия библиотеки, которую можно найти по адресу http://sourceforge.net/projects/easycad/. Это черновой вариант, не готовый по мнению автора к использованию в рабочих проектах. С тех пор были разработаны и доработаны некоторые новые возможности, исправлены ошибки и утечки, библиотека была адаптирована под разные версии Delphi, однако эта тестовая версия хорошо подойдет желающим познакомиться поглубже. В принципе, если будет такое желание, ее даже можно попробовать использовать в своих проектах, автор возражений не имеет, но сразу хочу предупредить — "допиливать" до нужной кондиции придется самостоятельно.
Ниже будет представлен поверхностный обзор возможностей этой библиотеки. Обзор действительно будет поверхностным, так подробное описание библиотеки, включающей более 30 юнитов и сотни классов, привести в рамках данного материала проблематично. Однако на тех местах, которые требуют более детального пояснения, я постараюсь остановиться чуть подробнее.
На момент написания данного обзора структура библиотеки следующая:
- Common
- ECADConsts.pas
- ECADCommonTypes.pas
- ECADCommonClasses.pas
- ECADTypeInfo.pas
- ECADGraphics.pas
- ECADFillPatterns.pas
- ECADMarkers.pas
- CommonControls
- ECADCommonControls.pas
- ECADObjectInspector.pas
- ECADPropertyEditors.pas
- ECADGraphicPropertyEditors.pas
- Diagram
- ECADDiagramCommon.pas
- ECADDiagramObjects.pas
- ECADDiagramShapes.pas
- ECADDiagramDocument.pas
- ECADDiagramLayers.pas
- ECADDiagramPages.pas
- DiagramControls
- ECADDiagramControls.pas
- ECADDiagramDesigner.pas
- ECADLayersManager.pas
- ECADObjectPalette.pas
- Streams
- ECADDocumentFilers.pas
- ECADFormatBin.pas
- ECADFormatXML.pas
- ECADFormatImg.pas
- ECADFormatSVG.pas
- ECADClipbrd.pas
- Printing
- ECADPrinters.pas
- ECADPrintControllers.pas
- Plugins
- ECADPluginsCommon.pas
- ECADPluginsHost.pas
Рассмотрим эти директории и модули подробнее.
Директория Common содержит Runtime-инструментарий общего назначения, который в принципе может быть использован и не при проектировании векторных графических приложений. Забегая чуть вперед, можно сказать, что директории Common и CommonControls по сути представляют собой библиотеку внутри библиотеки, независимую от всех прочих юнитов, а последние в свою очередь являются надстройкой над этой общей библиотекой.
Юнит ECADConsts.pas не содержит какого-либо полезного кода, в нем объявлены лишь константы, использующиеся в библиотеке.
Юнит ECADCommonTypes.pas содержит объявления основных типов, с которыми работает библиотека, таких как вещественные координаты точки, прямоугольника и матрица преобразования системы координат. Кроме самих типов, содержатся и функции для работы с ними.
ECADCommonClasses.pas — один из самых объемных модулей. Он содержит объявления и реализацию базовых классов и интерфейсов общего назначения, используемых повсеместно в коде библиотеки, таких как "Объект-одиночка (Singleton)", интерфейсные объекты, различного рода контейнеры данных (списки, деревья), классы исключений, базовый класс сериализатора объектов (класс, осуществляющий взаимодействие объекта с потоком данных) и другие.
ECADTypeInfo.pas — это юнит, расширяющий возможности стандартных инструментов для работы с RTTI Delphi, а также облегчающий доступ к этой информации. Delphi 2010 предоставляет свои решение для этих проблем, однако библиотека ориентирована и на более ранние версии. Так, например, юнит содержит типы, инкапсулирующие информацию о свойствах, классах, реализует реестр классов, позволяет определять у объектов дополнительные "псевдосвойства" во время выполнения, задавать классам и свойствам произвольные атрибуты и т.д. Инструментарий этого модуля используется библиотекой для сериализации, копирования объектов, а также при отображении свойств в компоненте TECADObjectInspector (см. далее).
Если юнит ECADCommonClasses.pas был одним из самых объемных, то ECADGraphics.pas — самый объемный модуль библиотеки. Он содержит большое количество классов и типов, так или иначе связанных с отображением 2D-графики. В этом юните объявлен тип "ARGB-цвет" и функции для работы с ним, классы графических инструментов (кисть, перо и т.д.), изображений (битмапы, метафайлы), абстрактный класс холста для рисования (TECADCanvas), содержащий методы вывода графических примитивов и управления областью вывода (трансформации, отсечение), и другие классы. Потомки TECADCanvas реализуют вывод средствами GDI и GDI+ (TECADGDICanvas, TECADGDIPlusCanvas), потенциально могут использовать другие технологии (Direct2D, например), а могут вместо вывода записывать графические команды в память (TECADRecordCanvas) или в файл определенного формата (например, класс TECADSVGCanvas из юнита ECADFormatSVG). Клиентский код, обращаясь к ним через интерфейс базового класса TECADCanvas, может и не знать как именно будут обработаны эти вызовы.
Важная особенность графического инструментария — возможность программисту самому расширять его функциональность, вводя собственные векторные шаблоны заливки, типы маркеров, стили пера.
Небольшой юнит ECADFillPatterns.pas содержит простые классы, реализующие заливку фигур векторными текстурами (шаблонами). Базовый класс для них объявлен в юните ECADGraphics.pas.
Рис. 2. Шаблоны заливки
Как уже было сказано, программист может реализовать собственные узоры заливок, зарегистрировать их в приложении в процессе выполнения, сделав доступными для графической системы библиотеки.
Подобно последнему, юнит ECADMarkers содержит описания маркеров — небольших рисунков (например различные стрелки, поперечные штрихи, кружочки, квардатики и т.д.), которые отображаются на концах линий при выводе отрезков, дуг, ломаных и кривых Безье.
Следующий небольшой код демонстрирует некоторые из возможностей графической системы библиотеки.
uses ECADCommonTypes, ECADGraphics, ECADFillPatterns, ECADMarkers;
procedure TForm1.FormCreate(Sender: TObject);
var
Reg: TECADGraphStylesRegistry;
begin
Reg := TECADGraphStylesRegistry.GetInstance;
Reg.RegisterPattern('Diag', TECADDiagFillPattern.Create(20));
Reg.RegisterMarker('Circle', TECADEllipseMarker);
Reg.RegisterMarker('Arrow', TECADFigureArrowMarker);
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
var
ACanvas: TECADCanvas;
Pts: array[0..2] of TECADFloatPoint;
begin
ACanvas := TECADGDIPlusCanvas.Create(PaintBox1.Canvas.Handle);
try
ACanvas.Pen.Width := 2;
ACanvas.Brush.Pattern := 'Diag';
ACanvas.DrawRect(100, 100, 400, 300);
ACanvas.Pen.Width := 1;
ACanvas.DrawLine(100, 100, 100, 40);
ACanvas.DrawLine(400, 100, 400, 40);
ACanvas.Pen.StartMarker.Style := 'Arrow';
ACanvas.Pen.EndMarker.Style := 'Arrow';
ACanvas.DrawLine(100, 50, 400, 50);
ACanvas.DrawText('300', MakeRect(100, 30, 400, 50));
ACanvas.Pen.StartMarker.Style := 'Circle';
ACanvas.Pen.EndMarker.Style := '';
Pts[0] := MakePoint(350, 250);
Pts[1] := MakePoint(450, 200);
Pts[2] := MakePoint(500, 200);
ACanvas.DrawPolyline(@Pts[0], 3);
ACanvas.DrawText('1', MakeRect(450, 180, 500, 200));
finally
ACanvas.Free;
end;
end;
|
|
Результат работы этого кода будет примерно следующим:
Рис. 3. Вывод графики средствами библиотеки
Эта директория содержит визуальные компоненты общего назначения (как законченные, так и базовые классы), а также всевозможный инструментарий для них.
Юнит ECADCommonControls.pas содержит курсоры, типы и базовые классы, используемые различными визуальными компонентами библиотеки.
Юнит ECADObjectInspector.pas содержит компонент, функционал которого хорошо знаком всем программистам на Delphi — инспектор объектов.
Рис. 4. Инспектор объектов
Компонент позволяет отображать и редактировать свойства загруженного в него объекта во время выполнения программы, а сервисные классы для инспектора определяют, какие именно параметры объекта, в каком виде нужно отобразить, и что представляет собой редактор этих параметров.
Редакторы свойств для инспектора объектов может написать и зарегистрировать сам программист, в компонент нигде жестко не зашито как отображать и редактировать тот или иной параметр. Так один редактор может редактировать свойство в поле Edit-а, второй — показывать выпадающий список, третий — вызывать диалоговое окно, четвертый — раскрываться в список внутренних свойств и т.д. Написав класс редактора, его можно ассоциировать с конкретным свойством конкретного класса, со свойствами определенного типа или со свойствами определенной категории типов (все строковые свойства, все множества и т.д.). Юнит ECADPropertyEditors.pas как раз содержит базовые классы для редакторов свойств инспектора объектов, а также несколько простых законченных редакторов.
Юнит ECADGraphicsPropertyEditors.pas содержит классы редакторов свойств для типов, объявленных в ECADGraphics.pas, таких как цвет, шрифт, стиль пера, шаблон заливки, выравнивание текста и других.
С файлов этой директории начинается то, для чего данная библиотека и непосредственно предназначена — проектирование векторных графических редакторов. Классы, реализованные в юнитах, служат для построения логики приложения. Большинство классов являются базовыми, т.е. реализуют общий абстрактный функционал, а конкретизировать его в соответствии с решаемой задачей предлагается программисту.
Юнит ECADDiagramCommon.pas содержит объявления основных типов и констант, которые используются при реализации объектов схемы.
ECADDiagramObjects.pas вводит в библиотеку базовый класс всех объектов схемы — TECADDiagramObject. Этот класс подробно рассмотрен на сайте проекта http://easycad-lib.com/news/tecaddiagramobject_article.html. Все прочие классы объектов схемы должны быть унаследованы от него. В базовый класс заложены основные общие для всех объектов возможности: управление своим положением в документе, вывод на холст, обработка пользовательского ввода, сохранение и загрузка состояния, и др.
Все объекты на схеме выстраиваются в иерархию в виде дерева. Каждый объект может являться дочерним по отношению к какому-либо другому и также в свою очередь иметь "деток". Родитель у объекта может быть только один, а вот количество детей — неограниченно. Такая иерархия должна быть привычна для программиста на Delphi, так как элементы управления на форме выстаиваются таким же самым образом. И так же, как и для них, уничтожение родителя ведет к уничтожению всех его дочерних элементов. У каждого документа схемы есть корневой невидимый объект, который стоит на вершине иерархии, все объекты документа так или иначе вложены в него.
Положение точек объекта задается относительно своего родителя, т.е. скажем, при перемещении составного объекта, перемещаются все его части. На этом принципе, например, реализован объект "группа", позволяющий манипулировать произвольным набором объектов как одним целым. Другим "невидимым" группирующим объектом, может являться объект "слой". Можно выключить видимость слоя, и разом исчезнут все объекты, находящиеся на нем, или, скажем, можно поместить слой наверх в порядке ZOrder, и разом наверх всплывут все его объекты.
Кроме класса TECADDiagramObject, юнит содержит несколько его потомков для реализации группы объектов, слоя документа, базовые классы для объектов, задающихся прямоугольной областью, объектов-линий, и текстовых элементов, а также некоторые сервисные классы, такие как фабрика объектов.
Несколько законченных типов вводится в юните ECADDiagramShapes.pas. Например, там реализованы такие простые объекты-фигуры, как прямоугольник, отрезок, ломаная, полигон, эллипс, закругленный прямоугольник, текстовая надпись и некоторые другие.
Юнит ECADDiagramDocument.pas содержит класс документа схемы. Документ содержит некоторые параметры (размер листа, описание), все объекты, и позволяет управлять ими. Кроме того, документ представляет собой субъект шаблона "Наблюдатель" (Observer). Для читателей, не знакомых с шаблонами дизайна от GoF поясню, что документ может вести список "наблюдателей" за своим состоянием, и уведомлять их, если это состояние как-либо изменится, а они в свою очередь могут должным образом на это отреагировать. Такими наблюдателями могут быть, например, компонент(ы) для визуализации схемы, которые будут перерисовывать содержимое при изменении в документе, или какой-либо внутренний модуль, реализующий расчет или сохранение параметров схемы. Такой подход позволяет отделить внутреннюю логику от представления на экране. Класс документа может использоваться и как законченный, и как базовый.
В юните ECADDiagramLayers.pas содержатся классы для поддержки схем, которые могут иметь несколько слоев.
Юнит ECADDiagramPages.pas вводит в библиотеку реестр форматов бумаги. Программист может создать ряд форматов, таких как A1, A2 и т.д. или собственных, и при создании документа не настраивать параметры вручную, а просто указать имя формата. Также зарегистрированные форматы доступны для перечисления, что можно использовать в интерфейсе пользователя, предоставляя ему при создании нового документа возможность выбрать формат самому из списка всех возможных.
Визуальные компоненты для работы со схемой и ее объектами располагаются в этой директории.
ECADDiagramControls.pas содержит общие типы и классы, которые могут понадобиться визуальным компонентам, работающим со схемой.
В юните ECADDiagramDesigner.pas содержится законченный компонент для отображения и редактирования схем и диаграмм — TECADDiagramDesigner. Он предназначен для использования, а не наследования, несмотря на то, что способ визуализации или управления документом не конкретизирует. Решение о том, как показать документ или как им управлять с помощью мыши или клавиатуры, дизайнер делегирует двум компонентам, которые можно к нему подключить: контроллер ввода и контроллер вывода. Эти компоненты достаточно гибки и позволяют настроить ввод/вывод так, как того требует задача. В частности, дизайнер можно использовать не только как рабочую область своего приложения, но и в окне предварительного просмотра печати, причем, как целого документа, так и его отдельных частей (например, отдельных листов), нужно просто подключить соответствующие контроллеры. Забегая вперед, контроллеры для предварительного просмотра печати реализованы в библиотеке, в демонстрационном приложении, которое вы найдете на сайте проекта, можно посмотреть их в действии.
Как уже было сказано, контроллеры вывода можно настроить на отображение не полного документа, а определенной интересующей его части. Это свойство можно использовать не только в контроллерах предварительного просмотра печати, когда нужно показать определенный лист, но и в другом случае. Допустим, у нас имеется программа для построения геологических разрезов. Объекты схемы — достаточно сложные скважины, для редактирования которых целесообразно открывать специальное окно диалога и настраивать параметры в нем. В этом окне можно разместить дизайнер, а контроллер вывода к нему — специализированный, который покажет только редактируемую скважину в центре окна дизайнера, независимо от ее реального расположения на документе. Так же контроллер вывода может управлять отображением графики на более низком уровне: использовать ли GDI, GDI+, антиалиасинг и т.д.
Рассмотрим подробнее контроллеры ввода. Очевидно, что в одном приложении на одни кнопки мыши или клавиатуры требуется назначить одни операции, в другом — другие. Дизайнер не берется решать, как именно следует поступать, а делегирует право это контроллеру ввода. Библиотека содержит несколько контроллеров ввода как для режима "только просмотр", так и для редактирования схемы. Если требуется иное поведение, нежели реализовано в этих контроллерах — можно реализовать свои классы и подключить их к дизайнеру. Ну, а можно вообще не подключать контроллер ввода, а по-старинке обрабатывать события дизайнера вроде OnMouseDown. Контроллер — это просто способ разом установить поведение, менять его "на лету", плюс некоторая "разгрузка" кода формы.
Юнит ECADLayersManager.pas содержит компонент для визуализации слоев документа.
Компонент из юнита ECADObjectPalette.pas — это палитра объектов диаграммы, с которой пользователь может перетаскивать элементы в окно дизайнера, и наоборот — объект (или группу объектов) из окна дизайнера пользователь может перетащить на палитру, для дальнейшего повторного использования.
Рис. 5. Палитра объектов
Палитра поддерживает методы сохранения и загрузки из файла, что позволит пользователям собирать и хранить свои собственные коллекции объектов.
Файлы данной директории содержат различного рода инструментарий для чтения и записи документов во внешнее хранилище.
Базовый инструментарий для этого содержится в юните ECADDocumentFilers.pas. Ни документ, ни объекты в его составе, в явном виде нигде не содержат кода чтения и записи данных в поток. Эту задачу берут на себя т.н. классы файлеров. Файлер представляет собой класс, который обеспечивает взаимодействие документа с потоком (Stream) или файлом.
Файлеры в библиотеке делятся на два основных типа: объектные и графические. Объектные файлеры осуществляют запись параметров документа и всех его объектов, сохраняя их композицию, с возможностью дальнейшего восстановления этой информации. Другими словами, объектные файлеры обеспечивают и сохранение, и загрузку документа в определяемом ими формате. Графические файлеры, в свою очередь, осуществляют только экспорт документа в графические форматы, такие как точечный рисунок или метафайл. Загрузка и восстановление параметров документа и композиции объектов эти файлеры не обеспечивают.
Юнит ECADDocumentFilers.pas содержит базовые классы и интерфейсы для поддержки различного рода файлеров. Программист может вывести свои классы файлеров или воспользоваться готовыми, содержащимися в других юнитах (см. далее).
Кроме того, юнит содержит реализацию менеджера файловых форматов. Менеджер позволяет ассоциировать выбранный тип файлера с определенным расширением файла, и тогда при сохранении или загрузке документа нужный файлер будет выбран библиотекой автоматически, исходя из расширения.
Юнит ECADFormatBin.pas содержит объектный файлер для чтения и записи документа в поток в бинарном виде.
Подобно предыдущему, юнит ECADFormatXML.pas содержит объектный файлер для работы с данными в формате XML.
Юнит ECADFormatImg.pas содержит графические файлеры для экспорта документа в следующие форматы:
- Windows Bitmap (BMP)
- JPEG Interchange Format (JPEG)
- Portable Network Graphics (PNG)
- Graphics Interchange Format (GIF)
- Tag Image Format (TIF)
- Enhanced Metafiles (EMF)
- EMF+ Metafiles
- EMF+ Dual Metafiles
Также при подключении данного юнита файлеры ассоциируются с соответствующими им расширениями файла.
В юните TECADFormatSVG содержится потомок класса TECADCanvas, который вместо вывода на графическое устройство записывает команды в поток в формате Scalable Vector Graphics (SVG). Также имеется графический файлер для экспорта документа в SVG. Юнит может служить примером того, как экспортировать документ и в другие векторные форматы, не поддерживаемые непосредственно стандартным API Windows.
Юнит ECADClipbrd.pas содержит класс, заменяющий стандартный TClipboard, и позволяющий дополнительно копировать и вставлять из буфера обмена объекты схемы. Данные копируются в буфер во внутреннем бинарном формате с сохранением параметров и композиции объектов, что позволяет восстановить состояние при вставке в документ. Кроме этого, каждый конкретный тип объекта может дополнительно определить любую информацию, которая так же будет помещена в буфер. Так для всех типов этой информацией является еще и картинка в формате Windows-метафайла, которую можно вставить в графическом редакторе или, например, программе Microsoft Word. Объекты "текстовые надписи" помещают в буфер обмена содержащийся в них текст, который можно будет вставить в любом текстовом редакторе.
Как понятно из названия, файлы из данной директории содержат инструменты, так или иначе связанные с выводом документа на печать.
Юнит ECADPrinters.pas содержит класс TECADDocumentPrinter, который позволяет определить параметры принтера, документа, выводимого на печать, и собственно отправить документ на печать.
Юнит ECADPrintControllers.pas содержит контроллер ввода и контроллер вывода, подключив которые к дизайнеру диаграмм, можно использовать для реализации в программе функции предварительного просмотра печати.
Библиотека поддерживает возможность подключать к приложению новые типы объектов диаграммы в DLL. Такую DLL мы будем называть плагином, а приложение, которое может ее подключить и использовать — хостом плагинов. К библиотеке плагина предъявляются некоторые требования:
- Библиотека должна экспортировать функцию InitPlugin следующего вида:
function InitPlugin(const AHost: IECADPluginsHost): HResult; stdcall;
Эта функция автоматически вызывается хостом при загрузке плагина. В ней передается интерфейс, используя который, плагин должен сообщить имена всех типов создаваемых им объектов.
- Библиотека должна экспортировать функцию CreateInstance следующего вида:
function CreateInstance(const Name: WideString; out Obj: IECADPluginObject): HResult; stdcall;
Используя эту функцию, хост может попросить плагин создать экземпляр объекта нужного типа, сообщив плагину имя этого типа. Имена типов плагин сообщает хосту при инициализации. Созданный объект возвращается в виде интерфейса IECADPluginObject.
Когда приложение хочет создать экземпляр объекта диаграммы, тип которого описан в плагине, на самом деле создается прокси-объект на стороне хоста, который сохраняет ссылку на полученный вызовом CreateInstance интерфейс. При обращении к этому объекту, прокси определяет, может ли он самостоятельно обработать запрос, и если нет — передает его интерфейсу из плагина. Грубо говоря, прокси-объект самостоятельно отвечает на вызовы статических методов, а виртуальные перекрыты таким образом, что они вызывают соответствующую функцию из плагина.
Юнит ECADPluginsCommon.pas содержит все необходимые для создания плагина объявления. Если вы хотите создать плагин в Delphi, обязательно подключите этот юнит. В нем также содержится базовый класс, который удобно использовать в качестве родителя реализуемых в плагине объектов схемы.
Юнит ECADPluginsHost.pas необходим для работы хоста плагинов. В самом плагине подключать его нет никакой нужды. Юнит решает две задачи: реализацию класса прокси-объекта и реализацию менеджера плагинов, который умеет загружать и выгружать плагины, ведет их учет и т.д. Именно менеджер может определить, в какой именно библиотеке описан интересующий тип объекта, и вызвать у нее функцию CreateInstance для создания экземпляра.
На этом обзор завершен, с радостью отвечу на любые вопросы. Более подробную информацию и демонстрационные примеры вы найдете на сайте проекта:
http://easycad-lib.com
[GDI, рисование на канве] [Создание собственных компонент]
Обсуждение материала [ 25-11-2015 06:58 ] 10 сообщений |