欢迎光临
我们一直在努力

使用智能设备扩展在 C# 中开发自定义控件-.NET教程,C#语言

建站超值云服务器,限时71元/月

控件

发布日期: 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 pins 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 对象的窗体

赞(0)
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com 特别注意:本站所有转载文章言论不代表本站观点! 本站所提供的图片等素材,版权归原作者所有,如需使用,请与原作者联系。未经允许不得转载:IDC资讯中心 » 使用智能设备扩展在 C# 中开发自定义控件-.NET教程,C#语言
分享到: 更多 (0)