Visual Studio .NET 2003 为Windows Mobile用户提供的控件大多不太理想。有些是样式不大如意。更多时候是我们找不到一个能满足我们功能的控件。比如带有图片的ListBox, 带有CheckBox的ListBox,带有Icon的Menu等等。(同时也奇怪为什么M$不把Smartphone里显示短信的控件放在ToolBox里)
因此自定义控件(Custom Control)。几乎就是.NET CF开发的必修课了。在这片文章里,通过制作一个简单的按钮控件来说明自定义控件的基本步骤。
publicclass CustomControl: System.Windows.Forms.Control{ // … protectedoverridevoid OnPaint(PaintEventArgs e) { // DO SOMETHING } protectedoverridevoid OnPaintBackground(PaintEventArgs pevent) { // DO NOTHING } // … } |
由于.NET CF并不支持控件的OwnerDraw机制,所以要想自定义控件只能派生自System.Windows.Forms.Control了。然后白手起家,慢慢搭建自己的控件库。
从Windows.Forms.Control派生出自己的CustomControl。之后还有重载OnPaint和OnPaintBackground两个方法,对控件和控件背景进行高效的绘制。因为.NET CF是运行在移动设备之上,因此绘图的效率对显示的影响是十分明显的。为了提高效率我们从以下几个方面着手:
l使用缓冲区绘图。将绘制过程放在缓冲区内进行。需要绘制控件时,直接将缓冲区内容全部绘出。
l努力将绘图运算放置在OnPaint和OnPaintBackground函数之外。
l充分利用类继承特性,避免重复绘制。
l避免在OnPaint和OnPaintBackground函数内产生和销毁对象。
//… protected Bitmap m_bmpBuffer = null; protected Graphics m_gxBuffer = null; //… protectedoverridevoid OnResize(EventArgs e) { // Dispose Old Graphic Buffer if (this.m_bmpBuffer != null) this.m_bmpBuffer.Dispose(); if (this.m_gxBuffer != null) this.m_gxBuffer.Dispose(); m_borderRect = new Rectangle ( this.ClientRectangle.Left ,this.ClientRectangle.Top ,this.ClientRectangle.Right - 1 ,this.ClientRectangle.Bottom - 1 );
this.m_bmpBuffer = new Bitmap ( this.ClientRectangle.Width , this.ClientRectangle.Height );
this.m_gxBuffer = Graphics.FromImage(this.m_bmpBuffer); this.Invalidate(); base.OnResize (e); } |
上面的代码中,开辟了一个m_gxBuffer 缓冲区。并通过重载OnResize函数来调整缓冲区的大小用以适应控件的大小。
protectedoverridevoid OnPaint(PaintEventArgs e) { if (Enabled) { Pen borderPen = new Pen(m_borderColor); m_gxBuffer.Clear(this.BackColor); if (m_border) m_gxBuffer.DrawRectangle(borderPen, m_borderRect); } else { Pen disablePen = new Pen(Color.DarkGray); m_gxBuffer.Clear(Color.Gray); if (m_border) m_gxBuffer.DrawRectangle(disablePen, m_borderRect); }
if (m_bufferOutput) e.Graphics.DrawImage(m_bmpBuffer,0,0); } |
之后在OnPaint中就开始使用m_gxBuffer来进行缓冲区绘图了。当然在整个OnPaint结束之前应该调用e.Graphics.DrawImage将缓冲区输出到屏幕上。很快一个控件的雏形就出现了。现在给他增加一些功能让他看起来更像是个按钮(而不是现在这个丑样子)。
privatebool m_active = false; private Color m_activeBackColor = Color.LightGray; private Color m_activeForeColor = Color.FromArgb(74,97,148); |
首先我们给控件增加一些有用的属性。比如我们要判断按钮是否被按下了。以及按下按钮时,按钮应该是什么样子的。(最简单的就是改变他的颜色了吧)
不要过多考虑按钮可能出现的状态。比如在普通的使用触摸屏的移动设备上,我们根本无需把Hover特性考虑在内。因为凭借Windows CE你永远不会知道你的笔是否在一个按钮的上方。
几条简单的if语句就可以把状态的判断完全搞定
protectedoverridevoid OnMouseMove(MouseEventArgs e) { if (e.Button MouseButtons.Left) { if (this.m_active && this.ClientRectangle.Contains(e.X, e.Y) false) { this.m_active = false; this.Invalidate(); } elseif (!this.m_active && this.ClientRectangle.Contains(e.X, e.Y) true) { this.m_active = true; this.Invalidate(); } } base.OnMouseMove (e); } protectedoverridevoid OnMouseDown(MouseEventArgs e) { if (e.Button MouseButtons.Left) { this.m_active = true; this.Focus(); this.Invalidate(); } base.OnMouseDown (e); } protectedoverridevoid OnMouseUp(MouseEventArgs e) { if (e.Button MouseButtons.Left) { this.m_active = false; this.Invalidate(); } base.OnMouseUp (e); } |
这样就可以准确的判断出按钮是否被点击(Click)。
protectedoverridevoid OnPaint(System.Windows.Forms.PaintEventArgs e) { // … if (this.m_active) { m_gxBuffer.Clear(this.m_activeBackColor); m_gxBuffer.DrawRectangle(activePen, m_borderRect);
if (this.Text.Length > 0) { m_gxBuffer.DrawString ( this.Text , this.Font, activeForeBrush ,this.TextPosX+ 2 ,this.TextPosY+ 1 ); } } else { base.OnPaint(e); } // … } |
好了,现在来看看OnPaint,他利用上面得到的m_active来区分绘制那种状态的按钮。
一个按钮就算基本完成了,下面我们要做的工作就是如何把它加入到我们的ToolBox中,以便随时使用。
由于.NET 对于Design Time(设计时期设计器里看到的控件) 和 Runtime(程序运行时看到的控件)的控件需要分别用不同的方式编译。所以要采用宏定义作为开关来控制一些特性。
首先,DesignTime的控件必须包含System.ComponentModel命名空间。
#if NETCFDESIGNTIME using System.ComponentModel; #endif |
为了能在设计器里设置控件的属性,还要对这些加以特殊的声明。例如BorderColor属性
#if NETCFDESIGNTIME [System.ComponentModel.Category("Appearance")] [System.ComponentModel.Description("Border Color")] #endif public Color BorderColor { get { if (m_borderColor Color.Empty) m_borderColor = Color.FromArgb(165,165,165); returnthis.m_borderColor; } set { this.m_borderColor = value; this.Invalidate(); } } |
这里System.ComponentModel.Category表示属性的类别,比如Appearance就是外观类。下面的Description看字面就知道是属性的描述了。此外还可以通过加System.ComponentModel.DefaultValue来设置属性的默认值。但是这里DefaultValue只能是一些基本类型。如果是特殊类型比如Color之类,提前初始化就可以了。另外,还可以设置控件的默认处理事件。也就是当在DesignTime双击这个控件时自动安排的处理事件。比如
#if NETCFDESIGNTIME [System.ComponentModel.DefaultProperty("Text")] [System.ComponentModel.DefaultEvent("Click")] #endif |
之后进入.NET 2003的命令行窗口进行DesignTime控件的编译
csc /noconfig /define:NETCFDESIGNTIME /target:library /out:CustomButton.dll Button.cs /res:"Bitmaps\CustomControl.Button.bmp" /r:"C:\Program Files\Microsoft Visual Studio .NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\Designer\System.CF.Design.dll" /r:"C:\Program
Files\Microsoft Visual Studio .NET
2003\CompactFrameworkSDK\v1.0.5000\Windows
CE\Designer\System.CF.Windows.Forms.dll" /r:"C:\Program Files\Microsoft Visual Studio .NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\Designer\System.CF.Drawing.dll" /r:System.Windows.Forms.dll /r:System.Drawing.dll /r:System.dll /r:System.XML.dll /r:System.Web.Services.dll /r:System.Data.dll /nowarn:1595 |
这里可以添加一个和你控件命名控件同名的BMP文件用来做控件在ToolBox上显示的图标。不然的话,.NET IDE会用一个难看的齿轮代替的。BMP可以用.NET的资源编辑器创建。格式为16×16×256.
最后在.NET IDE的ToolBox中添加自己的控件就可以使用了。
Recent Comments