Перейти к содержанию

Расширение для представлений, которое заменяет стандартный диалог настройки параметров фильтрации на заданный

Расширение для представлений, которое заменяет стандартный диалог настройки параметров фильтрации на заданный

Допустим, требуется написать расширение, которое заменяет стандартный диалог настройки параметров фильтрации представления Cars.

Для этого используются расширения IWorkplaceViewComponentExtension вместе с типом карточки диалог для отображения параметров.

В сборке Tessa.Extensions.Client создадим папку Views и добавим в неё необходимые объекты.

Создадим объект, который будет отображать диалоговое окно с параметрами фильтрации.

Tessa.Extensions.Client\Views\IAdvancedFilterViewDialogManager.cs

#nullable enable

using System.Threading; using System.Threading.Tasks; using Tessa.UI.Views.Parameters;

namespace Tessa.Extensions.Client.Views { /// <summary> /// Объект, предоставляющий методы для открытия модального диалога с параметрами фильтрации представления. /// </summary> public interface IAdvancedFilterViewDialogManager { /// <summary> /// Открывает диалог с параметрами фильтрации представления. /// </summary> /// <param name="descriptor"><inheritdoc cref="FilterViewDialogDescriptor" path="/summary"/></param> /// <param name="parameters"><inheritdoc cref="IViewParameters" path="/summary"/></param> /// <param name="cancellationToken">Объект, посредством которого можно отменить асинхронную задачу.</param> /// <returns>Асинхронная задача.</returns> Task OpenAsync( FilterViewDialogDescriptor descriptor, IViewParameters parameters, CancellationToken cancellationToken = default); } }

Tessa.Extensions.Client\Views\AdvancedFilterViewDialogManager.cs

#nullable enable

using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using Tessa.Cards; using Tessa.Platform.Formatting; using Tessa.UI; using Tessa.UI.Cards; using Tessa.UI.Views.Parameters; using Tessa.Views; using Tessa.Views.Metadata; using Tessa.Views.Parser;

namespace Tessa.Extensions.Client.Views { /// <inheritdoc cref="IAdvancedFilterViewDialogManager"/> public class AdvancedFilterViewDialogManager : IAdvancedFilterViewDialogManager { #region Fields

private readonly CreateDialogFormFuncAsync createDialogFormFuncAsync;

private readonly IAdvancedCardDialogManager advancedCardDialogManager;

#endregion

#region Constructors

/// <summary> /// Инициализирует новый экземпляр класса. /// </summary> /// <param name="createDialogFormFuncAsync"><inheritdoc cref="CreateDialogFormFuncAsync" path="/summary"/></param> /// <param name="advancedCardDialogManager"><inheritdoc cref="IAdvancedCardDialogManager" path="/summary"/></param> public AdvancedFilterViewDialogManager( CreateDialogFormFuncAsync createDialogFormFuncAsync, IAdvancedCardDialogManager advancedCardDialogManager) { this.createDialogFormFuncAsync = NotNullOrThrow(createDialogFormFuncAsync); this.advancedCardDialogManager = NotNullOrThrow(advancedCardDialogManager); }

#endregion

#region IAdvancedViewParametersDialogManager Members

/// <inheritdoc/> public virtual async Task OpenAsync( FilterViewDialogDescriptor descriptor, IViewParameters parameters, CancellationToken cancellationToken = default) { ThrowIfNull(descriptor); ThrowIfNull(parameters);

var dialogCardModel = await this.CreateDialogCardModelAsync( descriptor.DialogName, descriptor.FormAlias, cancellationToken);

await this.ShowDialogAsync( descriptor, parameters, dialogCardModel, cancellationToken); }

#endregion

#region Protected Methods

/// <summary> /// Создаёт модель карточки диалога, содержащего параметры представления. /// </summary> /// <param name="dialogName">Имя типа диалога.</param> /// <param name="formAlias">Алиас формы диалога или <see langword="null"/>, если требуется создать форму для первой вкладки типа диалога.</param> /// <param name="cancellationToken">Объект, посредством которого можно отменить асинхронную задачу.</param> /// <returns>Модель карточки диалога.</returns> /// <exception cref="InvalidOperationException">При создании модели карточки диалога произошла ошибка.</exception> protected async ValueTask<ICardModel> CreateDialogCardModelAsync( string dialogName, string? formAlias, CancellationToken cancellationToken = default) { (var form, var cardModel) = await this.createDialogFormFuncAsync( dialogName, formAlias, modifyModelAsync: static (newCardModel, _) => { newCardModel.Card.Version = 1; newCardModel.Flags |= CardModelFlags.IgnoreChanges;

return ValueTask.CompletedTask; }, cancellationToken: cancellationToken);

