Вверх ↑
Этот топик читают: Гость
Главный модератор
Ответов: 2947
Рейтинг: 388
#1: 2019-10-14 19:08:03 ЛС | профиль | цитата
Эта тема создана для возможных разработчиков элементов проекта HiAsm.NET. Это предполагает, что Вы разработчик программного обеспечения на каком-либо языке и не обязательно высокого уровня, а также уже создавали программы в HiAsm какой-либо версии. Очень помог бы Ваш опыт разработки элементов для других пакетов, но это также не обязательно. Для разработки полноценного элемента понадобится инструмент в виде программы Visual Studio, начиная с версии 2010 года. Если у Вас возникли трудности с установкой, то поищите в интернете информацию по этому вопросу. Например, вот - первая обнаруженная ссылка на данную тему на не очень русском языке, но вполне "понимабельная".

  Предположим, что Вы установили инструмент разработки, собственно конструктор программ HiAsm.NET и готовы создать новый элемент для пакета Core. Следуя традиции, сначала элемент будет очень простым, но постепенно будем добавлять в него функционал до максимально возможного уровня, достигнутого на сегодня.

Создание проекта нового элемента

  Для создания проекта нового элемента надо правильно выбрать его тип:

Внимание! Важные пункты выбора типа помечены жёлтым маркером на картинке.
Примерно вот так будет выглядеть экран, если Вы установите VS2017, выберете темную тему и английский язык:

Добавление ссылок на другие сборки

  Чтобы «разговаривать» с конструктором на одном языке, элементу надо указать где находятся две сборки конструктора, в которых хранится код классов общения со средой. Воспользуемся панелью обозревателя решения (Solution Explorer) и укажем на файлы конструктора MSDK.dll и ProxyDomain.dll в папке C:\HiAsm.NET\:





Теперь Visual Studio будет нам подсказывать на какие буковки на клавиатуре давить пальцем . Эта функциональность называется IntelliSense. Для сборок конструктора устанавливаем свойство CopyLocal значением False, чтобы в момент компиляции Visual Studio не копировала эти файлы никуда.

В заключении наведём порядок среди ссылок на сборки при помощи контекстного меню, вызываемого правой кнопкой мыши на самих ссылках, чтобы удалить ненужные нам сейчас и напротив добавить одну нужную системную сборку System.Drawing.dll:

Вот так окончательно должен выглядеть почищенный список:

Для эстетической завершенности переименуем класс из бездушного названия в понятное только нам :


Bingo
Установка атрибутов

  Теперь надо установить обязательные атрибуты, чтобы проект HiAsm.NET признал элемент и прочитал из него необходимую информацию для взаимодействия с ним. Прежде всего это жестко определённое пространство имён (namespace):
using HiAsm;
...
namespace ElementVirtual
{
...
}
Если пространство имён будет названо по-другому среда не признает сборку как контейнер элемента пакета. Отмечу, что сборкой будем называть, полученный после компиляции проекта файл с расширением .dll, который будет содержать в себе код одного или нескольких элементов.

Информационный атрибут с почтой автора и версией элемента:
...
namespace ElementVirtual
{
[About(version = "1.0", author = "Developer Name", mail = "developer@mail.net")]
...
}
Атрибут с информацией об элементе:
...
namespace ElementVirtual
{
...
[Type("Example", "Brief description", tab = "Core")]
...
}
Этим атрибутом задаётся имя класса, краткое описание и группа в панели инструментов.

Теперь, после выбора имени класса элемента, зададим атрибут с информацией об иконке элемента:
...
namespace ElementVirtual
{
...
[ToolboxBitmap(typeof(Example), "Resources.picture.ico")]
...
}
Позднее добавим иконку с именем picture.ico в ресурсы сборки.

Определим элементу свойтво с именем Greeting:
...
namespace ElementVirtual
{
...
[Property("Greeting", "The greeting message", DataType.data_str)]
...
}
Кроме имени здесь определяем краткое описание свойства и его тип. Про тип пока отметим, что это строка текста. Полный список типов будет позже.

Определим элементу левую точку с именем doSayHello:
...
namespace ElementVirtual
{
...
[Point("doSayHello", "Do greeting message", DataType.data_str, PointType.pt_work)]
...
}
Слева направо задаются имя точки, краткое описание, тип данных, принимаемых точкой, место расположения точки на элементе. У точки могут быть только 4-ре места расположения на элементе:
    pt_work - левая точка;
    pt_event - правая точка;
    pt_var - нижняя точка;
    pt_data - верхняя точка;

Аналогично добавим элементу по одной точке на каждую сторону:
...
namespace ElementVirtual
{
...
[Point("onGreeting", "Returns greeting message", DataType.data_str, PointType.pt_event)]
[Point("Greeting", "Returns greeting message", DataType.data_str, PointType.pt_var)]
[Point("Data", "Defines greeting message", DataType.data_str, PointType.pt_data)]
...
}

Посмотрим как это выглядит суммарно:
...
namespace ElementVirtual
{
[About(version = "1.0", author = "Developer Name", mail = "developer@mail.net")]
[Type("Example", "Brief description", tab = "Core")]
[ToolboxBitmap(typeof(Example), "Resources.picture.ico")]
[Property("Greeting", "The greeting message", DataType.data_str)]
[Point("doSayHello", "Do greeting message", DataType.data_str, PointType.pt_work)]
[Point("onGreeting", "Returns greeting message", DataType.data_str, PointType.pt_event)]
[Point("Greeting", "Returns greeting message", DataType.data_str, PointType.pt_var)]
[Point("Data", "Defines greeting message", DataType.data_str, PointType.pt_data)]
...
}
С помощью атрибутов мы сообщили достаточно информации об элементе для его взаимодействия со средой конструктора. Можно определить ещё больше подробностей, но для простого элемента этого набора достаточно.
Создание кода основного класса элемента

  Простые элементы являются наследниками базового класса Element:
...
namespace ElementVirtual
{
[About(version = "1.0", author = "Developer Name", mail = "developer@mail.net")]
...
public class Example : Element
{
...
}
...
}

Добавим конструктор, который будет вызван средой во время создания экземпляра класса элемента:
...
namespace ElementVirtual
{
[About(version = "1.0", author = "Developer Name", mail = "developer@mail.net")]
...
public class Example : Element
{
public Example(PackElement pe, SDK sdk, int x, int y) : base(pe, sdk, x, y)
{
flag |= (int)(ElementFlag.IS_SYSTEM | ElementFlag.IS_VIRTUAL);
}
...
}
...
}
Пока скажем только о флагах элемента: IS_VIRTUAL - сообщает среде, что класс загружен из внешней сборки; IS_SYSTEM - разрешает использование элемента для построения программ (другими словами этот флаг отличает элемент от элементов помощников, которые работают только в редакторе конструктора).

Посмотрим что у нас получилось после всех предыдущих изменений кода:

Если мы теперь построим сборку элемента у Visual Studio не будет к нашему коду никаких претензий:

По умолчанию Visual Studio сохранила файл сборки в папке проекта. Куда именно увидим, посмотрев на свойства проекта:


Чтобы сократить телодвижения по копированию сборки руками в нужное нам место, надо изменить выходной путь. Так как сейчас мы делаем элемент для пакета Core, укажем путь к папке со сборками виртуальных элементов этого пакета:

Ещё установим некоторые важные свойства проекта:

Можем заполнить форму с подробной информацией о сборке и если будете использовать данный проект как шаблон для создания других элементов, то следите чтобы уникальный идентификатор сборки (GUID) оставался уникальным:

Теперь, если опять выполним построение (Build) проекта элемента, то обнаружим в указанной ранее папке файл сборки нашего нового элемента:

Размещение элемента на панели инструментов

  В принципе, поместив файл сборки в папку \virt пакета Core, можем использовать элемент в конструкторе, но только пока для отображения схем с его использованием. Элемент надо поместить на панель инструментов, чтобы можно было его вставлять в схемы. Сделать это можно несколькими способами. Не буду описывать их все, а предложу самый простой и быстрый способ. На мой взгляд, проще всего открыть файл сборки в конструкторе через меню Open или набросить его на окно программы. Но прежде чем это делать, надо добавить к элементу ещё один атрибут с информацией о пакетах в которые надо его установить (пакетов может быть несколько):
...
namespace ElementVirtual
{
...
[Install("_base", "Simple demo", "Core")]
...
}
Слева направо задаются имя пакета, краткое описание и группа в панели инструментов. Компилируем сборку элемента снова и можем попытаться установить элемент в конструкторе. Запускаем конструктор (попытка перекомпиляции элемента при запущенном конструкторе будет неудачной, так как он уже используется и загружен в память процесса программы) и открываем его из папки \virt:
Demo


Добавление иконки элемента в ресурсы сборки

  Чтобы добавить иконку элемента в ресурсы сборки надо сначала её создать. Надеюсь на просторах интернета Вы сможете отыскать изображение достойное разрабатываемого элемента. Требования к файлу иконки следующие: 24 пикселя или точки в ширину и высоту, 32 бита глубины цвета и формат иконки Windows. С именем файла иконки мы определились ранее при добавлении атрибута ToolboxBitmap, а именно picture.ico. Теперь надо решить как добавить файл иконки в ресурсы. Есть по крайней мере два способа известных мне (вероятно их больше). Один способ очевидный, а другой нет, но зато быстрый. Давайте посмотрим сначала на очевидный способ добавления через свойства проекта:
Demo


Дополнительно в обозревателе решения устанавливаем свойства для файла иконки:

Если теперь перекомпилим проект элемента и запустим конструктор, то увидим новую иконку на панели инструментов и можем добавить её на рабочее поле схемы:


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

Создание кода методов обработки обращений к точкам элемента

  При помощи связей (линий) к элементу можно обращаться из других элементов. Причём сам элемент обращается к другим элементам через верхние и правые точки, а к нему обращения приходят на левые и нижние точки. Значит в коде элемента мы должны реализовать два метода-обработчика обращений к левым и нижним точкам и научиться получать данные с левых и верхних точек, а также отправлять результаты своей работы на правые и нижние точки.

  Метод-обработчик обращений к левым точкам элемента выглядит так:
public override void do_work(ElementPoint point, ref TData data)
{
base.do_work(point, ref data);
...
}

  Метод-обработчик обращений к нижним точкам элемента выглядит так:
public override void read_var(ElementPoint point, ref TData data)
{
base.read_var(point, ref data);
...
}
Аргументы методов - это экземпляр объекта, вызываемой точки, и ссылка на объект данных, отправленных другим элементом этому. Прочитать данные, пришедшие на левую точку можно так:
    object x = data.value; // читаем нетипизированные данные с левой точки
но грамотным с точки зрения парадигмы схемотехники HiAsm будет другой код:
    // читаем данные как объект типа TData
TData dt = readData(data, getPointByName("Data"), getPropertyByName("Greeting"));

// или читаем данные как строку
string text = readString(data, getPointByName("Data"), getPropertyByName("Greeting"));

// или читаем данные как целое число
int number = readInteger(data, getPointByName("Data"), getPropertyByName("Greeting"));

// или читаем данные как действительное число
double real = readReal(data, getPointByName("Data"), getPropertyByName("Greeting"));

Отправить данные другому элементу через правую точку может следующий код:
    // отправляем строку другому элементу через точку к именем onGreeting
on_event(getPointByName("onGreeting"), "Hello!");

// отправляем целое число другому элементу через точку к именем onGreeting
on_event(getPointByName("onGreeting"), 123);

// отправляем действительное число другому элементу через точку к именем onGreeting
on_event(getPointByName("onGreeting"), Math.PI);
Универсальным способом отправки данных через правую точку будет следующий код:
    // создаём новый объект типа TData и указываем наши данные для отправки как аргумент конструктора
TData dt = new TData("Hello!");

// отправляем данные другому элементу через точку к именем onGreeting
on_event(getPointByName("onGreeting"), ref dt);

Для возвращения данных с нижних точек надо записать наши данные в объект типа TData, ссылку на который нам прислал, обращающийся к нашей точке элемент. Следующий код показывает как можно это сделать:
    // записываем наши данные типа TData по ссылке другого элемента
data.assign(new TData("Hello!"));

// или записываем наши данные любого типа по ссылке другого элемента
data.assignT(this);

Теперь мы готовы научить наш элемент реагировать на внешние обращения к нему из других элементов:
private TData local;

public override void do_work(ElementPoint point, ref TData data)
{
base.do_work(point, ref data);

this.local = readData(data, getPointByName("Data"), null);
TData dt = new TData(this.local);
this.on_event(getPointByName("onGreeting"), ref dt);
}

public override void read_var(ElementPoint point, ref TData data)
{
base.read_var(point, ref data);

if (this.local.isEmpty())
data.assign(new TData(Properties.Resources.greeting));
else
data.assign(this.local);
}
Добавим в ресурсы сборки строку приветствия, которая будет возвращаться с нижней точки по умолчанию:

Посмотрим на получившийся код элемента:

Можете скачать полный код проекта элемента: MyElementCore.zip

  Мы реализовали необходимый функционал элемента и можем проверить как он работает:
Demo



P.S. Продолжение следует. Эта тема будет редактироваться и дополняться новой информацией для разработчиков, которая призвана помочь им преодолеть порог вхождения. Автор будет удалять комментарии не относящиеся к данной теме, чтобы не засорять её бесполезной информацией, для которой на форуме есть специальные разделы. Спасибо за понимание.

Редактировалось 80 раз(а), последний 2020-01-02 20:07:33
карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
2
Голосовали:Konst, strannik_nebes
vip
#1.1контекстная реклама от партнеров
Главный модератор
Ответов: 2947
Рейтинг: 388
#2: 2019-10-18 11:59:40 ЛС | профиль | цитата
    Атрибуты элемента

  В других версиях конструктора описание шаблона элемента хранится в отдельном файле по одному на каждый элемент. Если подсчитать количество файлов, которые хранят информацию об одном элементе, то результат получится немаленький. В проекте HiAsm.NET концепция хранения информации другая. Если бы элемент умел говорить, то мог бы сказать: «Всё своё ношу с собой». К тому же, сборка может служить контейнером нескольким элементам сразу. Получается, что новая концепция сокращает количество файлов пакета примерно на порядок величины. Не забудем ещё отметить, что решена задача локализации элемента под любые национальные языки. Давайте рассмотрим подробнее, что позволило достигнуть такого результата.

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

  В .NET атрибуты представляют собой типы классов, которые расширяют абстрактный базовый класс System.Attribute. В поставляемых в .NET пространствах имен доступно множество предопределенных атрибутов, которые полезно применять в своём коде. Более того, можно также создавать собственные атрибуты и тем самым дополнительно уточнять поведение своих типов, создавая для атрибута новый тип, унаследованный от Attribute. В проекте HiAsm.NET была использована эта возможность. Теперь разработчик имеет в своём распоряжении следующий список атрибутов для описания метаданных своего элемента:
НазваниеТипОписание
AboutHiasm.AboutAttributeАтрибут описания версии элемента и его разработчика
InstallHiasm.InstallAttributeАтрибут добавления элемента к пакету и группе на панели инструментов
TypeHiasm.TypeAttributeАтрибут описания класса элемента и метаданных для отображения в конструкторе
EditHiasm.EditAttributeАтрибут метаданных для отображения элемента в редакторе формы
WidgetHiasm.WidgetAttributeАтрибут описания виджета элемента
HandlersHiasm.HandlersAttributeАтрибут метаданных редактора пользовательских свойств элемента
AssemblyHiasm.AssemblyAttributeАтрибут метаданных сторонних сборок связанных с элементом
PropertyHiasm.PropertyAttributeАтрибут описания свойств элемента
PointHiasm.PointAttributeАтрибут описания точек элемента

Атрибут About
  Атрибут About элемента не имеет дополнительных полей. Вся информация задаётся как аргументы конструктора
Атрибут Install

  Атрибут Install элемента может содержать дополнительное поле lang:
НазваниеТипОписание
langSystem.StringОпределяет локализацию описания двухбуквенным языковым кодом. Указывается после аргументов конструктора

Поле являются необязательным. Значение по умолчанию для поля null.
[Install("_base", "Simple demo", "Core")]
[Install("_base", "Простой пример", "Core", lang = "ru")]
Внимание, для каждой локализации создаётся отдельный атрибут.

  Примеры атрибутов для установки элемента в различные пакеты:
[Install("delphi", "Simple demo", "Core")]
[Install("CNET", "Simple demo", "Core")]
Атрибут Type

  Атрибут Type описания элемента может содержать дополнительные поля:
НазваниеТипОписание
langSystem.StringОпределяет локализацию описания двухбуквенным языковым кодом. Указывается после аргументов конструктора
tabSystem.StringЗадаёт имя вкладки на панели инструментов
groupSystem.StringЗадаёт имя группы на вкладке панели инструментов
categorySystem.StringЗадаёт имя категории на панели инструментов редактора формы, если данный элемент должен на ней отображаться
inheritSystem.StringЗадаёт имя наследуемого класса
interfacesSystem.StringЗадаёт список интерфейсов, реализуемых данным элементом
iconSystem.StringЗадаёт имя свойства элемента, значение которого определяет иконку элемента в редакторе схемы
subSystem.StringЗадаёт имя класса элемента, помещаемого внутрь элемента-контейнера при его создании
viewSystem.StringЗадаёт информацию для отображения на элементе в редакторе схемы
flagsHiAsm.ElementFlagЗадаёт специальные флаги элемента

Все дополнительные поля являются необязательными. Значение по умолчанию для полей типа System.String будет null. При использовании поля lang можно указывать его сразу после аргументов конструктора и без имени поля:
[Type("Example", "Say Hello World!", tab = "Core")]
[Type("Example", "Сказать Привет Мир!", "ru", tab = "Core")]
[Type("Example", "Sag Hallo Welt!", "de", tab = "Core")]
Внимание, для каждой локализации создаётся отдельный атрибут.

  Примеры атрибутов для описания элемента различных классов:
[Type("HCImageList", "Collection of Images", tab = "Componets", category = "Componets", interfaces = "ImageList")]
[Type("HCTplListView", "Display a list of items", tab = "Controls", group = "Common", category = "Common", inherit = "TplWinControl")]
[Type("HCMath", "Math operations", tab = "Logic", view = "OpType,8")]
[Type("HCConvertor", "Casting and type conversions", tab = "Logic", icon = "Mode")]
[Type("HCChildTabControl", "Container of TabPage controls", tab = "Controls", group = "Containers", category = "Containers", sub = "HCTplTabControl", flags = ElementFlag.IS_MULTI)]
Атрибут Edit

  Атрибут Edit имеет следующие поля:
НазваниеТипОписание
ClassSystem.StringОпределяет имя класса для отображения элемента в редакторе формы
proSystem.BooleanЗадаёт значение, определяющее необходимость редактирования элемента в дизайнере формы
listSystem.String[]Определяет список строк как пары Ключ=Значение для сопоставления имён свойств элемента со свойствами виджета в редакторе формы

Все поля являются необязательными. Значение по умолчанию для полей типа System.String будет null, для типа System.Boolean - false.

  Примеры атрибутов Edit:
[Edit(Class = "System.Windows.Forms.Button", pro = true)]
[Edit("FlatStyle=Flat", "TextAlign=Alignment", Class = "System.Windows.Forms.Label")]
Атрибут Widget

  Атрибут Widget имеет следующие поля:
НазваниеТипОписание
assemblySystem.StringОпределяет информацию об используемой в элементе сторонней сборке
typeSystem.StringЗадаёт имя используемого типа в сторонней сборке

Все поля являются необязательными. Значение по умолчанию для полей типа System.String будет null

  Пример атрибута Widget:
[Widget(assembly = "System.Windows.Forms.dll", type = "System.Windows.Forms.Panel")]
Внимание, для каждой сторонней сборки создаётся отдельный атрибут.
Атрибут Handlers

  Атрибут Handlers имеет поле list:
НазваниеТипОписание
listSystem.String[]Определяет список строк как пары Ключ=Значение для сопоставления имён свойств элемента с именами пользовательских редакторов

Все поля являются необязательными. Значение по умолчанию для полей типа System.String будет null, для типа System.Boolean - false.

  Пример атрибута Handlers:
[Handlers("HandlerName=PropreryName", "HandlerName2=PropreryName2")]
Атрибут Assembly

  Атрибут Assembly имеет поле list:
НазваниеТипОписание
listSystem.String[]Определяет список строк как пары Ключ=Значение для указания имени сторонней сборки и используемого типа


  Пример атрибута Assembly:
[Assembly("System.Windows.Forms.dll=System.Windows.Forms.Panel", "ColorPicker.dll=Sano.PersonalProjects.ColorPicker.Controls.ColorPanel")]
Атрибут Property

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

НазваниеТипОписание
data_intSystem.Int32Целое число
data_strSystem.StringСтрока символов в кодировке UTF8
data_dataHiAsm.TDataКомплексный тип, способный задать значение четырёх типов: data_int, data_str, data_real и data_null
data_comboSystem.Int32Задаёт индекс значения в фиксированном списке строк
data_listSystem.StringЗадаёт список строк
data_data_iconSystem.Drawing.IconКартинка в формате иконки Windows
data_realSystem.DoubleДействительное число двойной точности
data_colorSystem.Int32Задаёт цвет
data_scriptSystem.StringИспользуется для установки кода сценария
data_streamSystem.IO.MemoryStreamИспользуется для установки бинарных данных
data_bitmapSystem.Drawing.BitmapКартинка в формате Bitmap
data_waveSystem.IO.MemoryStreamИспользуется для установки звуковых данных в Wave формате
data_arrayHiAsm.ArrayValueКомплексный тип, способный задать значение массива различных типов
data_comboExSystem.StringЗадаёт строковое значение из фиксированного списка строк
data_fontHiAsm.FontRecordКомплексный тип, используемый для установки шрифта
data_jpegSystem.IO.MemoryStreamИспользуется для установки картинки в Jpeg формате
data_codeSystem.StringИспользуется для установки кода сценария
data_elementSystem.StringИспользуется для установки ссылки на другие элементы схемы
data_flagsSystem.Int32Задаёт значение отдельных битов
data_objectHiAsm.ObjectRecordКомплексный тип, способный задать значение различных типов
data_timeSystem.DateTimeИспользуется для установки даты и времени
data_boolSystem.BooleanИспользуется для установки логичекого значения True или False
data_longSystem.Int6464-битное целое число
data_charSystem.CharИспользуется для установки символьного значения
data_floatSystem.SingleДействительное число одинарной точности
data_decimalSystem.DecimalЗадаёт десятичное число
data_componentHiAsm.ObjectRecordИспользуется для установки значения типа IComponent

Список полей, используемых при создании свойства элемента

  Кроме обязательных аргументов конструктора атрибута Property можно использовать другие поля, определяющие дополнительные установки для свойства элемента:
НазваниеТипОписание
langSystem.StringОпределяет локализацию описания двухбуквенным языковым кодом. Указывается после аргументов конструктора
groupSystem.StringЗадаёт имя группы свойств для отображения в редакторе свойств элемента
valueSystem.StringЗадаёт строкой значение свойства по умолчанию
listSystem.StringЗадаёт строкой фиксированный список возможных значений свойства. В качестве разделителя используется символ запятой
doubleopenSystem.BooleanУказывает на единственное свойство элемента, которое будет открываться на редактирование при двойном клике мышью на элементе
makemethodSystem.BooleanРазрешает создание на элементе точки для изменения значения свойства во время выполнения
readOnlySystem.BooleanУказывает, что свойство в редакторе доступно только для чтения
visibleSystem.BooleanЗадаёт значение, определяющее видимость свойства в редакторе элемента
editorSystem.TypeЗадаёт тип редактора свойства
converterSystem.TypeЗадаёт тип конвертора для свойства

Все дополнительные поля являются необязательными. Значение по умолчанию для полей типа System.String будет null, для типа System.Boolean - false, кроме поля visible для которого значение по умолчанию true. При использовании поля lang можно указывать его сразу после аргументов конструктора и без имени поля:
[Property("Greeting", "The greeting message", DataType.data_str)]
[Property("Greeting", "Приветствие", DataType.data_str, "ru")]
Внимание, для каждой локализации создаётся отдельный атрибут.

  Примеры атрибутов для создания свойств элемента различных типов:
[Property("Name", "Brief description. ARG(Int32)", DataType.data_int, makemethod = true, value = "123")]
[Property("Name", "Brief description. ARG(Double)", DataType.data_real, value = "3.1415926")]
[Property("Name", "Brief description. ARG(Boolean)", DataType.data_bool, group = "Group name", value = "true")]
[Property("Name", "Brief description. ARG(DateTime)", DataType.data_time, value = "01/01/1753")]
[Property("Name", "Brief description. ARG(Color)", DataType.data_color, value = "Transparent")]
[Property("Name", "Brief description. ARG(LeftRightAlignment)", DataType.data_comboEx, value = "0", list = "Left,Right")]
[Property("Name", "Brief description. ARG(ImageList)", DataType.data_array, value = "System.Drawing.Bitmap", readOnly = true)]
[Property("Name", "Brief description. ARG(Size)", DataType.data_object, value = "System.Drawing.Size", list = "{'Width':16,'Height':16}")]
Атрибут Point

  Список полей, используемых при создании точки элемента:
НазваниеТипОписание
langSystem.StringОпределяет локализацию описания двухбуквенным языковым кодом. Указывается после аргументов конструктора
hiddenSystem.BooleanЗадаёт значение, определяющее видимость точки элемента по умолчанию
indexSystem.StringЗадаёт имя свойства элемента, значение которого определяет постфикс для модификатора имени точки

Все дополнительные поля являются необязательными. Значение по умолчанию для полей типа System.String будет null. При использовании поля lang можно указывать его сразу после аргументов конструктора и без имени поля:
[Point("doSayHello", "Returns greeting message", DataType.data_str, PointType.pt_work)]
[Point("doSayHello", "Возвращает приветствие", DataType.data_str, PointType.pt_work, "ru")]
Внимание, для каждой локализации создаётся отдельный атрибут.

  Пример атрибута для создания точки элемента с использованием дополнительных полей:
[Point("Name", "Brief description. ARG()", DataType.data_null, PointType.pt_work, "ru", hidden = true, index = "PropertyName")]

