本文主要說到以下內容:
- 什么是.Net中的組件,組件和類、控件的區別和聯系。
- 組件的特性。
- 利用IExtenderProvider接口進行組件擴展。
- “擴展組件”的簡單應用——控件倒影。
1. 什么是.Net中的組件,組件和類、控件的區別和聯系
必須說的是,“組件”一詞在編程中經常遇到,意義眾多,當然不管什么意思,從字面上來看就知道它應該有“一個可重復使用的單元”的意思。在.Net中,“組件”就特指實現了System.ComponentModel.IComponent接口的類,或者從實現了該接口的類直接或間接派生的子類。因此,“組件”它屬於“類”,滿足一定標准的類就是組件,而這個標准就由IComponent接口提供。那么什么是控件?組件跟控件有什么關系?我們知道,所有的控件都從System.Windows.Forms.Control類直接或間接派生(Asp.Net中從System.Web.UI.Control),而再查看System.Windows.Forms.Control源碼我們會發現,它繼承自System.ComponentModel.Component類,后者又實現了System.ComponentModel.IComponent接口,因此,我們可以說,控件也是一種組件。
綜上,我們可以得出結論:
- 類包含組件,組件包含控件。
- 類不一定是組件,但組件一定是類,組件不一定是控件,但控件一定是組件。
- 不管是類、組件還是控件,它們都是“一個可重復使用的代碼單元”。
- 不管是類、組件還是控件,他們都應該符合OO編程規律。
舉一個FCL中的例子,來說明問題:
- System.Windows.Forms.Application是一個類,既不是組件也不是控件。
- System.Windows.Forms.Timer是一個類,也是一個組件,但不是控件(注意)。
- System.Windows.Forms.ToolTip是一個類,也是一個擴展組件(后面說明什么是擴展組件),但不是控件。
- System.Windows.Forms.Button是一個類,也是一個組件,同時也是控件。
上一張圖說明關系:
圖1
2. 組件的特性
1)假設各位知道Dispose模式,Dispose模式一般用於對象中使用了資源(包括托管資源和非托管資源)的場合,具體就是讓定義的類實現IDisposable接口。查看System.ComponentModel.IComponent源碼會發現,它實現了IDisposable接口,因此,可以說,所有的組件都具備有效管理資源的特性。
2)如果各位使用VS開發工具,那么,凡是實現了System.ComponentModel.IComponent接口的類,即凡是組件者,都可以將它添加在ToolBox中(工具箱),也就是常說的“可視化支持”。
3)Winfrom開發過程中,在設計窗體時,查看Form1.Desinger.cs文件,Form1類中有private System.ComponentModel.IContainer components數據成員,查看InitializeComponent()方法中會發現,一切類似Timer、ImageList、ToolTip這樣的組件在構造時,都會像下面這樣:
this.imageList1 = new System.Windows.Forms.ImageList(this.components);
其中System.ComponentModel.IContainer字面意思可以看出它是一個容器,也就是說,每個組件在自我構造的時候,都會把自己加到一個容器中去。事實上,組件一般都不會單獨去使用,都是由容器去管理,容器生命期結束時,容器中的各個組件也會結束自己生命期。
注:容器包含組件只是邏輯包含,跟通常的List、Array等集合包含元素不同。
有關IDE對組件的可視化支持,以及容器與組件之間的詳細關系是一個非常復雜的話題,本文暫時不涉及。
3. 利用IExtenderProvider接口進行組件擴展
通常當使用一個組件時,需要在原有的基礎上添加新的功能,也就是說需要擴展原有功能,我們做不到修改原有組件的代碼,比如我們需要一個圓角按鈕,現有的標准按鈕(System.Windows.Forms.Button,前面說過它屬於組件)是直角,第一想到的辦法就是新建一個ButtonEx類繼承Button,建立一個新的擴展控件。
以上是一種辦法,但當需要給一類組件增加新的功能時,使用以上方法就會導致出現許多的擴展控件,而且有些時候只是新增加一個小功能,並不需要產生新的控件,因此,這時候有必要看看System.ComponentModel.IExtenderProvider這個接口。關於這個接口,我就不引用MSDN上的定義了,描述得太抽象,基本沒什么參考價值,我將該接口的功能描述如下:
當需要為其它某個(或某些)組件擴展新的功能,又不能做到直接修改原有組件的源代碼時,我們就可以定義一個類,讓其實現IExtenderProvider接口。
IExtenderProvider接口源碼如下:

public interface IExtenderProvider { bool CanExtend(object extendee); }
FCL中已有的例子有很多,前面提到過的ToolTip就屬於這種,查看System.Windows.Forms.ToolTip源碼會發現,它繼承自System.ComponentModel.Component,並且實現了System.ComponentModel.IExtenderProvider接口,大概源碼如下:(模仿,非實際)

1 [ProvideProperty(“ToolTip”,typeof(Control))] 2 Public class ToopTip:Component,IExtenderProvider 3 { 4 Public ToolTip() 5 { 6 7 } 8 Public ToolTip(IContainer cont) 9 { 10 Cont.Add(this); 11 } 12 13 Public string GetToolTip(Control control) 14 { 15 //具體實現 16 } 17 Public void SetToolTip(Control control,string caption) 18 { 19 //具體實現 20 } 21 //符合擴展的條件 22 public bool CanExtend(object target) 23 { 24 return ((target is Control) && !(target is ToolTip)); //所有控件 25 } 26 }
解釋:[ProvideProperty(“ToolTip”,typeof(Control))]的意思是給所有的Control類及其派生類增加屬性“ToolTip”,也就是說原來的標准控件Button現在應該增加了ToolTip屬性,在界面設計器中,從工具欄中拖出一個ToolTip組件,那么所有的其他控件在屬性欄中增加了一項“toolTip1 上的 ToolTip”,可以設置該新增的屬性,比如我給按鈕button1設置該屬性“it’s a button”,那么設計器生成的代碼就是toolTip1.SetToolTip(button1,”it’s a button”);。
注:以上GetToolTip(Control control)和SetToolTip(Control control,string caption)方法具體實現暫沒說明,不同情況實現不一樣,具體可以參考下面的“控件倒影”demo代碼。
4.“擴展組件”的簡單應用——控件倒影。
讓每一個標准控件都具有倒影效果,這一要求完全符合IExtenderProvider接口的使用范圍,第一,需要給組件擴展新的功能;第二,數量之多,單單通過繼承創建新的擴展控件麻煩;第三,新加功能很小,只是增加一個倒影效果。先上一張效果圖:
圖2
為了讓任何一個控件都具有“倒影效果”,可以給每個控件擴展一個HasMirror的屬性,數據類型為Bool型,另外,需要創建一個倒影類,負責顯示倒影。
擴展類:

1 [ProvideProperty("HasMirror",typeof(Control))] 2 class MirrorExtender : Component, IExtenderProvider 3 { 4 Dictionary<Control, MirrorItem> _controllist = new Dictionary<Control, MirrorItem>(); 5 public MirrorExtender() 6 { 7 8 } 9 public MirrorExtender(IContainer cont) 10 { 11 cont.Add(this); 12 } 13 public void SetHasMirror(Control control, bool hasMirror) 14 { 15 if (_controllist.ContainsKey(control)) 16 { 17 if (!hasMirror) 18 { 19 _controllist[control].Close(); 20 _controllist.Remove(control); 21 } 22 } 23 else 24 { 25 if (hasMirror) 26 { 27 MirrorItem i = new MirrorItem() { HasMirror = hasMirror, Mirror = new Mirror(control) }; 28 _controllist.Add(control, i); 29 } 30 } 31 } 32 public bool GetHasMirror(Control control) 33 { 34 if (_controllist.ContainsKey(control)) 35 { 36 return _controllist[control].HasMirror; 37 } 38 else 39 { 40 return false; 41 } 42 } 43 44 #region IExtenderProvider 成員 45 public bool CanExtend(object extendee) 46 { 47 return (extendee is Control); 48 } 49 #endregion 50 } 51 class MirrorItem 52 { 53 public bool HasMirror { get; set; } 54 public Mirror Mirror { get; set; } 55 public void Close() 56 { 57 Mirror.Dispose(); 58 } 59 }
倒影類:

1 class Mirror : Control 2 { 3 Control _target; 4 Padding Padding { get; set; } 5 Bitmap CtrlBmp { get; set; } 6 byte[] CtrlPixels { get; set; } 7 int CtrlStride { get; set; } 8 Bitmap Frame { get; set; } 9 public Mirror(Control target) 10 { 11 _target = target; 12 _target.VisibleChanged += new EventHandler(_target_VisibleChanged); //目標控件“可見”屬性發生變化 13 _target.LocationChanged += new EventHandler(_target_LocationChanged); //目標控件“位置”屬性發生變化 14 _target.ParentChanged += new EventHandler(_target_ParentChanged); //目標控件“父控件”屬性發生變化 15 _target.Paint += new PaintEventHandler(_target_Paint); //目標控件發生重繪 16 17 SetStyle(ControlStyles.Selectable, false); //鏡子應無焦點 18 SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true); 19 20 InitPadding(); 21 } 22 23 /// <summary> 24 /// 目標控件發生變化時,鏡子中的內容需要重繪 25 /// </summary> 26 /// <param name="sender"></param> 27 /// <param name="e"></param> 28 void _target_Paint(object sender, PaintEventArgs e) 29 { 30 if (!isSnapshotNow) 31 { 32 Invalidate(); 33 } 34 } 35 /// <summary> 36 /// 目標控件改變父控件時,初始化鏡子 37 /// </summary> 38 /// <param name="sender"></param> 39 /// <param name="e"></param> 40 void _target_ParentChanged(object sender, EventArgs e) 41 { 42 Init(); 43 } 44 /// <summary> 45 /// 目標控件位置變化時,初始化鏡子 46 /// </summary> 47 /// <param name="sender"></param> 48 /// <param name="e"></param> 49 void _target_LocationChanged(object sender, EventArgs e) 50 { 51 Init(); 52 } 53 /// <summary> 54 /// 目標控件顯示或隱藏時,初始化鏡子 55 /// </summary> 56 /// <param name="sender"></param> 57 /// <param name="e"></param> 58 void _target_VisibleChanged(object sender, EventArgs e) 59 { 60 Init(); 61 } 62 /// <summary> 63 /// 初始化鏡子 64 /// </summary> 65 void Init() 66 { 67 this.Parent = _target.Parent; 68 this.Location = _target.Location; 69 this.Visible = _target.Visible; 70 if (Parent != null) 71 { 72 var i = Parent.Controls.GetChildIndex(_target); 73 Parent.Controls.SetChildIndex(this, i + 1); 74 } 75 76 var newSize = new Size(_target.Width + Padding.Left + Padding.Right, _target.Height + Padding.Top + Padding.Bottom); 77 if (newSize != Size) 78 { 79 this.Size = newSize; 80 } 81 } 82 /// <summary> 83 /// 鏡子位置 84 /// </summary> 85 void InitPadding() 86 { 87 Padding = new Padding(0, 0, 0, 20); 88 } 89 /// <summary> 90 /// 獲取倒影 91 /// </summary> 92 /// <returns></returns> 93 Bitmap OnNonLinearTransfromNeeded() 94 { 95 Bitmap bmp = null; 96 if (CtrlBmp == null) 97 return null; 98 99 try 100 { 101 bmp = new Bitmap(Width, Height); 102 103 const int bytesPerPixel = 4; 104 PixelFormat pxf = PixelFormat.Format32bppArgb; 105 Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); 106 BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, pxf); 107 IntPtr ptr = bmpData.Scan0; 108 int numBytes = bmp.Width * bmp.Height * bytesPerPixel; 109 byte[] argbValues = new byte[numBytes]; 110 111 Marshal.Copy(ptr, argbValues, 0, numBytes); 112 var e = new TransfromNeededEventArg() {ClientRectangle = ClientRectangle, Pixels = argbValues, Stride = bmpData.Stride, SourcePixels = CtrlPixels, SourceClientRectangle = new Rectangle(Padding.Left, Padding.Top, _target.Width, _target.Height), SourceStride = CtrlStride }; 113 114 DoBottomMirror(e); 115 116 Marshal.Copy(argbValues, 0, ptr, numBytes); 117 bmp.UnlockBits(bmpData); 118 } 119 catch 120 { 121 } 122 123 return bmp; 124 } 125 /// <summary> 126 /// 轉化成字節數組 127 /// </summary> 128 /// <param name="bmp"></param> 129 /// <returns></returns> 130 byte[] GetPixels(Bitmap bmp) 131 { 132 const int bytesPerPixel = 4; 133 PixelFormat pxf = PixelFormat.Format32bppArgb; 134 Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); 135 BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, pxf); 136 IntPtr ptr = bmpData.Scan0; 137 int numBytes = bmp.Width * bmp.Height * bytesPerPixel; 138 byte[] argbValues = new byte[numBytes]; 139 Marshal.Copy(ptr, argbValues, 0, numBytes); 140 //Marshal.Copy(argbValues, 0, ptr, numBytes); 141 bmp.UnlockBits(bmpData); 142 return argbValues; 143 } 144 /// <summary> 145 /// 獲取控件截圖 146 /// </summary> 147 /// <param name="ctrl"></param> 148 /// <returns></returns> 149 Bitmap GetForeground(Control ctrl) 150 { 151 Bitmap bmp = new Bitmap(this.Width, this.Height); 152 153 if (!ctrl.IsDisposed) 154 { 155 isSnapshotNow = true; 156 ctrl.DrawToBitmap(bmp, new Rectangle(Padding.Left, Padding.Top, ctrl.Width, ctrl.Height)); 157 isSnapshotNow = false; 158 } 159 return bmp; 160 } 161 bool isSnapshotNow = false; 162 const int bytesPerPixel = 4; 163 /// <summary> 164 /// 重繪時,畫出目標控件的倒影 165 /// </summary> 166 /// <param name="e"></param> 167 protected override void OnPaint(PaintEventArgs e) 168 { 169 try 170 { 171 CtrlBmp = GetForeground(_target); 172 CtrlPixels = GetPixels(CtrlBmp); 173 174 if (Frame != null) 175 Frame.Dispose(); 176 Frame = OnNonLinearTransfromNeeded(); 177 178 if (Frame != null) 179 { 180 e.Graphics.DrawImage(Frame, Point.Empty); 181 } 182 } 183 catch 184 { 185 186 } 187 base.OnPaint(e); 188 } 189 /// <summary> 190 /// 計算倒影 191 /// </summary> 192 /// <param name="e"></param> 193 void DoBottomMirror(TransfromNeededEventArg e) 194 { 195 var source = e.SourcePixels; 196 var output = e.Pixels; 197 198 var s = e.Stride; 199 var dy = 1; 200 var beginY = e.SourceClientRectangle.Bottom + dy; 201 var sy = e.ClientRectangle.Height; 202 var beginX = e.SourceClientRectangle.Left; 203 var endX = e.SourceClientRectangle.Right; 204 var d = sy - beginY; 205 206 for (int x = beginX; x < endX; x++) 207 for (int y = beginY; y < sy; y++) 208 { 209 var sourceY = (int)(beginY - 1 - dy - (y - beginY)); 210 if (sourceY < 0) 211 break; 212 var sourceX = x; 213 int sourceI = sourceY * s + sourceX * bytesPerPixel; 214 int outI = y * s + x * bytesPerPixel; 215 output[outI + 0] = source[sourceI + 0]; 216 output[outI + 1] = source[sourceI + 1]; 217 output[outI + 2] = source[sourceI + 2]; 218 output[outI + 3] = (byte)((1 - 1f * (y - beginY) / d) * 90); 219 } 220 } 221 } 222 public class TransfromNeededEventArg : EventArgs 223 { 224 public Rectangle ClientRectangle { get; internal set; } 225 public byte[] Pixels { get; internal set; } 226 public int Stride { get; internal set; } 227 228 public Rectangle SourceClientRectangle { get; internal set; } 229 public byte[] SourcePixels { get; internal set; } 230 public int SourceStride { get; set; } 231 public bool UseDefaultTransform { get; set; } 232 }
測試時,從工具箱中拖放一個MirrorExtender組件,窗體設計器中各個控件就會增加一個“mirrorExtender1 上的 HasMirror”屬性,設置為true,該控件具有倒影效果,反之,則沒有倒影效果。
圖3
希望對各位有幫助,O(∩_∩)O~