How-To: Создание собственного UserControl для SCSM 2010 SP1

ВНИМАНИЕ! Описанные здесь техники в основном являются не документированными и не поддерживаемыми со стороны Microsoft. Информация предоставлена “как есть”, автор не несет ответственности за возможную потерю инфомрацию.

Вступление

Всё чаще я встречаю вопросы о том, как можно расширить функциональность SCSM с помощью собственных форм или контролов. Лично я предпочитаю не использовать полностью переписанные формы без крайней необходимости: это довольно большой объем работы, и при выходе новой версии продукта (выход намечен на Q4 2011 – Q1 2012) с почти 100% гарантией ваша форма перестанет работать. Контролы требуют меньше времени на разработку, а шанс, что они будут работать и в следующей версии продукта, довольно велик.

Чтобы понимать, о чем пойдет речь в данной статье, необходимо иметь представление о следующих технологиях:

  • WPF (Windows Presentation Foundation), в особенности Binding
  • DependencyProperty
  • XML-схема пакета управления

Итак, нам необходимо решить следующие задачи:

  1. Создать новый UserControl  в Visual Studio
  2. Подключить этот контрол к форме
  3. Доставить библиотеку с контролом на все компьютеры с консолью SCSM

Создание UserControl-а

Для создания нового UserControl-а необходимо открыть Visual Studio, и выбрать новый проект WPF User Conrtol Library:
image

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

Затем необходимо подключить к проекту несколько библиотек:

  1. Microsoft.EnterpriseManagement.Core.dll (расположена в папке c:\Program Files\Microsoft System Center\Service Manager 2010\SDK Binaries на сервере SCSM)
  2. Microsoft.EnterpriseManagement.UI.Foundation.dll (расположена в папке c:\Program Files\Microsoft System Center\Service Manager 2010\)
  3. Microsoft.EnterpriseManagement.UI.SdkDataAccess.dll (расположена в папке c:\Program Files\Microsoft System Center\Service Manager 2010\)

Если вы планируете использовать стандартные контролы SCSM вам также понадобиться подключить библиотеки Microsoft.EnterpriseManagement.UI.Controls.dll, Microsoft.EnterpriseManagement.UI.ExtendedControls.dll, Microsoft.EnterpriseManagement.UI.SMControls.dll и WPFToolKit.dll
Все контролы, которые мы подключаем через расширение форм, обязаны иметь атрибут ContentProperty. С чем именно связано такое требование, мне выяснить не удалось, но такое требование обязательно. В связи с этим нам необходимо создать переменную, которую мы будем использовать в качестве значения для атрибута ContentProperty. Тип и название переменной могут быть любыми, но она должна быть определена с помощью
DependencyProperty.

В итоге у вас должно получиться нечто, похожее на это:

namespace SCSMControls
{
    [ContentProperty("SelectedItem")]
    public partial class SCSMControl : UserControl, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
 
        public SCSMControl()
        {
            InitializeComponent();
        }

        public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(string), typeof(SCSMControl), new UIPropertyMetadata(null, new PropertyChangedCallback(SCSMControl.OnSelectedItemChanged)));

        public string SelectedItem
        {
            get
            {
                return (string)base.GetValue(SelectedItemProperty);
            }
            set
            {
                base.SetValue(SelectedItemProperty, value);
                NotifyPropertyChanged("SelectedItem");
            }
        }

        private static void OnSelectedItemChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            //TODO
        }

        /// <summary>
        /// INotifyPropertyChanged implementation
        /// </summary>
        /// <param name="propertyName"></param>
        private void NotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

Я также добавил интерфейс INotifyPropertyChanged, чтобы внешние компоненты могли подписываться на изменение свойств нашего контрола. Кроме этого, я показал как можно подписаться на изменение свойства нашего компонента. Иногда это бывает полезно. Но использовать оба эти подхода не обязательно.

Теперь наш контрол можно добавлять на форму в SCSM, но он пока не делает ничего полезного. Для начала нужно отобразить какие-нибудь данные. В этом нам поможет такой мощный механизм WPF, как привязка (binding). Но чтобы использовать привязку, необходимо знать к каким свойствам привязываться.

DataContext формы (а значит и нашего контрола) заполняется объектом типа IDataItem. Этот тип не документирован, поэтому вам придется исследовать его самим или поверить мне)). Объект типа IDataItem хранит в себе все свойства объекта, для которого открыта форма. Доступ к объектам осуществляется операцией взятия индекса ([]), а индексатором выступает внутреннее имя поля (или имя Type Projection). Несколько примеров:

IDataItem item = this.DataContext as IDataItem;
string title = (string)item["Title"];
string status = (string)(item["Status"] as IDataItem)["DisplayName"];
string affectedUser = (string)(item["AffectedUser"] as IDataItem)["DisplayName"];

Как видно из примера, мы можем обращаться к вложенным свойствам полученных объектов также через IDataItem.Чтобы использовать эти значения в привязке, достаточно указать название свойства. Добавим на наш контрол несколько элементов:

<TextBox Name="boxID" Grid.Column="1" Grid.Row="0" Text="{Binding Path=$Id$, Mode=OneWay}" VerticalAlignment="Center" HorizontalAlignment="Stretch" IsReadOnly="True"  />
<TextBox Name="boxName" Grid.Column="1" Grid.Row="1" Text="{Binding Path=Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Stretch" />
<TextBox Name="boxAffectedUser" Grid.Column="1" Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Stretch" IsReadOnly="True"  >
  <TextBox.Text>
    <Binding Path="AffectedUser.DisplayName" Mode="OneWay" FallbackValue="No Affected User"/>
  </TextBox.Text>
</TextBox>
<TextBox Name="boxItem" Grid.Column="1" Grid.Row="3" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=SelectedItem}" VerticalAlignment="Center" HorizontalAlignment="Stretch" />

Здесь показаны несколько приемов привязки. В первом случае мы привязываемся к внутреннему идентификатору элемента (тип Guid). Во втором случае – к стандартному. Третий способ показывает, как можно задать текст для случая, если значение поля пустое. Заметьте, что это не значение по-умолчанию, а именно подстановка текста. Четвертый способ показывает, как можно сделать привязку к свойствам нашего контрола.

Итак, наш контрол умеет отображать данные. Но не плохо бы научить его также и изменять данные. IDataItem не слишком хорошо подходит для манипуляции с объектами SDK, т.к. он содержит лишь информацию об одном объекте. Не плохо бы получить доступ к SDK, т.к. с помощью него мы можем производить любые манупуляции с данными. Это мы можем сделать следующим образом (см. функцию GetSession):

[ContentProperty("SelectedItem")]
public partial class SCSMControl : UserControl, INotifyPropertyChanged
{
        EnterpriseManagementGroup mg;
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        public SCSMControl()
        {
            InitializeComponent();
            GetSession();
        }

        void GetSession()
        {
            // Get the current session, more info: http://blogs.technet.com/servicemanager/archive/2010/02/11/tasks-part-1-tasks-overview.aspx
            IServiceContainer container = (IServiceContainer)FrameworkServices.GetService(typeof(IServiceContainer));
            IManagementGroupSession curSession = (IManagementGroupSession)container.GetService(typeof(IManagementGroupSession));
            if (curSession == null)
                throw new ValueUnavailableException("curSession is null");
            mg = curSession.ManagementGroup;
        }
}

Итак, мы имеет доступ к SDK, давайте сделаем что-нибудь полезное. Н-р установим свойства по-умолчанию для нового объекта. Для этого нам необходимо получить объект IDataItem из DataContext, а затем установить свойства. Делать это в обработчике события FormLoaded не стоит – в этом момент DataContext еще не заполнен. Вместо этого мы подписываемся на изменение свойства DataContext, и когда там оказывается нужный нам объект – устанавливаем свойства:

private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    // wait binding
    if (this.DataContext is IDataItem)
    {
        instance = (this.DataContext as IDataItem);
        // If this is new incident, set some default properties
        if ((bool)instance["$IsNew$"])
        {
            instance["Title"] = "WOW! Now we can set the default value for property!!!";
            instance["Description"] = "And we can use SDK. Current management group: " + mg.Name;
            //IncidentTierQueuesEnum.Tier2
            instance["TierQueue"] = mg.EntityTypes.GetEnumeration(new Guid("df3896f5-3145-0546-4d25-e485de6765af"));
        }
    }
}

Обратите внимание на свойство $IsNew$ – оно определяет открыта ли форма для создание элемента (true) или для редактирования (false).

Итак, на этом наш компонент полностью готов. Проект Vusial Studio 2010 с примером вы можете скачать в конце статьи.

Добавление контрола на форму

Теперь нам необходимо добавить наш контрол на форму. Для этого нам потребуется создать с помощью Authoring Tool новую модификацию для формы, а затем отредактировать её XML-код в любом редакторе. Чтобы добавить собственный контрол на форму необходимо:

  1. Создать новую модификацию формы. Как это сделать описано много где в сети, например здесь и здесь, а вот здесь даже с видео
  2. Добавить на форму в место, где должен находится наш конрол, любой стандартный контрол, например Label
  3. Сохранить пакет управления, затем открыть его в любом текстовом редакторе.
  4. Найти  секцию Forms, а в ней добавленный контрол.
  5. Заменить атрибуты Assembly и Type на данные нашего контрола. PublicKey можно узнать с помощью каманды sn.exe –T <путь к сборке> (находится в папке c:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\). Можно добавить эту команду в инструменты Visual Studio
  6. При необходимости, добавить другие свойства с помощью элемента <PropertyChange>

Вот пример для формы инцидента:

<Form ID="CustomForm_650994d4_1a69_4f1a_975f_7d060b90a3f3" Accessibility="Public" Target="CustomForm_650994d4_1a69_4f1a_975f_7d060b90a3f3_TypeProjection" BaseForm="Alias_cf5e40e8_e299_4731_9572_41860eb78176!System.WorkItem.Incident.ConsoleForm" TypeName="Microsoft.EnterpriseManagement.ServiceManager.Incident.Forms.IncidentFormControl">
<Category>Form</Category>
<Customization>
  <AddControl Parent="StackPanel206" Assembly="PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Type="System.Windows.Controls.Label" Left="92" Top="14.2" Right="0" Bottom="0" Row="0" Column="0" />
  <PropertyChange Object="Label_1" Property="HorizontalAlignment">
	<NewValue>Left</NewValue>
  </PropertyChange>
  <PropertyChange Object="Label_1" Property="VerticalAlignment">
	<NewValue>Bottom</NewValue>
  </PropertyChange>
  <PropertyChange Object="Label_1" Property="Content">
	<NewValue>Custom control:</NewValue>
  </PropertyChange>
  <AddControl Parent="StackPanel206" Assembly="SCSMControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e2bef97f2bc99659" Type="SCSMControls.SCSMControl" Left="100.8" Top="9.40000000000003" Right="46" Bottom="0" Row="0" Column="0" />
  <PropertyChange Object="SCSMControl_1" Property="Width">
	<NewValue>Auto</NewValue>
  </PropertyChange>
  <PropertyChange Object="SCSMControl_1" Property="Height">
	<NewValue>Auto</NewValue>
  </PropertyChange>
  <PropertyChange Object="SCSMControl_1" Property="VerticalAlignment">
	<NewValue>Bottom</NewValue>
  </PropertyChange>
  <PropertyChange Object="SCSMControl_1" Property="Margin">
	<NewValue>0,0,0,0</NewValue>
  </PropertyChange>
  <PropertyChange Object="SCSMControl_1" Property="SelectedItem">
	<NewValue>Cool control</NewValue>
  </PropertyChange>
</Customization>
</Form>

В итоге у меня получился вот такой контрол:

image

Для новых инцидентов автоматически заполняется Название и Группа подержки:

image

Доставка библиотеки

Теперь нам необходимо скопировать нашу библиотеку на все рабочие станции с консолью SCSM. Хорошо, если их 3-4, а если их 50?100? А как потом обновлять эту библиотеку? Не самые приятные и простые вопросы.

К счастью для нас, разработчики SCSM позаботились об этом. В SCSM существует так называемый бандл пакетов управления (management pack bundle). Данный тип пакетов управления может содержать в себе другие пакеты управления (как запечатанные, так и нет), а также различные сборки, изображения и прочие ресурсы.

Необходимо лишь перед созданием бандла в пакете управления указать ссылку на ресурс (в нашем случае библиотека). Добавленные таки образом ресурсы копируются в локальный профиль пользователя, который запускает консоль, в папку %USERPROFILE%\AppData\Local\Microsoft\System Center Service Manager 2010\%GROUPNAME%\%MPVERSION%,

где

   %GROUPNAME% – имя группы управления

   %MPVERSION% – версия пакета управления, который содержит ссылку на ресурс

Нам достаточно добавить в конце нашего пакет управления ссылку на сборку:

  </LanguagePacks>
  <!-- Section For Assembly -->
  <Resources>
    <Assembly ID="SCSMControlAssembly" Accessibility="Public" QualifiedName="SCSMControl" FileName="SCSMControl.dll" />
  </Resources>
</ManagementPack>

а затем упаковать его в бандл. Для упаковки вы можете использовать скрипт по ссылке выше или мою утилиту MPBMaker (перед упаковкой не забудьте скопировать библиотеку в ту же папку, где расположен пакет управления):

MPBMaker.exe SCSMControlBundle “d:\Examples\Example.SCSMControl.xml”

Полученный пакет необходимо импортировать в SCSM.

Возникающие ошибки

Если во время импортирования пакета управления вы получили ошибку вроде этой:

: Failed to verify form: CustomForm_650994d4_1a69_4f1a_975f_7d060b90a3f3

The form base is not valid. Form CustomForm_650994d4_1a69_4f1a_975f_7d060b90a3f3 extends form System.WorkItem.Incident.ConsoleForm, which already has another extension (CustomForm_8c4ec25b_1dc3_4b58_bd43_7c8a83f619a0)

то это означает, что форма уже модифицированна в другом пакете управления.

Если после импортирования пакета ваш контрол выглядит вот таким образом:

image

то скорее всего вы забыли добавить атрибут ContentProperty.

Заключение

С помощью собственных контролов вы можете полностью контролировать поведение формы:

  • Задавать значение по-умолчанию для свойств
  • Изменять параметры других контролов (н-р отключать или включать обязательность полей)
  • Отключать или прятать другие контролы на основе каких-то параметров или роли пользователя

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

Реклама

2 Responses to How-To: Создание собственного UserControl для SCSM 2010 SP1

  1. SigmuS says:

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

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: