使用智能设备扩展在 C# 中开发自定义控件

控件

控件
发布日期: 7/19/2004 | 更新日期: 7/19/2004
Chris Tacke, Windows Embedded MVP
Applied Data Systems

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

摘要:学习如何使用 Smart Device Extensions for Microsoft Visual Studio .NET (SDE) 创建自定义控件。

本页内容
简介
问题
对象模型
构建自定义连接器

简介
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,但是我认为这种替代方案看起来很难看。让我们尝试编写自己的控件。

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

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

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

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

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

图 2. 控件对象模型

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

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

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

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

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

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

public Pin()
{
showValue = false;
pinValue = 0;
type = PinType.Digital;
Alignment = PinAlignment.PinOnRight;
}
public PinAlignment Alignment
{ // determines where the pin rectangle is placed
set
{
align = value;
if(value == PinAlignment.PinOnRight)
{
this.pinBorder = new Rectangle(
this.ClientRectangle.Width - (pinSize.Width + 10),
1,
pinSize.Width + 9,
this.ClientRectangle.Height - 2);
this.pinBounds = new Rectangle(
this.ClientRectangle.Width - (pinSize.Width + 5),
((this.ClientRectangle.Height -
pinSize.Height) / 2) + 1,
pinSize.Width,
pinSize.Height);
this.textBounds = new Rectangle(
5,
5,
this.ClientRectangle.Width - (pinSize.Width + 10),
20);
}
else
{
this.pinBorder = new Rectangle(
1,
1,
pinSize.Width + 9,
this.ClientRectangle.Height - 2);
this.pinBounds = new Rectangle(
6,
this.ClientRectangle.Height - (pinSize.Height + 4),
pinSize.Width,
pinSize.Height);
this.textBounds = new Rectangle(
pinSize.Width + 10,
5,
this.ClientRectangle.Width - (pinSize.Width + 10),
20);
}
this.Invalidate();
}
get
{
return align;
}
}

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

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

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

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

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

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

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

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

清单 2. OnDraw() 方法

protected override void OnPaint(PaintEventArgs pe)
{
Brush b;
// determine the Pin color
if(this.Enabled)
{
if(type == PinType.Digital)
{
// digital pins have different on/off color
b = new System.Drawing.SolidBrush(
this.Value == 0 ? (digitalOffColor) : (digitalOnColor));
}
else
{
// analog pin
b = new System.Drawing.SolidBrush(analogColor);
}
}
else
{
// disabled pin
b = new System.Drawing.SolidBrush(disabledColor);
}
// draw the pin
if(this.PinNumber == 1)
pe.Graphics.FillRectangle(b, pinBounds);
else
pe.Graphics.FillEllipse(b, pinBounds);
// draw a border Rectangle around the pin
pe.Graphics.DrawRectangle(new Pen(Color.Black), pinBorder);
// draw the text centered in the text bound
string drawstring;
// are we showing the Text or Value?
if(showValue)
drawstring = Convert.ToString(this.Value);
else
drawstring = this.Text;
// determine the actual string size
SizeF fs = pe.Graphics.MeasureString(
drawstring,
new Font(FontFamily.GenericMonospace, 8f,
FontStyle.Regular));
// draw the string
pe.Graphics.DrawString(
drawstring,
new Font(FontFamily.GenericMonospace, 8f,
FontStyle.Regular),
new SolidBrush((showValue ? analogColor : Color.Black)),
textBounds.X + (textBounds.Width - fs.ToSize().Width) / 2,
textBounds.Y + (textBounds.Height - fs.ToSize().Height) /
2);
// clean up the Brush
b.Dispose();
}
}

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

public class PinClickEventArgs : EventArgs
{
// a PinClick passes the Pin Number and the Pin's Text
public int number;
public string text;
public PinClickEventArgs(int PinNumber, string PinText)
{
number = PinNumber;
text = PinText;
}
}

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

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

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

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

public event PinClickHandler PinClick;

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

bool midClick;

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

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

private void PinMouseDown(object sender, MouseEventArgs e)
{
if(!this.Enabled)
return;
// if the user clicked in the "pin" rectangle, start a click process
midClick = pinBorder.Contains(e.X, e.Y);
}
private void PinMouseUp(object sender, MouseEventArgs e)
{
// if we had a mousedown and then up inside the "pin" rectangle,
// fire a click
if((midClick) && (pinBorder.Contains(e.X, e.Y)))
{
if(PinClick != null)
PinClick(this, new PinClickEventArgs(
this.PinNumber, this.Text));
}
}

最后,我们需要为每个引脚实现事件处理程序。引脚的基本构造函数是添加这些挂钩的好地方,我们可以通过直接在构造函数中添加以下代码来完成:

this.MouseDown += new MouseEventHandler(PinMouseDown);
this.MouseUp += new MouseEventHandler(PinMouseUp);

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

清单 4. Pins 类

public class Pins : CollectionBase
{
public void Add(Pin PinToAdd)
{
List.Add(PinToAdd);
}
public void Remove(Pin PinToRemove)
{
List.Remove(PinToRemove);
}
// Indexer for Pins
public Pin this[byte Index]
{
get
{
return (Pin)List[Index];
}
set
{
List[Index] = value;
}
}
public Pins(){}
}

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

清单 5. Connector 类

public class Connector : System.Windows.Forms.Control
{
public event PinClickHandler PinClick;
protected Pins pins;
byte pincount;
public Connector(byte TotalPins)
{
pins = new Pins();
pincount = TotalPins;
InitializeComponent();
}
private void InitializeComponent()
{
for(int i = 0 ; i < pincount ; i++)
{
Pin p = new Pin(PinType.Digital,
(PinAlignment)((i + 1) % 2), 0);
p.PinClick += new PinClickHandler(OnPinClick);
p.PinNumber = i + 1;
p.Text = Convert.ToString(i);
p.Top = (i / 2) * p.Height;
p.Left = (i % 2) * p.Width;
this.Pins.Add(p);
this.Controls.Add(p);
}
this.Width = Pins[0].Width * 2;
this.Height = Pins[0].Height * this.Pins.Count / 2;
}
public Pins Pins
{
set
{
pins = value;
}
get
{
return pins;
}
}
private void OnPinClick(Pin sender, PinClickEventArgs e)
{
// pass on the event
if(PinClick != null)
{
PinClick(sender, e);
if(sender.Type == PinType.Digital)
sender.Value = sender.Value == 0 ? 1 : 0;
else
sender.DisplayValue = !sender.DisplayValue;
}
}
protected override void Dispose( bool disposing )
{
base.Dispose( disposing );
}
}

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

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

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

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

时间: 2024-09-16 12:30:46

使用智能设备扩展在 C# 中开发自定义控件的相关文章

在Silverlight 2 Beta2中开发自定义控件

本文主要讲述如何在Silverlight2中开发一个自定义控件,我使用环境是VS2008 Silverlight2 Beta2 . 一:创建Silverlight2 类库项目,如下图: 然后我们添加一个控件类,该可以继承自Control类,也可以继承自其他类比如ContentControl, ItemControl.我们继承自ContentControl,代码如下: using System; using System.Net; using System.Windows; using Syste

Visual C++2005中开发自定义绘图控件

本文源代码下载:CustomDraw.exe. 在您决定开发 Windows 提供的常规免费自定义控件范围之外的控件之后,您必需确定自己的控件将有多少独到之处 - 在功能和外观两方面.例如,我们假定您正在创建一个类似于计速表的控件.由于公共控件库 (ComCtrl32.dll) 中没有类似的控件,您完全需要自己进行以下操作:编写所有控件功能需要的代码,进行绘制,默认终端用户的交互,以及控件与其父窗口之间需要的任意消息处理. 另一方面,还包括一些您只想调整公共控件功能的情况.例如,我们假定您想创建

扩展Eclipse的Java开发工具

