Отображение диалога при завершении задания
Отображение диалога при завершении задания¶
Допустим, что сотрудник может иметь одновременно несколько должностей (например, в разных подразделениях). При завершении задания типового процесса согласования требуется спросить у пользователя, от имени какой должности он хочет завершить задание. Визуально диалог выбора должности может выглядеть как диалог выбора категории файла, только вместо категории выбирается название должности (строка), которая записывается в пакет задания CardTask.Info
и обрабатывается на сервере (например, дописывается к имени сотрудника, завершившего задание). При закрытии окна диалога без выбора должности производится отмена завершения задания.
Диалог выбора должности разделим по паттерну MVVM на составляющие:
-
модель Model - список должностей в виде массива строк;
-
модель представления View Model - список должностей с указанием выбранной должности и команды ICommand для выбора должности из UI;
-
представление View - UserControl, содержащий UI для диалога.
Создадим модель представления PositionDialogViewModel
, разместим класс в папке Tessa.Extensions.Client/ViewModels
.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Tessa.UI;
using Tessa.UI.Controls;
public sealed class PositionDialogViewModel : ViewModel<EmptyModel>
{
public PositionDialogViewModel(IEnumerable<string> positions)
{
this.positions = new ObservableCollection<string>(positions);
this.elementClickCommand = new DelegateCommand(this.ElementClickAction);
}
private readonly ObservableCollection<string> positions;
public ObservableCollection<string> Positions
{
get { return this.positions; }
}
private string selectedPosition;
public string SelectedPosition
{
get { return this.selectedPosition; }
set
{
this.selectedPosition = value;
this.OnPropertyChanged("SelectedPosition");
}
}
private readonly ICommand elementClickCommand;
public ICommand ElementClickCommand
{
get { return this.elementClickCommand; }
}
private void ElementClickAction(object obj)
{
this.SelectedPosition = (string)((AttachedEventParameter)obj).CommandParameter;
}
}
Визуальное отображение диалога разместим в классе PositionDialogView
типа UserControl
, в папке Tessa.Extensions.Client/Views
.
<!-- PositionDialogView.xaml: UserControl -->
<!-- xmlns:tuic="clr-namespace:Tessa.UI.Controls;assembly=Tessa.UI" -->
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Positions, Mode=OneTime}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border tuic:AttachedCommands.ClickCommand="{Binding DataContext.ElementClickCommand,
RelativeSource=
{RelativeSource AncestorType=ItemsControl},
Mode=OneTime}"
tuic:AttachedCommands.CommandParameter="{Binding Mode=OneTime}"
BorderBrush="{StaticResource FileButtonBrush}"
MouseLeftButtonDown="ButtonOk_OnClick">
<Border.Background>
<SolidColorBrush Color="{StaticResource FileNormalBorderColor}" />
</Border.Background>
<Border.Triggers>
<EventTrigger RoutedEvent="Border.MouseEnter">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="0:0:0.2"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="{StaticResource FileMouseOverBorderColor}" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Border.MouseLeave">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="0:0:0.2"
FillBehavior="Stop"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="{StaticResource FileNormalBorderColor}" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<TextBlock HorizontalAlignment="Center"
Padding="5"
Text="{Binding Converter={StaticResource LocalizableStringConverter},
Mode=OneTime}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
В code behind PositionDialogView.xaml.cs
определим обработчик ButtonOk_OnClick
, обрабатывающий закрытие диалога по клику на одной из должностей.
public partial class PositionDialogView
{
public PositionDialogView()
{
this.InitializeComponent();
}
private void ButtonOk_OnClick(object sender, RoutedEventArgs e)
{
Window window = Window.GetWindow(this);
if (window != null)
{
window.DialogResult = true;
window.Close();
}
}
}
Связь между PositionDialogView
и PositionDialogViewModel
опишем в виде DataTemplate
, который расположим в словаре ресурсов Tessa.Extensions.Client/Themes/Generic.xaml
.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:Tessa.Extensions.Client.Views"
xmlns:viewModels="clr-namespace:Tessa.Extensions.Client.ViewModels">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="pack://application:,,,/Tessa.Extensions.Default.Client;component/Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type viewModels:PositionDialogViewModel}">
<views:PositionDialogView />
</DataTemplate>
</ResourceDictionary>
При завершении задания согласования с вариантами “Согласовать” или “Не согласовать” будет срабатывать клиентское расширение на завершение задания CardStoreTaskExtension
, которое может отобразить диалог в потоке UI, а затем, в зависимости от результатов диалога, либо сохранить выбранную должность для передачи на сервер, либо отменить сохранение и отобразить пользователю сообщение.
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Tessa.Cards.Extensions;
using Tessa.Extensions.Default.Shared;
using Tessa.Platform;
using Tessa.Platform.Validation;
using Tessa.Themes;
using Tessa.UI;
using Tessa.UI.Windows;
public sealed class SelectApprovalPositionExtension : CardStoreTaskExtension
{
public override void StoreTaskBeforeRequest(ICardStoreTaskExtensionContext context)
{
// отслеживаем завершение заданий с вариантами "Согласовать" и "Не согласовать"
if (context.IsCompletion
&& context.TaskType.ID == DefaultTaskTypes.KrApproveTypeID
&& (context.CompletionOption.ID == DefaultCompletionOptions.Approve
|| context.CompletionOption.ID == DefaultCompletionOptions.Disapprove))
{
// пусть должность - это некоторая строка, в т.ч. строка локализации
// получаем список должностей для текущего пользователя
// (можем получить его с сервера, например, обратившись к представлению)
string[] positions =
new[]
{
"Заместитель генерального директора",
"Руководитель отдела разработки",
"$Workplaces_Administrator"
}
.OrderByLocalized(x => x)
.ToArray();
// сохранение выполняется асинхронно, поэтому обращаемся за синхронным выполнением в потоке UI
DispatcherHelper.InvokeInUI(() =>
{
// создаём модель представления ViewModel с указанием модели - списка должностей
var vm = new PositionDialogViewModel(positions);
// создаём стилизованное окно, контентом которого является модель представления vm
var window = new TessaWindow
{
Content = vm,
Title = "Выберите должность",
Background = ThemeManager.Current.Theme.CreateSolidColorBrush(ThemeProperty.FileTypesBackground),
WindowStartupLocation = WindowStartupLocation.CenterOwner,
MinWidth = 360.0,
SizeToContent = SizeToContent.WidthAndHeight,
CanMinimize = false,
CanResize = false,
CloseKey = new KeyGesture(Key.Escape),
};
window.ResolveOwnerAsActiveWindow();
window.RestrictSizeToWorkingArea(restrictWidth: false);
// отображаем диалог выбора должности
bool? dialogResult = window.ShowDialog();
// если должность не выбрана, то пишем ошибку валидации,
// которая прерывает процесс сохранения и отображает пользователю сообщение
if (dialogResult != true || vm.SelectedPosition == null)
{
ValidationSequence
.Begin(context.ValidationResult)
.SetObjectName(this)
.ErrorText("Завершение задания отменено.")
.End();
return;
}
// устанавливаем выбранную должно в Info объекта задания, которое можно будет обработать на сервере
string position = vm.SelectedPosition;
context.Task.Info["Position"] = position;
});
}
}
}
Регистрация расширения выполняется со стороны клиента стандартным образом.
extensionContainer.RegisterExtension<ICardStoreTaskExtension, SelectApprovalPositionExtension>(x => x
.WithOrder(ExtensionStage.AfterPlatform, 1)
.WithSingleton<SelectApprovalPositionExtension>())
;