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

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

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

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

  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. {
  3. public void Add(Pin PinToAdd)
  4. {
  5. List.Add(PinToAdd);
  6. }
  7. public void Remove(Pin PinToRemove)
  8. {
  9. List.Remove(PinToRemove);
  10. }
  11. // Indexer for Pins
  12. public Pin this[byte Index]
  13. {
  14. get
  15. {
  16. return (Pin)List[Index];
  17. }
  18. set
  19. {
  20. List[Index] = value;
  21. }
  22. }
  23. public Pins(){}
  24. }

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

清单 5. Connector 类

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

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

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

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

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

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

C#自定义控件的开发:Pin和Connector的更多相关文章

  1. 企业搜索引擎开发之连接器connector(三十)

    连接器里面采用的什么样的数据结构,我们先从Document迭代器开始入手,具体的Document迭代器类都实现了DocumentList接口,该接口定义了两个方法 public interface D ...

  2. 企业搜索引擎开发之连接器connector(二十九)

    在哪里调用监控器管理对象snapshotRepositoryMonitorManager的start方法及stop方法,然后又在哪里调用CheckpointAndChangeQueue对象的resum ...

  3. ASP.NET自定义控件组件开发 第五章 模板控件开发

    原文:ASP.NET自定义控件组件开发 第五章 模板控件开发 第五章 模板控件开发 系列文章链接: ASP.NET自定义控件组件开发 第一章 待续 ASP.NET自定义控件组件开发 第一章 第二篇 接 ...

  4. ASP.NET2.0自定义控件组件开发 第六章 深入讲解控件的属性

    原文:ASP.NET2.0自定义控件组件开发 第六章 深入讲解控件的属性 深入讲解控件的属性持久化(一) 系列文章链接: ASP.NET自定义控件组件开发 第一章 待续 ASP.NET自定义控件组件开 ...

  5. ASP.NET自定义控件组件开发 第四章 组合控件开发CompositeControl

    原文:ASP.NET自定义控件组件开发 第四章 组合控件开发CompositeControl 第四章 组合控件开发CompositeControl 大家好,今天我们来实现一个自定义的控件,之前我们已经 ...

  6. ASP.NET自定义控件组件开发 第四章 组合控件开发CompositeControl 后篇 --事件冒泡

    原文:ASP.NET自定义控件组件开发 第四章 组合控件开发CompositeControl 后篇 --事件冒泡 CompositeControl  后篇 --事件冒泡 系列文章链接: ASP.NET ...

  7. ASP.NET自定义控件组件开发 第三章 为控件添加事件 前篇

    原文:ASP.NET自定义控件组件开发 第三章 为控件添加事件 前篇 第三章 为控件添加事件 好了,我们之前以前开发一个控件.而且也添加了属性,开发也很规范,但是那个控件还差最后一点:添加事件. 系列 ...

  8. ASP.NET自定义控件组件开发 第三章 为控件添加事件 后篇

    原文:ASP.NET自定义控件组件开发 第三章 为控件添加事件 后篇 第三章 为控件添加事件 后篇 前一篇文章只是简单的说了下事件,但是大家应该方法,在ASP.NET自定义控件中只是简单那么定义事件是 ...

  9. ASP.NET自定义控件组件开发 第一章 第三篇

    原文:ASP.NET自定义控件组件开发 第一章 第三篇 第三篇:第一章的完结篇 系列文章链接: ASP.NET自定义控件组件开发 第一章 待续 ASP.NET自定义控件组件开发 第一章 第二篇 接着待 ...

随机推荐

  1. UVa 1636 (概率) Headshot

    既然是第一道概率题,就正儿八经地分析一下吧. 题意: 有一个左轮枪,里面随机装了或者没装子弹,用一个01序列表示.现在已知扣动第一次扳机没有子弹,问是继续扣动扳机还是随机转动一下再扣,那种选择使得第二 ...

  2. UVa 11389 (贪心) The Bus Driver Problem

    题意: 有司机,下午路线,晚上路线各n个.给每个司机恰好分配一个下午路线和晚上路线. 给出行驶每条路线的时间,如果司机开车时间超过d,则要付加班费d×r. 问如何分配路线才能使加班费最少. 分析: 感 ...

  3. UVa 1149 Bin Packing 【贪心】

    题意:给定n个物品的重量l[i],背包的容量为w,同时要求每个背包最多装两个物品,求至少要多少个背包才能装下所有的物品 和之前做的独木舟上的旅行一样,注意一下格式就好了 #include<ios ...

  4. Java [Leetcode 94]Binary Tree Inorder Traversal

    题目描述: Given a binary tree, return the inorder traversal of its nodes' values. For example:Given bina ...

  5. .Net中的各种序列化

    我们知道将对象的状态保持在存储媒体中,以便可以在以后重新创建精确的副本这正是数据持久化所要做的.而且,不同应用程序之间的通讯需要相互传输数据.那么序列化和反序列化正是为此而生. 序列化和反序列化 所谓 ...

  6. ECshop 二次开发模板教程2

    不知道大家是学会用循环了呢,还是我的言语实在有问题,大家实在无法完成阅读哦,居然大家都没有问题,暂时心里安慰,把他当做好事情,大家都会调用了,呵呵,那我们继续循环调用商品了!好,继续在我们昨天的基础上 ...

  7. git pull冲突解决

    场景:用户UserA修改了文件File1,用户UserB也修改了文件File1并成功merge到了服务器上,而UserA和UserB改动了同一个代码块,当UserA拉取代码时git无法merge此改动 ...

  8. UML类图设计

    大纲: 在Visio里,包和类的关系是包含关系,将类拖入包的文件夹之后,关系就建立了,二元关联符号可以设置为:聚合.合成.接口:空心圆+直线(唐老鸭类实现了‘讲人话’):依赖:虚线+箭头(动物和空气的 ...

  9. JS触发ASP.NET服务器端控件的方法

    <asp:Button ID="Button_regId" runat="server" Font-Bold="False" OnCl ...

  10. Reading or Writing to Another Processes Memory in C# z

    http://www.jarloo.com/reading-and-writing-to-memory/ Declarations [Flags] public enum ProcessAccessF ...