Редактировалось 4 раз(а), последний 2020-02-08 15:11:22
карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
0
Главный модератор
Ответов: 2947
Рейтинг: 388
#3: 2019-11-03 15:50:22 ЛС | профиль | цитата
    Работа элемента в проекте Windows Forms

  Итак, наш элемент работает в проекте Шаблоны (Templates) пакета Core:

  С помощью проекта этого типа можно сделать:
    редактор пользовательского свойства для элементов;
    быстро отладить прототип будущей программы проекта Windows Forms;
    компилировать, запускаемую на машине с предустановленным HiAsm.NET, сборку (.exe);
  То есть, полностью самостоятельную программу, мы сделать с помощью проекта Шаблоны (Templates) не сможем. Чтобы получить полноценную программу, надо сделать её с помощью проекта «Приложение Windows Forms» (Windows Forms Application):

  Может возникнуть вопрос: зачем создавать прототип программы в проекте Шаблоны? Для схемы из десятка элементов время сборки проектов будет отличаться незначительно, но для больших схем это будет очень заметно. Поэтому, для отладки прототипа программы комфортнее это делать в проекте Шаблоны, а уже финальное тестирование в проекте Windows Forms. Это означает что элементы пакета Core должны уметь одинаково работать в обоих проектах. Значит следущим шагом мы сделаем необходимые дополнения для нашего элемента, чтобы он смог работать в проекте Windows Forms. Для этого надо создать скрипт, который в процессе построения нашего приложения, будет возвращать C# код, реализующий функционал нашего элемента. Создадим в папке C:\HiAsm.NET\elements\_base\code файл с именем wfExample.cs со следующим содержимым:
//css_ref System.Core;
//css_ref Microsoft.CSharp;

using System;
using codegen;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Reflection;
using System.Resources;
using System.IO;
using System.Text;
using System.Dynamic;
using System.Threading.Tasks;
using System.Threading;

public class Example : TElementObject
{
public override bool init(object entry)
{
Debug.WriteLine("{0}.init()".fString(this.codename));

sys.add_var(this, "local", "string", "Hello World!".toLiteral());

@this.doSayHello = (Func<TArgs, TValue>)((data) =>
{
sys.blk.println(@this.local, " = ", d("Data"), ";");

@event("onGreeting", new TValue(@this.local, true));

return TValue.empty;
});

@this.Greeting = (Func<TArgs, TValue>)((data) =>
{
return new TValue(@this.local, true);
});

return base.init(this);
}
}
  Данный скрипт реализует функционал нашего элемента для проекта Widows Forms. Теперь рассмотрим подробности построения скрипта. Имя файла скрипта состоит из двух обязательных частей: префикса «wf» от сокращения названия проекта Windows Forms и имени элемента для которого он предназначен «Example». Расположение файла в папке \code тоже имеет особое значение и это не только наследие предыдущей версии среды, но и возможность отладки скрипта без перекомпиляции сборки самого элемента. Тоже самое касается кода скрипта до определения класса:
//css_ref System.Core;
//css_ref Microsoft.CSharp;

using System;
using codegen;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Reflection;
using System.Resources;
using System.IO;
using System.Text;
using System.Dynamic;
using System.Threading.Tasks;
using System.Threading;
  Этот код нужен только на этапе отладки скрипта и в финальной версии заменяется следующим кодом:
//css_pc wfPrecompiler.cs;
  Это тегированный комментарий, который заменится развёрнутым полным кодом на этапе компиляции. Все «простые» элементы являются наследниками базового класса TElementObject:
public class Example : TElementObject
{
...
}
  В данном случае нам достаточно реализовать единственный метод init():
public override bool init(object entry)
{
...
return base.init(this);
}
  Для каждой левой точки должен быть реализован метод-обработчик с именем совпадающий с именем точки. В нашем случае это единственная левая точка doSayHello:
@this.doSayHello = (Func<TArgs, TValue>)((data) =>
{
...
return TValue.empty;
});
  Тоже самое касается и нижних точек элемента:
@this.Greeting = (Func<TArgs, TValue>)((data) =>
{
...
return TValue.empty;
});
  В самом начале метода инициализации правилом хорошего тона будет вставка кода, который выведет информацию о себе в окно отладки среды:
public override bool init(object entry)
{
Debug.WriteLine("...");
...
}
  Также нам нужна будет переменная для хранения сообщения в памяти:
public override bool init(object entry)
{
...
sys.add_var(this, "local", "string", "Hello World!".toLiteral());
}
  Здесь мы определяем переменную с именем local, типом String и инициализированную строкой «Hello World!». Все методы класса sys реализованы в файле C:\HiAsm.NET\elements\_base\code\Sys.cs и используются всеми элементами проекта Windows Forms. Поэтому изменения в этом файле очень важны для работы проекта в целом. Будьте внимательны при редактировании его содержимого, если, конечно, Вы уверены в своих силах.
  В методе точки doSayHello мы читаем данные, пришедшие в элемент через левую или верхнюю точки и сохраняем их в нашу переменную local:
@this.doSayHello = (Func<TArgs, TValue>)((data) =>
{
sys.blk.println(@this.local, " = ", d("Data"), ";");
...
});
  И сразу же отправляем данные другому элементу через правую точку onGreeting, «упаковав» их в тип TValue:
@this.doSayHello = (Func<TArgs, TValue>)((data) =>
{
...
@event("onGreeting", new TValue(@this.local, true));
...
});
  В конструкторе типа TValue мы указываем в качестве аргументов нашу переменную и признак того, что строка в данном случае является C# кодом, а не простой строковой константой "...". Тоже самое делаем для нижней точки Greeting, где мы возвращаем данные, запрошенные другим элементом у нашего, «упаковав» их в тип TValue:
@this.Greeting = (Func<TArgs, TValue>)((data) =>
{
return new TValue(@this.local, true);
});
  Финальный варинт кода скрипта будет выглядеть так:

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

  Теперь файл C:\HiAsm.NET\elements\_base\code\wfExample.cs нужно убрать в другое место или удалить с диска, так как его наличие в папке \code является приоритетным перед ресурсами сборки и при построении программы будет использован именно он. Кстати, таким образом может быть «изменена» работа любого элемента проекта Windows Forms.
  Наконец, оптимизируем скрипт для использования из ресурсов сборки. Заменим код скрипта до определения класса его эквивалентом в виде тегированного комментария:
//css_pc wfPrecompiler.cs;

public class Example : TElementObject
{
public override bool init(object entry)
{
Debug.WriteLine("{0}.init()".fString(this.codename));

sys.add_var(this, "local", "string", "Hello World!".toLiteral());

@this.doSayHello = (Func<TArgs, TValue>)((data) =>
{
sys.blk.println(@this.local, " = ", d("Data"), ";");

@event("onGreeting", new TValue(@this.local, true));

return TValue.empty;
});

@this.Greeting = (Func<TArgs, TValue>)((data) =>
{
return new TValue(@this.local, true);
});

return base.init(this);
}
}
Проверяем работу элемента в проекте Windows Forms:
Demo


Можете скачать полный код проекта элемента: MyElementCore.zip

Редактировалось 9 раз(а), последний 2020-01-02 20:09:14
карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
0
Главный модератор
Ответов: 2947
Рейтинг: 388
#4: 2019-11-04 06:03:50 ЛС | профиль | цитата
    Кодогенератор RTCG.NET

Dilma писал(а):
... кто уже знает FTCG разобраться в RTCG сможет сразу - исходная концепция осталась неизменной

  Сказанное автором ранее про кодогенератор RTCG, можно сказать и про RTCG.NET. Основные принципы работы кодогенератора были перенесены из оригинальной версии RTCG и даже названия большинства лексем были сохранены для простоты переноса ранее сделанных наработок. Поэтому частично описание оригинальной версии RTCG подходит для кодогенератора RTCG.NET. RTCG.NET, также как и RTCG реализует объектную модель. Есть некоторые отличия в синтаксисе, например, лексема event заменена лексемой @event. Это связано с тем, что в языке C# это ключевое слово и его невозможно использовать в качестве имени метода или класса. Также изменения коснулись API кодогенератора, но они обратно-совместимы с оригиналом, так как только расширяют количество методов.

  В RTCG.NET не реализована лексема gvar, поэтому глобальные переменные надо объявлять в классе sys. Лексема var также не реализована, но ключевое слово var языка C# почти совпадает по функционалу.

  Работа с динамическими переменными реализована своеобразно через класс @this. Например:
@this.test = "Text";
sys.blk.println(@this.test);
  Область видимости этих переменных ограничена классом элемента. По сути это и есть аналог лексемы var оригинального RTCG. Тогда как и полагается переменная, которая объявлена при помощи ключевого слова var подчиняется правилам C#.

  Нет реализации лексемы method. Вместо неё используются штатные в C# делегаты. Например:
@this.Result = (Func<TArgs, TValue>)((data) =>
{
return TValue.empty;
});
  Такое объявление метода соответствует созданию следующего кода:
TValue Result(TArgs data)
{
return TValue.empty;
}
  В RTCG в классе sys контекст каждого элемента доступен через лексему объекта this. В RTCG.NET это не реализовано, поэтому при вызове методов этого класса приходится передавать ссылку на элемент в качестве аргумента метода:
// initialize widget
@this.obj = sys.add_widget(this);
  При работе с блоками необходимо всегда указывать имя класса sys. В оригинальном RTCG допускалось его не указывать. Естественно, что при написании кода надо соблюдать правила синтаксиса языка C# версии 5.0. Поэтому, это лучше всего делать сразу в Visual Studio или хотя бы в Visual Studio Code. Если хотите подробнее ознакомиться с синтаксисом RTCG.NET, то посмотрите коды некоторых элементов в папке \_base\code

Редактировалось 6 раз(а), последний 2020-01-02 20:13:18
карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
0
Главный модератор
Ответов: 2947
Рейтинг: 388
#5: 2019-11-18 13:40:02 ЛС | профиль | цитата
    Инфраструктура элемента

  Под инфраструктурой элемента подразумеваются:
    - любые файловые потоки;
    - служебная и справочная информация об элементе с примерами его использования;
    - реализации элемента под каждый пакет в котором он будет функционировать;
    - различные версии реализации под конкретный пакет;
    - локализованные ресурсы для многоязычных данных;
  Сложные элементы могут обладать некоторой инфраструктурой, необходимой для его функционирования. Простым примером инфраструктуры может служить справочная информация об элементе, которую пользователь может получить, выделив элемент в редакторе схемы и нажав клавишу F1. Если элемент не имеет собственной справки, то для пользователя откроется справка о программе. Справочные материалы программы хранятся в папке C:\HiAsm.NET\help. На данный момент форматом справки по элементу должен быть HTML. Чтобы программа нашла справку, надо чтобы в папке C:\HiAsm.NET\help находился файл с именем элемента и расширением .html. Например, для нашего элемента имя должно быть: Example.html. Конечно, можно поместить файл справки в нужный каталог многими способами, но наша концепция «всё своё ношу с собой» предполагает, что справка будет храниться в ресурсах сборки контейнера элемента.

  Теперь рассмотрим какие необходимо сделать дополнения в коде элемента, чтобы он получил данную функциональность. Предполагается, что файл справки в формате HTML уже есть:
<html>
<head>
<meta content="http://schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema">
<LINK href="style.css" type="text/css" rel="stylesheet">
</head>
<body>
<table class="mes_table" border="1">
<tr class="prop_cap">
<td width="10%"><IMG src="../int/icons/components.ico"></td>
<td align="center">Example</td>
</tr>
<tr>
<td style="PADDING-RIGHT: 4px; PADDING-LEFT: 4px" colSpan="2">
<P align="justify">&nbsp;Элемент&nbsp;Example -&nbsp;демонстрирует возможности использования сборки как контейнера для хранения информации об элементе.
</P>
</td>
</tr>
<tr>
<td class="down_sel" colSpan="2">&nbsp;support: <A href="http://forum.hiasm.com">
Example</A></td>
</tr>
</table>
</body>
</html>

  Добавляем файл Example.html в ресурсы сборки как мы это уже делали с файлами иконки элемента и скрипта построения кода для проекта Windows Forms (смотрите предыдующие материалы темы). После добавления получим следующий вид ресурсов сборки:


  Чтобы поместить файл справки из ресурсов сборки в папку C:\HiAsm.NET\help надо переопределить код метода инициализации элемента:
public override void init(int flag = 0)
{
base.init(flag);
string content = Properties.Resources.Example;
string helpName = String.Format("{0}.html", GetType().Name);
string helpFilename = Path.Combine(Share.dataDir, Constants.HELP_PATH, helpName);
Helpers.overwriteIfDifferent(content, helpFilename);
}
  Читаем содержимое справки в строковую переменную, формируем имя файла, собираем полный путь для создания нового файла и передаём в метод-помощник, который перезапишет или создаст новый файл справки в указанном месте.

Проверяем работу справки элемента:
Demo


Можете скачать полный код проекта элемента: MyElementCore.zip

Редактировалось 2 раз(а), последний 2020-01-02 20:14:48
карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
0
Главный модератор
Ответов: 2947
Рейтинг: 388
#6: 2019-11-18 22:05:37 ЛС | профиль | цитата
    Наследование элемента

  Язык программирования C# отвечает трём основным принципам объектно-ориентированного программирования (ООП):
    Инкапсуляция - скрывает детали внутренней реализации объектов и предохраняет целостность данных;
    Наследование - позволяет строить новые определения классов на основе существующих;
    Полиморфизм - позволяет трактовать связанные объекты сходным образом;
  По сути, наследование позволяет расширять поведение базового (или родительского) класса, наследуя основную функциональность в производном подклассе (также именуемом дочерним классом). Это можно использовать при создании нового элемента, который будет наследовать функции другого. Давайте создадим новый элемент, который наследует функции нашего. Для этого добавим следующий код в файл Example.cs нашего примера:
...
namespace ElementVirtual
{
[About(version = "1.0", author = "Developer Name", mail = "developer@mail.net")]
[ToolboxBitmap(typeof(Derived), "Resources.DerivedClass.ico")]
[Install("_base", "A simple example of inheritance", "Core")]
[Type("Derived", "An example of inheritance", tab = "Core")]
public class Derived : Example
{
public Derived(PackElement pe, SDK sdk, int x, int y) : base(pe, sdk, x, y) { }
}
...

  Ранее в теме обещал показать неочевидный, но быстрый способ добавления иконки в ресурсы сборки. Для этого надо предварительно подготовленный файл иконки поместить в папку ресурсов проекта. В нашем случае это будет папка C:\Projects\MyElementCore\MyElementCore\Resources. Добавляем ещё одну иконку DerivedClass.ico в ресурсы сборки, используя «быстрый» способ:
Demo



  Вот так будет выглядеть код элементов после вставки:


  Новый элемент надо поместить на панель инструментов, чтобы можно было его вставлять в схемы. Сделать это можно ещё раз открыв файл сборки в конструкторе через меню Open или набросить его на окно программы. Для проверки работы «нового» элемента в предыдущем примере заменяем наш элемент Example элементом Derived:
Demo



  Теперь надо сделать наследование для скрипта проекта Windows Forms. Для этого надо добавить в ресурсы сборки ещё один скрипт с именем wfDerived.cs и следующим содержанием:
//css_pc wfPrecompiler.cs;
public class Derived : Example
{
public override bool init(object entry)
{
Debug.WriteLine("{0}.init()".fString(this.codename));
return base.init(this);
}
}
и вернуть блок using ссылок на пространства имён сторонних сборок вместо тегированного комментария скрипта элемента Example:


  Также необходимо дополнить метод инициализации элемента Example следующим кодом:
public override void init(int flag = 0)
{
base.init(flag);
...
string context = Path.Combine(this.parent.getMSDK().pack.pathCode(), "wfExample.cs");
if (File.Exists(context))
{
this.linkedPath.Add(context);
return;
}
string result = Helpers.compileScript(Properties.Resources.wfExample);
if (Helpers.validatePath(ref result))
this.linkedPath.Add(result);
else if (!result.isEmpty())
Debug.WriteLine(result, "!");
}

  Это добавление компилирует скрипт проекта Windows Forms в сборку на диске и сохраняет путь к ней в свойствах элемента. Вот так будет выглядеть код элементов после вставки:


  Проверим элемент в работе:
Demo



  Мы получили функциональную копию нашего элемента Example. Конечно мы можем определить для элемента Derived свою собственную функциональность, которая будет расширением базовой, наследованной из элемента Example. Можете скачать полный код проекта: MyElementCore.zip

Редактировалось 3 раз(а), последний 2020-01-02 20:15:26
карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
0
Главный модератор
Ответов: 2947
Рейтинг: 388
#7: 2019-11-19 13:33:44 ЛС | профиль | цитата
    Концепции виджета (Widget) и виртуальных точек элемента

  Философское понятие диалектики как единства и борьбы противоположностей наглядно отражается процессом разработки элементов. Разработчик буквально «разрывается» на части между желанием «объять необъятное» и сохранить минимализм визуализации представления элемента в схеме. Наполнение элемента функционалом снижает его универсальность как «кирпича мироздания», а погружение в подробности увеличивает размеры визуальной составляющей и как результат снижение читабельности схемы. Частично проблема решается контейнерами, менеджерами и разрывами линков, но это далеко не панацея. Дробление больших элементов на части перегружает панель элементов. Также надо учесть что дополнительный функционал - это время и силы разработчика (человеко-часы необходимые для разработки и отладки элемента). С другой стороны, дизайнер формы приложения использует понятие компонента как единой сущности представленной графическим эквивалентом на панели инструментов и визуальные элементы содержат в своей структуре его экземпляр. И вот, в попытках найти компромис между расширением функциональности элемента и сохранением его визуальной составляющей было введено понятие виджета элемента как части его структуры.

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

  Что такое виртуальные точки? Это точки элемента, которые заранее не определены автором элемента, но могут быть добавлены пользователем в элемент для расширения его возможностей. По сути это открытые члены класса виджета: конструкторы, поля, свойства, события, методы и т.д.

  Зачем они нужны? Например, .NET Framework предлагает к использованию большое количество классов, которые можно использовать для увеличения возможностей элемента, назначив реализацию класса виджету и получив через него доступ к открытым членам класса через добавление к элементу виртуальных точек. Однако парадигма HiAsm не рассчитана на использование большого количества точек у одного элемента и как решить какие из членов класса необходимы данному элементу? Чтобы разрешить эти вопросы предлагается функционал добавления на элемент точек, выбранных пользователем из списка доступных, но неописанных заранее в конфигурации данного элемента в том числе и как скрытые точки.

  Пример подключения к элементу нескольких виртуальных точек:
Demo



  Давайте расширим возможности элемента Example, используя виджет и виртуальные точки. Для этого назначим виджету элемента реализацию класса .NET Framework. Пусть, например, это будет String​Builder Класс.

  Добавим элементу атрибуты, описывающие метаданные виджета:
...
namespace ElementVirtual
{
...
[Edit(Class = "System.Text.StringBuilder")]
[Assembly("mscorlib.dll=System.Text.StringBuilder")]
...

  Заменим наследуемый базовый класс Element на ElementClass, который реализует работу с невизуальным виджетом:
...
namespace ElementVirtual
{
...
public class Example : ElementClass
...

  Переопределим метод подготовки элемента к запуску для получения экземпляра виджета и используем его для хранения данных элемента:
...
namespace ElementVirtual
{
...
public class Example : ElementClass
{
...
private StringBuilder local;
public override void prepareForRun(ref Element parent)
{
base.prepareForRun(ref parent);
this.local = this.widget as StringBuilder;
}

  Изменим методы-обработчики для работы с экземпляром виджета:
public override void do_work(ElementPoint point, ref TData data)
{
switch (point.name)
{
case "doSayHello":
this.local.Clear();
this.local.Append(readString(data, getPointByName("Data"), null));
this.on_event(getPointByName("onGreeting"), this.local.ToString());
break;

default:
base.do_work(point, ref data);
break;
}
}

public override void read_var(ElementPoint point, ref TData data)
{
switch (point.name)
{
case "Greeting":
if (this.local.isEmpty())
data.assign(new TData(Properties.Resources.greeting));
else
data.assign(new TData(this.local.ToString(), DataType.data_str));
break;

default:
base.read_var(point, ref data);
break;
}
}

  Так будет выглядеть код элемента после изменений:


  Посмотрим как изменились возможности элемента с использованием виджета:
Demo



  Теперь надо сделать изменения скрипта проекта Windows Forms.
//css_pc wfPrecompiler.cs;
public class Example : ElementClass
{
public override bool init(object entry)
{
Debug.WriteLine("{0}.init()".fString(this.codename));
@this.obj = sys.new_instance(this, "Hello World!".toLiteral());
@this.doSayHello = (Func<TArgs, TValue>)((data) =>
{
sys.blk.println(@this.obj, ".Clear();")
.println(@this.obj, ".Append(", d("Data"), ");");
@event("onGreeting", new TValue(String.Concat(@this.obj, ".ToString()"), true));
return TValue.empty;
});
@this.Greeting = (Func<TArgs, TValue>)((data) =>
{
return new TValue(String.Concat(@this.obj, ".ToString()"), true);
});
return base.init(this);
}
}

  Основные изменения - это наследование от класса ElementClass, который реализует работу с невизуальным виджетом и определение его экземпляра:
public class Example : ElementClass
{
...
@this.obj = sys.new_instance(this, "Hello World!".toLiteral());
...
}

  Проверим элемент в работе проекта Windows Forms:
Demo



  Если быть перфекционистом, то можно подгрузить описания виртуальных точек. Для этого добавим в ресурсы сборки ещё один файл в xml-формате с именем System.Text.Builder.xml, в котором находятся описания членов класса на русском и английском языках:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<doc>
<assembly>
<name>System.Text.StringBuilder</name>
</assembly>
<members>
<member name="M:System.Text.StringBuilder.ctor()">
<summary>Initializes a new instance of the StringBuilder class.</summary>
<summary xml:lang="ru">Инициализирует новый экземпляр класса StringBuilder.</summary>
</member>
...
<member name="M:System.Text.StringBuilder.ToString(Int32, Int32)">
<summary>Converts the value of a substring of this instance to a String.</summary>
<summary xml:lang="ru">Преобразует значение подстроки этого экземпляра в String.</summary>
</member>
</members>
</doc>

  И добавим пару строк кода в конструктор элемента, чтобы программа отобразила эту информацию в окне менеджера виртуальных точек:
public Example(PackElement pe, SDK sdk, int x, int y) : base(pe, sdk, x, y)
{
flag |= (int)(ElementFlag.IS_SYSTEM | ElementFlag.IS_VIRTUAL);
var widgets = pe.parent.widgets;
widgets.Add(new Widget(widgets, typeof(StringBuilder), Properties.Resources.System_Text_Builder));
}



  Мы увеличили возможности элемента Example, используя класс .NET Framework в качестве виджета. Можете скачать полный код проекта: MyElementCore.zip

Редактировалось 3 раз(а), последний 2020-02-11 08:34:55
карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
0
Главный модератор
Ответов: 2947
Рейтинг: 388
#8: 2019-11-23 13:43:53 ЛС | профиль | цитата
    Визуальный элемент

  Мы можем строить элементы с использованием виджета невизуального класса. Пора сделать визуальный элемент с использованием класса .NET Framework наследника System.Windows.Forms.Control. На этот раз, для примера, мы выберем MaskedTextBox класс. Элемент управления MaskedTextBox по сути представляет обычное текстовое поле ввода. Однако он позволяет контролировать ввод пользователя и проверять его автоматически на наличие ошибок. Добавим к нашему проекту новый файл класса с именем HCMaskedTextBox.cs. Только в качестве шаблона кода выберем Component Class:


  Добавим ссылку на сборку System.Windows.Forms.dll:

  Заменим содержимое шаблона в файле HCMaskedTextBox.cs следующим кодом:
using HiAsm;
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace ElementVirtual
{
public partial class HCMaskedTextBox : ElementTplWinControl
{
public HCMaskedTextBox(PackElement pe, SDK sdk, int x, int y) : base(pe, sdk, x, y)
{
flag |= (int)(ElementFlag.IS_VIRTUAL | ElementFlag.IS_SYSTEM);
InitializeComponent();
}
}
}
  Как видите изменился наследуемый базовый класс. Для всех визуальных элементов он должен быть ElementTplWinControl и добавился в конструкторе вызов метода инициализации. В нашем случае вызов метода инициализации можно удалить, но для «правильного» визуального элемента управления этот код нужен. Добавим обязательные атрибуты элемента:
[About(version = "1.0", author = "Developer Name", mail = "developer@mail.net")]
[DesignTimeVisible(true)]
[ToolboxBitmap(typeof(HCMaskedTextBox), "Resources.MaskedTextBox.ico")]
[Type("HCMaskedTextBox", "String editor with input validation", tab = "Controls", group = "Common", category = "Common", inherit = "TplWinControl")]
[Edit(Class = "System.Windows.Forms.MaskedTextBox")]
[Assembly("System.Windows.Forms.dll=System.Windows.Forms.MaskedTextBox")]
[Install("_base", "Simple visual demo", "Controls")]
  Из нового здесь только атрибут DesignTimeVisible, который разрешает отображение элемента на панели инструментов в дизайнере формы. Также надо добавить в ресурсы сборки иконку нового элемента с именем MaskedTextBox.ico. Теперь мы знаем два способа сделать это (смотрите предыдущие материалы). У каждого элемента управления свои значения размера по умолчанию. Узнать их можно или в документации на сайте или экпериментально в каком-либо тестовом проекте, добавив элемент на форму в режиме дизайнера, двойным кликом мыши на элементе в панели инструментов и посмотрев после этого размеры в окне свойств. После того как мы узнаем размер элемента по умолчанию, добавим атрибуты свойств геометрии:
[Property("Width", "Sets the width of the control. ARG(Int32)", DataType.data_int, group = "Geometry", makemethod = true, value = "100")]
[Property("Height", "Sets the height of the control. ARG(Int32)", DataType.data_int, group = "Geometry", makemethod = true, value = "20")]
  Добавим ещё несколько свойств и точек для проверки работы элемента:
[Property("TextMaskFormat", "Sets a value that determines whether literals and prompt characters are included in the formatted string. ARG(MaskFormat)", DataType.data_comboEx, group = "Behavior", makemethod = true, value = "1", list = "IncludePrompt,IncludeLiterals,IncludePromptAndLiterals,ExcludePromptAndLiterals")]
[Property("Mask", "Sets the input mask to use at run time. ARG(String)", DataType.data_str, group = "Behavior", doubleopen = true)]
[Point("onMaskInputRejected", "Occurs when the user's input or assigned character does not match the corresponding format element of the input mask. ARG(MaskInputRejectedEventArgs)", DataType.data_object, PointType.pt_event)]
[Point("Text", "Returns the text as it is currently displayed to the user. ARG(String)", DataType.data_str, PointType.pt_var)]
  Полный код элемента должен содержать ещё некоторое количество свойств и точек, но для нашей цели обучения этого будет достаточно. Переопределяем метод подготовки элемента к запуску, в котором создаём метод обработчик события MaskInputRejected:
private MaskedTextBox control;
public override void prepareForRun(ref Element parent)
{
base.prepareForRun(ref parent);
this.control = this.widget as MaskedTextBox;
this.control.MaskInputRejected += (s, e) =>
{
TData dt = new TData(e);
on_event(getPointByName("onMaskInputRejected"), ref dt);
};
}
  После всех изменений код элемента будет выгпядеть так:

  Компилируем сборку и добавляем новый элемент на панель элементов, открыв файл сборки в программе:

  Собираем простую тестовую схему проекта Core (Шаблоны):
Add(hcTplForm,2953706,21,105)
{
StartPosition=4
}
Add(Example,2,126,105)
{
Greeting="Hello!"
link(onGreeting,7:doWork2,[])
link(Data,5:Text,[])
}
Add(hcTplButton,3,70,105)
{
Left=36
Top=23
link(onClick,2:doSayHello,[])
}
Add(hcMessage,4,182,105)
{
link(Caption,2:Greeting,[(195,91)(167,91)(167,150)(132,150)])
}
Add(HubEx,7,168,105)
{
link(onEvent,4:doMessage,[])
}
Add(HCMaskedTextBox,5,126,49)
{
Left=97
Top=23
Mask="00/00/0000"
link(onMaskInputRejected,7:doWork1,[(172,55)])
}
  Проверяем элемент в работе проекта Core:
Demo



  Теперь сделаем скрипт проекта Windows Forms в файле wfHCMaskedTextBox.cs и добавим его в ресурсы сборки:
//css_pc wfPrecompiler.cs;
public class HCMaskedTextBox : WinElement
{
public override bool init(object entry)
{
Debug.WriteLine("{0}.init()".fString(this.codename));
@this.obj = sys.add_widget(this);
Task t = new Task(() =>
{
sys.event_imp(this, "onMaskInputRejected", null, "e");
});
this.taskManager.Add(new TaskState(t, "onMaskInputRejected"));
return base.init(this);
}
}
  Скрипт визуального элемента должен наследовать класс WinElement, который реализует работу с визуальным виджетом. Также в методе инициализации создаётся отложенное задание на создание кода метода-обработчика события MaskInputRejected, которое будет выполнено после завершения инициализации элемента. Копируем элементы тестовой схемы проекта Core (за исключением элемента HCTplForm) в проект Windows Forms:
Add(EntryPoint,2953706,21,105)
{
StartPosition=1
Point(onError)
link(onError,2:doMessage,[])
}
Add(hcMessage,2,70,119)
{
Caption="Error"
Icon=1
}
Add(Example,7,175,119)
{
Greeting="Hello!"
link(onGreeting,10:doWork2,[])
link(Data,11:Text,[])
}
Add(hcTplButton,8,119,119)
{
Left=36
Top=23
link(onClick,7:doSayHello,[])
}
Add(hcMessage,9,231,119)
{
link(Caption,7:Greeting,[(244,105)(216,105)(216,164)(181,164)])
}
Add(HubEx,10,217,119)
{
link(onEvent,9:doMessage,[])
}
Add(HCMaskedTextBox,11,175,63)
{
Left=97
Top=23
Mask="00/00/0000"
link(onMaskInputRejected,10:doWork1,[(221,69)])
}
  Проверяем элемент в работе проекта Windows Forms:
Demo



  Можете скачать полный код проекта: MyElementCore.zip

Редактировалось 1 раз(а), последний 2020-01-02 20:16:40
карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
1
Голосовали:Konst
Главный модератор
Ответов: 2947
Рейтинг: 388
#9: 2020-01-13 11:28:29 ЛС | профиль | цитата
    Отладка элемента

  Для разработчика необходимо иметь возможность контролировать работу кода, который он создаёт. В нашем случае у разработчика имеется такая возможность. Средства отладки Visual Studio позволяют контролировать ход выполнения кода нашего элемента от создания экземпляра класса и инициализации до завершения работы среды. Вашему вниманию представляется демо-ролик процесса отладки, созданного нами элемента средствами программы Visual Studio:
Demo



  В проекте Windows Forms возможна отладка кода скрипта RTCG.NET. Для этого надо копировать файл скрипта в папку \code пакета и вернуть блок using ссылок на пространства имён сторонних сборок вместо тегированного комментария скрипта элемента Example:

После открытия файла скрипта из папки \code, можно устанавливать точки останова непосредственно в коде для отладки процесса создания целевого кода, создаваемой программы:
Demo


карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
0
Главный модератор
Ответов: 2947
Рейтинг: 388
#10: 2020-01-18 14:45:42 ЛС | профиль | цитата
    Лицензирование элемента

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

  Платформа .NET Framework предлагает модель лицензирования, одинаковую для всех компонентов и элементов управления (включая элементы управления Windows Forms) и полностью совместимую с моделью лицензирования элементов управления Microsoft ActiveX®. Однако, следует отметить, что .NET Framework не реализует защиту, а предоставляет механизм её реализации.

  Библиотека .NET Framework разделяет логику проверки лицензии от кода самого компонента с помощью провайдера лицензий (license provider), представленного классом System.ComponentModel.LicenseProvider. Класс LicenseProvider является базовым абстрактным классом для всех провайдеров лицензий. Единственным его методом является метод GetLicense(), возвращающий экземпляр класса License:
public abstract License GetLicense(LicenseContext context, Type type, object instance, bool allowexceptions);
  Первый параметр этого метода, context, предоставляет сервис для работы с лицензиями, второй параметр передает тип лицензируемого компонента, а третий параметр экземпляр самого компонента. Последний параметр разрешает генерацию исключений, если файл лицензии некорректен.

  Для реализации защиты элемента при помощи текстового файла лицензии мы воспользуемся классом LicFileLicenseProvider. Класс LicFileLicenseProvider реализует простейшую защиту: если в каталоге, где расположен элемент, присутствует файл специального формата, лицензия считается корректной. Формат файла очень простой: имя файла должно быть в формате <пространство_имен>.<тип_элемента>.lic файл должен содержать строку: <пространство_имен>.<тип_элемента> is a licensed component. Подключение провайдера лицензий к классу элемента производится с помощью добавления атрибута LicenseProvider:
[LicenseProvider(typeof(LicFileLicenseProvider))]
public class Example : ElementClass
{
...
}
  Само по себе добавление атрибута еще не означает включение механизма проверки лицензии. Это просто определение провайдера лицензий. Для проверки правильности лицензии используется вызов метода Validate() класса LicenseManager:
License license = LicenseManager.Validate(this.GetType(), this);
  Давайте посмотрим, что изменится в поведении нашего элемента после добавления этих двух строчек кода:

  Попытаемся после компиляции открыть схему, которая содержит наш элемент, например такую:
Add(hcTplForm,2953706,21,105)
{
StartPosition=4
}
Add(Example,2,126,105)
{
Greeting="Hello!"
link(onGreeting,7:doWork2,[])
link(Data,5:Text,[])
}
Add(hcTplButton,3,70,105)
{
Left=36
Top=23
link(onClick,2:doSayHello,[])
}
Add(hcMessage,4,182,105)
{
link(Caption,2:Greeting,[(195,91)(167,91)(167,150)(132,150)])
}
Add(HubEx,7,168,105)
{
link(onEvent,4:doMessage,[])
}
Add(HCMaskedTextBox,5,126,49)
{
Left=97
Top=23
Mask="00/00/0000"
link(onMaskInputRejected,7:doWork1,[(172,55)])
}
  Вместо открытия нашей схемы мы увидим отладочную информации о произошедшей ошибке, которая сообщает нам среди прочего, что: «Создавался экземпляр типа 'ElementVirtual.Example', но для типа 'ElementVirtual.Example' не может предоставляться действительной лицензии. За дополнительной информацией обращайтесь к поставщику компонента.»:

  Использовать элемент нельзя до тех пор пока мы не поместим рядом со сборкой файла свой файл лицензии с именем ElementVirtual.Example.lic и следующего содержания:
ElementVirtual.Example is a licensed component.
  Теперь попытка открыть схему будет удачной:

  Разумеется, назвать это защитой сложно, но этот класс дает возможность реализовать собственный механизм защиты, перекрыв виртуальный метод:
protected virtual bool IsKeyValid(string key, Type type);
  В реализации наследника можно (и нужно) использовать более сложный механизм, чем простое сравнение строк. К тому же не следует забывать что, если Ваш код можно декомпилировать, то относительно легко обойти большинство систем лицензирования. Конечно, это при условии что Вы хорошо разбираетесь в этом.

  Можете скачать полный код проекта элемента: MyElementCore.zip

Редактировалось 6 раз(а), последний 2020-01-18 16:44:42
карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
1
Голосовали:Konst
Главный модератор
Ответов: 2947
Рейтинг: 388
#11: 2020-01-19 15:00:27 ЛС | профиль | цитата
    Демо-версия элемента

  На основе класса провайдера лицензий LicFileLicenseProvider можно реализовать простой режим демо-версии элемента. Для этого нам нужно создать класс провайдера лицензий, который будет наследником LicFileLicenseProvider и перекроет метод GetLicense(). Добавим в ресурсы сборки файл с именем wfDemoLicenseProvider.cs и следующим содержанием:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace ElementVirtual
{
internal class DemoLicenseProvider : LicFileLicenseProvider
{
public override License GetLicense(LicenseContext context, Type type, object instance, bool allowExceptions)
{
string text = "You are using demo version of the Example element.\n"
+ "To get full version please visit:\n\nhttp://hiasm.ddns.net/hiasm.net/license.html";
MessageBox.Show(text, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
return new DemoLicense("granted");
}
}
internal class DemoLicense : License
{
private string m_LicenseKey;
public override string LicenseKey { get { return m_LicenseKey; } }
public DemoLicense(string license)
{
m_LicenseKey = license;
}
public override void Dispose() { }
}
}
  Установим его свойство Build Action в значение Compile:

  Заменим в атрибуте LicenseProvider провайдера лицензий LicFileLicenseProvider нашим провайдером DemoLicenseProvider:
[LicenseProvider(typeof(DemoLicenseProvider))]
public class Example : ElementClass
{
...
}
   И перенесём вызов проверки правильности лицензии из конструктора элемента в метод подготовки элемента к запуску, который вызывается только при запуске проекта Шаблоны (Templates):
public override void prepareForRun(ref Element parent)
{
...
License license = LicenseManager.Validate(this.GetType(), this);
}
  Давайте посмотрим, что изменится в поведении нашего элемента после изменений:
Demo


  Теперь, вне зависимости от наличия или отсутствия файла лицензии, наша схема проекта Шаблоны (Templates) открывается, но при запуске схемы на выполнение отображается окно с информацией о демо-режиме данного элемента.

  Теперь сделаем демо-режим элемента для проекта Windows Forms. Для этого внесём необходимые изменения в скрипт RTCG.NET файла wfExample.cs:
public class Example : ElementClass
{
public override bool init(object entry)
{
Debug.WriteLine("{0}.init()".fString(this.codename));
sys.add_use("System.ComponentModel");
sys.add_use("ElementVirtual");
sys.useClass("DemoLicenseProvider");
string license = "[LicenseProvider(typeof(DemoLicenseProvider))]";
if (!sys.blk_class_attr.astext().Contains(license))
{
sys.blk_class_attr.println(license);
sys.blk_init.println("License license = LicenseManager.Validate(this.GetType(), this);");
}
...
  В скрипте мы подключаем необходимые пространства имён, добавляем к проекту приложения класс провайдера лицензий DemoLicenseProvider, устанавливаем атрибут LicenseProvider для главной формы приложения и вставляем код проверки лицензии в конструкторе главной формы. Чтобы избежать дублирования кода проверки лицензии при наличии в схеме более одного элемента Example в скрипте проверяется блок атрибутов класса на присутствие в нём кода с установкой атрибута LicenseProvider.

  Проверим демо-режим элемента в проекте Windows Forms:
Demo


  Можете скачать полный код проекта элемента: MyElementCore.zip

Редактировалось 3 раз(а), последний 2020-01-19 21:05:58
карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
0
Главный модератор
Ответов: 2947
Рейтинг: 388
#12: 2020-02-08 14:20:39 ЛС | профиль | цитата
    Работа элемента в разных пакетах

  Инфраструктура элемента может хранить реализации для разных пакетов и/или несколько версий реализации для каждого пакета. Давайте добавим в сборку реализации нашего элемента для пакетов Windows и CNET. Сначала поместим в ресурсы сборки файлы с кодом элемента для каждого из пакетов:
Реализация для пакета Windows


unit hi%CLASSNAME%;

interface

uses Kol,Share,Debug;

type
THI%CLASSNAME% = class(TDebug)
private
greeting:string;
public
_data_Data:THI_Event;
_prop_Greeting:string;
_event_onGreeting:THI_Event;
procedure _work_doSayHello(var _Data:TData; Index:word);
procedure _var_Greeting(var _Data:TData; Index:word);
end;

implementation

procedure THI%CLASSNAME%._work_doSayHello;
begin
greeting := ReadString(_Data,_data_Data,_prop_greeting);
_hi_CreateEvent(_Data,@_event_onGreeting,greeting);
end;

procedure THI%CLASSNAME%._var_Greeting(var _Data:TData; Index:word);
begin
if greeting='' then dtString(_Data,ReadString(_Data,_data_Data,_prop_greeting)) else dtString(_Data,greeting);
end;

end.
Реализация для пакета CNET


func init
if(linked("Greeting") and linked("doSayHello"))
sys.add_var(this.name, 2)
end
end

func doSayHello(data)
if(linked("Greeting"))
blk.println(this.codename, ' = ', d("Data", 2, this.props("Greeting").value), ';')
event("onGreeting", this.codename)
else
event("onGreeting", d("Data", 2, this.props("Greeting").value))
end
end

func Greeting
if(linked("doSayHello"))
return(this.codename)
else
return(this.props("Greeting").value)
end
end
  Сохраняем в папку \Resources два файла с именами hiExample.pas и hiExample.hws и помещаем их в ресурсы сборки:

  Переименуем ресурсы для этих файлов в понятные нам имена:

  Добавим в метод инициализации элемента код необходимый для развёртывания ресурсов в пакетах:

public override void init(int flag = 0)
{
base.init(flag);
deployEnvironment(Share.packSet.getPackByName("CNET"));
deployEnvironment(Share.packSet.getPackByName("delphi"));
...
}

protected virtual void deployEnvironment(Pack pack)
{
if (pack == null)
return;
byte[] buffer = null;
string codeName = null;
string className = GetType().Name;
switch (pack.name.ToLower())
{
default:
return;
case "cnet":
buffer = Properties.Resources.cnet_rtcg_script;
codeName = String.Format("hi{0}.hws", className);
break;
case "delphi":
string code = Encoding.UTF8.GetString(Properties.Resources.delphi_source_code);
code = code.Replace("%CLASSNAME%", className);
buffer = Encoding.UTF8.GetBytes(code);
codeName = String.Format("hi{0}.pas", className);
makeConfFile(pack);
break;
}
string codeFile = Path.Combine(pack.pathCode(), codeName);
if (File.Exists(codeFile))
{
if (Helpers.getChecksum(codeFile).Equals(Helpers.crc32(buffer)))
return;
Helpers.fileMoveOverwrite(codeFile);
}
File.WriteAllBytes(codeFile, buffer);
}

protected virtual void makeConfFile(Pack pack)
{
StringBuilder confText = new StringBuilder();
confText.AppendLine("[About]");
confText.AppendFormat("Version={0}{1}", this.tpl.version, Environment.NewLine);
confText.AppendFormat("Author={0}{1}", this.tpl.author, Environment.NewLine);
confText.AppendFormat("Mail={0}{1}", this.tpl.mail, Environment.NewLine);
confText.AppendLine();
confText.AppendLine("[Type]");
confText.AppendLine("Class=Element");
confText.AppendFormat("Info={0}{1}", this.tpl.conf.info, Environment.NewLine);
confText.AppendFormat("Tab={0}{1}", this.tpl.conf.tab, Environment.NewLine);
confText.AppendLine();
confText.AppendLine("[Property]");
foreach (ConfProperty prop in this.tpl.conf.prop)
confText.AppendFormat
(
"{0}{1}{2}={3}|{4}|{5}{6}"
, prop.flags.hasFlag(PropertyFlag.DBLOPEN) ? "+" : ""
, prop.flags.hasFlag(PropertyFlag.MAKEPOINT) ? "@" : ""
, prop.name
, prop.info
, (int)prop.type
, prop.list.isEmpty() ? prop.value.isEmpty() ? "" : String.Format("|{0}", prop.value.Replace(Environment.NewLine, @"\r\n")) : String.Format("|{0}", prop.list)
, Environment.NewLine
);
confText.AppendLine();
confText.AppendLine("[Methods]");
var methods = this.tpl.conf.mtd.Union(this.tpl.conf.def);
foreach (ConfMethod mtd in methods)
confText.AppendFormat
(
"{0}{1}={2}|{3}|{4}{5}"
, mtd.visible ? "" : "*"
, mtd.name
, mtd.info
, (int)mtd.mType
, (int)mtd.dataType
, Environment.NewLine
);
confText.AppendLine();
string confName = String.Format("{0}.ini", GetType().Name);
Helpers.overwriteIfDifferent(confText.ToString(), Path.Combine(pack.path(), Constants.ELEMENTS_CONF_PATH, confName));
}
  В приведённом коде развёртывания, реализуется создание файлов в папках пакетов \code, извлечённых из ресурсов сборки, а также для пакета Windows нужен соответствующий ini-файл элемента, так как кодогенератор считывает его во время создания целевого кода программы. Поэтому мы добавили метод makeConfFile(), который создаёт ini-файл «на лету» и помещает его в папку \conf пакета Windows. Добавим атрибуты установки элемента в дополнительных пакетах:

...
[Install("delphi", "Simple demo", "Core")]
[Install("CNET", "Simple demo", "Core")]
public class Example : ElementClass
{
...
}

  После успешной компиляции сборки открываем её в программе для размещения элемента на панели инструментов в указанной в аргументах атрибута вкладке и проверяем работу элемента в пакетах отличных от пакета Core:
Demo


  Можете скачать полный код проекта элемента: MyElementCore.zip
карма: 7
Дорогу осилит идущий. Install/Update HiAsm.NET
0
12
Сообщение
...
Прикрепленные файлы
(файлы не залиты)