Пример разработки элемента управления для выбора сотрудника

Для разработки элементов управления, совместимых с Конструктором разметок, требуется лицензия на компоненты (WinForms) DevExpress.

Дополнительная информация о разработке элементов управления приведена в разделе Разработка элемента управления для Конструктора разметок.

В данном примере будет разработан элемент управления (далее — Сотрудник с фотографией) для выбора сотрудника из Справочника сотрудников. Реализуемый компонент в определённых сценариях может быть использован в карточках вместо стандартного элемента управления Сотрудник.

Определим возможности и ограничения элемента управления:
  • Отображение ФИО (используемое отображаемое значение, определённое настройками Справочника сотрудников), должности, телефона и фотографии сотрудника без открытия его карточки.

  • Возможность настройки элемента управления:

    • Выбор подразделения, сотрудники которого отображаются в списке.

    • Фильтр состояний — должны отображаться только сотрудники с указанным состоянием ("Активен", "Уволен" и т.д.).

    • Опция, при активации которой в списке будут отображаться только сотрудники с состоянием "Активен". Если активирована данная опция и установлен фильтр состояний, то фильтр состояний будет иметь приоритет.

  • Для отображения сотрудников используется выпадающий список (без дерева подчинения).

  • Быстрый поиск отсутствует.

Элемент управления, который может быть использован в "Конструкторе разметок", должен содержать три обязательных компонента:
  • Собственно, элемент управления типа DevExpress.XtraEditors.XtraUserControl (сборка DevExpress.Utils.v13.2.dll).

  • Класс-контейнер, унаследованный от типа DocsVision.BackOffice.WinForms.Design.LayoutItems.FixedLayoutControlItem.

  • Компонент типа ControlExtensionInfoPackage (далее — пакет расширения), предоставляющий информацию об элементах управления, реализованных в сборке. При реализации нескольких элементов управления в одной сборке, должен быть реализован единственный пакет расширения.

Помимо обязательных компонентов, могут быть реализованы несколько дополнительных:
  • Компонент типа PropertySettingsForm, предоставляющий пользовательский интерфейс главного окна свойств элемента управления.

  • Компонент типа SpecialPropertyWrapper<T>, возвращающий список дополнительных настроек, добавляемых в список свойств элемента управления.

Полный код проекта доступен по ссылке.

Компоненты, описание реализации которых приведено далее, собраны в проекте SampleStaffControl (типа Class Library).

Элемент управления

Первым шагом на пути реализации собственного элемента управления является, собственно, разработка элемента управления (пользовательский интерфейс плюс соответствующая логика). В основе элемента управления должен выступать компонент (DevExpress) XtraUserControl. Также в классе должен быть реализован интерфейс IPropertyControl, который предоставляет доступ к значениям базовых настроек (признак отображения рамки, разрешения редактирования и т.п.), а также к значению элемента управления (IPropertyControl.ControlValue) и контексту объектов (IPropertyControl.ObjectContext).

Элемент управления для компонента Сотрудник с фотографией реализован в компоненте StaffPropertyControl:

public partial class StaffPropertyControl : XtraUserControl, IPropertyControl

Помимо реализации интерфейса IPropertyControl в класс StaffPropertyControl добавлен метод, отвечающий за загрузку данных из Справочника сотрудников в список сотрудников. Данный метод опирается на фильтры, установленные в настройках (значения настроек будут переданы другим компонентом) элемента управления в Конструкторе разметок:

private void LoadData()
{
 StaffUnit restrictionUnit = null;
 if (RestrictionUnitId != Guid.Empty)
 {
  restrictionUnit = ObjectContext.GetObject<StaffUnit>(RestrictionUnitId);
 }

 IEnumerable<StaffEmployee> employees = StaffService.GetUnitEmployees(restrictionUnit, (restrictionUnit = null) ? true : false, false); (1)

 if (SelectedEmployeeStatus != null)
  employees = employees.Where(t => t.Status = SelectedEmployeeStatus); (2)

 else if (HideNotActive)
  employees = employees.Where(t => t.Status = StaffEmployeeStatus.Active); (3)

 foreach (var item in employees)
 {
  Guid employeeId = ObjectContext.GetObjectRef(item).Id;
  ObjectCollection<StaffPicture> pictures = item.Pictures;

  this.staffControl1.StaffControlItems.Add(
   new StaffControlItem(employeeId,
    item.DisplayName,
    (pictures.Count = 0) ? null : item.Pictures[0].Image,
    item.PositionName, item.Phone));
 }
}
1 Выбираем сотрудников подразделения.
2 Если установлен фильтр состояний — применяем его.
3 Если установлен флаг для отображения только "активных" сотрудников — применяем его.

