.Net開發筆記(十九) 創建一個可以可視化設計的對象


閱讀本篇博客之前需要了解VS窗體設計器的工作原理,詳細可參見本系列博客(十)(十一)(十二)。必須需要知道的一條結論就是:處於窗體設計器(Form Designer)中的任何組件(包含控件,下同),都是實際存在的一個實例。也就是說,拖進去的button1,其實就是實例化一個Button控件。

通常編碼中,我們在使用一個類型對象時,通過以下方式:

1 Car c = new Car(); //實例化對象
2 c.Type = “標致308”; //設置屬性
3 c.Color = Color.Black; //設置屬性
4 c. InspectionInformation = new Inspection(“2013-11-12”,”張三”,”RS678T”);//設置屬性
5 c.SomethingHappened+=new SomethingHappenedEventHandler(c_SomethingHappened);//注冊事件
6 //以上為對象的初始化  以下開始使用c對象
7 //
View Code

如上所示,我們在使用Car類時,是通過new的方式來創建一個實例,然后給它初始化一些信息,這些所有操作都是通過我們手動來編寫代碼實現的。

我們注意到,在設計UI界面的時候,窗體中的所有控件、組件都是可以通過“屬性窗體”來編輯的,也就是說,界面上這些元素的初始化不需要我們手動編寫代碼,完全可以通過點點鼠標,按按鍵盤就可以做到。我們可以總結出來設計器可以幫我們做以下工作:

  • 實例化對象

    沒錯,不用你手動new對象了,設計器幫你來完成。

  • 編輯屬性

    選定一個組件,在屬性窗體中編輯它的屬性,跟你通過編寫“實例.屬性=屬性值”是一樣的效果。

  • 注冊事件

    選定一個組件,在屬性窗體的事件選項卡,雙擊事件空白處,自動注冊事件。

窗體設計器不需要你手動編寫一行代碼,對象的實例化、屬性編輯、事件注冊全部搞定,也就是說,窗體設計器能夠可視化設計一些對象。至於哪些類型的對象可以通過窗體設計器來進行可視化設計,請參見本系列(十、十一、十二),我在這里直接給出結果:窗體設計器能夠可視化設計實現了IComponent接口類型的對象也就是說,如果你定義了一個類型A,恰好它實現了IComponent接口(直接或者間接),那么你就完全可以通過窗體設計器來可視化設計A類型的對象。

由此可以看出,創建一個可以可視化設計的對象並不難,只要我們的類型實現了IComponent接口就行(官方稱這種類型為組件)。我們再來看一下,窗體設計器初始化出來的對象,跟我們自己手動編寫代碼初始化的對象有哪些相同點和不同點:

不同點:

  • 前者更直觀簡單,隱藏的東西太多,后者復雜,但是清楚內部過程。
  • 前者對象的初始化,在程序一啟動就開始,不能人工控制其時機,具體是在Form1的構造方法中的InitializeComponent()中進行,后者就更靈活,需要的時候編寫代碼就可以。
  • 前者初始化出來的對象幾乎都跟UI界面有關(這個很容易就能想到,窗體設計器肯定設計跟窗體界面有關的東西),而后者沒有這個原則,不管是什么對象,都是可以的。

相同點:

  • 都是初始化一個對象。
  • 都有代碼產生,前者產生的代碼在InitializeComponent()中,后者為人工編寫。

我們應該清楚,程序最終都是要經過將源代碼編譯成可執行文件之后才能運行的,所以源代碼是一切根本,沒有源代碼,其他的都是白扯。

綜上所有之述,我們可以手動編寫代碼來初始化任何對象,我們可以通過窗體設計器來初始化實現了IComponent接口的類型對象。

