Огляд робочого потоку Windows Workflow. Частина 2, Різне, Програмування, статті

Читати частина 1


Користувальницькі дії


До сих пір ви мали справу з діями, визначеними всередині простору імен System.Workflow.Activities. У цьому розділі ви дізнаєтеся, як створювати власні дії і розширювати їх для задоволення користувальницьких потреб – як під час проектування, так і під час виконання.


Для початку ви створите дію WriteLineActivity, яке можна буде використовувати для виведення рядка тексту на консоль. Хоча цей приклад досить тривіальний, пізніше він буде розширений, щоб продемонструвати повний набір можливостей, доступних для користувача дій. При створенні користувальницьких дій ви можете просто сконструювати клас всередині проекту робочого потоку, однакопредпочтітельнее створити ваші власні дії всередині окремої збірки, оскільки середу часу проектування Visual Studio (І особливо проекти робочих потоків) завантажить дії з ваших збірок і зможе заблокувати складання, яку ви спробуєте відновити. З цієї причини ви повинні створити проект простий бібліотеки класів для конструювання всередині неї ваших користувальницьких дій.


Просте дію начебто WriteLineActivity буде породжене безпосередньо від базового класу Activity. У наступному коді показаний сконструйований клас дії та визначено властивість Message, що відображається при виклику методу Execute.





using System;
using System.ComponentModel;
using System.Workflow.ComponentModel;
namespace SimpleActivity
{
/// <summary>
/ / / Просте дію, яка під час виконання відображає повідомлення на консолі
/// </summary>
public class WriteLineActivity : Activity
{
/// <summary>
/ / / Виконання дії – відображення повідомлення на екрані
/// </summary>
/// <param name=”executionContext”></param>
/// <returns></returns>
protected override ActivityExecutionStatus Execute
( ActivityExecutionContext executionContext )
{
Console.WriteLine( Message );
return ActivityExecutionStatus.Closed;
}
/// <summary>
/ / / Методи get / set відображуваного повідомлення
/// </summary>
[ Description( “The message to display” ) ]
[ Category( “Parameters” ) ]
public string Message
{
get
{
return _message;
}
set
{
_message = value;
}
}
/// <summary>
/ / / Збереження відображуваного повідомлення
/// </summary>
private string _message;
}
}

Усередині методу Execute ви можете вивести повідомлення на консоль і потім повернути стан Closed, сповістивши виконуючу систему про те, що дія завершено.


Також ви можете визначити атрибути властивості Message, визначивши для нього опис та категорію; ці властивості будуть використані в таблиці властивість всередині Visual Studio , Як показано на рис. 41.8.


Рис. 41.8. Атрибути властивості Message


Якщо ви скомпілюєте це рішення, то зможете додати власні дії до панелі інструментів усередині Visual Studio, вибравши пункт з контекстного меню Choose Items (Виберіть елементи) в панелі інструментів і перейшовши в папку, де знаходиться збірка, що містить дії. Всі дії, що містяться в збірці, будуть додані на панель інструментів.


У такому вигляді дію цілком зручно, а проте є кілька способів зробити його ще більш дружнім до користувача. Як ви бачили на прикладі CodeActivity раніше в цій главі, існує кілька обов’язкових властивостей, які, не будучи визначені, призведуть до появи знаку помилки на поверхні конструктора. Щоб отримати той же ефект для вашого дії, вам потрібно побудувати клас, успадкований від ActivityValidator, і асоціювати клас з вашим дією.


Верифікація дій


Коли дія поміщено на поверхню конструктора, Workflow Designer шукає в цій дії атрибути, які визначають клас, який здійснює верифікацію даної дії. Для верифікації дії необхідно перевірити, чи встановлено властивість Message.


Користувальницький верифікатор передається примірнику дії, і тут ви можете визначити, які властивості є обов’язковими (якщо такі є), але не були ще визначені, а також додати помилку в ValidationErrorCollection, використовувану конструктором. Цю колекцію потім читає Workflow Designer, і будь-які знайдені в колекції помилки викличуть додавання до дії попереджувального знаку, і необов’язково зв’яжуть кожну помилку з властивістю, що вимагає вашої уваги.





