Урок 22. Создание кастомных команд для форм

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

Этот урок посвящен созданию кастомной команды на клиентской части, а в следующем уроке мы узнаем, как создавать кастомные команды на сервере. Платформа Workflow Technology написана на языке C#, поэтому все кастомные команды и объекты будем так же писать на этом языке. В уроках в качестве среды разработки будем использовать Microsoft Visual Studio 2022.

Подготовка

При разработке кастомных библиотек к проекту в Visual Studio будем подключать штатные библиотеки. В этом уроке нам понадобятся бинарники, которые мы скачивали при разворачивании клиентской части. Штатные библиотеки платформы WT позволят использовать классы из этих библиотек и создавать собственные классы, которые платформа WT будет с легкостью интегрировать в приложение.

В дальнейшей работе с платформой Workflow Technology при разработке новых приложений будет удобнее, если для штатных бинарников платформы будет отведен отдельный каталог, в котором они будут храниться. А при создании нового WT-приложения можно будет копировать бинарники и файлы конфигурации из соответствующих папок этого каталога в папку, где будет разворачиваться приложение. Такое разделение позволит хранить штатные библиотеки отдельно от кастомных, созданных для конкретного приложения, что упростит копирование dll-файлов платформы в новые приложения.

В уроке будем работать с отдельным каталогом для штатных библиотек, и вам советуем сделать так же.

Настройка Visual Studio

Запустите Visual Studio Installer.

По кнопке "Изменить" откроется окно установки компонент среды разработки:

На вкладке "Рабочие нагрузки" поставьте галочку на компоненте ASP.NET и разработка веб-приложений и нажмите кнопку "Изменить".

Создание проекта

В папке \Template\Projects\1. Template создадим папку Objects, в которой будут храниться исходники кастомных объектов для форм.

Запустим Visual Studio 2022.

На начальной странице выберем пункт Create a new project (Создать проект):

Если среда разработки Visual Studio уже открыта, то проект можно создать, выбрав пункт File -> New -> Project... в строке меню. А также нажав кнопку New Project на панели инструментов, или нажав комбинацию клавиш Ctrl+Shift+N.

На странице Create a new project введите в поле поиска library. Так как платформа написана на платформе .NET Core, то нам необходимо выбрать соответствующий тип приложения:

Если вы не видите шаблоны .NET, вероятно, у вас не установлена требуемая рабочая нагрузка. В сообщении Not finding what you're looking for? (Не удается найти то, что ищете?) выберите ссылку Install more tools and features (Установка других средств и компонентов). Откроется Visual Studio Installer. Убедитесь, что у вас установлена рабочая нагрузка ASP.NET и разработка веб-приложений.

В диалоговом окне Configure your new project (Настройка нового проекта) доступны параметры, позволяющие присвоить имя проекту (и решению), выбрать расположение на диске:

В поле Project name укажем имя нашего проекта - Template. В поле Location - ранее созданную папку Objects.

Галочку Place solution and project in the same directory можно снять.

В диалоговом окне Additional information (Дополнительные сведения) содержится параметр для выбора версии платформы:

Оставим здесь .NET Core 3.1 (Out of support) и нажмем на кнопку Create.

По умолчанию новый проект имеет один пустой класс Class1 в файле Class1.cs:

Сборка проекта

Откроем свойства проекта, вызвав контекстное меню и выбрав пункт Properties. Или нажав комбинацию клавиш Alt+Enter.

На вкладке Build->Events необходимо прописать команду, которая будет выполняться после сборки решения:

В поле Post-build event пропишем команду копирования собранного dll-файл из папки проекта в папку с установленной клиентской частью:

По умолчанию Visual Studio собирает проект в папку:

\Template\Projects\1. Template\Objects\Template\Template\bin\Debug\netcoreapp3.1

Давайте пересоберем проект, для этого в контекстном меню выберем пункт Build или Rebuild:

Проверьте, что файл Template.dll скопировался в папку с установленной клиентской частью.

Отладка проекта

Чтобы запускать приложение напрямую из Visual Studio, сделаем настройки режима отладки. Для этого откроем свойства проекта и перейдем на вкладку Debug:

Кликнем по тексту Open debug launch profiles UI. В открывшемся окне профилей запуска кликнем по кнопке Create a new profile. В меню выберем пункт Executable:

Новый профиль сразу переименуем, кликнув по кнопке Rename selected profile:

В поле Executable укажем путь до exe-файла приложения, размещенного в папке развернутой клиентской части:

Теперь мы можем запустить приложение, нажав клавишу F5 или кнопку на панели инструментов:

В проекте появился новый файл launchSettings.json, в котором будут храниться настройки профилей запуска приложения:

Кастомный объект

В этом уроке мы создадим кастомный объект на форме заказа.

Первым делом необходимо описать xml-код объекта, чтобы иметь представление, какие элементы нам понадобятся:

Здесь стоит обратить внимание на несколько моментов.

Во-первых, у тэга <MyObject> есть атрибут Assembly. В нем указывается имя библиотеки, в которой описан класс объекта. Так как это кастомный объект, то в атрибуте указано имя кастомной сборки Template, которую мы создали ранее.

Во-вторых, в атрибуте Type указывается имя класса, который описывает логику работы объекта.

Итак, в объекте мы будем передавать имена таблиц и их колонок. По этим именам в C# коде мы сможем получать доступ к объектам формы и их свойствам.

Объект PaymentCounter должен будет рассчитывать сумму начислений (позиций заказа), сумму всех оплат и остаток к оплате. Эти три значения будем получать через get-проперти объекта и хранить в переменных на форме.

Ранее в уроках мы создавали переменные TotalSumToPayVariable, TotalPaidSumVariable и TotalDebtVariable, в которых обращались к таблицам и через get-проперти получали нужные значения. Перепишем эти переменные - теперь будем заполнять их через get-проперти кастомного объекта:

Подключение библиотеки

Подключим библиотеки WT-платформы, необходимы для создания кастомных объектов форм.

Для этого в контекстном меню проекта выберем пункт Add -> Project Reference...:

В открывшемся окне перейдем на вкладку Browse, где по кнопке Browse... откроем диалоговое окно выбора файлов и в папке со штатными бинарниками клиентской части найдем файлы WorkflowForms.dll и FormObjects.dll и добавим их.

WorkflowForms.dll - основная библиотека форм.

В FormObjects.dll описаны базовые классы, необходимые для работы любого объекта, а также класс Control, от которого будет наследоваться наш кастомный объект.

Дальше необходимо подключить фреймворк Microsoft.WindowsDesktop.App.WindowsForms.

Для этого в контекстном меню выберем пункт Edit Project File:

Откроется файл Template.csproj, в нем в тэг <PropertyGroup> добавим строчку:

Полный синтаксис файла Template.csproj

Таким образом, структура решения будет иметь вид:

Через файл Template.csproj так же можно подключать необходимые библиотеки, добавляя в тэг <ItemGroup> новый тэг <Reference> вида:

Создание класса

Удалим файл Class1.cs и создадим папку CustomControls, в которую добавим новый класс PaymentCounter:

Это минимально необходимый код для создания кастомного объекта.

В классе PaymentCounter мы переопределили свойства: Value (значение объекта), Visible (признак видимости объекта) и Enabled (признак активности объекта).

Так как кастомный объект не будет иметь значения, то его свойство Value возвращает null. И раз объект не будет иметь графического представления на форме, то свойства Visible и Enabled возвращают false.

Метод UpdateValue переопределяется для реализации логики обновления значения свойства Value. Подробнее об этом в примерах кастомных элементов, описанных в приложении к блоку.

Метод InternalInit срабатывает в момент загрузки формы, когда происходит парсинг xml-файла. В этом методе и будем описывать логику получения данных из файла.

Метод InternalInit принимает параметры:

  • Первый параметр XmlNode node - это сам тэг <MyObject> и его содержимое.

  • Второй параметр IWorkflowForm form - наша форма. Через нее сможем получать доступ ко всем элементам формы.

  • Третий параметр IControl parentControl - родительский объект формы, в котором описан текущий. В нашем случае это TotalPanel.

  • Четвертый параметр System.Windows.Forms.Control parentWindowsControl - родительский контрол, который является экземпляром класса System.Windows.Forms.Panel.

Обратите внимание, что для кастомных контролов в качестве namespace указывается WorkflowForms.Controls. Это позволяет обращаться к публичным классам штатных библиотек платформы без использования директивы using для импорта типов.

Давайте немного изменим начальный код класса:

В свойства Value, Visible и Enabled мы добавили генерацию ошибки NotImplementedException, чтобы обозначить, что для данного класса эти свойства не реализованы. Аналогичную ошибку генерируем в методе UpdateValue.

Также добавили директиву #region, чтобы объединить базовые свойства класса в единый логический блок кода, который можно сворачивать.

Парсинг xml-кода

Получать данные из xml-файла можно в текущем классе (PaymentCounter). Либо можно создать дополнительный класс настроек, чтобы разделить парсинг xml и бизнес-логику.

Давайте создадим класс PaymentCounterSettings:

Вернемся в файл PaymentCounter.cs и создадим метод, в котором будем получать данные от PaymentCounterSettings:

Добавим вызов этого метода в InternalInit.

Вернемся в файл PaymentCounterSettings.cs и вспомним описание нашего объекта на xml:

Для начала создадим константы с именами вложенных тэгов:

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

Для того чтобы использовать исключение типа InvalidXmlException, подключите к проекту библиотеку Exceptions.dll, а в код добавьте необходимый using.

В метод GetRequiredAttributeValue первыми двумя параметрами передаем объект формы (IWorkflowForm form) и объект xml-узела (XmlNode node), который содержит xml-код нашего кастомного объекта. В третьем параметре (string path) указываем полный путь до тэга, значение атрибута которого нужно получить. Имя атрибута указывается в четвертом параметре (string attribute). В данном случае используется константа NAME_ATTRIBUTE, описанная в базовом классе. Если элемент или его атрибут отсутствует, будет возвращено исключение типа InvalidXmlException.

Подключите к проекту библиотеку ComplexControls.dll, чтобы иметь доступ до класса DatabaseTable.

Создадим проперти для хранения ссылок на таблицу PositionDatabaseTable:

Зная имя таблицы, мы можем получить на нее ссылку. Для этого обратимся к объекту form и его методу GetControl. Добавим код в блок if:

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

С помощью метода GetRequiredAttributeValue так же получим значение атрибута Name тэга <ColumnSumToPay>:

Так как тэг <ColumnSumToPay> является вложенным, то полный путь до него включает имя внешнего тэга <PositionDatabaseTable> и его собственное имя, разделенные символом /.

Полный синтаксис блока if для получения информации об элементах формы с позициями заказа:

Аналогично сделайте и для таблицы оплат (PaymentDatabaseTable).

Полный код файла PaymentCounterSettings.cs

Отлично! Теперь можем вернуться в файл PaymentCounter.cs и продолжить работу с методом LoadSettings.

Создадим переменные:

Дополним метод LoadSettings:

Отлично! У нас есть данные с формы, и мы можем перейти к реализации расчетов.

Реализация бизнес-логики

Чтобы наш кастомный объект PaymentCounter самостоятельно следил за изменением данных в таблицах OrderPositionDatabaseTable и OrderPaymentDatabaseTable, создадим handler, который будет срабатывать каждый раз при изменении данных в таблицах.

Добавим следующий код в метод InternalInit:

И добавим код самого handler и метода, который он будет вызывать:

В методе Recount получаем суммы в колонках таблиц на форме и рассчитываем остаток, необходимый к оплате. Ниже создадим, в котором опишем переменные TotalDebt, TotalSumToPay и TotalPaidSum.

Через метод GetProperty можем обращаться к get-проперти объекта, передавая в качестве аргументов имя get-проперти и словарь параметров:

Эта запись аналогична записи в xml-коде:

Полученные значения сохраняются в свойства класса. Добавим в PaymentCounter.cs следующий код:

Метод OnPropertyChange описан в родительском классе и необходим, чтобы объект формы рассылал сообщение о том, что значение его свойства изменилось. Благодаря этому переменные TotalSumToPayVariable, TotalPaidSumVariable и TotalDebtVariable будут получать свежие значения get-проперти.

Get-проперти

Переопределим метод GetProperty, чтобы кастомный объект имел get-проперти, которые можно использовать на форме:

Этот метод форма будет вызывать каждый раз, когда происходит обращение к get-проперти объекта. Таким образом, когда метод OnPropertyChange делает рассылку сообщения об изменении свойства объекта, переменные на форме, поймав это сообщение, инициируют вызов этого метода.

Первым делом проверяем, было ли обращение к нашему get-проперти и возвращаем соответствующее значение, иначе передаем вызов в базовый метод.

Запустим проект из Visual Studio, чтобы проверить значения в полях TotalSumToPayTextBox, TotalPaidSumTextBox и TotalDebtTextBox:

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

Реализуем set-проперти Recount, которое будет инициировать вызов метода Recount.

Set-проперти

Чтобы кастомный объект имел set-проперти, необходимо переопределить метод SetProperty:

Этот метод вызывается в момент выполнения команды ValueSetCommand.

Добавим в класс константу:

Добавим на форму команду ValueSetCommand, через которую обратимся к set-проперти Recount:

Теперь для этой команды создадим Execution на системное условие FormLoadedCondition, которое необходимо дополнительно прописать в тэге <Conditions>.

Запустите проект из Visual Studio и проверьте отображение данных на форме заказа.

Полный код файла PaymentCounter.cs

Итоги

В этом уроке мы рассмотрели возможность создавать кастомные объекты на формах, чтобы в них реализовать бизнес-логику, которую проще и быстрее описать на языке C#, либо настроить интеграцию со сторонними сервисами.

В приложении Клиентская часть собраны статьи о написании кастомных условий, команд и DataConnection.

Ответы

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

590KB
Open

Last updated