由于 Eclipse 具有功能强大的 Java 开发环境,这使它获得了人们的一致好评.这个 Java 开发环境(再加上团队环境和其它基础功能)使 Eclipse 成为引人注目的集成开发环境,对 Java 开发人员来说,这是个好消息.而且,Eclipse 还是一个开放源码项目.但真正使人们对 Eclipse 感到兴奋的是它提供了扩展的可能性. 许多基于 Eclipse 的商用产品都显示出这种提供集成产品的方法的实际意义.例如,IBM WebSphere Application Developer

使用ASP.Net中的自定义控件

自定义控件是ASP.NET中很重要的一部分,使用它可以提高程序代码的重用性,即一个自定义控件在网页.自定义控件或控件的内部都可以再次使用.本实例创建的复选框控件列CheckBoxColumn自定义控件也可以在网站的任何地方再次使用. 本实例介绍如何在ASP.NET中创建自定义控件.如何使用自定义控件,以及如何在自定义控件中定义公开属性和方法的实现方法. 1.创建新ASP.NET应用程序 在Visual Studio .NET 2003集成开发环境中创建新的ASP.NET Web应用程序,命名为E

架构师之路-在Dubbo中开发REST风格的远程调用

概述 dubbo支持多种远程调用方式,例如dubbo RPC(二进制序列化 + tcp协议).http invoker(二进制序列化 + http协议,至少在开源版本没发现对文本序列化的支持).hessian(二进制序列化 + http协议).WebServices (文本序列化 + http协议)等等,但缺乏对当今特别流行的REST风格远程调用(文本序列化 + http协议)的支持. 有鉴于此,我们基于标准的Java REST API--JAX-RS 2.0(Java API for REST

分析在Worklight中开发本地功能的三种模式

文章将分析在 Worklight 中开发本地功能的三种模式:调用 Cordova 支持的本地功能:通过 Worklight common API 调用开发的本地功能:编写 Cordova 的插件,调用本地功能.然后通过相应的实例展现不同方法的使用模式,向读者展现 Worklight 在和本地功能结合上的能力.最后分析各自的优缺点和使用的环境. 纯 web 模式的局限性 在 Worklight 架构下,纯 web 模式虽然可以像本地应用一样被安装在手机上,但是所提供的服务和传统的网页相比,几乎没有

在 Visual C++ 中开发自定义的绘图控件

本文讨论的重点介于两者 之间 - 公共控件赋予您想要的大部分功能,但控件的外观并不是您想要的.例如,列表视图控件提供在许多视图风格中显示数据列表的方式 - 小图标.大图标.列表和详细列表(报告).然而,如果您想要一个网格控件,那结果怎样呢?尽管公共控件库里没有特别包含网格,但是列表视图控件与它较为接 近,它以行和列显示数据,并有一个相关的标头控件.因此,许多人以一个标准的列表视图控件为起点创建自己的网格控件,然后重写该控件及其子项的呈现方式或 绘制方式.  主宰绘图操作即使"只"进行绘

asp.net中使用自定义控件的方式实现一个分页控件的代码_实用技巧

一.概述 在web开发中,常常需要显示一些数据,而为了方便排版及浏览,我们只需要显示所有记录中的一部分.一般情况下,我们采用分页来实现这个需求.实现分页的方法多种多样,在本文中,我们采用了一个分页空间来记录记录总数.当前页.总页数及页面大小等.为了有一个直观上的印象,先展示该控件运行后的效果,效果如下图所示: 二.实现方案 为了实现该效果图,在asp.net中,可以使用Custom Controls and User Controls两种方式,User Controls的实现方式及其简单,而且使

开发自定义控件

问题描述 说有开发自定义控件的例子啊,书也行,最好能有说明 解决方案 解决方案二:你指的是C#控件还是OCX控件.OCX控件C#好象做不出来,VB等工具可以.C#控件制作很简单,新建控件就搞定了.解决方案三: 解决方案四:这是winform的,很详细解决方案五:持续关注解决方案六:扩展控件咋创建啊?解决方案七:对ocxVB做的很好做,C#的里面做的也好做