Создание описателя элемента управления и расширения программы Конструктор Web-разметок
Прежде всего определим перечень настроек разрабатываемого элемента управления, которые должны быть доступны пользователю программы Конструктор Web-разметок:
-
Стандартный набор свойств: текст метки, подсказка, обязательные и пр..
-
Свойства для выбора источника данных.
-
Специальное свойство для ограничения области выбора дел.
-
Стандартный набор событий (при наведении/отведении курсора и при смене данных).
Описатель элемента управления
Для описания данного элемента управления сформируем текстовый описатель со следующим содержимым:
RefCategoriesDesignerExtension\xml\RefCasesControlDescription.xml
<?xml version="1.0" encoding="utf-8" ?>
<Controls>
<Control Name="RefCases" ControlGroupResourceKey="ControlGroup_Directories" ResourceKey="Control_RefCases">
<Properties>
(1)
<Property Type="Name" /> (2)
<Property Type="PlaceHolder" /> (3)
<Property Type="Tip" /> (4)
<Property Type="LabelText" />
<Property Type="ShowEmptyLabel" />
<Property Type="Visibility" />
<Property Type="StandardCssClass" DefaultValue="ref-cases" />
<Property Type="CustomCssClasses" />
<Property Type="TabStop" DefaultValue="True" />
<Property Type="Required" />
(5)
<Property Type="ExtendedDataSource" /> (6)
<Property Type="DataSource" /> (7)
<Property Type="DataField" Editor="RefCasesFieldMetadataEditor" />
<Property Type="Binding" BindingConverter="RefCasesConverter" /> (8)
<Property Type="EditOperation" /> (9)
<Property Name="RootSection" ResourceKey="Control_RefCases_Section"
Category="BehaviorCategory" DataType="System.Guid"
Editor="RefCasesSectionEditor" /> (10)
</Properties>
<Events> (11)
<Event Name="Click" ResourceKey="ControlTypes_ClickEventProperty" />
<Event Name="MouseOver" ResourceKey="ControlTypes_MouseOverEventProperty" /> (12)
<Event Name="MouseOut" ResourceKey="ControlTypes_MouseOutEventProperty" /> (13)
<Event Name="DataChanged" ResourceKey="ControlTypes_DataChangedEventProperty" /> (14)
</Events>
</Control>
</Controls>
1 | Стандартный набор свойств: |
2 | Название ; |
3 | Заполнитель ; |
4 | Подсказка . |
5 | Свойства для настройки источника данных и операции редактирования: |
6 | ExtendedDataSource — выбор расширенного источника данных; |
7 | DataSource , DataField — выбор секции и поля с данными элемента управления. Редактор RefCasesFieldMetadataEditor (будет разработан далее) реализует заявленную функциональность ограничения полей карточки, доступных для выбора; |
8 | Binding — системное свойство, с которым на клиент передаётся связь с источником данных.
Значение элемента управления — идентификатор строки дела, выбранного в Справочнике номенклатуры дел 5. Пользователю предпочтительно показывать название дела из справочника, а не идентификатор. Такое преобразование данных осуществляется с помощью конвертера, название которого указывается в атрибуте |
9 | EditOperation — выбор операции редактирования. |
10 | Новое свойство RootSection обеспечивает заявленную возможность выбора разделе Справочника номенклатуры дел 5, из которого пользователю будет разрешено выбирать дела.
Значение свойства будет устанавливаться с помощью специального редактора |
11 | Также для элемента управления определены события: |
12 | При наведении курсора; |
13 | При отведении курсора; |
14 | При смене данных. |
Расширение программы Конструктор Web-разметок
В описателе элемента управления определено несколько нестандартных редакторов, а также ресурсы с текстовыми значениями. Данные сущности нужно добавить в Конструктор Web-разметок с помощью расширения, код которого приведён ниже.
RefCategoriesDesignerExtension\Extension\RefCasesDesignerExtension.cs
using DocsVision.Platform.Tools.LayoutEditor.Extensibility;
using RefCasesDesignerExtension.Editors;
using System;
using System.Collections.Generic;
using System.Resources;
(1)
namespace RefCasesDesignerExtension.Extension
{
class RefCasesDesignerExtension : WebLayoutsDesignerExtension
{
public RefCasesDesignerExtension(IServiceProvider provider)
: base(provider)
{
}
protected override Dictionary<string, Type> GetEditors()
{
return new Dictionary<string, Type>
{
{ "RefCasesFieldMetadataEditor", typeof(RefCasesFieldMetadataEditor)}, (2)
{ "RefCasesSectionEditor", typeof(RefCasesSectionEditor)} (3)
};
}
protected override List<ResourceManager> GetResourceManagers()
{
return new List<ResourceManager>
{
Resources.ResourceManager
};
}
}
}
1 | В данном расширении регистрируется два редактора, которые указаны в текстовом описателе: |
2 | RefCasesFieldMetadataEditor — редактор для выбора поля карточки, содержащего значение элемента управления; |
3 | RefCasesSectionEditor — редактор для выбора раздела Справочника номенклатуры дел 5, из которого пользователям будет разрешено выбирать дела. |
Редактор "RefCasesFieldMetadataEditor"
Редактор RefCasesFieldMetadataEditor
является стандартным редактором редактором для выбора поля карточки, реализованном в классе Docsvision.BackOffice.WebLayoutsDesigner.Editors.FieldMetadataEditor
.
RefCategoriesDesignerExtension\Editors\RefCasesFieldMetadataEditor.cs
using DocsVision.BackOffice.WebLayoutsDesigner.Editors;
using DocsVision.Platform.Data.Metadata.CardModel;
using System;
using System.Windows;
using Xceed.Wpf.Toolkit.PropertyGrid;
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
namespace RefCasesDesignerExtension.Editors
{
public class RefCasesFieldMetadataEditor : ITypeEditor (1)
{
public FrameworkElement ResolveEditor(PropertyItem propertyItem)
{
var refCasesID = new Guid("246197EA-846A-44DA-9EA3-0BCAE5500388");
var sectionCasesID = new Guid("56AF8231-B918-42D4-AC15-90EC2E9A0725");
var editor = new FieldMetadataEditor
{
FieldFilter = (field) => (2)
{
return field.FieldType == FieldType.RefId
&& field.LinkedCardTypeId == refCasesID
&& field.LinkedSectionId == sectionCasesID;
}
};
return editor.ResolveEditor(propertyItem);
}
}
}
1 | Редактор для выбора поля карточки, ссылающегося на Дело в Справочника номенклатуры дел 5. |
2 | Устанавливаем фильтр для выбора полей только из справочника. |
Ограничение возможности выбора полей карточки включено с помощью фильтра FieldFilter
, в котором проверяется тип поля (field.FieldType
), которое должно быть ссылочным полем (FieldType.RefId
), ссылающимся на секцию Дела (field.LinkedSectionId == sectionCasesID
) Справочника номенклатуры дел 5 (field.LinkedCardTypeId == refCasesID
).
В стандартной реализации приложения Делопроизводство 5 поле карточки, используемое для хранения ссылки на Дело, не является ссылочным, поэтому для него не подходит фильтр, использованный в данном примере, при настройке разметки данное поле будет недоступно для выбора. Если в Web-клиенте нужно повторить настройки разметки Windows-клиента, фильтр нужно изменить следующим образом:
var editor = new FieldMetadataEditor
{
FieldFilter = (field) =>
{
return field.FieldType == FieldType.UniqueId; (1)
}
};
1 | FieldType.UniqueId позволяет выбирать любые поля с идентификатором. |
Редактор "RefCasesSectionEditor"
Редактор RefCasesSectionEditor
имеет более сложную реализацию (по сравнению с RefCasesFieldMetadataEditor
), из-за необходимости отображения дерева Разделов Справочника номенклатуры дел 5.
-
Графическая — предоставляет форму для выбора Разделов.
-
Функциональная — предоставляет функции, загружающие дерево Разделов и выполняющие сопутствующие операции.
В простейшем случае можно обойтись без сложного редактора, и предоставить возможность непосредственно вводить идентификатор требуемого Раздела справочника в значение настройки элемента управления, или отказаться от данной настройки. |
Графическая и функциональные составляющие также распределяются между двумя компонентами:
-
Основной компонент редактора с реализацией интерфейса
ITypeEditor
. -
Форма с деревом Разделов.
Далее приведён код основного компонента редактора без графической составляющей (см. полный исходный код примера на GitHub).
using DocsVision.Platform.Tools.LayoutEditor;
using DocsVision.Platform.Tools.LayoutEditor.PropertiesEditor;
using DocsVision.Platform.WebClient;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
namespace RefCasesDesignerExtension.Editors
{
public partial class RefCasesSectionEditor : UserControl, ITypeEditor
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(Guid), typeof(RefCasesSectionEditor),(1)
new FrameworkPropertyMetadata(Guid.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(RefCasesSectionEditor), new FrameworkPropertyMetadata(string.Empty));
public Guid Value (2)
{
get { return (Guid)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public string Text (3)
{
get { return (string)GetValue(TextProperty); }
set {
SetValue(TextProperty, value);
Clear.Visibility = value != ""? Visibility.Visible: Visibility.Collapsed; (4)
}
}
private IServiceProvider serviceProvider;
private SessionContext sessionContext;
public RefCasesSectionEditor()
{
InitializeComponent();
}
public FrameworkElement ResolveEditor(Xceed.Wpf.Toolkit.PropertyGrid.PropertyItem propertyItem) (5)
{
var bindingObject = (IControlPropertiesObject)propertyItem.Instance;
this.serviceProvider = bindingObject.ServiceProvider; (6)
var currentObjectContextProvider = ServiceUtil.GetService<ICurrentObjectContextProvider>(this.serviceProvider);
this.sessionContext = currentObjectContextProvider.GetOrCreateCurrentSessionContext();
Binding binding = new Binding("Value"); (7)
binding.Source = propertyItem;
binding.Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay;
BindingOperations.SetBinding(this, RefCasesSectionEditor.ValueProperty, binding);
if (this.Value != Guid.Empty) (8)
this.Text = new RefCasesUtils(sessionContext).GetSectionTitle(this.Value);
return this;
}
private void ShowSections_Click(object sender, RoutedEventArgs e) (9)
{
var sectionTree = new SectionsTree(sessionContext);
if (sectionTree.ShowDialog() == true)
{
this.Value = sectionTree.SelectedNodeID;
this.Text = sectionTree.SelectedNodeText;
}
}
private void Clear_Click(object sender, RoutedEventArgs e) (10)
{
this.Value = Guid.Empty;
this.Text = "";
}
}
}
1 | Объявляем свойства зависимости для связывания со значением настройки (идентификатор Раздела) и отображаемым значением. |
2 | Идентификатор выбранного Раздела справочника — является значение настройки. |
3 | Название выбранного Раздела справочника, отображаемое в строке настройки. |
4 | Кнопка очистки значения. |
5 | Реализация метода ITypeEditor.ResolveEditor . |
6 | Получаем поставщика сервисов из элемента управления. |
7 | Связываем значение компонента с ValueProperty . |
8 | Получаем отображаемое значение выбранного Раздела при загрузке элемента. |
9 | Открываем форму для выбора Раздела. |
10 | Очищаем значение настройки. |
Основные функции прокомментированы в коде. При реализации нового редактора (в данном случае не используются готовые реализации, как в редакторе RefCasesFieldMetadataEditor
) особое внимание следует обратить на необходимость связывания значение настройки — Value
— со свойством зависимости (в данном примере — ValueProperty
).
Метод ShowSections_Click
вызывается при нажатии кнопки выбора значения свойства. Данный метод открывает форму SectionsTree
с деревом Разделов Справочника номенклатуры дел 5 (будет разработан далее).
Метод Clear_Click
вызывается при нажатии кнопки очистки значения настройки.
Код вспомогательного метода RefCasesUtils.GetSectionTitle
, возвращающего текстовое описание для Раздела справочника, идентификатор которого передан в метод:
RefCategoriesDesignerExtension\Editors\RefCasesUtils.cs
public string GetSectionTitle(Guid sectionId)
{
SectionData sectionSesction = cardManager.GetDictionaryData(refCasesId).Sections[sectionsSectionID]; (1) (2) (3)
if (sectionSesction.RowExists(sectionId) == false) (4)
return "Ошибка!";
RowData sectionRow = sectionSesction.GetRow(sectionId);
RowData yearRow = sectionRow.SubSection.ParentRow;
if (yearRow != null)
return string.Format("{0}. {1}", yearRow["Year"], sectionRow["Name"].ToString());
return "Ошибка!";
}
1 | cardManager — Менеджер карточек (базовое API Docsvision). |
2 | refCasesId — идентификатор Справочника номенклатуры дел 5. |
3 | Возможно раздел был удалён. |
4 | sectionsSectionID — идентификатор секции "Разделы" справочника. |
Далее приведён код компонента дерева Разделов без графической составляющей (см. полный исходный код примера на GitHub).
RefCategoriesDesignerExtension\Editors\SectionsTree.xaml.cs
using Docsvision.Platform.WebClient;
using System;
using System.Windows;
namespace RefCasesDesignerExtension.Editors
{
public partial class SectionsTree : Window
{
public string SelectedNodeText = "";
public Guid SelectedNodeID = Guid.Empty;
private RefCasesUtils refCasesUtils;
public SectionsTree(SessionContext sessionContext)
{ (1)
InitializeComponent();
refCasesUtils = new RefCasesUtils(sessionContext);
Years.ItemsSource = refCasesUtils.GetYears(); (2)
}
private void Years_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) (3)
{
if (Years.SelectedIndex == -1 || !(Years.SelectedItem is Year))
return;
Year selectedYear = Years.SelectedItem as Year;
Sections.ItemsSource = refCasesUtils.GetSections(selectedYear.ID); (4)
}
private void Accept_Click(object sender, RoutedEventArgs e) (5)
{
if (Sections.SelectedItem == null)
return;
var selectedNode = Sections.SelectedItem as Node;
var selectedYear = Years.SelectedItem as Year;
this.SelectedNodeText = string.Format("{0}. {1}", selectedYear.Value, selectedNode.Name);
this.SelectedNodeID = selectedNode.ID;
this.DialogResult = true;
this.Close();
}
private void Cancel_Click(object sender, RoutedEventArgs e) (6)
{
this.Close();
}
}
}
1 | Для формирования дерева Разделов нужно получить данные из Справочника номенклатуры дел 5. Для этого объявляем необходимость передачи контекста сессии в конструкторе класса. |
2 | Получаем список лет из Справочника номенклатуры дел 5. |
3 | При выборе Года формируем дерево Разделов для данного года. |
4 | Получаем список Разделов из Справочника номенклатуры дел 5. |
5 | Обработка нажатия кнопки сохранения выбора. |
6 | Обработка нажатия отмены. |
Основная "работа" здесь выполняется методами RefCasesUtils.GetYears
, RefCasesUtils.GetSections
.
Метод RefCasesUtils.GetYears
получает все строки из секции Года Справочника номенклатуры дел 5:
RefCategoriesDesignerExtension\Editors\RefCasesUtils.cs
public IEnumerable<Year> GetYears()
{
refCasesData = cardManager.GetDictionaryData(refCasesId);
SectionData yearSection = refCasesData.Sections[yearsSectionId];
return yearSection.Rows.Select<RowData, Year>(row => new Year() { ID = row.Id, Value = row["Year"].ToString() });
}
Метод RefCasesUtils.GetSections
получает дерево строк для секции Разделы Справочника номенклатуры дел 5.
RefCategoriesDesignerExtension\Editors\RefCasesUtils.cs
public List<Node> GetSections(Guid yearID)
{
var yearSection = refCasesData.Sections[yearsSectionId];
if (yearSection.RowExists(yearID)) {
RowDataCollection sectionRows = yearSection.GetRow(yearID).ChildSections[sectionsSectionID].Rows;
return GetNodesFromRows(sectionRows);
}
return new List<Node>();
}
List<Node> GetNodesFromRows(RowDataCollection rows) (1)
{
var nodes = new List<Node>();
foreach (var row in rows)
{
var node = new Node() { ID = row.Id, Name = row["Name"].ToString() };
if (row.HasChildRows)
node.Nodes = GetNodesFromRows(row.ChildRows);
nodes.Add(node);
}
return nodes;
}
-
Возвращает список Разделов для строк секции справочника