using System;
using System.Workflow.ComponentModel.Compiler;
namespace SimpleActivity
{
public class WriteLineValidator : ActivityValidator
{
public override ValidationErrorCollection Validate
( ValidationManager manager, object obj )
{
if ( null == manager )
throw new ArgumentNullException( “manager” );
if ( null == obj )
throw new ArgumentNullException( “obj” );
ValidationErrorCollection errors = base.Validate( manager, obj );
/ / Привести до WriteLineActivity
WriteLineActivity act = obj as WriteLineActivity;
if ( null != act )
{
if ( null != act.Parent )
{
/ / Перевірити властивість Message
if ( string.IsNullOrEmpty( act.Message ) )
errors.Add( ValidationError.GetNotSetValidationError( “Message” ) );
}
}
return errors;
}
}
}

Метод Validate викликається конструктором, коли оновлюється будь-яка частина дії і також коли дія поміщається на поверхню конструктора. Конструктор викликає метод Validate і передає дію у вигляді нетипізований параметра obj. У цьому методі спочатку перевіряються передані йому аргументи, і потім викликається метод Validate базового класу, щоб отримати ValidationErrorCollection. Хоча це тут і не обов’язково, але якщо має місце спадкування від дії з безліччю властивостей, які також потребують верифікації, то виклик методу базового класу гарантує, що всі вони також будуть перевірені.


Наведіть переданий параметр до примірника WriteLineActivity і перевірте, чи є у дії батько. Ця перевірка необхідна, тому що функція Validate викликається під час компіляції дії (якщо ця дія всередині проекту робочого потоку або бібліотеки дій), а в цій точці ніякого батьківського дії не визначено. Без цієї перевірки ви не зможете в дійсності зібрати збірку, яка містить дію і його верифікатор. Цей додатковий крок не потрібний, якщо типом проекту є бібліотека класів.


Останній крок – перевірка установки властивості Message в значення, відмінне від порожнього рядка; цим займається статичний метод класу ValidationError, який конструює помилку, яка вказує на те, що властивість не було визначено.


Для підтримки вашого дії WriteLineActivity необхідний останній крок – додати до дії атрибут ActivityValidation, як показано в наступному фрагменті:





[ActivityValidator(typeof(WriteLineValidator))]
public class WriteLineActivity : Activity
{

}

Якщо ви скомпілюєте додаток і потім помістіть WriteLineActivity в робочий потік, то побачите помилку верифікації, як показано на рис. 41.9; клацнувши на символі помилки, ви перейдете до відповідного властивості в таблиці властивостей.


Якщо ви введете деякий текст у властивості Message, то помилка верифікації зникне, і ви зможете скомпілювати і запустити додаток.


Тепер, коли ви маєте готову верифікацію дії, наступне, що потрібно змінити – це поведінка візуалізації дії, щоб додати колір його заливки. Щоб зробити це, вам потрібно визначити два класи ActivityDesigner і ActivityDesignerTheme, як описано в наступному розділі.


Теми та конструктори


Екранне відображення дії виконується з використанням класу ActivityDesigner, при цьому також може використовуватися клас ActivityDesignerTheme.


Клас теми використовується для простого зміни поведінки відображення дії всередині Workflow Designer.





public class WriteLineTheme : ActivityDesignerTheme
{
/// <summary>
/ / / Конструювання теми і установка ряду значень за замовчуванням
/// </summary>
/// <param name=”theme”></param>
public WriteLineTheme(WorkflowTheme theme)
: base(theme)
{
this.BackColorStart = Color.Yellow;
this.BackColorEnd = Color.Orange;
this.BackgroundStyle = LinearGradientMode.ForwardDiagonal;
}
}

Тема успадкована від ActivityDesignerTheme, який має конструктор, який приймає аргумент WorkflowTheme. Усередині конструктора встановлюються початковий і кінцевий кольору дії, а потім визначається кисть лінійного градієнта, використовувана для фарбування фону.


