<MyObject Name="CustomControl" Type="CustomControl" Assembly="Template">
<!-- Элементы объекта -->
</MyObject>
В атрибуте Type указывается имя класса, описывающего кастомный объект, а в качестве значения атрибута Assembly указывается имя сборки, в которой реализован этот класс.
Для удобства и разделения логики объекта, исходный код можно разбить на два класса:
CustomControlSettings - класс загрузки данных из xml-файла;
CustomControl - класс с бизнес-логикой.
Парсинг xml
Полный код шаблона класса CustomControlSettings:
using System.Xml;
namespace WorkflowForms.Controls
{
public class CustomControlSettings : ControlSettings
{
/*
* Набор свойств для хранения данных
* public string PropertyName { get; private set; }
*/
public CustomControlSettings(IDependable parent, XmlNode node, IWorkflowForm form, IDataBindingProvider dataBindingProvider)
: base(node, form)
{
/*
* Здесь должен быть парсинг xml-кода,
* чтобы получить значения тэгов и их атрибутов.
*/
}
}
}
Конструктор класса принимает параметры:
parent IDependable - ссылка на экземпляр класса CustomCommand, для которого создавался объект типа CustomCommandSettings;
form IWorkflowForm - форма, на которой описана кастомная команда;
node XmlNode - узел, который соответствует тэгу <Command>, описанному в xml-файле формы.
dataBindingProvider IDataBindingProvider -
Исполняемый код
Класс кастомного объекта наследуем от класса Control:
using System;
using System.Collections.Generic;
using System.Xml;
namespace WorkflowForms.Controls
{
public class CustomControl : Control
{
private const string SET_PROPERTY = "SetProperty";
private const string ANY_PROPERTY = "AnyProperty";
#region Base Properties
/*
* Кастомный объект может не иметь какого-то конкретного значения,
* а необходимые данные можно получать через get-проперти
*/
public override object Value
{
get => null;
set => throw new NotImplementedException();
}
/*
* Так как кастомный объект не имеет графического представления,
* свойства Visible и Enabled не имеют реализации и возврящают false
*/
public override bool Visible
{
get => false;
set => throw new NotImplementedException();
}
public override bool Enabled
{
get => false;
set => throw new NotImplementedException();
}
#endregion
#region Custom Properties
private string _anyProperty;
private string AnyProperty
{
get
{
return _anyProperty;
}
set
{
_anyProperty = value;
// Когда изменяется значение свойства объекта, необходимо сделать рассылку события всем подписчикам.
OnPropertyChange(ANY_PROPERTY);
}
}
#endregion
protected override void UpdateValue()
{
/*
* Метод обновления значения базового свойства Value.
*/
throw new NotImplementedException();
}
public CustomControl(IWorkflowForm form, XmlNode node) : base(form, node)
{
}
#region Инициализация объекта
protected override void InternalInit(XmlNode node, IWorkflowForm form, IControl parentControl, System.Windows.Forms.Control parentWindowsControl)
{
base.InternalInit(node, form, parentControl, parentWindowsControl);
LoadSettings(node, form);
/*
* На объекты формы и DataBinding можно повесить Handler,
* которые будут автоматически срабатывать при изменении
* объектов и DataBinding
*
* Например:
* _anyDatabaseTable.Change += RecountChangeHandler;
* _somethingDataBinding.Change += RecountChangeHandler;
*
* В зависимости от задачи, каждый объект и DataBinding
* могут быть привязаны разные Handler
*/
}
private void LoadSettings(XmlNode node, IWorkflowForm form)
{
var settings = new CustomControlSettings(this, node, form, new DataBindingProvider());
/*
* Получение значений и сохранение в переменные
*/
}
#endregion
private void RecountChangeHandler(object sender, EventArgs args)
{
Recount();
}
private async void Recount()
{
/*
* Здесь должен быть код бизнес-логики
*/
// Свойству кастомного объекта присваивается какое-то значение
AnyProperty = "Result";
}
#region SetProperty
public override void SetProperty(string propertyName, Dictionary<string, object> parameters)
{
if (propertyName.Equals(SET_PROPERTY, StringComparison.OrdinalIgnoreCase))
{
// Логика обработки
Recount();
}
else
{
base.SetProperty(propertyName, parameters);
}
}
#endregion
#region GetProperty
public override object GetProperty(string propertyName, Dictionary<string, object> parameters)
{
if (propertyName.Equals(ANY_PROPERTY, StringComparison.OrdinalIgnoreCase))
{
return AnyProperty;
}
else
{
return base.GetProperty(propertyName, parameters);
}
}
#endregion
}
}
При наследовании класса Control необходимо реализовать свойства Value, Visible и Enabled. Кастомный объект не имеет значения, поэтому свойство Value возвращает null. Свойства Visible и Enabled возвращают false, так как объект не имеет графического интерфейса.
Get/Set-проперти
Для удобного взаимодействия с объектом и полного контроля за его работой следует реализовывать Get/Set-проперти, с помощью которых можно отдавать команды на проведение вычислений или взаимодействия со сторонними сервисами. Реализация get и set-проперти описывается в методах GetProperty и SetProperty соответственно.
В классе необходимо переопределить метод GetProperty:
public override object GetProperty(string propertyName, Dictionary<string, object> parameters)
{
if (propertyName.Equals("AnyGetProperty", StringComparison.OrdinalIgnoreCase))
{
// Логика обработки
// return AnyProperty;
}
else
{
return base.GetProperty(propertyName, parameters);
}
}
В параметре parameters метода GetProperty хранится словарь всех параметров, описанных в тэге <Parameters> при обращении к get-проперти.
Элементы объекта
Константы и переменные
Для задания констант используем значения атрибутов, а для задания переменных - значения тэгов. В примере рассмотрим xml-код объекта с необязательным тэгом <Flag> и обязательным тэгом <Something>:
В конструкторе класса CustomControlSettings необходимо прописать код вида:
// public bool Flag { get; private set; }
Flag = XmlParser.GetAttributeValue(form, node, "Flag", "Value", false);
Так как тэг <Flag> является необязательным, то для получения его значения используется статический метод GetAttributeValue класса XmlParser. В третьем параметре (string path) указываем полный путь до тэга, значение атрибута которого нужно получить. Имя атрибута указывается в четвертом параметре (string attribute). Если тэг <Flag> или его атрибут Value будут отсутствовать в описании объекта, то свойству Flag будет присвоено значение по умолчанию, переданное пятым параметром (T defaultValue).
Для работы с переменным значением, указанным в тэге <Something>, используется механизм привязки данных, который позволяет в любой момент выполнения логики объекта получать актуальное значение объекта с именем MyVariable.
Для получения привязки к источнику данных в тэге <Something> используем статический метод GetRequiredElementDataBinding.
Исполняемая часть
В методе LoadSettings из объекта settings, переданного в метод в качестве параметра, можно получить нужные значения, обратившись к соответствующим публичным свойствам:
// using WorkflowForms.DataBindings;
// public bool _flag;
// public IDataBinding _somethingDataBinding;
_flag = settings.Flag;
_somethingDataBinding = settings.SomethingDataBinding;
Если используется привязка данных, то на изменение привязанного объекта можно повесить Handler, который будет автоматически выполняться:
В конструкторе класса CustomControlSettings необходимо прописать код вида:
// using WorkflowForms.Conditions;
// public ICondition AnyCondition { get; private set; }
string name = XmlParser.GetRequiredAttributeValue<string>(
form, node, "AnyCondition", NAME_ATTRIBUTE);
AnyCondition = form.GetCondition(name);
Так как имя условия указывается в качестве значения атрибута обязательного тэга, то для его получения используется статический метод GetRequiredAttributeValue класса XmlParser. В третьем параметре (string path) указываем полный путь до тэга, значение атрибута которого нужно получить. Имя атрибута указывается в четвертом параметре (string attribute). В данном случае используется константа NAME_ATTRIBUTE, описанная в базовом классе. Если элемент или его атрибут отсутствует, будет возвращено исключение типа InvalidXmlException.
Если необходимо получить значение необязательного атрибута, то следует использовать метод GetAttributeValue. Этот метод в случае отсутствия элемента или его атрибута будет возвращать значение по умолчанию, переданное в параметрах метода.
Имея имя условия, объект типа ICondition можно получить через переменную form типа IWorkflowForm, используя метод GetCondition.
Исполняемая часть
В методе LoadSettings из объекта settings, переданного в метод в качестве параметра, можно получить объект типа ICondition, обратившись к публичному свойству AnyCondition:
// using WorkflowForms.Conditions;
// public ICondition _anyCondition;
_anyCondition = settings.AnyCondition;
При реализации бизнес-логики объекта можно получать значение условия, обращаясь к свойству Value:
if (_anyCondition.Value)
{
// ...
}
Команды
В примере рассмотрим xml-код объекта с обязательным тэгом <AnyCommand> для передачи имени команды, которую должен вызвать объект:
В конструкторе класса CustomControlSettings необходимо прописать код вида:
// public ICommand AnyCommand { get; set; }
string name = XmlParser.GetRequiredAttributeValue<string>(
form, node, "AnyCommand", NAME_ATTRIBUTE);
AnyCommand = form.GetCommand(name);
Для получения имени команды используется статический метод GetRequiredAttributeValue класса XmlParser. В третьем параметре (string path) указываем полный путь до тэга, значение атрибута которого нужно получить. Имя атрибута указывается в четвертом параметре (string attribute). В данном случае используется константа NAME_ATTRIBUTE, описанная в базовом классе. Если элемент или его атрибут отсутствует, будет возвращено исключение типа InvalidXmlException.
Если необходимо получить значение необязательного атрибута, то следует использовать метод GetAttributeValue. Этот метод в случае отсутствия элемента или его атрибута будет возвращать значение по умолчанию, переданное в параметрах метода.
Имея имя команды, объект типа ICommand можно получить через переменную form типа IWorkflowForm, используя метод GetCommand.
Исполняемая часть
В методе LoadSettings из объекта settings, переданного в метод в качестве параметра, можно получить объект типа ICommand, обратившись к публичному свойству AnyCommand:
В конструкторе класса CustomControlSettings необходимо прописать код вида:
// using WorkflowForms.Controls;
// using WorkflowForms.Exceptions;
// public DatabaseTable AnyDatabaseTable { get; private set; }
// public string AnyColumnName { get; private set; }
if (node["AnyDatabaseTable"] != null)
{
string name = XmlParser.GetRequiredAttributeValue<string>(
form, node, "AnyDatabaseTable", NAME_ATTRIBUTE);
AnyDatabaseTable = (DatabaseTable)form.GetControl(name);
AnyColumnName = XmlParser.GetRequiredAttributeValue<string>(
form, node, "AnyDatabaseTable/AnyColumn", NAME_ATTRIBUTE);
}
else
{
throw new InvalidXmlException($"Не задано имя таблицы в поле AnyDatabaseTable объекта \"{Name}\" типа {GetType().Name}.");
}
Исполняемая часть
В методе LoadSettings из объекта settings, переданного в метод в качестве параметра, можно получить объект типа DatabaseTable, обратившись к публичному свойству AnyDatabaseTable:
// public DatabaseTable _anyDatabaseTable;
// public string _anyColumnName;
_anyDatabaseTable = settings.AnyDatabaseTable;
_anyColumnName = settings.AnyColumnName;
var arr = (object[])_anyDatabaseTable.GetProperty(
"Column", new Dictionary<string, object>() { ["ColumnName"] = _anyColumnName });
Первым аргументом идет имя get-проперти, а затем словарь параметров, которые принимает проперти объекта.
Так же можно вызывать метод SetProperty, чтобы обращаться к set-проперти объекта:
_anyDatabaseTable.SetProperty("UpdateRow", new Dictionary<string, object>()
{
["RowIndex"] = 0,
["ColumnNames"] = new object[] { "ThirdColumn" },
["Values"] = new object[] { false }
});
Логирование
Для добавления записей в журнал событий Windows используется класс Logger со статическим методом Write.
Пример добавления сообщения уровня Error в методе ExecuteAsyncCommand:
Logger.Write(new LogEntry()
{
Form = Form,
Message = $"Ошибка выполнения команды \"{Name}\" типа \"{GetType().Name}\".\r\n{e}",
Severity = EventLogEntryType.Error
});
Обычно используются уровни событий: Error, Warning, Information.
Пример добавления сообщения уровня Information в конструкторе CustomCommandSettings при парсинге xml-кода команды:
Logger.Write(new LogEntry()
{
Form = Form,
Message = $"Парсинг команды \"{((AbstractCommand)parent).Name}\" типа \"{parent.GetType().Name}\".",
Severity = EventLogEntryType.Information
});
Last updated
Обращение к set-проперти происходит через команду :
Полный список методов по .
При реализации бизнес-логики у любого объекта можно вызывать метод GetProperty для обращения к get-проперти, реализованному у объекта. Например, можно обратиться к get-проперти у объекта DatabaseTable: