CIMTool 用于 Windows Management Instrumentation - 第 2 部分





5.00/5 (5投票s)
使用 WMI 检索有关您的系统的信息。
- 下载 Harlinn.CIMTool-2013-02-03-02-noexe.zip - 113.5 KB
- 下载 Harlinn.CIMTool-2013-02-03-02.zip - 536.3 KB
引言
在CIMTool for Windows Management Instrumentation - 第一部分[^]中,我们对 Windows Management Instrumentation (WMI) 进行了简要介绍,并探讨了如何实现 WMI 命名空间和类的浏览。
第三部分在此提供:CIMTool for Windows Management Instrumentation - 第三部分[^]
在本文中,我们将探讨 CIMTool 如何使用标准的 .Net 机制,如 ICustomTypeDescriptor 和自定义 PropertyDescriptor 类,来欺骗 .Net,使其将动态信息公开为可绑定的属性。

由于 CIMTool 使我们能够查询 WMI,因此我们需要一种机制来显示这些查询的结果。
我希望做一些简单的事情,比如这样
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(); } }
我期望结果的 UI 看起来是这样的

ManagementObjectSearcher 对象 searcher
以 ManagementObjectCollection 的形式提供结果,该集合包含结果集中的每一行对应的 ManagementBaseObject,虽然这些 ManagementBaseObjects 包含我们要显示的信息,但它们确实没有将这些信息公开为可绑定的 .Net 属性。
我们需要一种机制来定制 .Net 用于将结果集绑定到网格显示的行和列的信息。幸运的是,.Net 依赖于 System.ComponentModel 命名空间中的特殊类型,为用户界面组件提供有关其绑定的对象的信息。
在访问元数据以获取属性信息之前,TypeDescriptor 会首先检查被检查的类型是否实现了 System.ComponentModel.ICustomTypeDescriptor 接口。如果实现了,TypeDescriptor 将通过调用 ICustomTypeDescriptor.GetProperties 方法向对象询问它支持哪些属性。这允许对象向 .Net 提供一个 PropertyDescriptorCollection,其中包含它希望向用户界面公开的属性。
对于我们的目的,这意味着我们可以欺骗用户界面组件,使其绑定到我们对象实际上不拥有的属性,为了做到这一点,我们需要为要显示的每一列创建一个 PropertyDescriptor - 在这种情况下,对于表示结果集中一行的 ManagementBaseObject 所公开的 PropertyDataCollection 中的每个 PropertyData 元素,都需要创建一个 PropertyDescriptor。
我们还需要创建一组包装类来包装 System.Management 命名空间中的类,因为我们需要为需要提供自定义属性的类实现 ICustomTypeDescriptor。ManagementBaseObject 的包装类名为 ManagementBaseObjectWrapper – 这在一定程度上描述了类的目的。
ManagementBaseObjectPropertyDescriptor
要为每个 PropertyData 元素创建 PropertyDescriptor,我们首先需要创建一个派生自 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; }
构造函数接受两个参数,一个代表结果集中一行的 ManagementBaseObjectWrapper,以及一个代表我们希望在网格中显示为列的属性的 PropertyDataWrapper。
public override bool Equals(object obj) { ManagementBaseObjectPropertyDescriptor other = obj as ManagementBaseObjectPropertyDescriptor; return other != null && other.property.Equals(property); }
Equals 方法比较存储的 PropertyDataWrapper 对象,而不是 ManagementBaseObjectPropertyDescriptor,并且 PropertyDataWrapper 调用其包装的 PropertyData 的 Equals 方法。
public override int GetHashCode() { return property.GetHashCode(); }
由于任何重写 Object.Equals 的类型也应该重写 Object.GetHashCode,因此我们在此进行重写。
public override string DisplayName { get { return property.Name; } }
通过重写 DisplayName 属性,我们可以控制将显示为列标题的文本。
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 和 GetValue 允许我们设置和检索行中列显示的数据。请注意,IsReadOnly 属性始终返回 true。如果您想实际编辑数据,需要为具有写入限定符设置为 true 的属性返回 false。
public override bool IsReadOnly { get { return true; } } public override Type PropertyType { get { return property.GetDotNetType(); } } }
PropertyType 为 .Net 提供表示属性的 Type。
ManagementBaseObjectWrapper
ManagementBaseObjectWrapper 实现了 ICustomTypeDescriptor – 它还公开了与 System.Management.ManagementBaseObject 相同的函数,但它是通过其他包装类实现的。
ICustomTypeDescriptor 接口所需的大部分方法都可以通过调用 TypeDescriptor 为此目的公开的静态方法来实现,如下所示
AttributeCollection ICustomTypeDescriptor.GetAttributes() { return TypeDescriptor.GetAttributes(this, true); }
我们只需要在需要提供有关我们希望为 ManagementBaseObjectWrapper 对象公开的属性的信息时才做不同的处理
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() { return GetProperties(); }
GetProperties() 方法只需遍历 PropertyDataWrapper 对象的集合,并为每个元素调用虚拟方法 GetPropertyDescriptor。
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; }
我相信您已经注意到,生成的 PropertyDescriptorCollection 会被缓存以供将来调用,因此该方法仅返回缓存的集合,而不是重新计算它。
GetPropertyDescriptor 方法只是为 PropertyDataWrapper 对象创建一个 ManagementBaseObjectPropertyDescriptor 对象
protected virtual PropertyDescriptor GetPropertyDescriptor(PropertyDataWrapper property) { ManagementBaseObjectPropertyDescriptor result = new ManagementBaseObjectPropertyDescriptor(this, property); return result; }
在其他控件中显示数据
由于 CIMTools 在属性窗格中显示所选节点的信息,因此对于网格中选定的行也显示这些信息似乎很自然。有时列太多,无法轻松地适应网格的显示部分,因此我们将让属性窗格也显示列数据。
由于网格绑定到 BindingSource 对象,因此很容易检测用户何时在行之间导航 – 我们只需将事件处理程序附加到 CurrentChanged 事件即可
private void bindingSource_CurrentChanged(object sender, EventArgs e) { try { MainForm.Instance.SelectedObject = bindingSource.Current; } catch (Exception) { } }
上面的代码中的 Instance 是 MainForm 类的静态属性,它返回 MainForm 的当前实例。MainForm 公开了 PropertyGridControl 的 SelectedObject 属性,因此实现此功能非常简单,并表明 PropertyGridControl 与我们的 ICustomTypeDescriptor 实现配合得非常好

备注
使用 ICustomTypeDescriptor 接口非常简单,当您需要显示在编译时未知的信息时,实现它是一个绝佳的选择。
历史
- 2013 年 2 月 3 日 - 首次发布