• C#自定义控件的开发:Pin和Connector


    C#自定义控件的开发:Pin和Connector

    2009-08-03 14:46 wonsoft hi.baidu 我要评论(0) 字号:T | T

    一键收藏,随时查看,分享好友!

    本文介绍了如何使用智能设备扩展C#自定义控件。

    AD: 2013云计算架构师峰会超低价抢票中

    适用于:Microsoft Windows CE .NET/Smart Device Extensions for Microsoft Visual Studio .NET

    简介

    Smart Device Extensions for Microsoft Visual Studio .NET (SDE) 提供了一种可以在应用程序中使用的很好的基本控件。遗憾的是,嵌入式设备应用程序涉及的范围非常广,这就使得开发人员几乎肯定会在某些地方缺少合适的控件,此时,基本上有两个选择:重新进行应用程序的结构设计以使用可用的控件,或者采用您自己的自定义控件。

    SDE 的第一个版本不支持设计时自定义控件,这意味着为了使用它们,必须手动编写将它们放入窗体并设置其大小和属性的代码。它只需很少的额外工作量,并且只需要您接受没有可用于自定义控件的 Form Design Support 这一事实。

    问题

    最近,我一直在为 Visual Studio .NET 创建类库,用于包装很多硬件的功能。通过使用一个可以为他们完成所有 P/Invoking 和资源管理工作的类库,托管代码开发人员使用这个类库来访问机载微型控制器和 Microsoft Windows CE 端口就容易多了。我开发用于 Graphics Master 设备的 I/O 的类库,以便提供对两个单独的头上的引脚的读取和写入功能。

    我需要一个测试和示例应用程序,该程序能够使用户轻松地通过恰当的图形接口设置或读取数字 I/O 状态并读取模拟 I/O。我希望有某个东西看起来像示意图上的接头或类似板上的物理插头。由于我要处理两个物理上不同大小的接头,所以我需要多个控件,或最好是一个可以定义大小的控件。很显然,SDE 的工具箱中没有我想要的控件。

    我本来可以使用大量的 Label、CheckBox、PictureBox 和 TextBox,但是我认为这种替代方案看起来很难看。让我们尝试编写自己的控件。

    C#自定义控件对象模型

    第一个任务是决定整个对象模型。我们需要什么样的组成部分,这些组成部分将如何融合在一起,它们如何相互交互,如何与它们的环境交互?

    我的连接器控件概念

    图 1. 我的连接器控件概念

    我们将创建连接器,用来包含大小可变的引脚集合,以便能够连接不同大小的接头。每个引脚必须有可以放在被显示的"引脚"的左侧或右侧(取决于它是偶数还是奇数引脚)的标识标签。每个引脚还可以是数字的或模拟的 I/O,因此每个引脚都需要有范围从零到 0xFFFF 的单独的值。最好能够一眼即可识别每个引脚的类型和值,所以将需要使用一些颜色。当然,并非接头上的所有引脚都可用于 I/O,所以我们需要能够禁用它们中的一部分,此外,我们希望引脚是交互的,这样当我们接通一个引脚时,它可以做某些操作,比如更改状态。

    图 1 是一个控件在屏幕上显示的外观的很好模型。

    基于这些要求,我们提出了一个如图 2 所示的对象模型。

    控件对象模型

    图 2. 控件对象模型

    整体的思路是,我们将有一个 Connector 基类,然后从它派生出其他几个自定义的 Connector 类。Connector 将包含一个 Pins 类,这个类只是通过从 CollectionBase 派生,使用索引器来公开 Pin 对象的 ListArray。

    C#自定义控件:实现 Pin 对象

    因为此控件的骨干是 Pin 对象,所以我们首先介绍它。Pin 对象将处理控件的大多数显示属性,并处理用户交互。一旦我们可以成功地在窗体上创建、显示单个引脚并与之交互,构建一个连接器将它们组合在一起就非常简单了。

    Pin 对象有四个在创建它时必须设置的属性。默认的构造函数会设置它们中的每一个,但其他构造函数还可以用来允许创建者传递非默认的值。

    最重要的属性是 Alignment。这个属性确定了绘制对象时文本和引脚的位置,但更重要的是,设置属性时,它将创建和放置用于绘制引脚和文本的矩形。这些矩形的使用将在随后解释 OnDraw 时进行讨论。

    清单 1 显示了基本构造函数和 Alignment 属性的代码。为引脚子组件周围所定义的偏移量和边框使用了常量,但这些常量也很容易成为控件的其他属性。

    清单 1. 引脚构造函数和 Alignment 属性

    1. public Pin()    
    2. {    
    3.   showValue = false;    
    4.   pinValue = 0;    
    5.   type = PinType.Digital;    
    6.   Alignment = PinAlignment.PinOnRight;    
    7. }    
    8. public PinAlignment Alignment    
    9. { // determines where the pin rectangle is placed    
    10.   set   
    11.   {    
    12.     align = value;    
    13.     if(value == PinAlignment.PinOnRight)    
    14.     {    
    15.       this.pinBorder = new Rectangle(    
    16.         this.ClientRectangle.Width - (pinSize.Width + 10),    
    17.         1,    
    18.         pinSize.Width + 9,    
    19.         this.ClientRectangle.Height - 2);    
    20.         this.pinBounds = new Rectangle(    
    21.         this.ClientRectangle.Width - (pinSize.Width + 5),    
    22.         ((this.ClientRectangle.Height -    
    23.         pinSize.Height) / 2) + 1,    
    24.         pinSize.Width,    
    25.         pinSize.Height);    
    26.         this.textBounds = new Rectangle(    
    27.         5,    
    28.         5,    
    29.         this.ClientRectangle.Width - (pinSize.Width + 10),    
    30.         20);    
    31.     }    
    32.     else   
    33.     {    
    34.       this.pinBorder = new Rectangle(    
    35.         1,    
    36.         1,    
    37.         pinSize.Width + 9,    
    38.         this.ClientRectangle.Height - 2);    
    39.         this.pinBounds = new Rectangle(    
    40.         6,    
    41.         this.ClientRectangle.Height - (pinSize.Height + 4),    
    42.         pinSize.Width,    
    43.         pinSize.Height);    
    44.         this.textBounds = new Rectangle(    
    45.         pinSize.Width + 10,    
    46.         5,    
    47.         this.ClientRectangle.Width - (pinSize.Width + 10),    
    48.         20);    
    49.     }    
    50.     this.Invalidate();    
    51.   }    
    52.   get   
    53.   {    
    54.     return align;    
    55.   }    
    56. }   

    由于 Pin 对象不会提供很好的用户交互或可自定义性,所以引脚的核心功能是我们将重写的绘图例程 OnDraw,重写该例程是为了可以由我们来绘制整个引脚。

    每个引脚将绘制三个部分:引脚本身将是一个圆(除非它是 Pin 1,这时它将是一个方块),我们将围绕引脚绘制边框矩形,然后在引脚的左侧或右侧留出一个区域用来绘制引脚的文本。

    要绘制引脚,我们首先确定表示实际引脚的圆所使用的颜色。如果引脚被禁用,它的颜色是灰色。如果启用,则要确定它是什么类型。模拟引脚将是绿色,而数字引脚根据情况而不同,如果是低 (关)则是蓝色,如果是高(开)则是橙色。

    下一步,我们使用 FillEllipse 来绘制所有实际的引脚,但 PinNumber=1 时除外,这时使用 FillRectangle 绘制引脚。通过绘制在矩形 (pinBounds) 中而不是控件的边界上,我们能够在创建引脚时设置引脚的位置(左侧或右侧),并且从这一点开始,我们可以在不用关心引脚的位置的情况下进行绘制。

    下一步我们绘制标签,它将是引脚的文本或引脚的值,这取决于 ShowValue 属性。

    我们使用与绘制引脚时类似的策略来绘制文本,但这次我们必须计算水平和垂直偏移量,因为在 Microsoft .NET 压缩框架中,DrawText 方法不允许有 TextAlign 参数。

    最终,我们通过调用 Dispose 方法清理我们手动使用的 Brush 对象。

    清单 2 显示了完整的 OnDraw 例程。

    清单 2. OnDraw() 方法

    1. protected override void OnPaint(PaintEventArgs pe) 
    2.   Brush b; 
    3.         // determine the Pin color 
    4.   if(this.Enabled) 
    5.   { 
    6.     if(type == PinType.Digital) 
    7.     { 
    8.       // digital pins have different on/off color 
    9.       b = new System.Drawing.SolidBrush( 
    10.       this.Value == 0 ? (digitalOffColor) : (digitalOnColor)); 
    11.     } 
    12.     else
    13.     { 
    14.       // analog pin 
    15.       b = new System.Drawing.SolidBrush(analogColor); 
    16.     } 
    17.   } 
    18.   else
    19.   { 
    20.     // disabled pin 
    21.     b = new System.Drawing.SolidBrush(disabledColor); 
    22.   } 
    23.   // draw the pin 
    24.   if(this.PinNumber == 1) 
    25.     pe.Graphics.FillRectangle(b, pinBounds); 
    26.   else
    27.     pe.Graphics.FillEllipse(b, pinBounds); 
    28.   // draw a border Rectangle around the pin 
    29.   pe.Graphics.DrawRectangle(new Pen(Color.Black), pinBorder); 
    30.   // draw the text centered in the text bound 
    31.   string drawstring; 
    32.   // are we showing the Text or Value? 
    33.   if(showValue) 
    34.     drawstring = Convert.ToString(this.Value); 
    35.   else
    36.     drawstring = this.Text; 
    37.   // determine the actual string size 
    38.   SizeF fs = pe.Graphics.MeasureString( 
    39.         drawstring, 
    40.         new Font(FontFamily.GenericMonospace, 8f, 
    41.                  FontStyle.Regular)); 
    42.   // draw the string 
    43.   pe.Graphics.DrawString( 
    44.       drawstring, 
    45.       new Font(FontFamily.GenericMonospace, 8f, 
    46.       FontStyle.Regular), 
    47.       new SolidBrush((showValue ? analogColor : Color.Black)), 
    48.       textBounds.X + (textBounds.Width - fs.ToSize().Width) / 2, 
    49.       textBounds.Y + (textBounds.Height - fs.ToSize().Height) / 
    50.                  2); 
    51.   // clean up the Brush 
    52.   b.Dispose(); 
    53.   } 

    构建 Pin 类的最后一步是添加 Click 处理程序。对于我们的 Pin 类来说,我们将使用自定义的 EventArg,以便可以向事件处理程序传递引脚的文本和编号。要创建自定义的 EventArg,我们只是创建了一个从 EventArgs 类派生的类:

    1. public class PinClickEventArgs : EventArgs 
    2.   // a PinClick passes the Pin Number and the Pin's Text 
    3.   public int number; 
    4.   public string text; 
    5.   public PinClickEventArgs(int PinNumber, string PinText) 
    6.   { 
    7.     number = PinNumber; 
    8.     text = PinText; 
    9.   } 

    下一步,我们将一个委托添加到命名空间中:

    1. public delegate void PinClickHandler(Pin source, PinClickEventArgs args);

    现在,我们需要添加代码来确定什么时候发生单击,然后引发事件。对于我们的 Pin 类,当引脚的边框矩形内部发生 MouseDown 和 MouseUp 事件时即为一个逻辑上的单击 - 这样,如果用户单击引脚的文本部分,则不会触发 Click 事件,但如果点击表示实际引脚的区域,则触发该事件。

    首先,我们需要一个公共 PinClickHandler 事件,其定义如下:

    1. public event PinClickHandler PinClick;  

    我们还需要一个私有的布尔变量,我们将在 MouseDown 事件发生时设置该变量,用于指示我们正在单击过程中。然后,我们检查 MouseUp 事件的该变量,以确定事件是否是按连续的顺序发生的:

    1. bool midClick; 

    下一步,我们需要为 MouseDown 和 MouseUp 添加两个事件处理程序,如清单 3 所示。

    清单 3. 用于实现 PinClick 事件的事件处理程序

    1. private void PinMouseDown(object sender, MouseEventArgs e)    
    2. {    
    3.   if(!this.Enabled)    
    4.     return;    
    5.   // if the user clicked in the "pin" rectangle, start a click process    
    6.   midClick = pinBorder.Contains(e.X, e.Y);    
    7. }    
    8. private void PinMouseUp(object sender, MouseEventArgs e)    
    9. {    
    10.   // if we had a mousedown and then up inside the "pin" rectangle,    
    11.   // fire a click    
    12.   if((midClick) && (pinBorder.Contains(e.X, e.Y)))    
    13.   {    
    14.     if(PinClick != null)    
    15.       PinClick(this, new PinClickEventArgs(    
    16.                this.PinNumber, this.Text));    
    17.   }    
    18. }   

    最后,我们需要为每个引脚实现事件处理程序。引脚的基本构造函数是添加这些挂钩的好地方,我们可以通过直接在构造函数中添加以下代码来完成:view plaincopy to clipboardprint?this.MouseDown += new MouseEventHandler(PinMouseDown);   this.MouseUp += new MouseEventHandler(PinMouseUp);  this.MouseDown += new MouseEventHandler(PinMouseDown);

    this.MouseUp += new MouseEventHandler(PinMouseUp); 实现 Pins 类一旦有了 Pin 类,就可以创建从 CollectionBase 派生的 Pins 类。该类的目的是提供索引器,这样我们就可以很容易在集合内添加、删除和操纵 Pin 类。

    清单 4. Pins 类

    1. public class Pins : CollectionBase 
    2.   public void Add(Pin PinToAdd) 
    3.   { 
    4.     List.Add(PinToAdd); 
    5.   } 
    6.   public void Remove(Pin PinToRemove) 
    7.   { 
    8.     List.Remove(PinToRemove); 
    9.   } 
    10.   // Indexer for Pins 
    11.   public Pin this[byte Index] 
    12.   { 
    13.     get
    14.     { 
    15.       return (Pin)List[Index]; 
    16.     } 
    17.     set
    18.     { 
    19.       List[Index] = value; 
    20.     } 
    21.   } 
    22.   public Pins(){} 

    实现 Connector 类既然我们已经获得了 Pins 类,我们现在需要构建 Connector 类,该类将是一个简单的包装类,这个包装类包含 Pins 类,并在每个引脚和连接器容器之间封送 PinClick 事件,而且它有一个表示连接器上的引脚数的构造函数。清单 5 显示了完整的 Connector 类。

    清单 5. Connector 类

    1. public class Connector : System.Windows.Forms.Control 
    2.   public event PinClickHandler PinClick; 
    3.   protected Pins pins; 
    4.   byte pincount; 
    5.   public Connector(byte TotalPins) 
    6.   { 
    7.     pins = new Pins(); 
    8.     pincount = TotalPins; 
    9.     InitializeComponent(); 
    10.   } 
    11.   private void InitializeComponent() 
    12.   { 
    13.     for(int i = 0 ; i <  pincount ; i++) 
    14.     { 
    15.       Pin p = new Pin(PinType.Digital, 
    16.             (PinAlignment)((i + 1) % 2), 0); 
    17.       p.PinClick += new PinClickHandler(OnPinClick); 
    18.       p.PinNumber = i + 1; 
    19.       p.Text = Convert.ToString(i); 
    20.       p.Top = (i / 2) * p.Height; 
    21.       p.Left = (i % 2) * p.Width; 
    22.       this.Pins.Add(p); 
    23.       this.Controls.Add(p); 
    24.     } 
    25.     this.Width = Pins[0].Width * 2; 
    26.     this.Height = Pins[0].Height * this.Pins.Count / 2; 
    27.   } 
    28.   public Pins Pins 
    29.   { 
    30.     set
    31.     { 
    32.       pins = value; 
    33.     } 
    34.     get
    35.     { 
    36.       return pins; 
    37.     } 
    38.   } 
    39.   private void OnPinClick(Pin sender, PinClickEventArgs e) 
    40.   { 
    41.     // pass on the event 
    42.     if(PinClick != null) 
    43.     { 
    44.       PinClick(sender, e); 
    45.       if(sender.Type == PinType.Digital) 
    46.         sender.Value = sender.Value == 0 ? 1 : 0; 
    47.       else
    48.         sender.DisplayValue = !sender.DisplayValue; 
    49.     } 
    50.   } 
    51.   protected override void Dispose( bool disposing ) 
    52.   { 
    53.     base.Dispose( disposing ); 
    54.   } 

    Connector 的 InitializeComponent 方法是创建所有被包含的 Pin 类并将其添加到连接器的控件中的地方,并且是连接器本身调整大小的地方。InitializeComponent 也是最终被 Form Designer 用来显示我们的连接器的方法。

    C#自定义控件:构建自定义连接器

    Connector 类本身很简单,它不会修改任何默认的引脚设置。但是,我们现在可以通过从 Connector 类派生新的类,从而构建一个自定义连接器,并修改单个引脚(例如,使某些引脚成为模拟引脚,或将其禁用)。

    在示例应用程序中,我为 Applied Data Systems 的 Graphics Master 板创建了两个连接器,一个用于 J2,一个用于 J7。构造函数基于连接器设置引脚的类型以及引脚的文本。图 2 是窗体上有 J2 和 J7 的示例应用程序的屏幕快照。

    使用两个 Connector 对象的窗体

    图 3. 使用两个 Connector 对象的窗体

  • 相关阅读:
    [Oracle DBA学习笔记] STARTUP详解
    亦步亦趋完成在CentOS 6.4下安装Oracle 11gR2
    ‘程序员’与‘页面仔’
    Linux下建立Oracle服务及其开机自启动
    解析并验证IE6及之前版本的'!important’ BUG
    浅谈CSS选择器中的空格
    在CentOS安装CMake
    关于CentOS下RPM的一些实例
    CentOS配置ssh无密码登录的注意点
    CentOS下的账户管理
  • 原文地址:https://www.cnblogs.com/sczw-maqing/p/3324367.html
Copyright © 2020-2023  润新知