Click here to Skip to main content
Click here to Skip to main content
Go to top

CIMTool for Windows Management Instrumentation - Part 2

, 22 Feb 2013
Rate this:
Please Sign up or sign in to vote.
Use WMI to retrieve information about your system

Introduction

In CIMTool for Windows Management Instrumentation - Part 1[^] we had a quick introduction to Windows Management Instrumentation (WMI), and we had a look at how browsing the WMI namespaces and classes can be implemented.

Part 3 is available here: CIMTool for Windows Management Instrumentation - Part 3[^]

In this article we will be looking at how CIMTool uses standard .Net mechanism like ICustomTypeDescriptor and custom PropertyDescriptor classes to trick .Net into exposing dynamic information as bindable properties.

Since CIMTool enables us to query WMI, we need a mechanism that allows us to display the results of those queries.

I’d like to do something as simple as this:

ObjectQuery objectQuery = new ObjectQuery(query);
ManagementObjectSearcher searcher = new ManagementObjectSearcher(managementScope, objectQuery, enumerationOptions);
using (searcher)
{
    ManagementObjectCollection objectCollection = searcher.Get();
    using (objectCollection)
    {
        ManagementObjectCollectionWrapper results = new ManagementObjectCollectionWrapper(objectCollection);
        bindingSource.DataSource = results;

        gridView.RefreshData();
    }
}

And I would expect the UI for the results to look something like this:

ManagementObjectSearcher object searcher gives us the results as a ManagementObjectCollection, which contains a ManagementBaseObject for each row in the result set, and while those ManagementBaseObjects’ contains the information we want to display, they certainly doesn’t expose that information as bindable .Net properties.

What we need is a mechanism to customize the information .Net uses to bind our result set to the rows and columns displayed by the grid. Luckily .Net relies on special types from the System.ComponentModel namespace to provide user interface components with information about the objects to which it is bound.

Before accessing the metadata to get property information, TypeDescriptor first checks to see if the type being examined implements the System.ComponentModel.ICustomTypeDescriptor interface. When it does the TypeDescriptor will ask the object, by calling the ICustomTypeDescriptor.GetProperties method, which properties it supports. This allows the object to provide .Net with a PropertyDescriptorCollection containing the properties it wants to expose to the user interface.

For our purpose this means that we can trick the user interface components into binding against properties that our object actually does not have, and to do that we need to create a PropertyDescriptor for each column we want displayed - in this case, one for each PropertyData element in the PropertyDataCollection exposed by the Properties property of the ManagementBaseObject representing a row in the result set.

We also need to create a set of wrapper classes around the classes in the System.Management namespace, since we need to implement ICustomTypeDescriptor for those classes that will provide custom properties. The wrapper class for ManagementBaseObject is named ManagementBaseObjectWrapper – which kind of describes the purpose of the class.

ManagementBaseObjectPropertyDescriptor

To create a PropertyDescriptor for each PropertyData element, we first need to create a class that derives from PropertyDescriptor.

public class ManagementBaseObjectPropertyDescriptor : PropertyDescriptor
{
    private ManagementBaseObjectWrapper objectWrapper;
    PropertyDataWrapper property;

    public ManagementBaseObjectPropertyDescriptor(ManagementBaseObjectWrapper objectWrapper, 
                                                  PropertyDataWrapper property)
        : base(property.Name, null)
    {
        this.objectWrapper = objectWrapper;
        this.property = property;
    }

The constructor takes two arguments, a ManagementBaseObjectWrapper representing a row in the result set, and a PropertyDataWrapper for the property we want to display as a column in the grid.

    public override bool Equals(object obj)
    {
        ManagementBaseObjectPropertyDescriptor other = obj as ManagementBaseObjectPropertyDescriptor;
        return other != null && other.property.Equals(property);
    }

The Equals method compares the stored PropertyDataWrapper objects rather than the ManagementBaseObjectPropertyDescriptor, and the PropertyDataWrapper calls the Equals method of the PropertyData it wraps.

    public override int GetHashCode() 
    { 
        return property.GetHashCode(); 
    }

Since any type that overrides Object.Equals should also override Object.GetHashCode, we do so here.

    public override string DisplayName
    {
        get
        {
            return property.Name;
        }
    }

By overriding the DisplayName property we’re able to control the text that will be displayed as the column heading.

    public override string Description
    {
        get
        {
            return string.Empty;
        }
    }


    public override AttributeCollection Attributes
    {
        get
        {
            return new AttributeCollection(null);
        }
    }

    public override bool CanResetValue(object component)
    {
        return false;
    }


    public override Type ComponentType
    {
        get
        {
            return objectWrapper.GetType();
        }
    }

    public override bool ShouldSerializeValue(object component)
    {
        return true;
    }

    public override void ResetValue(object component)
    {

    }

    public override void SetValue(object component, object value)
    {
        ((ManagementBaseObjectWrapper)component)[property.Name] = value;
    }

    public override object GetValue(object component)
    {
        return ((ManagementBaseObjectWrapper)component)[property.Name];
    }

SetValue and GetValue allow us to set and retrieve the data displayed for a column in a row. Note that the IsReadOnly property always return true. If you want to actually edit the data, you need to return false for properties with a write qualifier set to true.

    public override bool IsReadOnly
    {
        get
        {
            return true;
        }
    }

    public override Type PropertyType
    {
        get
        {
            return property.GetDotNetType();
        }
    }
}

PropertyType provides .Net with the Type representing the property.

ManagementBaseObjectWrapper

ManagementBaseObjectWrapper implements ICustomTypeDescriptor – and it also exposes the same functionality as System.Management.ManagementBaseObject, but it does so in terms of other wrapper classes.

Most of the methods mandated by the ICustomTypeDescriptor interface can be implemented by calling static methods exposed by TypeDescriptor for just that purpose, like this:

AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
    return TypeDescriptor.GetAttributes(this, true);
}

We only need to do something differently when we need to provide information about the properties we want to expose for the ManagementBaseObjectWrapper object:

PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
    return GetProperties();
}

The GetProperties() method simply iterates over the collection of PropertyDataWrapper objects, calling the virtual method GetPropertyDescriptor for each element.

PropertyDescriptorCollection propertyDescriptorCollection;

public PropertyDescriptorCollection GetProperties()
{
    if (propertyDescriptorCollection == null)
    {
        propertyDescriptorCollection = new PropertyDescriptorCollection(null);

        for (int i = 0; i < Properties.Count; i++)
        {
            PropertyDataWrapper property = Properties[i];
            if (propertyDescriptorCollection.Find(property.Name, true) == null)
            {
                PropertyDescriptor propertyDescriptor = GetPropertyDescriptor(property);
                propertyDescriptorCollection.Add(propertyDescriptor);
            }
        }
    }

    return propertyDescriptorCollection;  
}

I’m sure you’ve noticed that the resulting PropertyDescriptorCollection is cached for future calls so that this method then just returns the cached collection rather than recomputing it.

And the GetPropertyDescriptor method just creates a ManagementBaseObjectPropertyDescriptor object for the PropertyDataWrapper object:

protected virtual PropertyDescriptor GetPropertyDescriptor(PropertyDataWrapper property)
{
 ManagementBaseObjectPropertyDescriptor result = new ManagementBaseObjectPropertyDescriptor(this, property);
 return result;
}

Displaying the data in other controls

Since CIMTools displays information about selected nodes in the Properties pane, it seems to be somewhat natural that it should do so for selected rows in the grid. Sometimes there are far more columns than can easily fit into the displayed portion of the grid, so we’ll have the Properties pane display the column data too.

Since the grid is bound to a BindingSource object, it’s easy to detect when the user navigates from one row to the next – we just need to attach an event handler to the CurrentChanged event:

private void bindingSource_CurrentChanged(object sender, EventArgs e)
{
    try
    {
        MainForm.Instance.SelectedObject = bindingSource.Current;
    }
    catch (Exception)
    { }
}

In the above code Instance is a static property of the MainForm class, and it returns the current instance of the MainForm. MainForm exposes the SelectedObject property of the PropertyGridControl, so implementing this functionality was pretty simple, and shows that the PropertyGridControl works very well with our ICustomTypeDescriptor implementation:

Remarks

Working with the ICustomTypeDescriptor interface is pretty easy, and implementing it is an excellent choice when you need to display information that is not known at compile time.

History

  • 3. of February, 2013 - Initial posting

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Espen Harlinn
Architect Powel AS
Norway Norway
Chief Architect - Powel AS.
 
Specializing in integrated operations and high performance computing solutions.
 
I’ve been fooling around with computers since the early eighties, I’ve even done work on CP/M and MP/M.
 
Wrote my first “real” program on a BBC micro model B based on a series in a magazine at that time. It was fun and I got hooked on this thing called programming ...
 
A few Highlights:
  • High performance application server development
  • Model Driven Architecture and Code generators
  • Real-Time Distributed Solutions
  • C, C++, C#, Java, TSQL, PL/SQL, Delphi, ActionScript, Perl, Rexx
  • Microsoft SQL Server, Oracle RDBMS, IBM DB2, PostGreSQL
  • AMQP, Apache qpid, RabbitMQ, Microsoft Message Queuing, IBM WebSphereMQ, Oracle TuxidoMQ
  • Oracle WebLogic, IBM WebSphere
  • Corba, COM, DCE, WCF
  • AspenTech InfoPlus.21(IP21), OsiSoft PI
 
More information about what I do for a living can be found at: harlinn.com or LinkedIn
 
You can contact me at espen.harlinn@powel.no

Comments and Discussions

 
QuestionCrashes on start [modified] Pinmemberledtech33-Feb-13 18:39 
AnswerRe: Crashes on start PinmvpEspen Harlinn4-Feb-13 21:16 
GeneralRe: Crashes on start Pinmemberledtech35-Feb-13 3:14 
GeneralBrilliant, glad you solved it - was: Crashes on start PinmvpEspen Harlinn5-Feb-13 3:59 
GeneralRe: Brilliant, glad you solved it - was: Crashes on start Pinmemberledtech35-Feb-13 4:04 
GeneralRe: Brilliant, glad you solved it - was: Crashes on start PinmvpEspen Harlinn5-Feb-13 4:18 
GeneralRe: Brilliant, glad you solved it - was: Crashes on start Pinmemberledtech35-Feb-13 4:28 
GeneralRe: Brilliant, glad you solved it - was: Crashes on start PinmvpEspen Harlinn5-Feb-13 4:33 
QuestionDevExpress PinmemberMalamberOrg3-Feb-13 10:27 
AnswerRe: DevExpress PinmvpEspen Harlinn4-Feb-13 21:18 
AnswerRe: DevExpress [modified] PinmvpEspen Harlinn5-Feb-13 14:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140926.1 | Last Updated 22 Feb 2013
Article Copyright 2013 by Espen Harlinn
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid