Service Manager 2012 – How to disable form controls for a Resolved or Closed Incident – Part 1

In Service Manager 2010, when you open an Incident (or most any other Work Item) that is resolved or closed, all of the controls on the form are disabled. You can no longer edit the incident, it cannot be changed.

This is great, you don’t want your analysts, or, indeed, anyone else, making changes to such incidents.

However, in Service Manager 2012, the controls on the form are not disabled. Therefore, anyone with access to the incident can change it after it has been resolved or even closed.

Not good!

So, how do you get around this?

It is possible by adding your own custom control to the incident form. In fact, this technique can be used for the other class forms, too.

Here’s how…

In Visual Studio, create a new .NET Framework 3.5 WPF User Control Library:

Name it like this and add the highlighted references:

Check the code for the xaml file, make sure the namespace, etc match:

namespace DisableForm
{
    public partial class DisableFormControl : UserControl
    {

Right-click “InitializeComponent()” Go To Definition and check the same there:

namespace DisableForm {       
 
    public partial class DisableFormControl : System.Windows.Controls.UserControl, System.Windows.Markup.IComponentConnector {

Open the xaml file in the designer and replace all of the xaml with this:

<UserControl x:Class="DisableForm.DisableFormControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             Visibility="Collapsed"
             Width="Auto" Height="Auto" DataContextChanged="DisableFormControl_DataContextChanged" >
    <Grid Height="Auto" Width="Auto">
        <TextBox Height="0" Margin="0,0,0,0" Name="textId" VerticalAlignment="Top" Width="0" Text="{Binding Path=$Id$, Mode=OneWay}" Visibility="Collapsed" />
    </Grid>
</UserControl>

The textbox is purely to give us a binding to our control to work with.

Now replace all of the xaml.cs code with this:

/*Custom control to disable incident form if status is Resolved or Closed for Service Manager 2012
 * 
 * Rob Ford 2012
 * 
 */

//Standard for .NET 3.5 WPF control
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

//Additional requirements
using System.ComponentModel;
using System.ComponentModel.Design;
using Microsoft.EnterpriseManagement;
using Microsoft.EnterpriseManagement.Common;
using Microsoft.EnterpriseManagement.Configuration;
using Microsoft.EnterpriseManagement.UI.DataModel;
using Microsoft.EnterpriseManagement.UI.SdkDataAccess;
using Microsoft.EnterpriseManagement.ConsoleFramework;
using Microsoft.EnterpriseManagement.UI.WpfControls;

namespace DisableForm
{  
    public partial class DisableFormControl : UserControl
    {
        //Current session
        private EnterpriseManagementGroup emg = null;

        //Current instance
        private IDataItem inst = null;

        public DisableFormControl()
        {
            InitializeComponent();

            //Connect to the current console session
            this.emg = GetSession();
        }

        private EnterpriseManagementGroup GetSession()
        {   
            //Get the current console session management group
            IServiceContainer container = (IServiceContainer)FrameworkServices.GetService(typeof(IServiceContainer));
            Microsoft.EnterpriseManagement.UI.Core.Connection.IManagementGroupSession curSession =
                (Microsoft.EnterpriseManagement.UI.Core.Connection.IManagementGroupSession)container.GetService(typeof(Microsoft.EnterpriseManagement.UI.Core.Connection.IManagementGroupSession));
            return curSession.ManagementGroup;            
        }

        private void DisableFormControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            //Check binding, we want an IDataItem
            if (this.DataContext is IDataItem)
            {
                //Get the IDataItem
                inst = (IDataItem)this.DataContext;

                //Check if this is a new incident form
                if ((bool)inst["$IsNew$"])
                {
                    //You could add code here to set fom defaults, etc
                }
                else
                {
                    //Editing existing incident

                    //Status Guids
                    Guid gStatus = Guid.NewGuid();
                    Guid gResolved = new Guid("2b8830b6-59f0-f574-9c2a-f4b4682f1681");
                    Guid gClosed = new Guid("bd0ae7c4-3315-2eb3-7933-82dfc482dbaf");

                    //Get Status Guid
                    if (inst["Status"] != null) gStatus = (Guid)(inst["Status"] as IDataItem)["Id"];

                    //Check incident status and disable form if needed                    
                    if (gStatus == gResolved || gStatus == gClosed) DisableForm();
                }
            }
        }

        //Disables all required controls on the incident form to prevent editing
        private void DisableForm()
        {
            try
            {
                //Attempt to get the parent TabControl of our custom control
                //There is a TabControl near the top of the tree, we want the one below that
                DependencyObject doParentRoot = GetParentDependancyObject(this, "System.Windows.Controls.TabControl");

                //Did we get the TabControl?
                if (doParentRoot != null)
                {                
                    //Now process each TabItem on the tab control
                    foreach (DependencyObject doChild in LogicalTreeHelper.GetChildren(doParentRoot))
                    {    
                        //Is this a TabItem?
                        if (doChild.GetType().ToString() == "System.Windows.Controls.TabItem")
                        {
                            //Process each child on current tab and disable or set readonly
                            DisableLogicalFormControls(doChild);
                        }
                    }
                }            

                //Now, we must go to the top form level so we can disable the OK and Apply buttons
                //Passing "" will navigate to top
                doParentRoot = GetParentDependancyObject(this, "");
                if (doParentRoot != null)
                {                   
                    //If we got the top parent, disable the buttons
                    DisableVisualButtons(doParentRoot);
                }      
            }
            catch 
            {                
            }
        }

        //Navigates the Visual tree for passed parent and disables buttons, except for Cancel
        private void DisableVisualButtons(DependencyObject rootparent)
        {
            //Get count of children for current parent
            int iCount = VisualTreeHelper.GetChildrenCount(rootparent);

            //Process each child
            for (int i = 0; i < iCount; i++)
            {
                //Get next child 
                DependencyObject rootChild = VisualTreeHelper.GetChild(rootparent, i);

                try
                {
                    //Do we have a button?
                    if (rootChild.GetType().ToString() == "System.Windows.Controls.Button")
                    {
                        //Disable all buttons except for Cancel
                        Button b = (Button)rootChild;                       
                        if (b.Content.ToString().IndexOf("Cancel") == -1) b.IsEnabled = false;
                    }
                }
                catch
                {
                }
                //Disable further children of this child
                DisableVisualButtons(rootChild);
            }
        }

        //Navigates the Logical tree for passed parent and disables common controls or sets to readonly for textboxes
        private void DisableLogicalFormControls(DependencyObject rootparent)
        {
            //Navigate the logical tree for the children          
            foreach (var rootChild in LogicalTreeHelper.GetChildren(rootparent))
            {
                try
                {
                    if (rootChild is DependencyObject)
                    {
                        if (rootChild.GetType().ToString() == "Microsoft.EnterpriseManagement.UI.WpfControls.ListPicker")
                        {
                            //Disable all listpickers
                            ListPicker l = (ListPicker)rootChild;
                            l.IsEnabled = false;
                        }
                        else if (rootChild.GetType().ToString() == "System.Windows.Controls.TextBox")
                        {
                            //Set all textboxes readonly
                            TextBox tb = (TextBox)rootChild;
                            tb.IsReadOnly = true;
                        }
                        else if (rootChild.GetType().ToString() == "System.Windows.Controls.CheckBox")
                        {
                            //Disable all checkboxes
                            CheckBox cb = (CheckBox)rootChild;
                            cb.IsEnabled = false;
                        }
                        else if (rootChild.GetType().ToString() == "System.Windows.Controls.Button")
                        {
                            //Disable all buttons
                            Button b = (Button)rootChild;
                            b.IsEnabled = false;
                        }
                        else if (rootChild.GetType().ToString() == "Microsoft.EnterpriseManagement.UI.WpfControls.SpinControl")
                        {
                            //Disable all spin controls
                            SpinControl sc = (SpinControl)rootChild;
                            sc.IsEnabled = false;
                        }
                    }
                }
                catch
                {
                }

                //Disable further logical children of this child
                if (rootChild is DependencyObject) DisableLogicalFormControls(rootChild as DependencyObject);
            }
        }

        //Returns specified parent object, if found, if name is empty, then navigate to top
        private DependencyObject GetParentDependancyObject(DependencyObject child, string name)
        {
            try
            {
                //We need the logical tree to get our parent
                DependencyObject parent = LogicalTreeHelper.GetParent(child);
                DependencyObject lastparent = null;

                //Is the parent our specified control?
                if (name != "" && parent.GetType().ToString() == name) return parent;

                //No, process further
                while (parent != null)
                {
                    string s = parent.GetType().ToString();
                    if (s == name && name != "") return parent;
                    parent = LogicalTreeHelper.GetParent(parent);
                    if (parent != null) lastparent = parent;
                }
                //Return results
                if (name != "") return null;
                else return lastparent;
            }
            catch
            {
                return null;
            }
        }
    }
}

This code contains everything you need. It connects to the current console session and gets the data binding. A check is made for a new (being created) incident or not. If not, the status is checked to see if the incident is resolved or closed here:

//Editing existing incident

//Status Guids
Guid gStatus = Guid.NewGuid();
Guid gResolved = new Guid("2b8830b6-59f0-f574-9c2a-f4b4682f1681");
Guid gClosed = new Guid("bd0ae7c4-3315-2eb3-7933-82dfc482dbaf");

//Get Status Guid
if (inst["Status"] != null) gStatus = (Guid)(inst["Status"] as IDataItem)["Id"];

//Check incident status and disable form if needed                    
if (gStatus == gResolved || gStatus == gClosed) DisableForm();

DisableForm() is called if the incident status is Resolved or Closed. This then calls several routines that walk the Visual and Logical WPF control trees to obtain the parent TabControl of your custom control. Each tab on this control is then processed and all controls specified and either disabled or set readonly. The controls are specified here:

if (rootChild.GetType().ToString() == "Microsoft.EnterpriseManagement.UI.WpfControls.ListPicker")
{
    //Disable all listpickers
    ListPicker l = (ListPicker)rootChild;
    l.IsEnabled = false;
}
else if (rootChild.GetType().ToString() == "System.Windows.Controls.TextBox")
{
    //Set all textboxes readonly
    TextBox tb = (TextBox)rootChild;
    tb.IsReadOnly = true;
}
else if (rootChild.GetType().ToString() == "System.Windows.Controls.CheckBox")
{
    //Disable all checkboxes
    CheckBox cb = (CheckBox)rootChild;
    cb.IsEnabled = false;
}
else if (rootChild.GetType().ToString() == "System.Windows.Controls.Button")
{
    //Disable all buttons
    Button b = (Button)rootChild;
    b.IsEnabled = false;
}
else if (rootChild.GetType().ToString() == "Microsoft.EnterpriseManagement.UI.WpfControls.SpinControl")
{
    //Disable all spin controls
    SpinControl sc = (SpinControl)rootChild;
    sc.IsEnabled = false;
}

Setting text boxes to read-only allows their text to be copied if required. Also, attachments can still be opened if needed.

A 2nd step then walks to the very top of the tree and sets the Apply and OK buttons on the main form to disabled.

That’s it for the DLL.

Note – if you are not using English as your console language, you’ll need to tweak the code where the OK and Apply buttons are disabled.

Compile the assembly to create DisableFormControl.dll. Those of you who are familiar with adding controls to to your own custom forms can now add this control onto the incident form. That is all that is required. Opening a non-active incident will set the form controls to disabled.

For the rest of you, I will show you how to do this in part 2

This entry was posted in Code, Forms and tagged , . Bookmark the permalink.