Использование пользовательских расширений для рабочих элементов TFS

Источник: cmcons
Шамрай Александр

Очень часто возникает ситуация, когда стандартный набор функций используемой системы перестает удовлетворять ее пользователей или возникает необходимость "скрестить" текущую систему с другой. Данная статья описывает дополнительные возможности Team Foundation Server, которые можно использовать при создании и модификации шаблонов процессов для расширения стандартных возможностей системы.

Введение

Стандартный набор элементов пользовательского интерфейса и его возможности не всегда удовлетворяют взыскательных пользователей любой системы. И MS Visual Studio Team Foundation Server (TFS), в этом смысле, не является исключением. Однако в TFS предусмотрена возможность создания пользовательских элементов для расширения стандартных свойств рабочих элементов системы. Использование технологий ".NET" позволяет просто создавать пользовательские расширения и интегрировать их в свою форму управления рабочим элементом. В данной статье описывается возможность создания дополнительных пользовательских элементов графического интерфейса на простом примере реализации связанных списков и сравнение со стандартной возможностью организации связанных списков.

Стандартные списки

Для формирования связанных списков нам помогут встроенные глобальные списки (элемент GLOBALLIST). Глобальные списки очень удобны при работе с часто меняющимися списками и большими списками и одни и те же списки могут использоваться в различных рабочих элементах. Для нашего примера мы создадим несколько глобальных списков (см. Рисунок 1):

  • Список "Operating systems" представляет собой набор типов операционных систем, который будет главным списком;
  • Списки "OS Microsoft Windows", "OS Linux" и т.д. представляют собой список версий для каждого из типа операционной системы.

Рисунок 1. Глобальные списки

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

  • Поле "OS Type" будет содержать информацию о типе операционной системы;
  • Поле "OS Version" будет содержать информацию о версии операционной системы.

 Рисунок 2. Новые поля

 Теперь необходимо для каждого поля определить список. Особенность в нашем случае заключается в том, что для поля "OS Type" должен быть статический список, а для поля "OS Version" необходимо определить динамический список, которые будет менять свой набор значений в зависимости от значения, которое выбрано в поле "OS Type". Для того чтоб подключить список к полю "OS Type", для него необходимо указать правило "ALLOWEDVALUES" и в нем установить ссылку на глобальный список "Operating systems" как изображено на рисунке ниже (см. Рисунок 3).

Рисунок 3. Подключение списка к полю

 Для поля "OS Version" необходимо определить условия, по которым будет изменяться содержимое его списка. Для этого необходимо для поля определить правило "WHEN", которое выполнится при истинности прописанного в нем выражения. Правило "WHEN" может отслеживать изменения значения любого поля, которое принадлежит рабочему элементу. Как условие определим соответствие значения ссылки "CMC.Bug.OSType", которая определяет поле "OS Type", каждому значению из глобального списка "Operating systems" (см. Рисунок 4).

 

Рисунок 4. Определение условия для поля

 Результатом выполнения правила должно быть установка в значение "ALLOWEDVALUES" наименования необходимого глобального списка (одного из "OS Microsoft Windows", "OS Linux" и т.д.)

Рисунок 5. Подключение списка к условию

И на последнем шаге необходимо новые поля разместить на форме рабочего элемента, которые должны иметь встроенный тип стандартного элемента пользовательского интерфейса "FieldControl" (см. Рисунок 6).

 

Рисунок 6. Определение полей на форме

Результатом проведенных изменений на форме должны быть два списковых поля с зависимыми значениями (см. Рисунок 7).

Рисунок 7. Новые поля на форме рабочего элемента

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

Использование пользовательских элементов

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

  • Стандартные конструкторы правил не удовлетворяют все необходимые условия;
  • Используются очень большие объемы данных;
  • Данные используются из внешнего источника;
  • Значение поля будет храниться в другом месте, а не в стандартом хранилище данных рабочих элементов;

Подготовка проекта

Для создания и редактирования пользовательских расширений интерфейса используется среда разработки MS Visual Studio 2005 или 2008, причем, использование одного и того же расширения допускается в обоих версиях MS Visual Studio.

Для нового пользовательского элемента необходимо создавать проект как "Windows Control Library" (см. Рисунок 8).

