Урок 22. Создание кастомных команд для форм
Last updated
Last updated
Как говорится в статье Состав элементов архитектуры WT-программы серверная и клиентская части WT-приложения помимо штатных библиотек платформы (dll-файлов) могут включать кастомные библиотеки, созданные разработчиками для расширения функциональности платформы для приложения.
Этот урок посвящен созданию кастомной команды на клиентской части, а в следующем уроке мы узнаем, как создавать кастомные команды на сервере. Платформа Workflow Technology написана на языке C#, поэтому все кастомные команды и объекты будем так же писать на этом языке. В уроках в качестве среды разработки будем использовать Microsoft Visual Studio 2022.
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.
При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.
Инструкция по подключению шаблонов находится по ссылке.
При разработке кастомных библиотек к проекту в Visual Studio будем подключать штатные библиотеки. В этом уроке нам понадобятся бинарники, которые мы скачивали при разворачивании клиентской части. Штатные библиотеки платформы WT позволят использовать классы из этих библиотек и создавать собственные классы, которые платформа WT будет с легкостью интегрировать в приложение.
В дальнейшей работе с платформой Workflow Technology при разработке новых приложений будет удобнее, если для штатных бинарников платформы будет отведен отдельный каталог, в котором они будут храниться. А при создании нового WT-приложения можно будет копировать бинарники и файлы конфигурации из соответствующих папок этого каталога в папку, где будет разворачиваться приложение. Такое разделение позволит хранить штатные библиотеки отдельно от кастомных, созданных для конкретного приложения, что упростит копирование dll-файлов платформы в новые приложения.
В уроке будем работать с отдельным каталогом для штатных библиотек, и вам советуем сделать так же.
Запустите 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 так же можно подключать необходимые библиотеки, добавляя в тэг <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-файла можно в текущем классе (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).
Отлично! Теперь можем вернуться в файл 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-проперти.
Переопределим метод GetProperty, чтобы кастомный объект имел get-проперти, которые можно использовать на форме:
Этот метод форма будет вызывать каждый раз, когда происходит обращение к get-проперти объекта. Таким образом, когда метод OnPropertyChange делает рассылку сообщения об изменении свойства объекта, переменные на форме, поймав это сообщение, инициируют вызов этого метода.
Первым делом проверяем, было ли обращение к нашему get-проперти и возвращаем соответствующее значение, иначе передаем вызов в базовый метод.
Запустим проект из Visual Studio, чтобы проверить значения в полях TotalSumToPayTextBox, TotalPaidSumTextBox и TotalDebtTextBox:
Но если мы внесем изменения в одну из таблиц, то значения текстовых полей обновятся. Это связано с тем, что метод Recount вызывается только в момент изменения таблиц OrderPositionDatabaseTable и OrderPaymentDatabaseTable, а не при открытии формы.
Реализуем set-проперти Recount, которое будет инициировать вызов метода Recount.
Чтобы кастомный объект имел set-проперти, необходимо переопределить метод SetProperty:
Этот метод вызывается в момент выполнения команды ValueSetCommand.
Добавим в класс константу:
Добавим на форму команду ValueSetCommand, через которую обратимся к set-проперти Recount:
Теперь для этой команды создадим Execution на системное условие FormLoadedCondition, которое необходимо дополнительно прописать в тэге <Conditions>
.
Запустите проект из Visual Studio и проверьте отображение данных на форме заказа.
В этом уроке мы рассмотрели возможность создавать кастомные объекты на формах, чтобы в них реализовать бизнес-логику, которую проще и быстрее описать на языке C#, либо настроить интеграцию со сторонними сервисами.
В приложении Клиентская часть собраны статьи о написании кастомных условий, команд и DataConnection.
В архиве присутствуют xml-файлы форм и серверный xml-файл, исходный код кастомки Template, а также бэкап базы данных - с помощью файлов можете проверить себя.