.Net開發筆記(七)使用組件編程


本文主要說到以下內容:

  • 什么是.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接口源碼如下:

View Code
public interface IExtenderProvider
{  
       bool CanExtend(object extendee);
}

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

View Code
 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型,另外,需要創建一個倒影類,負責顯示倒影。

擴展類:

View Code
 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     }

倒影類:

View Code
  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~


免責聲明!

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



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