2016年7月18日 星期一

自動化設備PC程式參數檔案架構介紹與改進(三)

     PropertyGrid 是一個.Net很方便的控制項,可以顯示Instance中公開的屬性,基本的使用方式就不多著墨了,直接探討在應用上面臨的問題:
  • PropertyGrid只是一個控制項,要讓它像Form可以Show出來。
  • 要有按鈕與User交互(Save, Save as, Accept, Close)
  • 要能用選擇檔案(OpenFileDialog)/目錄(FolderBrowserDialog)的方式設定參數。
  • 基本數值型別可以直接放到PropertyGrid顯示/修改,而自訂型別需要另外處理。
  • 依照不同權限,在PropertyGrid中顯示不同的參數。
先簡述一下如何解決上述問題,首先必須先提到『依照權限顯示參數項目』功能,因為需要有隱藏項目功能的PropertyGrid作為核心控制項,此處參考Code Project中的Filtering properties in a PropertyGrid,把Filter Property Grid拉到Form中,並做一些交互的功能設計(如下圖),之後就會以這個Form作為參數類別的容器與使用者交互。


要能在PropertyGrid中選擇目錄和檔案(下兩圖),雖然FilePath和Folder都是字串型別而已,但總不能要使用者自己手打路徑吧,其實只要在參數上標示特性(Attribute)即可達成,相當容易,參考以下。
// 以FileNameEditor編輯字串,用來選檔案。
[EditorAttribute(typeof(System.Windows.Forms.Design.FileNameEditor) , typeof(System.Drawing.Design.UITypeEditor))]
public string FilePath { get; set; }


// 以FolderNameEditor編輯字串,用來選目錄。
[EditorAttribute(typeof(System.Windows.Forms.Design.FolderNameEditor) , typeof(System.Drawing.Design.UITypeEditor))]
public string Folder { get; set; }

在PropertyGrid中顯示自訂型別(如下圖),這部份比較麻煩一點,自訂類別要繼承ExpandableObjectConverter,然後override CanConvertTo、ConvertTo、CanConvertFrom三個方法,參考以下範例,自訂PointXYZ類別:
public class PointXYZ : ExpandableObjectConverter
{
    public double X { set; get; }
    public double Y { set; get; }
    public double Z { set; get; }

    public PointXYZ()
    {
        X = 0;
        Y = 0;
        Z = 0;
    }

    public PointXYZ(double x , double y , double z)
    {
        X = x;
        Y = y;
        Z = z;
    }

    public override bool CanConvertTo(ITypeDescriptorContext context ,System.Type destinationType)
    {

        if (destinationType == typeof(PointXYZ))
            return true;
        return base.CanConvertTo(context , destinationType);
    }

    public override object ConvertTo(ITypeDescriptorContext context ,
                           CultureInfo culture ,
                           object value ,
                           System.Type destinationType)
    {
        if (destinationType == typeof(System.String) && value is PointXYZ)
        {
            PointXYZ p = (PointXYZ)value;
            return "X:" + p.X +
                   ",Y:" + p.Y +
                   ",Z:" + p.Z;
        }
        return base.ConvertTo(context , culture , value , destinationType);
    }
    public override bool CanConvertFrom(ITypeDescriptorContext context ,
                          System.Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;
        return base.CanConvertFrom(context , sourceType);
    }
}

最後在類別參數標示特性(Attribute) TypeConverterAttribute:
[TypeConverterAttribute(typeof(PointXYZ))]
public PointXYZ SomePoint { get; set; }
詳細作法可以參考MSDN的文章Getting the Most Out of the .NET Framework PropertyGrid Control 中Support for Custom Types章節。



回到依照權限顯示不同項目的功能上,首先要自訂一個特性PermissionsAttribute : Attribute,有需要依照權限隱藏的項目標示特性如下:
[Permissions(2)]
public string Info { get; set; }
在打開參數容器時,需要定義容器權限值,當容器權限小於參數定義的權限特性值時,該參數就會被隱藏,參考以下參數容器程式片段:
PropertyInfo[] piObj = inputObject.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var eachItem in piObj)
{
     PermissionsAttribute attPermission = (PermissionsAttribute)Attribute.GetCustomAttribute(eachItem , typeof(PermissionsAttribute));
     if (attPermission != null)
     {
          if (permission < attPermission.Value)
          {
               hiddenProperties.Add(eachItem.Name);
          }
     }
}
this.filteredPropertyGrid.SelectedObject = inputObject;
this.filteredPropertyGrid.HiddenProperties = hiddenProperties.ToArray();
this.filteredPropertyGrid.Refresh();
上述針對應用面的問題提出解法,並整合了許多網路資源來達到目標,除了本文提到的用法之外,在Getting the Most Out of the .NET Framework PropertyGrid Control文章中也有許多常用的用法,下篇文章將會以範例的方式介紹本文的程式用法。