Открыть в полный размер' href="http://cmcons.com/images/tfs_custom_control/8.png" target=_blank style=color:white;>Открыть в полный размер' href="http://cmcons.com/images/tfs_custom_control/8.png" target=_blank>

 

Рисунок 8. Создание нового проекта

Кроме того, класс, который создается, должен реализовывать интерфейс "IWorkItemControl". Этот интерфейс определен в сборке "Microsoft.TeamFoundation.WorkItemTracking.Controls.dll", которая обычно находится в каталоге "<Диск>:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies". Также необходим доступ к сборке "Microsoft.TeamFoundation.WorkItemTracking.Client.dll", в которой находится описание основных классов, которые необходимы для работы с рабочим элементом. Для того, чтоб получить доступ к интерфейсу и к основным классам рабочего элемента, необходимо добавить ссылку на сборки "Microsoft.TeamFoundation.WorkItemTracking.Controls.dll" и "Microsoft.TeamFoundation.WorkItemTracking.Client.dll" (см. Рисунок 9) и добавить в исходный код строки:

using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Controls;

Рисунок 9. Ссылки на сборки

Интерфейс "IWorkItemControl" имеет следующие важные составляющие (см. Таблица 1):

Таблица 1.  Элементы интерфейса "IWorkItemControl"

Наименование
 
Тип
 
Описание
 
 WorkItemDatasource   Свойство Определяет доступ к объекту рабочего элемента. Для использования этого свойства, его необходимо приводить к типу WorkItem (определение этого типа описано в сборке "Microsoft.TeamFoundation.WorkItemTracking.Controls.dll")
 WorkItemFieldName   Свойство Свойство определяет наименование поля, с которым ассоциирован пользовательский элемент. Пользовательский элемент может быть ассоциирован с одним полем или не ассоциирован вовсе.
 InvalidateDatasource   Метод Этот метод используется для перерисовки пользовательского элемента.
 SetSite   Метод Передает указатель на интерфейс IServiceProvider. Используется если необходимо получить доступ сервисам VS Services.
 Clear   Метод Используется для сброса содержимого пользовательского элемента.
 Properties   Свойство Дает доступ ко всем атрибутам, которые были установлены для этого пользовательского элемента при описании рабочего элемента.
 BeforeUpdateDatasource   Обработчик события События вызываются перед и после обновления значений объекта WorkItem соответственно. Когда значение, которое установлено в пользовательском элементе, изменяется, форма рабочего элемента выполняет для всех элементов (через вызов метода InvalidateDatasource) для обновления их значений.
 AfterUpdateDatasource   Обработчик события
 ReadOnly   Свойство  Определяет доступ только для чтения к пользовательскому элементу.
 FlushToDatasource   Метод Пользовательский элемент запрашивается для сброса значения в объект рабочего элемента. Обычно вызывается при операции сохранения или когда вышли из формы рабочего элемента. Этот метод можно не использовать, если значение пользовательского элемента устанавливается в объект рабочего элемента сразу после его изменения.

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

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Collections.Specialized;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Controls;

namespace DependentComboBox
{
    public partial class DependentComboBoxControl : UserControl, IWorkItemControl
    {
        public DependentComboBoxControl()
        {
            InitializeComponent();            
        }

        #region IWorkItemControl Members
    }
}

Регистрация пользовательского элемента

Перед началом использования пользовательского расширения и его отладкой, dll-файл, который содержит расширение, необходимо разместить в определенном месте. Все пользовательские элементы для MS Visual Studio 2005 находятся в специальной папке "Microsoft\Team Foundation\Work Item Tracking\Custom Controls", местонахождение которой система определяет в следующей последовательности:

  1. В первую очередь поиск папки производится по пути "Environment.SpecialFolder.CommonApplicationData";
  2. Далее по пути "Environment.SpecialFolder.LocalApplicationData".

Если Вы используете MS Visual Studio 2008, то пользовательские элементы размещаются в подкаталоге "9.0" вышеприведенного каталога. Такой подход сделан для тех случаев, если на одном рабочем месте используются MS Visual Studio 2005 и MS Visual Studio 2008.

Примечание: Если на одном рабочем месте используются MS Visual Studio 2005 и MS Visual Studio 2008, то важно помнить, что пользовательское расширение необходимо перекомпилировать под каждую среду, в которой оно будет использоваться. Это связано с различными dll-зависимостями для каждой среды.

Кроме этого необходимо определить для каждого пользовательского расширения специальный (.wicc) файл, в котором находится описание элемента. Этот файл должен находиться вместе с dll-файлом пользовательского элемента и иметь наименование как "<имя_элемента>.wicc". Файл описания расширения представляет собой xml-файл и имеет следующую структуру:

<?xml version="1.0"?>
<CustomControl xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Assembly>DependentComboBox.dll</Assembly>
  <FullClassName> DependentComboBox.DependentComboBoxControl</FullClassName>
</CustomControl>

Как видно из структуры, файл описания определяет следующее:

  • Наименование сборки (элемент <Assembly>) пользовательского расширения;
  • Полное наименование класса (элемент <FullClassName>), в котором это расширение реализован.

Отладка проекта

Для отладки пользовательского расширения можно использовать другую запущенную копию MS Visual Studio, в которой это расширение будет использоваться. Для этого необходимо сконфигурировать опцию отладки проекта, как это изображено на рисунке ниже (см. Рисунок 10) для MS Visual Studio 2005.

Рисунок 10. Конфигурирование отладки для MS Visual Studio 2005

Размещение элемента на форме

Для редактирования форм, состава полей и состояний рабочего элемента используются два подхода:

  1. С помощью специального xml-файла, который используется для описания рабочих элементов, и утилит импорта (witimport) и экспорта (witexport) рабочих элементов из шаблона процесса на сервере. С правилами редактирования и составом xml-файла можно ознакомиться по адресу http://msdn.microsoft.com/en-us/library/ms243849.aspx ;
  2. С помощью графического редактора шаблона процесса, который входит в состав утилит Team Foundation Server Power Tools. Скачать и просмотреть полную информацию об этом продукте можно:
    • Для MS Visual Studio 2005 - http://msdn2.microsoft.com/en-us/teamsystem/aa718351.aspx ;
    • Для MS Visual Studio 2008 - http://msdn.microsoft.com/tfs2008/bb980963.aspx .

Решение для динамических списков

Ниже предлагается два из возможных решений реализации динамических списков на форме рабочего элемента:

  1. Использование одного расширения для нескольких полей - в этом случае предполагается использование одного пользовательского элемента, который включает в себя несколько выпадающих списков, для нескольких полей рабочего элемента;
  2. Использование событий рабочего элемента - используется одно расширение как выпадающий список для каждого поля и для выполнения изменения состава списков используется событие изменения значения поля рабочего элемента.

Использование одного элемента для нескольких полей

Как говорилось выше, суть данного подхода заключается в использовании одного пользовательского расширения, который включает несколько выпадающих списков, для нескольких полей. В нашем случае мы реализуем два выпадающих списка (см. Рисунок 11) для двух полей "OS Type" и "OS Version", которые были созданы ранее.

 

Рисунок 11. Определение на форме общего элемента

Указывать наименование поля при размещении такого пользовательского расширения нет смысла, т.к. он будет устанавливать значение сразу для нескольких полей. Также не указывается метка элемента, т.к. все объекты уже подписаны. Основная идея этого пользовательского расширения заключается в том, что установка нового значения в поля рабочего элемента происходит на событиях изменения выбранного индекса выпадающего списка.

string[] OSTypes = { "Microsoft Windows", "Linux", "Novell Netware", "Sun Solaris" };
string[] OSMSVer = { "95", "98", "2000", "Me", "XP"};
string[] OSLVer = { "Red Hat", "SuSE", "Madrake", "Mandriva", "Ubuntu" };
string[] OSNovVer = { "NetWare 5", "NetWare 6" };
string[] OSSunVer = { "Solaris 9", "Solaris 10" };

//Сохранение поля "OS Type" и создание списка для версий

private void OpSystem_SelectedIndexChanged(object sender, EventArgs e)
{
    if (m_workItem == null) return;
        string OSType = OpSystem.Text;

    if (m_workItem.Fields["OS Type"].Value.ToString() == OpSystem.Text)
        OpVersion.SelectedIndexChanged -= OpVersion_SelectedIndexChanged;

    switch (OSType)
    {
         case "Microsoft Windows": OpVersion.DataSource = OSMSVer; break;
         case "Linux": OpVersion.DataSource = OSLVer; break;
         case "Novell Netware": OpVersion.DataSource = OSNovVer; break;
         case "Sun Solaris": OpVersion.DataSource = OSSunVer; break;
    }

    if (m_workItem.Fields["OS Type"].Value.ToString() == OpSystem.Text)
    {
        OpVersion.Text = m_workItem.Fields["OS Version"].Value.ToString();
        OpVersion.SelectedIndexChanged += OpVersion_SelectedIndexChanged;
    }

    if (m_workItem.Fields["OS Type"].Value.ToString() != OpSystem.Text)
    {
        m_workItem.Fields["OS Type"].Value = OpSystem.Text;

        OpVersion.Text = "";
    }
}

//Сохранение поля "OS Version" и создание списка для версий

private void OpVersion_SelectedIndexChanged(object sender, EventArgs e)
{
    if (m_workItem == null) return;

    if (m_workItem.Fields["OS Version"].Value.ToString() != OpVersion.Text)
        m_workItem.Fields["OS Version"].Value = OpVersion.Text;
}
 

Результат работы формы с пользовательским расширением изображен на рисунке ниже (см. Рисунок 12)

Рисунок 12. Пользовательский элемент на форме

Использование событий рабочего элемента

Второй метод - это использование событий рабочего элемента, в частности обработка события изменения значения для его полей. Для этого в пользовательском расширении необходимо установить обработчик события FieldChanged, когда устанавливается свойство WorkItemDatasource

 

private WorkItem m_workItem;
object IWorkItemControl.WorkItemDatasource
{
    get
    {
        return m_workItem;
    }
    set
    {
        //Удаление события при обнулении объекта
        if (value == null && m_workItem != null)
            m_workItem.FieldChanged -= m_workItem_FieldChanged;

        m_workItem = (WorkItem)value;

        if (m_workItem != null)
        {
            m_workItem.FieldChanged += new WorkItemFieldChangeEventHandler(m_workItem_FieldChanged);
            //Установка схраненных значений для полей
           if (m_fieldName == "CMC.Bug.OSType")
                comboBoxCtrl.Text = m_workItem.Fields["OS Type"].Value.ToString();
            if (m_fieldName == "CMC.Bug.OSVersion")
            {
                SetVersionSource(m_workItem.Fields["OS Type"].Value.ToString());
                comboBoxCtrl.Text = m_workItem.Fields["OS Version"].Value.ToString();
            }
        }
    }
}

Примечание: Важно чтоб обработчик событий был удален, перед тем как сменится объект рабочего элемента в пользовательском расширении (свойство WorkItemDatasource), т.к. пользовательский элемент будет продолжать получать события от объекта, даже если форма уже была закрыта. Также, пользовательское расширение не будет выгружено из памяти, пока объект рабочего элемента не будет выгружен, что может привести к утечке памяти.

Этот обработчик будет вызываться при изменениях значений любых полей рабочего элемента, поэтому при обработке события нужно отслеживать поле-источник, в нашем случае поле "OS Type".

 void m_workItem_FieldChanged(object sender, WorkItemEventArgs e)
{
    if (!this.IsDisposed && e.Field != null && e.Field.Name == "OS Type"
        && this.m_fieldName == "CMC.Bug.OSVersion")
    {
        SetVersionSource(e.Field.Value.ToString());
    }
}

//Установка новых значений для списка версий

private void SetVersionSource(string ostype)
{
    switch (ostype)
    {
        case "Microsoft Windows": comboBoxCtrl.DataSource = OSMSVer; break;
        case "Linux": comboBoxCtrl.DataSource = OSLVer; break;
        case "Novell Netware": comboBoxCtrl.DataSource = OSNovVer; break;
        case "Sun Solaris": comboBoxCtrl.DataSource = OSSunVer; break;
        default: comboBoxCtrl.DataSource = OSMSVer;
        /*Для нового воркитема по умолчанию "Microsoft Windows"*/
        break;
    }
}
 

Для каждого пользовательского элемента на форме устанавливается наименование поля и наименование метки поля (см. Рисунок 13).

Рисунок 13. Определение пользовательского элемента на форме

Заключение

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


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=23091