При реализации бизнес-логики или иных функций (например, как в приведенном коде — загрузка данных из Справочника сотрудников в список) необходимо учитывать порядок присвоения значений свойствам класса. После инициализации экземпляра класса, из сущности карточки передается значение контекста объектов, а далее — значение элемента управления. Таким образом при разработке функций, которые обращаются к данным платформы, необходимо дождаться присвоения значения свойству ObjectContext. Если функция опирается на значение элемента управления, дождитесь присвоения значения свойству ControlValue.

В данном проекте, данные из Справочника сотрудников загружаются при присвоении значения свойству ObjectContext, а после передачи значения в ControlValue из списка выбирается соответствующий сотрудник. Конкретная реализация работы с данными должна определяться собственными требованиями. К примеру, если элемент управления загружает большие объемы информации, но вероятность его использования низкая или его значение выбирается/отображается последним, имеет смысл сделать задержку загрузки данных, чтобы сократить временя загрузки карточки.

При реализации пользовательского интерфейса элемента управления можно использовать стандартные компоненты WinForms или компоненты DevExpress. Интерфейс элемента управления Сотрудник с фотографией (компонент StaffControl) был реализован на WPF (через ElementHost) с использованием двух комопнентов: Image (для отображения фотографии сотрудника) и ComboBox (для отображения списка сотрудников).

Элемент управления "Сотрудник с фотографией"
Рисунок 1. [Элемент управления "Сотрудник с фотографией"

Контейнер элемента управления

Контейнер обеспечивает сохранение значения свойств элемента управления при работе в Конструкторе разметок, а также передаёт эти значения в элемент управления. Также контейнер должен содержать тип данных элемента управления (для преобразования значения по умолчанию) и типы полей, которые могут выступать источником данных для элемента управления. Дополнительно можно указать название элемента управление (иначе будет использовано название класса) и иконку для отображения элемента управления на панели инструментов Конструктора разметок.

Класс контейнера должен быть унаследован от типа DocsVision.BackOffice.WinForms.Design.LayoutItems.FixedLayoutControlItem<T>, где T — тип, в котором реализован элемент управления (предыдущий шаг).

Для компонента Сотрудник с фотографией контейнер реализован в классе StaffControlLayoutItem. Данный класс обеспечивает сохранение свойств, отмеченных атрибутом DevExpress.Utils.Serializing.XtraSerializableProperty: Подразделение, Состояние и Скрывать неактивных сотрудников.

[XtraSerializableProperty]
public Guid RestrictionUnitId (1)
{
 get
 {
  if (base.PropertyControl != null)
   return base.PropertyControl.RestrictionUnitId;
  return restrictionUnitId;
 }
 set
 {
  if (this.PropertyControl != null)
   this.PropertyControl.RestrictionUnitId = value;
  restrictionUnitId = value;
 }
}

[XtraSerializableProperty]
public StaffEmployeeStatus? SelectedEmployeeStatus (2)
{
 get
 {
  if (base.PropertyControl != null)
   return base.PropertyControl.SelectedEmployeeStatus;
  return selectedEmployeeStatus;
 }
 set
 {
  if (this.PropertyControl != null)
   this.PropertyControl.SelectedEmployeeStatus = value;
  selectedEmployeeStatus = value;
 }
}

[XtraSerializableProperty]
public bool HideNotActive (3)
{
 get
 {
  if (base.PropertyControl != null)
   return base.PropertyControl.HideNotActive;
  return hideNotActive;
 }
 set
 {
  if (this.PropertyControl != null)
   this.PropertyControl.HideNotActive = value;
  hideNotActive = value;
 }
}
1 Подразделение.
2 Состояние.
3 Скрывать сотрудников с состоянием, отличным от "Активен".

Значение свойства, не отмеченные атрибутом XtraSerializableProperty, не будут сохранены при выходе из Конструктора разметок.

Помимо этого, крайне важно обеспечить передачу значений свойств в свой элемент управления, для чего переопределяем свойство Control:

public override Control Control
{
 get
 {
  return base.Control;
 }
 set
 {
  base.Control = value;
  if (value != null)
  {
   // Передача значений свойств в элемент управления
   this.PropertyControl.RestrictionUnitId = restrictionUnitId;
   this.PropertyControl.SelectedEmployeeStatus = selectedEmployeeStatus;
   this.PropertyControl.HideNotActive = hideNotActive;
  }
 }
}

Если источником данных для элемента управления выступает ссылочное поле, то в классе контейнера можно реализовать дополнительный интерфейс DocsVision.BackOffice.WinForms.Design.LayoutItems.IReferencePropertyItem. Интерфейс определяет свойства для ограничения источника (тип карточки и её секция) данных элемента управления:

public Guid CardTypeId
{
 get { return RefStaff.ID; }
}

public Guid SectionTypeId
{
 get { return RefStaff.Employees.ID; }
}

Пакет расширения

Последним обязательным компонентом является класс, предоставляющий информацию обо всех элементах управления, реализованных в сборке.

При подключении сборки, Конструктор разметок определяет наличие в ней класса DocsVision.BackOffice.WinForms.Design.ControlExtensionInfoPackage, из которого он получает информацию об элементах управления.

В данном примере тип ControlExtensionInfoPackage реализован в классе ExtensionPackage:

public sealed class ExtensionPackage : ControlExtensionInfoPackage
{
 public override ControlExtensionInfo[] GetControlExtensions()
 {
  return new ControlExtensionInfo[]
  {
   new ControlExtensionInfo(typeof(StaffControlLayoutItem), typeof(StaffControlWrapper),typeof(StaffPropertySettingsForm))
  };
 }
}

В конструктор ControlExtensionInfo должен быть передан тип контейнера элемента управления, и, при необходимости (если были реализованы), типы компонентов, предоставляющих список дополнительных настроек элемента управления и главное окно настройки параметров элемента управления.

На этом обязательная часть разработки закончена, но для соответствия элемента управления Сотрудник с фотографией критериям, определённым ранее, необходимо добавить возможность его настройки.

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

Главное окно свойств элемента управления

Для элемента управления "Сотрудник с фотографией" определено три требования, связанных с настройкой:
  • Возможность выбора подразделения, сотрудники которого будут отображаться в списке.

  • Фильтр состояния отображаемых сотрудников.

  • Настройка, при активации которой в списке будут отображаться только сотрудники в состоянии "Активен".

Для настройки элемента управления Конструктор разметок предлагает использовать специальную форму, открываемую при выборе команды Свойства из контекстного меню элемента управления. Именно на данную форму вынесем флаг, ограничивающий отображение сотрудников с признаком "Активен".

Чтобы реализовать в своем проекте такой компонент, достаточно создать новый класс и унаследовать его от типа DocsVision.BackOffice.WinForms.Design.PropertySettingsForm.

В данном решении компонент для настройки элемента управления реализован в StaffPropertySettingsForm. В дизайн компонента был добавлен требуемый флаг:

Флаг "Скрывать неактивных сотрудников"
Рисунок 2. Флаг "Скрывать неактивных сотрудников"
В коде были переопределены методы для сохранения и получения значений собственной настройки:
protected override void LoadPropertyAttributes() (1)
{
 base.LoadPropertyAttributes();
 if (this.PropertyItem != null)
 {
  hideNotActive.Checked = ((StaffControlLayoutItem)this.PropertyItem).HideNotActive;
 }
}

protected override void UpdatePropertyAttributes() (2)
{
 base.UpdatePropertyAttributes();
 if (this.PropertyItem != null)
 {
  ((StaffControlLayoutItem)this.PropertyItem).HideNotActive = hideNotActive.Checked;
 }
}
1 Загрузка значения.
2 Сохранение значения.

Чтобы ограничить типы полей, которые могут выступать в качестве источника данных, в базовом классе предусмотрен метод IsSectionFieldSupported. Этот метод требуется переопределить, добавив логику проверки поля перед отображением в списке доступных полей:

protected override bool IsSectionFieldSupported(SectionField sectionField)
{
 return sectionField.LinkedCardTypeId = RefStaff.ID && sectionField.LinkedSectionId = RefStaff.Employees.ID; (1)
}
1 Поддерживается только Справочник сотрудников — секция Сотрудники.

Тип данного компонента должен быть указан в пакете расширения.

Свойства элемента управления

Основной набор настроек элемента управления открывается после нажатия кнопки Больше в главном окне настроек. Чтобы добавить собственные настройки в данный список необходимо реализовать класс с базовым типом DocsVision.BackOffice.WinForms.Design.PropertyWrappers.SpecialPropertyWrapper<T>, где T — тип контейнера элемента управления.

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

[Category("Дополнительные настройки"), DisplayName("Подразделение"), Description("Будут выведены только сотрудники указанного подразделения и подчиненных подразделений")]
[TypeConverter(typeof(UnitConverter))]
[Editor(typeof(RestrictionUnitEditor), typeof(UITypeEditor))]
public Guid RestrictionUnitId
{
 get
 {
  return this.Item.RestrictionUnitId;
 }
 set
 {
  this.Item.RestrictionUnitId = value;
 }
}

[Category("Дополнительные настройки"), DisplayName("Состояние"), Description("Будут выведены только сотрудники с указанным состоянием")]
[TypeConverter(typeof(StaffEmployeeStatusConverter))]
public StaffEmployeeStatus? SelectedEmployeeStatus
{
 get
 {
  return this.Item.SelectedEmployeeStatus;
 }
 set
 {
  this.Item.SelectedEmployeeStatus = value;
 }
}

Чтобы настройка попадала в собственную секцию настроек, а также имела нужное название, к свойствам должны быть добавлены атрибуты: Category, DisplayName и Description (дополнительная информация). Свойства передают или получают свои значения из контейнера элемента управления, который, в свою очередь, получает или передает значения в сам элемент управления.

Дополнительный атрибут TypeConverter указывает на необходимость предварительной обработки значения свойства перед его отображением (пример реализации см. в коде).

Атрибут Editor позволяет указать собственный редактор для выбора значения свойства (пример реализации см. в коде).

Тип данного компонента должен быть указан в пакете расширения.

Регистрация компонента

После разработки обязательных компонентов необходимо зарегистрировать сборку.

Чтобы зарегистрировать сборку:
  1. В редакторе реестра откройте ветку:

    • HKEY_CURRENT_USER\Software\DocsVision\BackOffice\5.5\Client\PropertyControls — для текущего пользователя.

    • HKEY_LOCAL_MACHINE\Software\DocsVision\BackOffice\5.5\Client\PropertyControls — для всех пользователей.

  2. Добавьте новый строковый параметр с любым названием. В значении добавленного параметра должен быть указан путь к сборке компонента или строгое имя сборки:

    • Полный путь к сборке — если сборка не зарегистрирована в GAC. Например: C:\Users\KurkinSA\AppData\Local\Docsvision\5.5\Client\Docsvision.TreeControl.dll.

    • Строгое имя сборки — если сборка зарегистрирована в GAC. Например: Docsvision.TreeControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7148afe997f90519.

Когда компонент зарегистрирован, после повторного входа в Docsvision Windows-клиент на панель инструментов будет представлен разработанный элемент управления:

Элемент управления "Сотрудник с фотографией" на панели инструментов
Рисунок 3. Элемент управления "Сотрудник с фотографией" на панели инструментов

Для нового элемента управления доступны все настройки, определённые начальными условиями:

Настройки элемента управления "Сотрудник с фотографией"
Рисунок 4. Настройки элемента управления "Сотрудник с фотографией"

При работе с карточкой элемент управления Сотрудник с фотографией выглядит так:

Сотрудник с фотографией
Рисунок 5. "Сотрудник с фотографией" в карточке