if (form is null || cardModel is null) { throw new InvalidOperationException( $"Failed to create dialog. Dialog name: \"{dialogName}\". Form alias: \"{FormattingHelper.FormatNullable(formAlias)}\"."); }

cardModel.MainForm = form;

return cardModel; }

/// <summary> /// Отображает диалог, содержащий параметры фильтрации представления. /// </summary> /// <param name="descriptor"><inheritdoc cref="FilterViewDialogDescriptor" path="/summary"/></param> /// <param name="parameters"><inheritdoc cref="IViewParameters" path="/summary"/></param> /// <param name="dialogCardModel">Модель карточки диалога.</param> /// <param name="cancellationToken">Объект, посредством которого можно отменить асинхронную задачу.</param> /// <returns>Асинхронная задача.</returns> protected virtual async Task ShowDialogAsync( FilterViewDialogDescriptor descriptor, IViewParameters parameters, ICardModel dialogCardModel, CancellationToken cancellationToken = default) { try { var isApplied = false; var context = await this.advancedCardDialogManager.ShowCardAsync( dialogCardModel, prepareEditorActionAsync: (editor, _) => { editor.StatusBarIsVisible = false;

FillFields( parameters, editor.CardModel.Card, descriptor.ParametersMapping);

editor.Toolbar.Actions.Clear(); editor.BottomToolbar.Actions.Clear(); editor.BottomDialogButtons.Clear();

editor.BottomDialogButtons.Add( new UIButton( "$UI_Common_OK", async _ => { var editor = UIContext.Current.CardEditor;

if (editor is null || editor.OperationInProgress) { return; }

isApplied = true;

await editor.CloseAsync( cancellationToken: CancellationToken.None); }, isDefault: true));

editor.BottomDialogButtons.Add( new UIButton( "$UI_Common_Cancel", async static _ => { var editor = UIContext.Current.CardEditor;

if (editor is null || editor.OperationInProgress) { return; }

await editor.CloseAsync( cancellationToken: CancellationToken.None); }, isCancel: true));

return ValueTask.FromResult(true); }, options: new OpenCardOptions { DisplayValue = "$Views_FilterDialog_Caption", WithDialogWallpaper = false, DialogWindowModifierAction = static window => { window.MaxWidth = 800; window.SizeToContent = SizeToContent.Height; } }, cancellationToken: cancellationToken);

if (isApplied) { using (parameters.SuspendChangesNotification()) { parameters.Clear(); CreateParameters( parameters, context.CardEditor.CardModel.Card, descriptor.ParametersMapping); } } } catch (NotSupportedException) { // Используется FakeUIHost, например, мы открыты в TessaAdmin, игнорируем ошибку. } }

/// <summary> /// Заполняет поля карточки, данными параметров запроса к представлению. /// </summary> /// <param name="parameters">Список параметров представления.</param> /// <param name="card">Карточка, содержащая параметры.</param> /// <param name="parameterMappings">Коллекция, содержащая информацию о связи параметров представления и полей карточки.</param> protected static void FillFields( IViewParameters parameters, Card card, IReadOnlyCollection<ParameterMapping> parameterMappings) { var sections = card.Sections;

foreach (var parameterMapping in parameterMappings) { FillField( parameters, sections, parameterMapping); } }

/// <summary> /// Заполняет поле <see cref="ParameterMapping.ValueFieldName"/> в секции <see cref="ParameterMapping.ValueSectionName"/>, содержащее параметр фильтрации представления <see cref="ParameterMapping.Alias"/>. /// </summary> /// <param name="parameters">Список параметров представления.</param> /// <param name="sections">Секции, содержащиеся в карточке диалога с параметрами представления.</param> /// <param name="parameterMapping"><inheritdoc cref="ParameterMapping" path="/summary"/></param> protected static void FillField( IViewParameters parameters, IReadOnlyDictionary<string, CardSection> sections, ParameterMapping parameterMapping) { var parameter = parameters.FirstOrDefault( i => ParserNames.IsEquals( i.Metadata.Alias, parameterMapping.Alias));

if (parameter is null) { return; }

var value = parameter .CriteriaValues .FirstOrDefault() ?.Values .FirstOrDefault();

if (value is null) { return; }

if (!sections.TryGetValue(parameterMapping.ValueSectionName, out var valueSection)) { return; }

valueSection.Fields[parameterMapping.ValueFieldName] = value.Value;

if (string.IsNullOrEmpty(parameterMapping.DisplayValueSectionName) || !sections.TryGetValue(parameterMapping.DisplayValueSectionName, out var displayValueSection)) { return; }

if (!string.IsNullOrEmpty(parameterMapping.DisplayValueFieldName)) { displayValueSection.Fields[parameterMapping.DisplayValueFieldName] = value.Text; } }

/// <summary> /// Создаёт параметры запроса к представлению. /// </summary> /// <param name="parameters">Список параметров представления.</param> /// <param name="card">Карточка, содержащая параметры.</param> /// <param name="parameterMappings">Коллекция, содержащая информацию о связи параметров представления и полей карточки.</param> protected static void CreateParameters( IViewParameters parameters, Card card, IReadOnlyCollection<ParameterMapping> parameterMappings) { var sections = card.Sections;

foreach (var parameterMapping in parameterMappings) { AddParameterIfValueNotEmpty( parameters, sections, parameterMapping); } }

/// <summary> /// Добавляет параметр в запрос к представлению, если поле <see cref="ParameterMapping.ValueFieldName"/> в секции <see cref="ParameterMapping.ValueSectionName"/> содержит данные. /// </summary> /// <param name="parameters">Список параметров представления.</param> /// <param name="sections">Секции, содержащиеся в карточке диалога с параметрами представления.</param> /// <param name="parameterMapping"><inheritdoc cref="ParameterMapping" path="/summary"/></param> protected static void AddParameterIfValueNotEmpty( IViewParameters parameters, IReadOnlyDictionary<string, CardSection> sections, ParameterMapping parameterMapping) { if (!sections.TryGetValue(parameterMapping.ValueSectionName, out var valueSection)) { return; }

if (!valueSection.Fields.TryGetValue(parameterMapping.ValueFieldName, out var valueField) || valueField is null) { return; }

var parameterMetadata = parameters.Metadata.FirstOrDefault( i => ParserNames.IsEquals( i.Alias, parameterMapping.Alias));

if (parameterMetadata is null) { return; }

string? displayValue;

if (!string.IsNullOrEmpty(parameterMapping.DisplayValueSectionName) && !string.IsNullOrEmpty(parameterMapping.DisplayValueFieldName) && sections.TryGetValue( parameterMapping.DisplayValueSectionName, out var displayValueSection) && displayValueSection.RawFields.TryGetValue( parameterMapping.DisplayValueFieldName, out var displayValueObj) && displayValueObj is not null) { displayValue = displayValueObj.ToString(); } else { displayValue = valueField.ToString(); }

var requestParameter = new RequestParameterBuilder() .WithMetadata(parameterMetadata) .AddCriteria( parameterMapping.CriteriaOperator ?? parameterMetadata.GetDefaultCriteria(), displayValue, valueField) .AsRequestParameter();

parameters.Add(requestParameter); }

#endregion } }

Создадим объекты, содержащие информацию о связи параметров представления и полей карточки диалога.

Tessa.Extensions.Client\Views\FilterViewDialogDescriptor.cs

#nullable enable

using System.Collections.Generic; using System.Threading; using Tessa.UI.Views.Parameters;

namespace Tessa.Extensions.Client.Views { /// <summary> /// Дескриптор, содержащий параметры настраиваемого диалога с параметрами фильтрации представления. /// </summary> public sealed class FilterViewDialogDescriptor { #region Constructors

/// <summary> /// Инициализирует новый экземпляр класса. /// </summary> /// <param name="dialogName"><inheritdoc cref="DialogName" path="/summary"/></param> /// <param name="parametersMapping"><inheritdoc cref="ParametersMapping" path="/summary"/></param> public FilterViewDialogDescriptor( string dialogName, IReadOnlyCollection<ParameterMapping> parametersMapping) { this.DialogName = NotWhiteSpaceOrThrow(dialogName); this.ParametersMapping = NotNullOrThrow(parametersMapping); }

#endregion

#region Properties

/// <summary> /// Имя типа диалога. /// </summary> public string DialogName { get; }

/// <summary> /// Алиас формы диалога или <see langword="null"/>, если требуется создать форму для первой вкладки типа диалога. /// </summary> public string? FormAlias { get; init; }

/// <summary> /// Коллекция, содержащая информацию о связи параметров представления и полей карточки. /// </summary> public IReadOnlyCollection<ParameterMapping> ParametersMapping { get; }

#endregion } }

Tessa.Extensions.Client\Views\ParameterMapping.cs

#nullable enable

using Tessa.Views.Metadata.Criteria;

namespace Tessa.Extensions.Client.Views { /// <summary> /// Предоставляет информацию о связи параметра представления и поля карточки. /// </summary> public sealed class ParameterMapping { #region Constructors

/// <summary> /// Инициализирует новый экземпляр <see cref="ParameterMapping"/>. /// </summary> /// <param name="alias"><inheritdoc cref="Alias" path="/summary"/></param> /// <param name="valueSectionName"><inheritdoc cref="ValueSectionName" path="/summary"/></param> /// <param name="valueFieldName"><inheritdoc cref="ValueFieldName" path="/summary"/></param> public ParameterMapping( string alias, string valueSectionName, string valueFieldName) { this.Alias = NotEmptyOrThrow(alias); this.ValueSectionName = NotEmptyOrThrow(valueSectionName); this.ValueFieldName = NotEmptyOrThrow(valueFieldName); }

#endregion

#region Properties

/// <summary> /// Алиас параметра представления. /// </summary> public string Alias { get; }

/// <summary> /// Имя секции, содержащей поле <see cref="ValueFieldName"/>. /// </summary> public string ValueSectionName { get; }

/// <summary> /// Поле, содержащее значение параметра. /// </summary> public string ValueFieldName { get; }

/// <summary> /// Имя секции, содержащей поле <see cref="DisplayValueFieldName"/>. Если не задано, то используется строковое представление значения параметра. /// </summary> public string? DisplayValueSectionName { get; init; }

/// <summary> /// Имя поля, содержащего отображаемое значение параметра. Если не задано, то используется строковое представление значения параметра. /// </summary> /// <remarks>Для корректной работы должно быть задано значение <see cref="DisplayValueSectionName"/>.</remarks> public string? DisplayValueFieldName { get; init; }

/// <summary> /// Условный оператор. Если не задан, то используется оператор по умолчанию. /// </summary> public CriteriaOperator? CriteriaOperator { get; init; }

#endregion } }

Создадим реестр объектов FilterViewDialogDescriptor.

Tessa.Extensions.Client\Views\IFilterViewDialogDescriptorRegistry.cs

#nullable enable

using System;

namespace Tessa.Extensions.Client.Views { /// <summary> /// Объект, предоставляющий <see cref="FilterViewDialogDescriptor"/>. /// </summary> public interface IFilterViewDialogDescriptorRegistry { /// <summary> /// Регистрирует <paramref name="descriptor"/> для указанного <paramref name="compositionID"/>. Метод замещает предыдущую регистрацию при её наличии. /// </summary> /// <param name="compositionID">Уникальный идентификатор элемента рабочего места, для которого должно использоваться переопределение диалога с параметрами представления.<para/> /// /// Значение расположено в TessaAdmin на вкладке "Рабочие места" в окне "Свойства" в поле "Id". К данному элементу дерева должно быть применено расширение <see cref="FilterViewDialogOverrideWorkplaceComponentExtension"/>.</param> /// <param name="descriptor"><inheritdoc cref="FilterViewDialogDescriptor" path="/summary"/></param> void Register( Guid compositionID, FilterViewDialogDescriptor descriptor);

/// <summary> /// Возвращает <see cref="FilterViewDialogDescriptor"/> для заданного <paramref name="compositionID"/>. /// </summary> /// <param name="compositionID">Уникальный идентификатор элемента рабочего места, для которого должно использоваться переопределение диалога с параметрами представления.</param> /// <returns>Объект <see cref="FilterViewDialogDescriptor"/> или значение <see langword="null"/>, если не найден объект соответствующий <paramref name="compositionID"/>.</returns> FilterViewDialogDescriptor? TryGet( Guid compositionID); } }

Tessa.Extensions.Client\Views\FilterViewDialogDescriptorRegistry.cs

#nullable enable

using System; using System.Collections.Concurrent;

namespace Tessa.Extensions.Client.Views { /// <inheritdoc cref="IFilterViewDialogDescriptorRegistry"/> public sealed class FilterViewDialogDescriptorRegistry : IFilterViewDialogDescriptorRegistry { #region Fields

private readonly ConcurrentDictionary<Guid, FilterViewDialogDescriptor> descriptors = new();

#endregion

#region IFilterViewDialogDescriptorRegistry Members

/// <inheritdoc/> public void Register( Guid compositionID, FilterViewDialogDescriptor descriptor) { ThrowIfNull(descriptor);

this.descriptors.AddOrUpdate( compositionID, descriptor, (_, _) => descriptor); }

/// <inheritdoc/> public FilterViewDialogDescriptor? TryGet( Guid compositionID) { this.descriptors.TryGetValue(compositionID, out var descriptor); return descriptor; }

#endregion } }

Укажем соответствия параметров и полей карточки диалога для представления Cars.

Tessa.Extensions.Client\Views\FilterViewDialogDescriptors.cs

using System.Collections.Generic; using System.Collections.ObjectModel; using Tessa.Views.Metadata; using Tessa.Views.Metadata.Criteria;

namespace Tessa.Extensions.Client.Views { /// <summary> /// Предоставляет объекты типа <see cref="FilterViewDialogDescriptor"/>. /// </summary> public static class FilterViewDialogDescriptors { #region Constants And Static Fields

/// <summary> /// Дескриптор, описывающий специальный диалог с параметрами фильтрации представления РМ Администратор/Тестирование/Автомобили. /// </summary> public static readonly FilterViewDialogDescriptor Cars = new FilterViewDialogDescriptor( "CarViewParameters", new ReadOnlyCollection<ParameterMapping>( new List<ParameterMapping>() { new ParameterMapping( "CarName", "Parameters", "Name"), new ParameterMapping( "CarMaxSpeed", "Parameters", "MaxSpeed") { CriteriaOperator = CriteriaHelper.GetCriteria(CriteriaOperatorConst.Equality), }, new ParameterMapping( "Driver", "Parameters", "DriverID") { DisplayValueSectionName = "Parameters", DisplayValueFieldName = "DriverName", }, new ParameterMapping( "CarReleaseDateFrom", "Parameters", "ReleaseDateFrom"), new ParameterMapping( "CarReleaseDateTo", "Parameters", "ReleaseDateTo"), }));

#endregion } }

Создадим расширение IWorkplaceViewComponentExtension для замены диалога с параметрами фильтрации представления в рабочем месте.

Tessa.Extensions.Client\Views\FilterViewDialogOverrideWorkplaceComponentExtension.cs

#nullable enable

using System; using System.Collections.Generic; using System.Threading; using System.Windows; using Tessa.Platform.Runtime; using Tessa.UI; using Tessa.UI.Views; using Tessa.UI.Views.Content; using Tessa.UI.Views.MessagingServices.Commands; using Tessa.UI.Views.MessagingServices.Queries;

namespace Tessa.Extensions.Client.Views { /// <summary> /// Расширение, переопределяющее диалог фильтрации представления. /// </summary> /// <remarks> /// У расширения есть конфигуратор <see cref="FilterViewDialogOverrideWorkplaceComponentExtensionConfigurator"/>. /// </remarks> public class FilterViewDialogOverrideWorkplaceComponentExtension : IWorkplaceViewComponentExtension { #region Fields

private readonly ISession session;

private readonly IAdvancedFilterViewDialogManager advancedFilterViewDialogManager;

private readonly IFilterViewDialogDescriptorRegistry filterViewDialogDescriptorRegistry;

#endregion

#region Constructors

/// <summary> /// Инициализирует новый экземпляр класса <see cref="FilterViewDialogOverrideWorkplaceComponentExtension"/>. /// </summary> /// <param name="session"><inheritdoc cref="ISession" path="/summary"/></param> /// <param name="advancedFilterViewDialogManager"><inheritdoc cref="IAdvancedFilterViewDialogManager" path="/summary"/></param> /// <param name="filterViewDialogDescriptorRegistry"><inheritdoc cref="IFilterViewDialogDescriptorRegistry" path="/summary"/></param> public FilterViewDialogOverrideWorkplaceComponentExtension( ISession session, IAdvancedFilterViewDialogManager advancedFilterViewDialogManager, IFilterViewDialogDescriptorRegistry filterViewDialogDescriptorRegistry) { this.session = NotNullOrThrow(session); this.advancedFilterViewDialogManager = NotNullOrThrow(advancedFilterViewDialogManager); this.filterViewDialogDescriptorRegistry = NotNullOrThrow(filterViewDialogDescriptorRegistry); }

#endregion

#region IWorkplaceViewComponentExtension Members

/// <inheritdoc/> public void Initialize( IWorkplaceViewComponent model) { // В TessaAdmin специально созданный диалог не будет работать из-за отсутствия рабочей реализации IUIHost (используется FakeUIHost). // Для тестирования представление в TessaAdmin в режиме "Просмотр", не переопределяем делегат // отвечающий за создание кнопки отображения диалога настройки параметров фильтрации. if (this.session.ApplicationID == ApplicationIdentifiers.TessaAdmin) { return; }

if (this.filterViewDialogDescriptorRegistry.TryGet(model.Id) is not { } descriptor) { return; }

// Замена стандартного диалога настройки параметров фильтрации, вызываемого при нажатии на соответствующую кнопку расположенную над представлением, на заданный. model.ContentFactories[StandardViewComponentContentItemFactory.FilterButton] = c => this.CreateFilterButtonContent( descriptor, c);

// Замена стандартного диалога настройки параметров фильтрации, вызываемого при нажатии на соответствующую кнопку расположенную над представлением при применённом фильтре (отображается при наведении указателя на область текущих параметров фильтрации), на заданный. model.ContentFactories[StandardViewComponentContentItemFactory.FilterTextView] = c => this.CreateFilterTextViewContent( descriptor, c); }

/// <inheritdoc/> public void Initialized( IWorkplaceViewComponent model) { }

/// <inheritdoc/> public void Clone( IWorkplaceViewComponent source, IWorkplaceViewComponent cloned, ICloneableContext context) { }

#endregion

#region Private Methods

/// <summary> /// Создает и инициализирует экземпляр <see cref="FilterButtonContent" />. /// </summary> /// <param name="descriptor"><inheritdoc cref="FilterViewDialogDescriptor" path="/summary"/></param> /// <param name="component"><inheritdoc cref="IWorkplaceViewComponent" path="/summary"/></param> /// <param name="placeAreas">Список областей вывода элемента.</param> /// <param name="dataTemplateFunc">Обработка выдачи шаблона в зависимости от области расположения <paramref name="placeAreas" /> элемента.</param> /// <param name="ordering">Порядок вывода элемента.</param> /// <returns>Экземпляр кнопки фильтрации.</returns> private FilterButtonContent CreateFilterButtonContent( FilterViewDialogDescriptor descriptor, IWorkplaceViewComponent component, IEnumerable<IPlaceArea>? placeAreas = null, Func<IPlaceArea, DataTemplate>? dataTemplateFunc = null, int ordering = PlacementOrdering.BeforeAll) { var parameters = component.Parameters;

var command = new DelegateCommand( async _ => await this.advancedFilterViewDialogManager.OpenAsync( descriptor, parameters, CancellationToken.None), _ => component.SubmitQuery( new CanFilterQuery( component.Parameters.Metadata)));

return new FilterButtonContent( command, parameters, placeAreas, dataTemplateFunc, ordering); }

/// <summary> /// Создает и инициализирует экземпляр <see cref="FilterTextViewContent" />. /// </summary> /// <param name="descriptor"><inheritdoc cref="FilterViewDialogDescriptor" path="/summary"/></param> /// <param name="component"><inheritdoc cref="IWorkplaceViewComponent" path="/summary"/></param> /// <param name="parametersConverter"><inheritdoc cref="IParameterContentConverter" path="/summary"/></param> /// <param name="placeAreas">Список областей вывода элемента.</param> /// <param name="dataTemplateFunc">Обработка выдачи шаблона в зависимости от области расположения <paramref name="placeAreas" /> элемента.</param> /// <param name="ordering">Порядок вывода элемента.</param> /// <returns>Экземпляр кнопки фильтрации.</returns> private FilterTextViewContent CreateFilterTextViewContent( FilterViewDialogDescriptor descriptor, IWorkplaceViewComponent component, IParameterContentConverter? parametersConverter = null, IEnumerable<IPlaceArea>? placeAreas = null, Func<IPlaceArea, DataTemplate>? dataTemplateFunc = null, int ordering = PlacementOrdering.BeforeAll) { var parameters = component.Parameters;

var filteringCommand = new DelegateCommand( async _ => await this.advancedFilterViewDialogManager.OpenAsync( descriptor, parameters, CancellationToken.None), _ => component.SubmitQuery(new CanFilterQuery(parameters.Metadata)));

var clearFilterCommand = new DelegateCommand( async _ => await component.SubmitCommandAsync(new ClearParametersCommand(parameters)), _ => component.SubmitQuery(new CanClearParametersQuery(parameters)));

return new FilterTextViewContent( filteringCommand, clearFilterCommand, parameters, parametersConverter ?? DefaultParameterContentConverter.Instance, placeAreas, dataTemplateFunc, ordering); }

#endregion } }

Также необходимо создать конфигуратор расширения:

Tessa.Extensions.Client\Views\FilterViewDialogOverrideWorkplaceComponentExtensionConfigurator.cs

using Tessa.UI.Views.Extensions;

namespace Tessa.Extensions.Client.Views { /// <summary> /// Конфигуратор расширения <see cref="FilterViewDialogOverrideWorkplaceComponentExtension"/>. /// </summary> public sealed class FilterViewDialogOverrideWorkplaceComponentExtensionConfigurator : ExtensionSettingsConfiguratorBase { #region Constants And Static Fields

private const string DescriptionLocalization = "$FilterViewDialogOverrideWorkplaceComponentExtension_Description";

private const string NameLocalization = null;

#endregion

#region Constructors

/// <summary> /// Инициализирует новый экземпляр класса <see cref="FilterViewDialogOverrideWorkplaceComponentExtensionConfigurator"/> /// </summary> public FilterViewDialogOverrideWorkplaceComponentExtensionConfigurator() : base( ViewExtensionConfiguratorType.None, NameLocalization, DescriptionLocalization) { }

#endregion } }

Зарегистрируем объекты в Unity-контейнере.

Tessa.Extensions.Client\Views\Registrator.cs

using Tessa.Platform; using Tessa.UI.Views.Extensions; using Unity;

namespace Tessa.Extensions.Client.Views { [Registrator] public sealed class Registrator : RegistratorBase { public override void RegisterUnity() { this.UnityContainer .RegisterType<IAdvancedFilterViewDialogManager, AdvancedFilterViewDialogManager>(new ContainerControlledLifetimeManager()) .RegisterType<IFilterViewDialogDescriptorRegistry, FilterViewDialogDescriptorRegistry>(new ContainerControlledLifetimeManager()) ; }

public override void FinalizeRegistration() { // типы могут быть не зарегистрированы в тестах или плагинах Chronos

this.UnityContainer .TryResolve<IWorkplaceExtensionRegistry>() ? .Register(typeof(FilterViewDialogOverrideWorkplaceComponentExtension)) .RegisterConfiguratorType( typeof(FilterViewDialogOverrideWorkplaceComponentExtension), type => this.UnityContainer.Resolve<FilterViewDialogOverrideWorkplaceComponentExtensionConfigurator>()) ;

this.UnityContainer .TryResolve<IFilterViewDialogDescriptorRegistry>() ? .Register( new Guid(0x23d03a10, 0xe610, 0x442d, 0x9e, 0x8d, 0x71, 0x4f, 0xf0, 0x58, 0x29, 0xf4), FilterViewDialogDescriptors.Cars) ; } } }

Для переопределения диалога, открываемого в рабочем месте по тайлу Левое меню -> Другие -> Фильтр ([Ctrl]+[F]) создадим расширение на тайл.

В сборке Tessa.Extensions.Client создадим папку Tiles и добавим в неё необходимые объекты.

Tessa.Extensions.Client\Tiles\FilterViewDialogOverrideTileExtension.cs

#nullable enable

using System; using System.Threading; using System.Threading.Tasks; using Tessa.Extensions.Client.Views; using Tessa.Platform; using Tessa.UI; using Tessa.UI.Tiles.Extensions; using Tessa.UI.Views;

namespace Tessa.Extensions.Client.Tiles { /// <summary> /// Расширение, переопределяющее действие тайла <see cref="TileNames.FilterView"/> для замены диалога фильтрации представления. /// </summary> public sealed class FilterViewDialogOverrideTileExtension : TileExtension { #region Fields

private readonly IAdvancedFilterViewDialogManager advancedFilterViewDialogManager;

private readonly IFilterViewDialogDescriptorRegistry filterViewDialogDescriptorRegistry;

#endregion

#region Constructors

/// <summary> /// Инициализирует новый экземпляр класса. /// </summary> /// <param name="advancedFilterViewDialogManager"><inheritdoc cref="IAdvancedFilterViewDialogManager" path="/summary"/></param> /// <param name="filterViewDialogDescriptorRegistry"><inheritdoc cref="IFilterViewDialogDescriptorRegistry" path="/summary"/></param> public FilterViewDialogOverrideTileExtension( IAdvancedFilterViewDialogManager advancedFilterViewDialogManager, IFilterViewDialogDescriptorRegistry filterViewDialogDescriptorRegistry) { this.advancedFilterViewDialogManager = NotNullOrThrow(advancedFilterViewDialogManager); this.filterViewDialogDescriptorRegistry = NotNullOrThrow(filterViewDialogDescriptorRegistry); }

#endregion

#region Base Overrides

/// <inheritdoc /> public override Task InitializingLocal(ITileLocalExtensionContext context) { var otherTile = context.Workspace.LeftPanel.Tiles .TryGet(TileNames.ViewsOther);

if (otherTile is null) { return Task.CompletedTask; }

var otherTiles = otherTile.Tiles;

var filterViewTile = otherTiles .TryGet(TileNames.FilterView);

if (filterViewTile is null) { return Task.CompletedTask; }

var filterViewTileOriginalCommand = filterViewTile.Command; filterViewTile.Command = new DelegateCommand(async o => await ExecuteInViewContextAsync( async viewContext => { if (this.filterViewDialogDescriptorRegistry.TryGet(viewContext.Id) is { } descriptor) { await this.advancedFilterViewDialogManager.OpenAsync( descriptor, viewContext.Parameters, CancellationToken.None); } else { filterViewTileOriginalCommand.Execute(o); } }, filterViewTileOriginalCommand.CanExecute));

return Task.CompletedTask; }

#endregion

#region Private Methods

/// <summary> /// Вызывает действие <paramref name="executeAsync"/> в текущем контексте <see cref="IViewContext"/> если доступен контекст, если определена <paramref name="canExecute"/> осуществляется проверка возможности выполнения операции. /// </summary> /// <param name="executeAsync">Делегат вызываемого в контексте действие.</param> /// <param name="canExecute">Делегат проверки возможности вызова действия.</param> private static async Task ExecuteInViewContextAsync( Func<IViewContext, Task> executeAsync, Func<IViewContext, bool>? canExecute = null) { var viewContext = UIContext.Current.ViewContext; if (viewContext is null) { return; }

if (canExecute is not null && !canExecute(viewContext)) { return; }

await executeAsync(viewContext); }

#endregion } }

Зарегистрируем расширение на тайл.

Tessa.Extensions.Client\Tiles\Registrator.cs

using Tessa.UI.Tiles.Extensions; using Unity; using Unity.Lifetime;

namespace Tessa.Extensions.Client.Tiles { /// <summary> /// Регистрация расширений, управляющих плитками. /// </summary> [Registrator] public sealed class Registrator : RegistratorBase { public override void RegisterUnity() { this.UnityContainer .RegisterType<FilterViewDialogOverrideTileExtension>(new ContainerControlledLifetimeManager()) ; }

public override void RegisterExtensions( IExtensionContainer extensionContainer) { // Local extensionContainer .RegisterExtension<ITileLocalExtension, FilterViewDialogOverrideTileExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform, 1) .WithUnity(this.UnityContainer)) ; } } }

Для отображения параметров фильтрации воспользуемся виртуальной карточкой имеющей тип диалог. Виртуальная схема состоит из одной строковой секции Parameters. Она содержит колонки соответствующие параметрам представления Cars.

Размещаем на форме требуемые элементы управления:

Скомпилируйте расширения Tessa.Extensions.Client, скопируйте их в папку TessaAdmin и перезапустите его. В рабочем месте добавьте представление в дерево, и укажите расширение FilterViewDialogOverrideWorkplaceComponentExtension.

Теперь вы можете скопировать расширения в папку TessaClient и опубликовать приложение. Если вы корректно настроили роли для представления Cars, то пользователи получат обновлённую версию приложения вместе с написанными расширениями.

В результате при нажатии на кнопку “Фильтрация данных” будет открыт специально созданный диалог:

Note

В TessaAdmin при нажатии на кнопку “Фильтрация данных” будет открыт стандартный диалог. Это связано с тем, что в нём недоступна рабочая реализация интерфейса IUIHost (используется FakeUIHost), необходимая для работы объекта AdvancedCardDialogManager, предоставляющего методы для открытий карточки в модальном диалоге.

Note

Описанное расширение доступно в типовом решении в desktop- и web-клиентах: FilterViewDialogOverrideWorkplaceComponentExtension.

Используемый тип карточки диалога: CarViewParameters.

Back to top