logo

Пример использования компонента «Дерево»

Рассмотрим пример использования компонента Дерево на основе списка бизнес-процессов и папок, в которых они находятся, в двух вариантах:

  • cтандартный:

  • с возможностью выбора узлов:

Создание структуры обмена данными

Для транспортировки и отображения данных создайте структуру обмена данными WorkflowModel со свойствами:

  • Uid — уникальный идентификатор, тип GUID;
  • ParentUid — уникальный идентификатор родителя, тип GUID с включенной опцией Может иметь пустое значение;
  • Caption — наименование, тип Строка;
  • Type — тип элемента, тип Целое число.

Сохраните и опубликуйте структуру обмена данными.

Создание структуры компонента

Создайте новый компонент TreeComponentExample, и выполните следующие настройки:

  1. В этот компонент добавьте новое свойство WorkflowModelList типа WorkflowModel с типом связи Список.

  2. На форму созданного компонента перетащите компонент Дерево. Для этого в разделе Компоненты откройте вкладку Основные.

Настройки компонента «Дерево»

Компонент Дерево состоит из двух частей:

  • основной контент элемента дерева — содержит основную информацию об элементе дерева, например, заголовок;
  • дополнительный контент элемента дерева — содержит дополнительную информацию об элементе дерева, например, позволяет настроить действия с ним. Отображается всегда справа от основного контента.

Перейдите к настройкам дерева, выделив его и нажав на значок шестерёнки.

В поле Источник данных укажите созданное свойство WorkflowModelList. После выбора этого свойства появляется возможность указать две настройки:

  • свойство, отвечающее за ключ;
  • свойство, отвечающее за ключ родителя.

Эти свойства позволяют связать родительский и дочерний элемент и получить древовидную структуру. Они могут быть следующих типов:

  • GUID;
  • Строка;
  • Целое число (long);
  • Целое число 32 бита (int);
  • Целое число 16 бит (short).
Примечание

1. В корневом элементе можно задать значение null , если в ключе родителя включена опция Может иметь пустое значение.

2. Корневых элементов не может быть больше одного.

Дополнительные настройки компонента «Дерево»

Задайте следующие дополнительные настройки дерева.

Показывать кнопки Свернуть всё/Развернуть всё — если эта опция включена, для пользователя в интерфейсе будут доступны кнопки, которые позволяют отображать или скрывать сразу все элементы дерева.

Выбор элементов — разрешает выбирать элементы дерева.

Включив эту опцию, задайте две появившиеся настройки:

  • Список выбранных элементов — укажите свойство из контекста компонента с типом связи Список такого же типа, как источник данных.
  • Метод при выборе элемента — укажите метод, который будет вызываться при выборе элемента. В этом методе задайте добавление и удаление элемента в контекстную переменную Список выбранных элементов.

Раскрыт ли узел дерева — позволяет отображать или скрывать вложенные элементы в дереве.

Действие при раскрытии узла — задайте действие, которое выполняется при попытке раскрыть или свернуть узел дерева.

Тип дерева — визуальная настройка дерева:

  • По умолчанию — компактный тип отображения, при наведении на элемент дерева он выделяется рамкой, дополнительный контент также отображается при наведении на элемент.

  • В виде блочных элементов — дерево растягивается на всю ширину, дополнительный контент отображается всегда и справа.

В основной контент элемента дерева добавьте компонент Кнопка и задайте его настройки. На вкладке Основные укажите:

  • текст кнопки — с помощью функции GetTreeItemCaption, которая задаёт наименование кнопки (имя процесса или группы);
  • иконку — с помощью функции GetTreeItemIcon, которая задаёт изображение на кнопке (процесс или группа).

В дополнительный контент элемента дерева добавьте компонент Кнопка и задайте его настройки. На вкладке Основные укажите:

  • текст кнопки — с помощью функции GetAdditionalCaption, которая задаёт наименование кнопки;
  • иконку — с помощью функции GetAdditionalButtonIcon, которая задаёт изображение на кнопке (добавить или удалить).

Клиентские сценарии

Если в компоненте Кнопка указаны функции, в клиентских сценариях генерируется соответствующий код. При этом, т. к. в компоненте Дерево указан источник данных, в функции внутренних компонентов аргументом передаётся элемент данных. Код сценария:

/// <summary>
/// Сценарий вычисления наименования кнопки в компоненте "Дерево"
/// </summary>
public string GetTreeItemCaption(EleWise.ELMA.DataClasses.WorkflowModel item0)
{
    return item0.Caption;
}

/// <summary>
/// Сценарий вычисления иконки кнопки в компоненте "Дерево"
/// </summary>
public string GetTreeItemIcon(EleWise.ELMA.DataClasses.WorkflowModel item0)
{
    switch (item0.Type)
    {
        case 0:
        {
            // Иконка "Папка"
            return "folder";
        }
        case 1:
        {
            // Иконка "Процесс"
            return "process";
        }
        default:
        {
            return null;
        }
    }
}

/// <summary>
/// Сценарий вычисления наименования дополнительной кнопки в компоненте "Дерево"
/// </summary>
public string GetAdditionalCaption(EleWise.ELMA.DataClasses.WorkflowModel item0)
{
    // Кнопка не содержит наименования
    return "";
}
 
/// <summary>
/// Сценарий вычисления иконки дополнительной кнопки в компоненте "Дерево"
/// </summary>
public string GetAdditionalButtonIcon(EleWise.ELMA.DataClasses.WorkflowModel item0)
{
    switch (item0.Type)
    {
        case 0:
        {
            // Иконка "Добавить"
            return "add";
        }
        case 1:
        {
            // Иконка "Корзина"
            return "remove";
        }
        default:
        {
            return null;
        }
    }
}

Серверные сценарии

Перейдите к настройкам пользовательского компонента TreeComponentExample, нажав в правом нижнем углу на значок шестерёнки. Затем задайте сценарий при загрузке формы.

Код серверного сценария:

/// <summary>
/// Сценарий при загрузке формы
/// </summary>
public void ServerOnLoad(ViewModel form)
{
    var workflowHeaders = EleWise.ELMA.Workflow.Managers.ProcessHeaderManager.Instance.FindAll();
    // Уникальный идентификатор корневого узла дерева
    var rootUid = Guid.Empty;
    // Добавим корневой узел дерева
    form.Context.WorkflowModelList.Add(
        new WorkflowModel
        {
            Type = 0, // Тип 0 пусть означает, что это - группа
            Caption = SR.T("Все процессы"),
            Uid = rootUid,
            ParentUid = null // Головные компоненты обязательно должны ссылаться на null
        }
    );
    foreach (var workflowHeader in workflowHeaders)
    {
        // Группа процесса
        var parentGroup = workflowHeader.ParentGroup;
        form.Context.WorkflowModelList.Add(new WorkflowModel
        {
            Type = 1, // Тип 1 пусть означает, что это - процесс
            Caption = workflowHeader.Name,
            Uid = workflowHeader.Uid,
            ParentUid = parentGroup != null ? parentGroup.Uid : rootUid
        });
 
        if (parentGroup != null)
        {
            AddGroupRecursive(form, rootUid, parentGroup);
        }
    }
}
 
private void AddGroupRecursive(ViewModel form, Guid rootUid, EleWise.ELMA.Workflow.Models.IProcessGroup parentGroup)
{
    if (parentGroup != null)
    {
        var parent = parentGroup.Parent;
        form.Context.WorkflowModelList.Add(new WorkflowModel
        {
            Type = 0, // Тип 0 пусть означает, что это - группа
            Caption = parentGroup.Name,
            Uid = parentGroup.Uid,
            ParentUid = parent != null ? parent.Uid : rootUid
        });
 
        AddGroupRecursive(form, rootUid, parent);
    }
}

Сохраните компонент, запустите в режиме эмуляции и проверьте полученный результат.

Реализация функции «Выбор элементов»

В компоненте добавьте новое свойство SelectedWorkflowModelList типа WorkflowModel с типом связи Список и выполните следующие настройки.

  1. Перейдите на вкладку Дополнительно нового свойства и включите опцию Только для веб-части, так как это свойство используется только в клиентских сценариях.
  2. Перейдите к настройкам дерева, выделив его и нажав на значок шестерёнки. Задайте следующие настройки:
  • включите опцию Выбор элементов;
  • в поле Список выбранных элементов выберите созданное свойство SelectedWorkflowModelList;
  • в поле Метод при выборе элемента укажите клиентскую функцию OnAddElement.

Код клиентского сценария:

Функция выбора элемента
/// <summary>
/// Сценарий действия "Метод при выборе элемента"
/// </summary>
public void OnAddElement(bool selected, EleWise.ELMA.DataClasses.WorkflowModel item0)
{
    if (selected)
    {
        // Добавляется только указанный узел
        Context.SelectedWorkflowModelList.Add(item0);
    }
    else
    {
        // Удаляется только указанный узел
        Context.SelectedWorkflowModelList.Remove(item0);
    }
}

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

Если нужно, чтобы при выборе родительского элемента выбирались дочерние или при снятии родительского элемента снимались дочерние, измените код функции:

Функция выбора элемента
/// <summary>
/// Сценарий действия "Метод при выборе элемента"
/// </summary>
public void OnAddElement(bool selected, EleWise.ELMA.DataClasses.WorkflowModel item0)
{
    var selectedElements = Context.SelectedWorkflowModelList;
    var tree = Context.WorkflowModelList;
    if (selected)
    {
        OnSelectElementInternal(tree, selectedElements, item0);
    }
    else
    {
        OnDeSelectElementInternal(tree, selectedElements, item0, true);
    }
}
 
private void OnSelectElementInternal(
    ICollection<EleWise.ELMA.DataClasses.WorkflowModel> tree,
    ICollection<EleWise.ELMA.DataClasses.WorkflowModel> selectedElements,
    EleWise.ELMA.DataClasses.WorkflowModel element
)
{
    if (!selectedElements.Contains(element))
    {
        // Выбираем себя
        selectedElements.Add(element);
        OnSelectElement(tree, selectedElements, element, true);
    }
}
 
private void OnSelectElement(
    ICollection<EleWise.ELMA.DataClasses.WorkflowModel> tree,
    ICollection<EleWise.ELMA.DataClasses.WorkflowModel> selectedElements,
    EleWise.ELMA.DataClasses.WorkflowModel element,
    bool processChildren)
{
    // Обработка родителя
    var parentId = element.ParentUid;
    var parent = tree.FirstOrDefault(a => a.Uid == parentId);
    if (parent != null)
    {
        var parentChildren = tree.Where(a => a.ParentUid == parentId);
        if (parentChildren.All(a => selectedElements.Contains(a)))
        {
            // Если все дочерние элементы выбраны - тогда мы выбираем и родителя тоже
            OnSelectElementInternal(tree, selectedElements, parent);
        }
    }
 
    if (processChildren)
    {
        // Выбираем все дочерние
        var itemId = element.Uid;
        var children = tree.Where(a => a.ParentUid == itemId);
        foreach (var child in children)
        {
            OnSelectElementInternal(tree, selectedElements, child);
        }
    }
}
 
private void OnDeSelectElementInternal(
    ICollection<EleWise.ELMA.DataClasses.WorkflowModel> tree,
    ICollection<EleWise.ELMA.DataClasses.WorkflowModel> selectedElements,
    EleWise.ELMA.DataClasses.WorkflowModel element,
    bool processChildren)
{
    if (selectedElements.Contains(element))
    {
        // Убираем из выбора себя
        selectedElements.Remove(element);
        OnDeSelectElement(tree, selectedElements, element, processChildren);
    }
}
 
private void OnDeSelectElement(
    ICollection<EleWise.ELMA.DataClasses.WorkflowModel> tree,
    ICollection<EleWise.ELMA.DataClasses.WorkflowModel> selectedElements,
    EleWise.ELMA.DataClasses.WorkflowModel element,
    bool processChildren)
{
    // Обработка родителя
    var parentId = element.ParentUid;
    var parent = tree.FirstOrDefault(a => a.Uid == parentId);
    if (parent != null)
    {
        var parentChildren = tree.Where(a => a.ParentUid == parentId);
        if (parentChildren.Any(a => !selectedElements.Contains(a)))
        {
            // Если не выбран хотя бы один элемент из дочерних - мы снимаем выбор с родителя
            OnDeSelectElementInternal(tree, selectedElements, parent, false);
        }
    }
 
    if (processChildren)
    {
        // Убираем все дочерние
        var itemId = element.Uid;
        var children = tree.Where(a => a.ParentUid == itemId);
        foreach (var child in children)
        {
            OnDeSelectElementInternal(tree, selectedElements, child, true);
        }
    }
}

Сохраните компонент, запустите в режиме эмуляции и проверьте полученный результат.