Клас Designer служить для перевизначення поведінки візуалізації дії – в даному випадку, ніякого перевизначення не потрібно, так що наступного коду буде цілком достатньо:





[ActivityDesignerTheme(typeof(WriteLineTheme))]
public class WriteLineDesigner : ActivityDesigner
{}

Зверніть увагу, що тема асоційована з конструктором допомогою атрибута ActivityDesignerTheme.


Останній крок полягає в тому, щоб забезпечити дію атрибутом Designer:





[ActivityValidator(typeof(WriteLineValidator))]
[Designer(typeof(WriteLineDesigner))]
public class WriteLineActivity : Activity
{

}


Рис. 41.9. Помилка верифікації


Коли все це буде готово, дія стане відображатися в конструкторі так, як показано на рис. 41.10.


Рис. 41.10. Додавання конструктора і теми


З таким додаванням конструктора і теми наша дія тепер виглядає набагато більш професійно. Є багато інших властивостей, доступних в темі, таких як перо, що використовується для малювання рамки, колір рамки і її стиль.


Перевизначаючи метод OnPaint класу ActivityDesigner, ви можете отримати повний контроль над відображенням дії. Вам надається можливість самостійно повправлятися в цьому; створіть дію, зовсім несхоже на будь-які інші дії в панелі інструментів.


Ще одним корисним властивістю класу ActivityDesigner, яке можна перевизначити, є властивість Verbs. Воно дозволяє додати пункти в контекстне меню дії і використовується конструктором ParallelActivity для вставки пункту Add Branch (Додати гілка) в контекстне меню дії, а також в меню Workflow (Робочий потік). Також ви можете змінити список властивостей, що надаються дією, перевизначивши метод конструктора PreFilterProperties, за допомогою якого розміщуються параметри методу для CallExternalMethodActivity в таблиці властивостей. Якщо вам потрібно внести розширення подібного роду в конструктор, ви повинні запустити інструмент Lutz Roeder “s Reflector і завантажити збірки робочого потоку, щоб побачити, як Microsoft визначає деякі з цих розширених властивостей.


Тепер ваше дію майже готове, але вам ще потрібно визначити піктограму, використовувану при відображенні властивості, а також елемент панелі інструментів для асоціації з дією.


ActivityToolboxItem і піктограми


Щоб завершити розробку вашого користувацького дії, вам потрібно додати піктограму і необов’язково додати клас, успадкований від ActivityToolboxItem, який використовується при відображенні дії в панелі інструментів Visual Studio.


Щоб визначити піктограму для дії, створіть зображення розміром 16.16 пікселів і включите його в проект, після чого встановіть дію збірки для цього зображення в Embedded Resource. Це дозволить включити зображення в маніфест ресурсів збірки. Ви можете додати в проект папку на ім’я Resources, як показано на рис. 41.11.


Рис. 41.11. Додавання в проект папки на ім’я Resources


Додавши файл зображення і встановивши його дію збірки в Embedded Resource, ви можете забезпечити свою дію ще одним атрибутом, показаним в наступному фрагменті коду:





[ActivityValidator(typeof(WriteLineValidator))]
[Designer(typeof(WriteLineDesigner))]
[ToolboxBitmap(typeof(WriteLineActivity),”Resources.WriteLine.png”)]
public class WriteLineActivity : Activity
{

}

Атрибут ToolboxBitmap має ряд конструкторів, і один з них, використаний тут, приймає тип, визначений у збірці дії, а також ім’я ресурсу. Коли ви додаєте ресурс в папку, його ім’я формується з найменування простору імен складання та імені папки, в якій міститься зображення, так що повним кваліфікованим ім’ям нашого ресурсу буде CustomActivities.Resources. WriteLine.png. Конструктор, використовуваний для атрибута ToolboxBitmap, додає простір імен, в якому знаходиться тип-параметр, до рядка, що передається в якості другого аргументу, так що це все перетворюється у відповідний ресурс при завантаженні Visual Studio.


Останній клас, який буде потрібно створити, успадкований від ActivityToolboxItem. Цей клас використовується при завантаженні дії в панель інструментів Visual Studio . Типове використання цього класу полягає у зміні відображуваного імені дії в панелі інструментів – все вбудовані дії мають змінені імена, в яких з типу виключено слово “Activity”. З вашим класом ви можете зробити те ж саме, встановивши властивість DisplayName в “WriteLine”.





[Serializable]
public class WriteLineToolboxItem : ActivityToolboxItem
{
/// <summary>
/ / / Встановити псевдонім WriteLine, тобто видалити з нього рядок “Activity”
/// </summary>
/// <param name=”t”></param>
public WriteLineToolboxItem( Type t )
: base( t )
{
base.DisplayName = “WriteLine”;
}
/// <summary>
/ / / Необхідно для середовища часу проектування Visual Studio
/// </summary>
/// <param name=”info”></param>
/// <param name=”context”></param>
private WriteLineToolboxItem( SerializationInfo info, StreamingContext context )
{
this.Deserialize( info, context );
}
}

Клас успадкований від ActivityToolboxItem і перевизначає конструктор, щоб змінити псевдонім; крім того, він представляє конструктор для сериализации, використовуваний панеллю інструментів при завантаженні елемента в цю панель. Без конструктора ви отримаєте помилку при спробі додати дію в панель інструментів. Зверніть також увагу, що клас позначений як [Serializable].


Елемент панелі інструментів додається до дії за допомогою використання атрибута ToolboxItem, як показано нижче:





ActivityValidator(typeof(WriteLineValidator))]
[Designer(typeof(WriteLineDesigner))]
[ToolboxBitmap(typeof(WriteLineActivity),”Resources.WriteLine.png”)]
[ToolboxItem(typeof(WriteLineToolboxItem))]
public class WriteLineActivity : Activity
{

}

Після внесення всіх цих змін ви можете скомпілювати збірку і потім створити новий проект робочого потоку. Щоб додати дію до панелі інструментів, відкрийте робочий потік і потім відобразіть його контекстне меню, в якому виберіть пункт Choose Items.


Потім ви зможете знайти збірку, яка містить ваша дія, і коли додасте її до панелі інструментів, вона буде виглядати приблизно так, як показано на рис. 41.12. Як бачите, піктограма дещо менше, ніж потрібно, але досить близько до того.


Рис. 41.12. Додана піктограма


У наступному розділі ми ще повернемося до ActivityToolboxItem, коли будемо говорити про користувача складових діях, оскільки в цьому класі є ще кілька додаткових коштів, які потрібні тільки при додаванні складових дій на поверхню конструктора.


Користувальницькі складові дії


Існують два основні типи дій, успадковані від Activity, які можна трактувати як функції, що викликаються з робочого потоку. Дії ж, успадковані від CompositeActivity (такі як ParallelActivity, IfElseActivity і ListenActivity), є контейнерами для інших дій, і їх поведінка часу проектування істотно відрізняється від поведінки простих дій в тому сенсі, що вони представляють область в конструкторі, куди можна поміщати їх дочірні дії.


У цьому розділі ми створимо дію, яке можна назвати DaysOfWeekActivity. Ця дія може бути використано для виконання різних частин робочого потоку на основі поточної дати. Наприклад, вам може знадобитися ініціювати виконання робочого потоку по іншому маршруту, обробляючи замовлення, які надійшли у вихідні, на відміну від тих, що надійшли протягом робочого тижня. Розбираючи даний приклад, ви познайомитеся з рядом вдосконалених властивостей робочих потоків, і до кінця розділу будете володіти хорошим розумінням того, як можна розширити систему за рахунок ваших власних складових дій.


Для початку створимо користувальницьке дія, що має властивість, за замовчуванням встановлене в поточну дату / час, і дозволимо встановлювати цю властивість в інші значення, які можуть надходити від інших дій робочого потоку або у вигляді параметра, переданого робочому потоку при його виконанні. Це складене дія буде містити кілька гілок – усі вони визначаються користувачем.


Кожна з цих гілок міститиме перечислимого константу, що визначає, в який день (або дні) гілка повинна виконуватися. Нижче визначається дія і дві гілки:





DaysOfWeekActivity
SequenceActivty: Monday, Tuesday, Wednesday, Thursday, Friday
<other activites as appropriate>
SequenceActivity: Saturday, Sunday
<other activites as appropriate>

Для даного прикладу вам знадобиться перерахування, що визначає дні тижня, і воно повинно включати атрибут [Flags] (тому ви не можете використовувати вбудоване перерахування DayOfWeek, визначене в просторі імен System, оскільки воно не має атрибута [Flags]).





[Flags]
[Editor(typeof(FlagsEnumEditor), typeof(UITypeEditor))]
public enum WeekdayEnum : byte
{
None = 0x00,
Sunday = 0x01,
Monday = 0x02,
Tuesday = 0x04,
Wednesday = 0x08,
Thursday = 0x10,
Friday = 0x20,
Saturday = 0x40
}

Також включений спеціальний редактор для цього типу, який дозволить обирати перечислимого значення за допомогою прапорців.


Визначивши тип перерахування, ви можете в якості початкової заглушки призначити сама дія. Користувальницькі складові дії зазвичай успадковуються від класу CompositeActivity, який крім іншого, визначає властивість Activities, що представляє собою колекцію всіх складових його дій. Властивість DateTime, що використовується при виконанні дії, також присутня.





public class DaysOfWeekActivity : CompositeActivity
{
/// <summary>
/ / / Методи get / set властивості дня тижня
/// </summary>
[Browsable(true)]
[Category(“Behavior”)]
[Description(“Привязано до властивості DateTime, вкажіть дату і час або залиште порожнім для DateTime.Now “)]
[DefaultValue(typeof(DateTime),””)]
public DateTime Date
{
get { return (DateTime)base.GetValue(DaysOfWeekActivity.DateProperty); }
set { base.SetValue(DaysOfWeekActivity.DateProperty, value); }
}
/// <summary>
/ / / Реєстрація властивості DayOfWeek
/// </summary>
public static DependencyProperty DateProperty =
DependencyProperty.Register(“Date”, typeof(DateTime),
typeof(DaysOfWeekActivity));
}

Властивість Date надає звичайні засоби доступу get і set, а крім цього доданий ряд стандартних атрибутів, щоб вони коректно відображалися в панелі властивостей. Тому код виглядає трохи інакше, ніж у звичайного властивості. NET, оскільки ці кошти get і set не використовують стандартне поле для зберігання значень, а замість цього застосовується те, що носить назву DependencyProperty (властивість залежності).


Клас Activity (а, отже, і даний клас, оскільки він обов’язково породжений від Activity) успадкований від класу DependencyObject, і це визначає словник ключових значень DependencyProperty. Це непряме отримання і установка значень властивості застосовується WF для підтримки прив’язки, тобто зв’язування властивості однієї дії з властивістю іншого. Як приклад прийнято передавати параметри за кодом, іноді за значенням, іноді по посиланню. WF використовує прив’язку для зв’язування разом властивостей, тому в цьому прикладі ви можете мати властивість DateTime, визначене в робочому потоці, а дія може бути прив’язане до значення під час виконання. Пізніше в цій главі буде показанпрімер такої прив’язки.


Якщо ви зберете це дія, воно буде робити не так багато – насправді воно навіть не дозволить розміщати в нього дочірні дії, оскільки ви не визначили клас конструктора для цього дії.


Додавання класу Designer


Як ви бачили в прикладі з WriteLineActivity раніше в цій главі, кожна дія може мати асоційований з ним клас Designer, який використовується для зміни поведінки дії під час проектування. Ви бачили порожній Designer в WriteLineActivity, але для складеного дії доведеться перевизначити кілька методів, щоб додати можливість обробки спеціальних випадків.





public class DaysOfWeekDesigner : ParallelActivityDesigner
{
public override bool CanInsertActivities
( HitTestInfo insertLocation, ReadOnlyCollection<Activity> activities )
{
foreach ( Activity act in activities )
{
if ( !( act is SequenceActivity ) )
return false;
}
return base.CanInsertActivities( insertLocation, activitiesToInsert );
}
protected override CompositeActivity OnCreateNewBranch()
{
return new SequenceActivity();
}
}

Цей Designer успадковується від ParallalActivityDesigner, який надає у ваше розпорядження поведінку часу проектування при додаванні дочірніх дій. Вам доведеться перевизначити CanInsertActivities для повернення false, коли будь-яке з доданих дій не буде SequenceActivity. Якщо все додаються дії будуть відповідного типу, то ви зможете викликати метод базового класу, який виконає деякі додаткові перевірки над типами дій, допустимими всередині вашого користувацького складеного дії.


Також ви повинні перевизначити метод OnCreateNewBranch, що викликається, коли користувач вибирає пункт меню Add Branch. Designer асоціюється з дією допомогою застосування атрибута [Designer], як показано нижче:





[Designer(typeof(DaysOfWeekDesigner))]
public class DaysOfWeekActivity : CompositeActivity
{}

Поведінка часу проектування майже готове, а проте в цю дію також потрібно додати клас, успадкований від ActivityToolboxItem, який визначить те, що станеться, коли примірник цієї дії буде перетягувати з панелі інструментів. Поведінка за замовчуванням складається просто в конструюванні нового дії, тим не менш, в нашому прикладі вам знадобиться відразу створити дві гілки за замовчуванням. Нижче наведено клас елемента панелі інструментів, який зробить це.





[Serializable]
public class DaysOfWeekToolboxItem : ActivityToolboxItem
{
public DaysOfWeekToolboxItem( Type t )
: base( t )
{
this.DisplayName = “DaysOfWeek”;
}
private DaysOfWeekToolboxItem( SerializationInfo info, StreamingContext context )
{
this.Deserialize( info, context );
}
protected override IComponent[] CreateComponentsCore( IDesignerHost host )
{
CompositeActivity parent = new DaysOfWeekActivity();
parent.Activities.Add( new SequenceActivity() );
parent.Activities.Add( new SequenceActivity() );
return new IComponent[] { parent };
}
}

Як видно в цьому коді, псевдонім дії змінено, реалізований конструктор сериализации і перевизначений метод CreateComponentsCore.


Цей метод викликається в кінці операції перетягування, і саме тут ви конструюєте примірник DaysOfWeekActivity. У коді ви також будуєте два дочірніх послідовних дії, оскільки це відразу полегшить роботу користувачеві під час проектування. Те ж саме здійснюють деякі вбудовані дії. Так, наприклад, коли ви ставите дію IfElseActivity на поверхню конструктора, його клас панелі інструментів також додає відразу дві гілки. Подібна річ трапляється також, коли ви додаєте в свій робочий потік дію ParallelActivity.


Конструктор сериализации і атрибут [Serializable] необхідні всім класам, успадкованим від ActivityToolboxItem.


Останнє, що потрібно зробити – це асоціювати цей клас елемента панелі інструментів з дією:





[Designer(typeof(DaysOfWeekDesigner))]
[ToolboxItem(typeof(DaysOfWeekToolboxItem))]
public class DaysOfWeekActivity : CompositeActivity
{}

Тепер користувальницький інтерфейс вашого дії майже завершено, як видно на рис. 41.13.


Рис. 41.13. Додавання Designer до призначеного для користувача дії


Тепер потрібно визначити властивості кожного з послідовних дій, показаних на рис. 41.13, щоб користувач зміг визначити, в які дні яка з них повинна виконуватися. Є два способи зробити це в Windows Workflow: ви можете визначити підклас SequenceActivity і визначити все це в ньому, або ж можна скористатися іншим засобом властивостей залежності, званим Attached Properties (прикріплені властивості).


Ми пропонуємо застосувати другий метод, який виключить необхідність в підкласі, але замість цього дозволить ефективно розширити послідовне дію без потреби в його вихідному коді.

Читати частина 3

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*