How-To: Создание собственного UserControl для SCSM 2010 SP1
01.07.2011 2 комментария
ВНИМАНИЕ! Описанные здесь техники в основном являются не документированными и не поддерживаемыми со стороны Microsoft. Информация предоставлена “как есть”, автор не несет ответственности за возможную потерю инфомрацию.
Вступление
Всё чаще я встречаю вопросы о том, как можно расширить функциональность SCSM с помощью собственных форм или контролов. Лично я предпочитаю не использовать полностью переписанные формы без крайней необходимости: это довольно большой объем работы, и при выходе новой версии продукта (выход намечен на Q4 2011 – Q1 2012) с почти 100% гарантией ваша форма перестанет работать. Контролы требуют меньше времени на разработку, а шанс, что они будут работать и в следующей версии продукта, довольно велик.
Чтобы понимать, о чем пойдет речь в данной статье, необходимо иметь представление о следующих технологиях:
- WPF (Windows Presentation Foundation), в особенности Binding
- DependencyProperty
- XML-схема пакета управления
Итак, нам необходимо решить следующие задачи:
- Создать новый UserControl в Visual Studio
- Подключить этот контрол к форме
- Доставить библиотеку с контролом на все компьютеры с консолью SCSM
Создание UserControl-а
Для создания нового UserControl-а необходимо открыть Visual Studio, и выбрать новый проект WPF User Conrtol Library:
Имя решения и имя класса могут быть любыми. После этого необходимо отредактировать название контрола.
Затем необходимо подключить к проекту несколько библиотек:
- Microsoft.EnterpriseManagement.Core.dll (расположена в папке c:\Program Files\Microsoft System Center\Service Manager 2010\SDK Binaries на сервере SCSM)
- Microsoft.EnterpriseManagement.UI.Foundation.dll (расположена в папке c:\Program Files\Microsoft System Center\Service Manager 2010\)
- 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-код в любом редакторе. Чтобы добавить собственный контрол на форму необходимо:
- Создать новую модификацию формы. Как это сделать описано много где в сети, например здесь и здесь, а вот здесь даже с видео
- Добавить на форму в место, где должен находится наш конрол, любой стандартный контрол, например Label
- Сохранить пакет управления, затем открыть его в любом текстовом редакторе.
- Найти секцию Forms, а в ней добавленный контрол.
- Заменить атрибуты Assembly и Type на данные нашего контрола. PublicKey можно узнать с помощью каманды sn.exe –T <путь к сборке> (находится в папке c:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\). Можно добавить эту команду в инструменты Visual Studio
- При необходимости, добавить другие свойства с помощью элемента <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>
В итоге у меня получился вот такой контрол:
Для новых инцидентов автоматически заполняется Название и Группа подержки:
Доставка библиотеки
Теперь нам необходимо скопировать нашу библиотеку на все рабочие станции с консолью 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)
то это означает, что форма уже модифицированна в другом пакете управления.
Если после импортирования пакета ваш контрол выглядит вот таким образом:
то скорее всего вы забыли добавить атрибут ContentProperty.
Заключение
С помощью собственных контролов вы можете полностью контролировать поведение формы:
- Задавать значение по-умолчанию для свойств
- Изменять параметры других контролов (н-р отключать или включать обязательность полей)
- Отключать или прятать другие контролы на основе каких-то параметров или роли пользователя
Скачать готовый проект с примером на Visual Studio, а также готовый пакет управления вы можете здесь.
Очень познавательная статья. Особенно порадовала возможность делать обязательными другие поля на форме, например «Описание», бывает полезным. Есть один вопросик — после кастомизации МП можно ли его запечатать, потому что незапечатав его, не получится применять к расширенной форме шаблоны.
Конечно можно.