好了知道怎樣才能創建一個可以可視化設計的對象之后,我們來創建一個試一下,定義一個類型MyComponent,使其繼承自Component:

 1 /// <summary>
 2     /// 可被 可視化設計的類,該類默認只包含屬性
 3     /// </summary>
 4     public partial class MyComponent : Component
 5     {
 6         public MyComponent()
 7         {
 8             InitializeComponent();
 9         }
10 
11         public MyComponent(IContainer container)
12         {
13             container.Add(this);
14             InitializeComponent();
15         }
16         /// <summary>
17         /// 字符串屬性 使用默認屬性編輯器
18         /// </summary>
19         public string StringProperty
20         {
21             set;
22             get;
23         }
24         /// <summary>
25         /// 顏色屬性  使用默認屬性編輯器
26         /// </summary>
27         public Color ColorProperty
28         {
29             set;
30             get;
31         }
32         /// <summary>
33         /// 自定義類型屬性 使用下拉列表編輯器
34         /// </summary>
35         [Editor(typeof(MyTypeEditor1),typeof(UITypeEditor)),DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
36         public MyType MyTypeProperty1
37         {
38             set;
39             get;
40         }
41         /// <summary>
42         /// 自定義類型屬性 使用彈出對話框編輯器
43         /// </summary>
44         [Editor(typeof(MyTypeEditor2),typeof(UITypeEditor)),DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
45         public MyType MyTypeProperty2
46         {
47             set;
48             get;
49         }
50         /// <summary>
51         /// 控件屬性 可以在設計器中選擇已經存在的控件
52         /// </summary>
53         [Editor(typeof(ControlEditor),typeof(UITypeEditor))]
54         public Control ControlProperty
55         {
56             set;
57             get;
58         }
59         /// <summary>
60         /// ImageList類型屬性 
61         /// </summary>
62         [Editor(typeof(ImageListEditor),typeof(UITypeEditor))]
63         public ImageList ImageListProperty
64         {
65             get;
66             set;
67         }
68     }
View Code

如上代碼所示,該類型只包含了幾個公共屬性,沒有其他內容。此類型對象就可以通過窗體設計器來設計了,也就是說,從工具箱中向設計器中拖放MyComponent類型之后,窗體設計器自動會實例化一個MyComponent對象,並且你可以通過屬性窗體來編輯該對象的屬性:

1)StringProperty

     String類型屬性,直接可以在屬性窗體中輸入。

2)ColorProperty

     Color類型屬性,屬於.NET自帶類型,所以有默認的屬性編輯器,如下圖:

圖1

3)MyTypeProperty1

    自定義類型屬性,需要我們自己定義一個屬性編輯器Editor(typeof(MyTypeEditor1),typeof(UITypeEditor))。

4)MyTypeProperty2

    自定義類型屬性,需要我們自己定義一個屬性編輯器Editor(typeof(MyTypeEditor2),typeof(UITypeEditor))。

5)ControlProperty

    Control類型屬性,我們可以將設計器中已經存在的Control賦值給該屬性,指定了屬性編輯器Editor(typeof(ControlEditor),typeof(UITypeEditor))。

6)ImageListProperty

    ImageList屬性,這個就是我們常見的一些控件(比如TabControl)含有ImageList屬性,點擊右方的小三角形,就可以列出窗體設計器中已經存在的ImageList,供你選擇。指定了屬性編輯器Editor(typeof(ImageListEditor),typeof(UITypeEditor))。

也就是說,當我們在窗體設計器中設計一個對象的時候,如果該對象包含一些特殊(非.NET默認自帶類型)類型屬性時,我們需要為該屬性提供一個“屬性編輯器”。

以下就分別為每個屬性對應的屬性編輯器了(假設諸位看官都知道了UITypeEditor的作用,不知道可以查一下):

1)MyTypeProperty1屬性

 1 class MyTypeEditor1 : UITypeEditor
 2     {
 3         public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
 4         {
 5             return UITypeEditorEditStyle.DropDown; //下拉列表 在下拉列表中輸入MyType屬性值
 6         }
 7         public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
 8         {
 9             usrMyTypeEditor usrme = new usrMyTypeEditor(); //下拉列表
10             usrme.EditedValue = value as MyType; //初始化下拉列表框 value為舊值
11 
12             IWindowsFormsEditorService ie = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
13             if (ie != null)
14             {
15                 ie.DropDownControl(usrme);  //顯示下拉列表
16                 return usrme.EditedValue; //返回編輯后的值
17             }
18             else
19             {
20                 return value;
21             }
22         }
23 }
View Code

鼠標點擊MyTypeProperty1屬性右側的小三角形,出現一個下拉列表框。如下圖:

圖2

2)MyTypeProperty2屬性

 1 class MyTypeEditor2 : UITypeEditor
 2     {
 3         public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
 4         {
 5             return UITypeEditorEditStyle.Modal; //模式對話框 在彈出對話框中輸入MyType屬性值
 6         }
 7         public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
 8         {
 9             using (frmMyTypeEditorForm frmmef = new frmMyTypeEditorForm())
10             {
11                 frmmef.EditedValue = value as MyType; //初始化對話框
12                 IWindowsFormsEditorService ie = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
13                 if (ie != null)
14                 {
15                     if (ie.ShowDialog(frmmef) == DialogResult.OK) //顯示模式對話框
16                     {
17                         return frmmef.EditedValue; //返回編輯后的值
18                     }
19                     else
20                     {
21                         return value; //返回舊值
22                     }
23                     
24                 }
25                 else
26                 {
27                     return value;
28                 }
29             }
30         }
31 }
View Code

鼠標點擊MyTypeProperty2右側的小三角形,彈出一個對話框。

圖3

3)ControlProperty屬性

 1 class ControlEditor : UITypeEditor
 2     {
 3         IWindowsFormsEditorService ie = null;
 4 
 5         public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
 6         {
 7             return UITypeEditorEditStyle.DropDown; //下拉列表 在下拉列表中選擇一個(設計器中已經存在)控件
 8         }
 9         public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
10         {
11             ListBox li = new ListBox(); //下拉列表
12             li.Click += new EventHandler(li_Click);
13             List<Control> liCo=new List<Control>(); //下拉列表每一項對應的控件值
14             foreach(Component c in context.Container.Components) //查找窗體設計器中的每一個組件
15             {
16                 if (c is Control && !(c is Form)) //若是控件 不是窗體
17                 {
18                     li.Items.Add((c as Control).Name); //將控件名稱寫入listbox
19                     if (value as Control == c as Control)
20                     {
21                         li.SelectedIndex = li.Items.Count - 1; //選中原來值
22                     }
23                     liCo.Add(c as Control); //對應控件值寫入list
24                 }
25             }
26             ie = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
27             if (ie != null)
28             {
29                 ie.DropDownControl(li);
30                 if (li.SelectedIndex > -1)
31                     return liCo[li.SelectedIndex];
32                 else
33                     return value;
34             }
35             else
36             {
37                 return value;
38             }
39         }
40 
41         void li_Click(object sender, EventArgs e)
42         {
43             if (ie != null)
44             {
45                 ie.CloseDropDown();
46             }
47         }
48 }
View Code

鼠標點擊ControlProperty右側的小三角形,出現下拉列表框,列表中顯示的都是窗體設計器中已經存在的Control。

圖4

4)ImageListProperty屬性

 1 class ImageListEditor : UITypeEditor
 2     {
 3         IWindowsFormsEditorService ie = null;
 4 
 5         public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
 6         {
 7             return UITypeEditorEditStyle.DropDown; //下拉列表 在下拉列表中選擇一個(設計器中已經存在)ImageList組件
 8         }
 9         public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
10         {
11             ListBox li = new ListBox(); //下拉列表
12             li.Items.Add("");
13             li.Click += new EventHandler(li_Click);
14             List<ImageList> liCo = new List<ImageList>(); //下拉列表每一項對應的ImageList值
15             foreach (Component c in context.Container.Components) //查找窗體設計器中的每一個組件
16             {
17                 if (c is ImageList) //若是ImageList
18                 {
19                     li.Items.Add((c as ImageList).ToString().Split(' ')[0]); //將ImageList名稱寫入listbox
20                     if (value as ImageList == c as ImageList)
21                     {
22                         li.SelectedIndex = li.Items.Count - 1; //選中原來值
23                     }
24                     liCo.Add(c as ImageList); //對應ImageList寫入list
25                 }
26             }
27             ie = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
28             if (ie != null)
29             {
30                 ie.DropDownControl(li);
31                 if (li.SelectedIndex == 0)  //
32                     return null;
33                 else if (li.SelectedIndex > 0)
34                     return liCo[li.SelectedIndex-1];
35                 else
36                     return value; 
37             }
38             else
39             {
40                 return value;
41             }
42         }
43 
44         void li_Click(object sender, EventArgs e)
45         {
46             if (ie != null)
47             {
48                 ie.CloseDropDown();
49             }
50         }
51 }
View Code

鼠標點擊ImageListProperty右側的小三角形,出現下拉列表,列表中顯示窗體設計器中已經存在的ImageList。

圖5

本篇博客介紹了怎么創建一個可以可視化設計的對象(准確來講,應該是怎樣創建一個可以可視化設計其對象的類型),首先讓類型(間接或者直接)實現IComponent接口(不要問我為什么,請參見前面的博客),然后為類型的某些特殊屬性創建對應的屬性編輯器,就這么簡單。類型創建者工作量大一點,但是類型使用者更方便一些,Demo中MyComponent類型就是最后的核心成果,供使用者使用,使用者不需要知道其他的類似屬性編輯器之類的東西,在他們看來,這些相當於沒有。

注意使用范圍,並不是任何時候都可以使用,最好參見前面提到的“相同點”和“不同點”那里。另外,本篇博客中設計到的知識點很多,像provider.GetService(typeof(IWindowsFormsEditorService))、foreach(Component c in context.Container.Components)這些需要和設計器打交道的地方我幾乎沒有詳細說到過,原因是這些太復雜了,要是細說的話,得說一大簍子,而且不是本篇文章的重點。

源碼下載地址:http://files.cnblogs.com/xiaozhi_5638/PropertyEditorInDesigner.rar

注意不要試圖運行源代碼,沒有任何效果,只能在設計器中看到效果。希望有幫助。

Update (2013-12-09)

上面結束提到了“支持可視化設計對象”的使用場合,建議如果該類型跟UI界面有關聯(需要與界面其他元素交互),並且對象的實例化不需要人工控制其時機,那么可以讓該類型支持可視化設計(也就是實現IComponent接口)。注意這里的建議,也就是說不是強制性的,你完全可以定義一個People類,讓其實現IComponent接口,這合法!結果就是,從工具欄中拖放一個People到窗體設計器中,它會自動幫你實例化了一個People對象實例,並且你也能通過屬性窗體編輯它的屬性值(這些后台都能自動生成對應代碼)。另外,經發現,具備某一些功能的類型,雖然跟界面無交互,但是它還是支持可視化設計,比如BackgroundWorker組件,拖一個BackgroundWorker到窗體設計器中去,設計器會自動幫你實例化一個Backgroundworker對象(生成對應代碼),然后你可以通過屬性窗體編輯它的屬性值(生成對應代碼),這跟你自己手動new BackgroundWorker沒有區別,你還是可以在其他地方一樣使用該backgroundworke對象,至少你目前看起來沒區別。

為了更好的說明通過窗體設計器設計出來的對象,跟我們手動編寫代碼搞出來的對象有什么相同和不同點,我們先來分析一下Form1.Designer.cs中的代碼:

我們注意到,我們在設計器中的每一步操作,對應生成的代碼都在InitializeComponent()方法中,它像是word錄制宏的功能,你在word中的每一個操作,都可以生成對應的VBA代碼,也就是說,窗體設計器從實例化對象,到編輯屬性,再到注冊事件等等等,都有代碼幫我們記錄下這些操作。我們再看一下實例化對象的代碼:

1 this.myComponent1 = new PropertyEditorInDesigner.MyComponent(this.components);
View Code

沒錯,任何一個實例化出來的對象,都給它傳遞了this.components容器,我們再看MyComponent的構造方法是這樣的:

1  public MyComponent(IContainer container)
2         {
3             container.Add(this);
4             InitializeComponent();
5         }
View Code

也就是說,對象實例化的時候,都將該對象放進了一個components的容器,這個容器專門用來存放由窗體設計器實例化出來的對象(控件除外)。這就像一個大的容器,專門來存放這些小個體。接下來我們再來看一下Form1.Designer.cs中的dispose方法:

1   protected override void Dispose(bool disposing)
2         {
3             if (disposing && (components != null))
4             {
5                 components.Dispose();
6             }
7             base.Dispose(disposing);
8         }
View Code

我們可以發現,在Form1對象Dispose的時候,它將components中的所有個體都Dispose掉了。到此,我們可以總結出來一條:由窗體設計器設計出來的對象由父窗體(父控件)的InitializeComponents方法統一初始化,由父窗體(父控件)的Dispose()方法統一釋放資源。我們來看一張Form1運行結構圖

圖(更新)1

由此可以看出,由窗體設計器設計出來的對象,它們的生命周期以及存放結構都比較有規律,這個就是窗體設計器設計出來對象的好處。

我們在往窗體設計器中拖放組件時,就是往Form1類型中添加新的成員對象,跟我們定義一個類型,向里面添加成員變量一個意思,只是前者更直觀,添加的每一個成員對象,在UI設計器中都能看見與它對應的一個對象實例,只要我們改變了這個對象實例的屬性,就能馬上看見設計器中的對象實例效果,緊接着后台生成代碼;而后者就沒有這么直觀了,只能手寫代碼,而且還看不見效果。

圖(更新)2

如上圖,窗體設計器中某一個對象實例屬性更新之后,馬上就能在設計器中看見效果(因為它是實實在在存在於堆中的對象),接着InitializeComponents中的代碼就會更新,注意,窗體設計器中的myComponent1對象實例跟.cs代碼中的myComponent1變量不是一個東西,他們只有一種映射關系。我們最終要的是.cs中的代碼文件,而不是我們看見的窗體設計器中的圖像,后者只是起到一個可視化的效果,最終一文不值。這個就像photoshop作圖一樣,最終保存到硬盤的圖片文件才是最重要的,作圖過程中作圖區域顯示的東西沒有價值。窗體設計器隱藏得越多,我們知道得越少。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM