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.
В итоге у вас должно получиться нечто, похожее на это:
010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445namespaceSCSMControls{[ContentProperty("SelectedItem")]publicpartialclassSCSMControl : UserControl, INotifyPropertyChanged{publiceventPropertyChangedEventHandler PropertyChanged;publicSCSMControl(){InitializeComponent();}publicstaticreadonlyDependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem",typeof(string),typeof(SCSMControl),newUIPropertyMetadata(null,newPropertyChangedCallback(SCSMControl.OnSelectedItemChanged)));publicstringSelectedItem{get{return(string)base.GetValue(SelectedItemProperty);}set{base.SetValue(SelectedItemProperty, value);NotifyPropertyChanged("SelectedItem");}}privatestaticvoidOnSelectedItemChanged(DependencyObject o, DependencyPropertyChangedEventArgs e){//TODO}/// <summary>/// INotifyPropertyChanged implementation/// </summary>/// <param name="propertyName"></param>privatevoidNotifyPropertyChanged(stringpropertyName){if(this.PropertyChanged !=null){this.PropertyChanged(this,newPropertyChangedEventArgs(propertyName));}}}}
Я также добавил интерфейс INotifyPropertyChanged, чтобы внешние компоненты могли подписываться на изменение свойств нашего контрола. Кроме этого, я показал как можно подписаться на изменение свойства нашего компонента. Иногда это бывает полезно. Но использовать оба эти подхода не обязательно.
Теперь наш контрол можно добавлять на форму в SCSM, но он пока не делает ничего полезного. Для начала нужно отобразить какие-нибудь данные. В этом нам поможет такой мощный механизм WPF, как привязка (binding). Но чтобы использовать привязку, необходимо знать к каким свойствам привязываться.
DataContext формы (а значит и нашего контрола) заполняется объектом типа IDataItem. Этот тип не документирован, поэтому вам придется исследовать его самим или поверить мне)). Объект типа IDataItem хранит в себе все свойства объекта, для которого открыта форма. Доступ к объектам осуществляется операцией взятия индекса ([]), а индексатором выступает внутреннее имя поля (или имя Type Projection). Несколько примеров:
1234IDataItem item =this.DataContextasIDataItem;stringtitle = (string)item["Title"];stringstatus = (string)(item["Status"]asIDataItem)["DisplayName"];stringaffectedUser = (string)(item["AffectedUser"]asIDataItem)["DisplayName"];
Как видно из примера, мы можем обращаться к вложенным свойствам полученных объектов также через IDataItem.Чтобы использовать эти значения в привязке, достаточно указать название свойства. Добавим на наш контрол несколько элементов:
12345678<TextBoxName="boxID"Grid.Column="1"Grid.Row="0"Text="{Binding Path=$Id$, Mode=OneWay}"VerticalAlignment="Center"HorizontalAlignment="Stretch"IsReadOnly="True"/><TextBoxName="boxName"Grid.Column="1"Grid.Row="1"Text="{Binding Path=Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"VerticalAlignment="Center"HorizontalAlignment="Stretch"/><TextBoxName="boxAffectedUser"Grid.Column="1"Grid.Row="2"VerticalAlignment="Center"HorizontalAlignment="Stretch"IsReadOnly="True"><TextBox.Text><BindingPath="AffectedUser.DisplayName"Mode="OneWay"FallbackValue="No Affected User"/></TextBox.Text></TextBox><TextBoxName="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):
1234567891011121314151617181920212223[ContentProperty("SelectedItem")]publicpartialclassSCSMControl : UserControl, INotifyPropertyChanged{EnterpriseManagementGroup mg;publiceventPropertyChangedEventHandler PropertyChanged;publicSCSMControl(){InitializeComponent();GetSession();}voidGetSession(){// Get the current session, more info: http://blogs.technet.com/servicemanager/archive/2010/02/11/tasks-part-1-tasks-overview.aspxIServiceContainer container = (IServiceContainer)FrameworkServices.GetService(typeof(IServiceContainer));IManagementGroupSession curSession = (IManagementGroupSession)container.GetService(typeof(IManagementGroupSession));if(curSession ==null)thrownewValueUnavailableException("curSession is null");mg = curSession.ManagementGroup;}}
Итак, мы имеет доступ к SDK, давайте сделаем что-нибудь полезное. Н-р установим свойства по-умолчанию для нового объекта. Для этого нам необходимо получить объект IDataItem из DataContext, а затем установить свойства. Делать это в обработчике события FormLoaded не стоит – в этом момент DataContext еще не заполнен. Вместо этого мы подписываемся на изменение свойства DataContext, и когда там оказывается нужный нам объект – устанавливаем свойства:
12345678910111213141516privatevoidUserControl_DataContextChanged(objectsender, DependencyPropertyChangedEventArgs e){// wait bindingif(this.DataContextisIDataItem){instance = (this.DataContextasIDataItem);// If this is new incident, set some default propertiesif((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.Tier2instance["TierQueue"] = mg.EntityTypes.GetEnumeration(newGuid("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>
Вот пример для формы инцидента:
12345678910111213141516171819202122232425262728293031<FormID="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><AddControlParent="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"/><PropertyChangeObject="Label_1"Property="HorizontalAlignment"><NewValue>Left</NewValue></PropertyChange><PropertyChangeObject="Label_1"Property="VerticalAlignment"><NewValue>Bottom</NewValue></PropertyChange><PropertyChangeObject="Label_1"Property="Content"><NewValue>Custom control:</NewValue></PropertyChange><AddControlParent="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"/><PropertyChangeObject="SCSMControl_1"Property="Width"><NewValue>Auto</NewValue></PropertyChange><PropertyChangeObject="SCSMControl_1"Property="Height"><NewValue>Auto</NewValue></PropertyChange><PropertyChangeObject="SCSMControl_1"Property="VerticalAlignment"><NewValue>Bottom</NewValue></PropertyChange><PropertyChangeObject="SCSMControl_1"Property="Margin"><NewValue>0,0,0,0</NewValue></PropertyChange><PropertyChangeObject="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% – версия пакета управления, который содержит ссылку на ресурс
Нам достаточно добавить в конце нашего пакет управления ссылку на сборку:
123456</LanguagePacks><!-- Section For Assembly --><Resources><AssemblyID="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, а также готовый пакет управления вы можете здесь.
Очень познавательная статья. Особенно порадовала возможность делать обязательными другие поля на форме, например «Описание», бывает полезным. Есть один вопросик — после кастомизации МП можно ли его запечатать, потому что незапечатав его, не получится применять к расширенной форме шаблоны.
Конечно можно.