|
|
-
更改启动画面 好了,到这里,我们的Windows Embedded Standard开发初体验活动接近尾声了,还有一个问题,有人觉得Windows Embedded Standard的启动界面与XP完全一样,让人感觉和用XP没有区别。于是,我们就需要修改一下启动画面来达到这个效果了:
我必须承认,自己是个很没有美工天赋的人(要么怎么当了没前途的程序员呢J)。加上启动界面的要求过于苛刻,做成这样已经很不错了。需要说明的是,我是按照雷志刚BLOG中的《更换XPE开关机画面和欢迎界面》来实现的,链接如下:
http://lzg-ad.blog.sohu.com/76781546.html
首先,我们需要创建一个640x480的位图,位图颜色为16色(注意不是16位色)。我用了Windows Embedded Standard的标准界面。然后,将位图改名为Boot.bmp,保存到WES Image的Windows目录下。还需要修改Boot.ini文件:
[boot loader] timeout=30 default=mulit(0)disk(0)rdisk(0)partition(1)\Windows [operating systems] default=mulit(0)disk(0)rdisk(0)partition(1)\Windows="Windows XP Professional"/fastdetect /bootlogo /noguiboot
其实主要是修改粗体字部分,其余部分保持原状即可。

好了,到这里,我们的Windows Embedded Standard初体验就完成了。必须承认,Silverlight为我们提供了强大的UI表现能力,而Windows Embedded Standard又是一个能够展现Silverlight强大功能的操作系统平台。这一点,对于嵌入式开发来说,是具有非常大的吸引力的。
这里只是一个起点,至于大家可以使用Windows Embedded Standard开发出怎样的嵌入式产品来,我无法想象,但是却乐观其成。
|
-
IE的安全限制 好了,让我们回过头来说Silverlight TV的那个安全限制吧,还是要强调一下,并不是所有的Silverlight都会遇到这个问题,只是Silverlight TV调用了ActiveX组件,才会遇到这个问题。
这个安全提示在我们平时使用IE时也经常会遇到,点一下也就没事了,毕竟为了安全着想嘛。不过如果将这个Silverlight组件作为Shell,每次启动之后,都是一个黑屏,需要人工点一下之后才能正常运行,用户体验似乎不够友好。

于是,我们就需要更改一些IE的高级安全设置来解决这个问题了。如果熟悉IE的朋友,可能很快就知道问题出在哪里了,不过我是在同事的帮助下花了两天才找到问题根源的。问题的原因在于:

对的,就是在IE的Internet Options对话框里,安全选项(Security)中“Allow active content to run in files on My computer”的选项默认是不选的。选上这个设置之后,运行Silverlight TV时就不会有安全提示出现了。
IE的问题解决了,可是到了Windows Embedded Standard里又有问题了。IE的Advanced设置在WES IE组件的设置里是没有的。所以,我们只能通过修改注册表这一条路了。
总之,经过了千难万险,我们知道了“Allow active content to run in files on My computer”对应:
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_LOCALMACHINE_LOCKDOWN
中的“iexplore.exe”项,而且非常特别的是,该项为0,表示选中该选项;为1时,则表示未选中该项。大家开发时一定注意。
总之,我们了解了这个设置对应的注册表项后,接下来就是在组件中添加相应的注册表项就可以了。这个工作在前边已经做过了。
|
-
导入组件数据库 将组建导入组件数据库的工作相对简单,只需要打开Component Database Manager,然后在Database属性页中,点击Import按钮。在弹出的Import SLD对话框中,首先选择SDL file,也就是我们刚才通过Component Designer编辑好的组件,然后点击Import按钮就可以了。

因为导入的文件有些多,所以可能会延时几秒钟,等看到文件被成功导入的提示后,就可以关闭Component Database Manager,打开Target Designer来设计我们的操作系统了。
创建Silverlight播放器
在已经创建过一个Target Image之后,我们创建一个以Silverlight为Shell的Image看起来也不是那么复杂。还是让我们回到Target Designer中吧。首先创建一个新的Configuration。
然后添加下面的这些组件:
1, Software àTest & Deployment à Virtual PC 2004 Helper Macro
2, SoftwareàSystemàSecurityàInfrastructureàWindows Logon (Standard)
3, Software à System àUser Interface àShells à MyShell(这个就是你创建的Shell组件)
4, Software à Test & Deployment à Runtime Quick Start Helper Macro

我们打开Runtime Quick Start Helper Macro节点,选中Setting,然后在右边的属性窗口中,不选中Explorer shell。如果不去掉Explorer shell,它将在依赖检查时与我们创建的MyShell发生冲突。

好了,在完成这一步后,按F5进行依赖检查。在依赖检查完成后,我们还需要设置一下显示器的分辨率,在中间区域被添加的组件中找到“S3 Trio64V+”显卡驱动组件,选中其Setting。在右边的设置窗口中,将Screen resolution选择为“1024 by 768 pixels”。

好了,完成这一步之后,我们可以放心大胆地F7,编译Windows Embedded Standard的Image,然后将其部署到另外的VPC中去。
在经历了FBA之后,我们会看到系统启动后,直接进入了Silverlight的显示界面:

COOL吧?至少我觉得挺酷的。
|
-
添加文件、依赖组件、注册表 接下来我们就要进入创建组件最重要的一环了,添加文件。为什么说重要,因为这里有一个大坑,我在Windows Embedded Standard产品组施卫娟老师的指导下,花了两周的时间才勉强爬出来,可见该坑之深。
在这里我们使用了一个现成的Demo,SilverlightTV作为我们的目标文件。SilverlightTV中包含很多文件和文件夹。我们要做的第一件事,就是将所有文件拷贝到一个文件夹中。这一点非常重要,因为考虑到效率问题,Windows Embedded Standard组件中的文件只能包括在同一个文件夹中。
主要提示:如果将包含子目录的文件夹添加到组件中,组件设计器不会报错,而要等到这个组件加入到Target Designer时,才会报文件找不到的错误,并且在构建好的Image文件中,只会包括文件夹,而不会包括其中的文件。
不过,新的问题就来了,如果我的文件结构是固定的,并且有相互的依赖关系,把所有文件都放到一个文件夹后,程序无法正常运行该怎么办呢?我们自然有办法,不过现在,还是赶紧将所有文件放在一个文件夹中吧,这个文件夹就是添加Repositories时指定的Source Path文件夹。不过千万记清文件原来所在的文件夹,否则下一步就麻烦了。

做完这步之后,回到Component Designer中,在Files节点单击右键,在菜单中选择Add – Files in Folder,在稍后弹出的对话框中,选择我们放置源文件的文件夹。经过下面的处理过程之后,文件夹中所有的文件都被加入到组件中了

不过问题是,所有的文件还被放置在同一个目录中,无法正常执行。这时,我们要通过一个比较繁琐的过程来解决这个问题——修改文件的目的文件夹。在右侧的文件列表中,选中某一个文件,双击,打开文件编辑对话框:

我们打开的是一个wmv文件,我们只需要修改Destination中的目录结构,%24%是一个转义字符,表示C盘,我们不要去动它。根据我们文件的原始位置填写目的文件的所在目录就可以了。这个工作要花费一段时间,需要点耐心。
接下来,我们要添加组件的Group Memberships,表示组件在组件目录树中所处的位置。我们在“Group Memberships”节点上右键选择“Add Group Membership”命令。在弹出的对话框中,选择SoftwareàSystemàUser InterfaceàShells,然后选择Shell即可。点击OK,关闭对话框。

接下来为组件添加依赖关系。依赖关系表明该组件依赖于那些组件,当我们添加了该组件到Target Image中时,在依赖检查时,会将其所依赖的组件也添加到Image中。

我们选择“Component or Group Dependency”,右键菜单中“Add”-“Component Dependency”,会弹出添加组件依赖对话框。
我们在“Component dependency source”选择Database,即选择组件数据库中的组件。然后找到SoftwareàSystemàNetworking & Communicationsà ApplicationsàInternet Explorer – Hotfix Q321232,并且确认My Component下选择的是“Depends on the selected component”,然后点击Apply按钮,添加依赖关系。
这时对话框不会关闭,所以我们还需要添加第二个依赖组件,就是大名鼎鼎的

好了,到这里,我们的组件就基本完成了,哦,还忘了件事,添加注册表。不过请大家注意的是,并不是每个Silverlight组件都需要添加注册表项,是因为SilverlightTV这个组件中调用了ActiveX,IE中会有安全限制,所以我们才通过修改注册表的方式来规避这个问题。关于IE安全限制的问题,我们后边会专门说明,在这里大家只要了解如何为组件添加注册表项就可以了。

在Component Designer中选择Registry Data,右键菜单中选择添加注册表项。我们可以点击Key name后边的Browse按钮,在注册表中定位要添加的项。我们要添加的项为:
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_LOCALMACHINE_LOCKDOWN
中的“iexplore.exe”项,值为DWORD,设置为0。
大家一看就知道这个选项是与IE有关的,至于什么关系,我们先卖个关子,稍后再说。先点击OK,关闭注册表编辑对话框。
好了,到这里我们支持Silverlight的Shell组件终于编辑完成了。好的,赶紧保存一下吧。不过,我们怎么在Target Designer里使用这个组件呢?
这就要看下一节的了……
|
-
作者:马宁
开始创建Silverlight的组件
Silverlight在嵌入式领域发挥的作用绝对不仅仅只有增加Web的用户体验而已。很多嵌入式设备需要丰富多彩的用户交互界面,比如ATM、自动售货机、自助查询终端、老虎机(喔)……Windows Embedded Standard的主要应用领域恰好在这几个方面,所以Silverlight对于来说Windows Embedded Standard来说是如虎添翼。可能有朋友也会说,我们使用其他技术也可以实现与Silverlight类似的界面,这个肯定是没错。但是考虑到与后台数据库或Web Service的交互、部署成本、维护成本等问题,我相信Silverlight肯定会成为开发者今后开发中的首选。
好了,先不说那么多,我们下一步要做的是将Silverlight变成WES的主界面,WES启动之后直接进入到Silverlight的应用之中。我们知道Silverlight是Host到IE等浏览器中运行的,在WES中也是这样,而Silverlight的部署有两种方式:第一种是将Silverlight文件部署到WES的Image中,随Image一起发布;另一种是让浏览器启动后访问Internet上的某个Silverlight站点。这两种方法有各自的应用场景,我们在这里不做过多分析。在这里,我们挑选比较难得一种:将Silverlight文件与WES的Image一起发布。
第一步,我们要创建一个组件:这个组件应该是一个Shell组件,在系统启动后不进入我们传统意义上的“桌面”,而是直接启动这个组件中指定的应用程序。而且组件中还应该包括一些Silverlight相关的文件、一些注册表项,还有一些高级设置。稍后我们会看得到。
我们还是先打开我们创建组件的工具:Component Designer。Windows Embedded Standard没有一个完整的IDE环境,所以做什么事情的时候要分别打开不同的工具。
Component Designer的界面如下,我们要先选择File-New创建一个新的组件,然后保存这个组件,并为sld文件设置一个文件名称。
接下来,我们要创建一个Repositories,这个相当于给组件住的房子。组件是保存在数据库中的,而Repositories在数据库中可以起到隔离组件的作用,而且也比较方便我们维护和升级组件。

我们右键点击上图中Repositories节点,选择“Add Repository”来创建一个新的Repositories。首先为Repositories起个名字,写在右侧属性窗口的Name中,然后还要点击Source Path后边的Browse按钮。Source Path顾名思义,我们要为该Repositories的源文件找一个保存的位置,而我们一般也会把组件所需的文件都放到这个文件夹下。这个文件夹一般没有规定,但是最好放到C盘下,自己创建个文件夹就可以了。
接下来,我们就该添加组件了,在上图的Components节点上右键单击,在右键菜单上选择“Add Component”。
创建组件之后,第一件事是在右侧的属性窗口中修改名字。

因为我们创建的这个组件是一个Shell组件,所以我们还要规定它在组件库中的原型组件,点击Prototype后边的Browse按钮。
我们会看到下图的对话框,初始化可能会花费一些时间。选择Software | System | User Interface | Shells下的Shell Prototype Component组件,作为我们组件的原型组件。

接下来还需要点击Advanced按钮,定义启动时的自定义动作:

在上面的对话框中,点击Add按钮添加一个叫做“cmiShellPath”的扩展属性,类型为String,具体指为:
%16426%\Internet Explorer\iexplore.exe -k "C:\SilverlightApplication2\Default.html"
怎么样看着眼熟吧,没错就是启动一个IE浏览器的实例,-k表示全屏显示,而后边的文件路径不是一成不变的,要根据你将这个HTML文件放到哪里而决定。如果是在线的网站其实更简单一些,直接写网址就好了。
然后为组件指定Repositories,点击“Repositories…”按钮,会弹出下面的对话框。好了,直接选择咱们前边创建的,就可以了。

然后到了非常重要的一步,添加文件。这个问题也曾经困扰了我很久,我们明天再说。
更多关于Windows Embedded开发文件,请参考:http://we.cnblogs.com/
|
-
支持Silverlight的Windows Embedded Standard
好了,完成安装之后,我们就可以来做Windows Embedded Standard的第一个操作系统镜像了。在开始菜单中找到“Microsoft Windows Embedded Studio”,启动其中的Target Designer。然后再Target Designer的File菜单中选择New,创建一个新的Configuration。

输入名称后,点击OK,进入Target Designer的主界面。为了简单起见,我们选择Virtual PC作为我们的目标设备,这样就可以省去获取硬件设备信息的步骤。
首先,我们要在左侧的组件列表中找到我们要添加的组件。第一个自然是Silverlight组件了。Windows Embedded Standard中包括的是Silverlight 1.0,目前Silverlight 2.0还是beta阶段,也许等WES正式推出时就是Silverlight 2.0了,即使不是,也肯定会有QFE的。

由于不确定Silverlight被放到哪里了,所以只好Search了一把。结果是,在Software-System-System Services-Application Support中包括了Silverlight组件。除了Silverlight之外,我们还需要添加几个组件:
首先,是Design Templates下的Information Application;然后是,Software节点下的Test & Development下的Runtime Quick Start Helper Macro和Virtual PC 2007 Helper Macro两个组件。

添加了这些组件之后,我们就可以进行依赖检查了,按F5,或者在Configuration菜单下的Check Dependencies项,就可以执行了。如下图:

依赖检查之后,如果没有错误,我们可以直接进行编译,选择Configuration下的Build Target Image或者按F7,构建过程如下图:

在编译完成之后,我们就可以在C:\Windows Embedded Images下找到已经编译好的文件。我们将其拷贝到准备好的虚拟硬盘中就可以了。如何构建VPC的开发环境,请参考我的Webcast 《XP Embedded开发初体验》中的介绍。
在第一次启动时,系统会启动FBA(First boot agent)来完成一些设置,我们需要耐心等待一些时间:

接下来,我们关闭WES开发环境的VPC,记得一定要选择Shut down。

然后运行XPETraget的VPC,我们编译出来的Windows Embedded Standard Image其实和XP Professional非常类似,只是少了比较多的组件。不过我们仍然能够通过开始菜单来打开IE浏览器,然后再里边输入URL: www.silverlight.net 。对Silverlight比较熟悉的朋友一定会知道这个网站。而访问这个网站时,并没有提示我们需要安装Silverlight,这表明Silverlight已经被加到了Windows Embedded Standard的Image之中了。
|
-
作者:马宁
Windows Embedded Standard,微软又创造了一个新名词。与此同时,XP Embedded,成为了一个历史名词。微软在年初推出了新的Windows Embedded产品线命名规则:Windows CE改名为Windows Embedded Compact,而XP Embedded改名为Windows Embedded Standard,WEPOS改名为Windows Embedded POSReady,还有新推出的针对导航产品的Windows Embedded NavReady。
好了,无论Windows Embedded Standard叫什么,我们只要记得,它就是一个组件化的XP Professional SP2就好了。为了适应嵌入式产品的定制化需求,微软将XP Professional拆成了两万多个组件,允许用户根据自己的需求进行二次开发,定制尺寸更小的操作系统。而且可以完成一些特殊的要求,比如换一个桌面程序,启动时执行自定义操作,还可以更换启动的XP界面等。还有一些EWF之类的高级功能。
但是被乱刀切块的XP Embedded也是XP,所以Windows Embedded Standard也只能运行在x86架构的CPU上,而不能支持其他的CPU,比如ARM。
当然Windows Embedded Standard比起之前版本的XP Embedded还是有很多进步的,最大的一个特点就是将Silverlight引入到了WES中,后边我们还会详细说到。
说到这里,相信大家会对Windows Embedded Standard有一个感性的理解。接下来,我们就要通过实践,让大家看到Windows Embedded Standard开发的流程。
安装Windows Embedded Standard
目前Windows Embedded Standard还没有正式发布,现在的CTP(社区预览版)相当于征求意见稿,可以免费从微软网站上下载的到。只需要在下列地址中,使用Live ID做一个简单的注册,下载项目中选择Windows Embedded Standard的ISO文件即可。下载地址:
https://connect.microsoft.com/windowsembedded
下载过程会安装微软的File Transfer Manager,文件有4G左右,下载过程会根据网速有所不同,请大家耐心等待。

下载完成后,我们可以刻盘或者使用Daemon虚拟ISO文件,然后自动运行。运行会为我们启动一个Windows Embedded Standard的安装向导。
首先在Prepare中需要首先安装SQL Server 2005 Express版本,可以从Web或者DVD安装。我选择从Web安装,30多M,不算太大。SQL Server Express主要是为了存储WES的组件数据库。

接下来直接进入WES的安装向导:

省略欢迎界面和License界面,反正所有软件都有这两个界面。下面是选择快速安装还是高级安装的界面。我选择了“Advanced”:

在高级安装模式中,果然有我们需要的东西。在“Windows Embedded Standard”-“Database”中选择“Chinese(PRC) Language Pack”和“Chinese(Taiwan) Language Pack”,简体中文和繁体中文不是默认安装,所以我们要选:

下面这一步非常重要:我在这里被困扰了两个小时……。安装过程需要输入SQL Server Express的名称。我在这里犯了错误(之前一直在用SQL Server),我们要填写计算机名+实例名称,而SQL Server Express安装时,我没有填写实例名称,然后就想当然的认为没有实例名称。不过经过试验,我知道SQL Server Express的数据库名是:计算机名+SQLEXPRESS。

然后点击旁边的Test按钮,如果数据库连接成功,则显示下图的对话框。需要说明的是,如果数据库连接不成功,也可以完成安装过程,不过在打开Target Designer时,会告诉你数据库初始化失败。(知道我为什么被困扰两个小时了吧?)
如果显示下面的界面则说明数据库连接没有问题,放心安装就好了。

后边的安装过程没有需要介绍的了,安装完成即可。
|
-
一旦支持了鼠标功能,MF可研究的领域,似乎变得无穷无尽,这也许是我近几天一直陷于此中不能自拔的原因了。
有了鼠标即使没有键盘,我们也可以通过虚拟键盘完成输入工作。这个工作很有意义,也很有挑战性,因为这不是windows平台,也不是Dos平台,这是“一穷二白”的MF平台,所有的工作都得从头做起(实现绘制键盘,向有输入焦点控件发送按键信息,删除,添加字符等等功能)。
目前字符串不仅无法转换为数字,并且没有字符插入功能,所以这个函数也必须自己来实现,此外输入焦点光标也需要自己绘制,好了,先不说难处了,先看看最终成果。
这是一个文本输入实例,当文本框有输入焦点时,自动弹出输入面板。
虚拟键盘上的“Shift”键也是有效的,切换后可以输入符号和大写字母。
当文本框失去输入焦点的时候,虚拟键盘消失。
以上应用实现的代码如下:
//个人信息
public class YFSelfInfo : YFDialog
{
YFLabel[] label=new YFLabel[3];
YFText[] text=new YFText[3];
string[] strLabelName = new string[] {"姓名:","单位:","博客:"};
public YFSelfInfo(string Title, int Left, int Top, int Width, int Height, YFWindowBase Parent): base(Title, Width, Height, Parent)
{
//-------------
for(int i=0;i<3;i++)
{
label[i] = new YFLabel(strLabelName[i], 20, ClientRect.Top + 10+30*i,40, 20);
Children.Add(label[i]);
text[i] = new YFText("", 70, label[i].Top, 140, 20);
text[i].Enter += new YFSoft.SPOT.Presentation.YFEventHandler(text_Enter);
text[i].Leave += new YFSoft.SPOT.Presentation.YFEventHandler(text_Leave);
Children.Add(text[i]);
}
}
//获得输入焦点时显示输入面板
void text_Leave(object sender, EventArgs e)
{
HideInputPanel();
this.Height = SystemMetrics.ScreenHeight;
}
//失去焦点时隐藏输入面板
void text_Enter(object sender, EventArgs e)
{
ShowInputPanel();
this.Height = this.InputPanelTop + 1;
}
//主窗体鼠标信息
public override void OnMouseClick(object sender, MouseEventArgs e)
{
text_Leave(null, null); //隐藏输入面板
base.OnMouseClick(sender, e);
}
}
用户程序是不是很简单?再看看我实现的一个记事本程序
正在输入界面
菜单功能支持
相关代码如下:
//记事本
public class YFNote : YFDialog
{
YFText text;
public YFNote(string Title,int Left, int Top, int Width, int Height, YFWindowBase Parent)
: base(Title, Width, Height, Parent)
{
text = new YFText("", ClientRect.Left, ClientRect.Top, ClientRect.Width, ClientRect.Height);
text.BorderStyle = BorderStyle.None;
text.Align = false;
text.Enter += new YFSoft.SPOT.Presentation.YFEventHandler(text_Enter);
text.Leave += new YFSoft.SPOT.Presentation.YFEventHandler(text_Leave);
Children.Add(text);
this.Left = Left;
this.Top = Top;
Menu.AddItem(new MenuItem("新建"));
Menu.AddItem(new MenuItem("保存"));
Menu.AddItem(new MenuItem("-"));
Menu.AddItem(new MenuItem("退出"));
}
//获得输入焦点时显示输入面板
void text_Leave(object sender, EventArgs e)
{
HideInputPanel();
this.Height = SystemMetrics.ScreenHeight;
text.Height = ClientRect.Height;
}
//失去焦点时隐藏输入面板
void text_Enter(object sender, EventArgs e)
{
ShowInputPanel();
this.Height = this.InputPanelTop+1;
text.Height = ClientRect.Height;
}
//主窗体鼠标信息
public override void OnMouseClick(object sender, MouseEventArgs e)
{
text_Leave(null, null); //隐藏输入面板
base.OnMouseClick(sender, e);
}
//菜单
public override void OnMenuClick(MenuEventArgs e)
{
switch (e.Text)
{
case "新建":
text.Text = "";
break;
case "保存":
MessageBox("不好意思,暂时没有保存功能","记事本", MessageBoxButtons.OK, MessageBoxIcon.Warning);
break;
case "退出":
Close();
break;
}
base.OnMenuClick(e);
}
}
程序也超简单,但功能还是蛮强大的,我们下面看看虚拟键盘的相关代码,实现思路和鼠标功能一样,也是直接借助基类的虚拟函数,向指定的窗体和控件发送按键消息。
核心代码如下:
public void TransactKey(KeyState state, object sender, MouseEventArgs e)
{
//向控件发送按键消息
if (this.Parent != null)
{
Keys key = Keys.None;
int keyValue=0;
char _char = (char)0;
YFButton b = (YFButton)sender;
if (b.Text == "" || b.Text=="En" || b.Text=="Cn") return;
Button btn = Button.None;
switch (b.Text)
{
case "Tab":
key = Keys.Tab;
break;
case "Caps":
key = Keys.CapsLock;
break;
case "Shift":
key = Keys.Select;
break;
case "Ent":
key = Keys.Enter;
btn = Button.Select;
break;
case "Esc":
key = Keys.Escape;
break;
case "Ins":
key = Keys.Insert;
break;
case "Del":
key = Keys.Delete;
break;
case "Menu":
key = Keys.Menu;
btn = Button.Menu;
break;
case "↑":
key = Keys.Up;
btn = Button.Up;
break;
case "←":
key = Keys.Left;
btn = Button.Left;
break;
case "↓":
key = Keys.Down;
btn = Button.Down;
break;
case "→":
key = Keys.Right;
btn = Button.Right;
break;
case "<-":
key = Keys.Back;
btn = Button.Back;
break;
default:
_char = b.Text[0];
key = (Keys)b.Name[0];
break;
}
keyValue = (int)key;
//向当前有输入焦点的窗口发送系统按键消息
if (btn!= Button.None)
{
if ((state & KeyState.Down) > 0) Parent.GetFocus().OnButtonDownEx(new ButtonEventArgs(null, null, new TimeSpan(), btn));
if ((state & KeyState.Up) > 0) Parent.GetFocus().OnButtonUpEx(new ButtonEventArgs(null, null, new TimeSpan(), btn));
}
//获得输入焦点的控件
YFControl c = this.Parent.Children.GetFocus();
if (c != null)
{
if ((state & KeyState.Down) > 0) c.OnKeyDown(sender, new KeyEventArgs(key, keyValue, button[41].Checked, button[28].Checked, button[55].Checked));
if ((state & KeyState.Press) > 0 && _char != (char)0) c.OnKeyPress(sender, new KeyPressEventArgs(_char));
if ((state & KeyState.Up) > 0) c.OnKeyUp(sender, new KeyEventArgs(key, keyValue, button[41].Checked, button[28].Checked, button[55].Checked));
}
//父窗体要求接收按键消息
if (Parent.KeyPreview)
{
if ((state & KeyState.Down) > 0) Parent.OnKeyDown(Parent, new KeyEventArgs(key, keyValue, button[41].Checked, button[28].Checked, button[55].Checked));
if ((state & KeyState.Press) > 0 && _char != (char)0) Parent.OnKeyPress(Parent, new KeyPressEventArgs(_char));
if ((state & KeyState.Up) > 0) Parent.OnKeyUp(Parent, new KeyEventArgs(key, keyValue, button[41].Checked, button[28].Checked, button[55].Checked));
}
}
}
以上仅仅实现的是英文输入,中文输入怎么做?似乎要困难的多,此外粘贴、复制功能要不要做?总之有很多很多的东西值得我们去探索去实现,这一切,似乎我们在走Microsoft的老路,要在MF平台上实现一个Micro Windows!说到这我想到一个笑话,有一个人给上帝进行打赌说他也能造人。上帝问他,你怎么做,他说,我先用泥土捏一个…,“等等,”上帝打断了他,“你要用你自己的泥土”。
|
-
基于MF系统的Windows SideShow界面是非常炫的(如下图)。既然微软能用.Net Micro Framework实现这么棒的界面效果,我想我们也能做到。
(SideShow模拟器界面和游戏程序中的右键菜单—注意菜单弹出后,其它的界面变暗了)
现在的任务是设计一套支持鼠标(或触摸屏)的窗体框架(目前MF提供的Window类仅支持按键功能),所以正好把SideShow如此炫的界面元素也可以添加进来。
用过MF的人知道是用下面的方法来实现按键事件接收的,既然我们要支持鼠标功能,所以最好也用类似的机理实现。
//按键事件
protected override void OnButtonDown(ButtonEventArgs e)
{
switch (e.Button)
{
//按下确定键
case Button.Select:
break;
//按下左键
case Button.Left:
break;
//按下右键
case Button.Right:
break;
//按向上
case Button.Up:
break;
//按向下
case Button.Down:
break;
//按下菜单
case Button.Menu:
break;
//按下返回键
case Button.Back:
break;
}
base.OnButtonDown(e);
}
用反编译工具仔细研究了MF底层库代码(.Net FrameWork 太庞大了,一个人绝对短时间内看不完,其实也很难看下去,但是对刚刚起步的MF来说,.Net Micro FrameWork就简单多了),终于理清了头绪。主要原理是在鼠标信息处理线程中通过Application.Current.Windows 属性(该属性存放了当前实例所有派生于Window类的窗体)和应用实例的this.Dispatcher属性的BeginInvoke方法,外部调用窗体鼠标事件函数。充分利用基类虚函数的妙处来实现类似按键信息处理的功能。
在YFWindowBase类中声明如下虚拟鼠标事件函数。
//鼠标移动
public virtual void OnMouseMove(object sender, MouseEventArgs e)
{
if (MouseMove != null) MouseMove(sender, e);
}
//鼠标单击
public virtual void OnMouseClick(object sender, MouseEventArgs e)
{
if (MouseClick != null) MouseClick(sender, e);
}
//按下
public virtual void OnMouseDown(object sender, MouseEventArgs e)
{
if (MouseDown != null) MouseDown(sender, e);
}
//抬起
public virtual void OnMouseUp(object sender, MouseEventArgs e)
{
if (MouseUp != null) MouseUp(sender, e);
}
在鼠标信息处理函数中执行如下的代码即可。
//处理鼠标消息
private static void TransactMouse(MouseState state, int x, int y, MouseButtons button)
{
if (Application.Current == null) return;
for (int i = Application.Current.Windows.Count - 1; i >= 0; i--)
{
try
{
YFWindowBase mw = Application.Current.Windows[i] as YFWindowBase;
if (mw.Enabled && mw.IsVisible)
{
//判断子窗体
bool bReturn = false;
for (int j = mw.Children.Count - 1; j >= 0; j--)
{
//仅最上层并且可视的控件接收鼠标消息
YFControl cl = mw.Children[j];
if (!bReturn && cl.Visible && IsRectContains(x, y, mw.Left + cl.Left, mw.Top + cl.Top, cl.Width, cl.Height))
{
if (cl.Enable) //Enable和Visible不一样,Enable即使无效,下层控件也没有机会获得鼠标消息
{
if (!cl._EnterFlag)
{
cl._EnterFlag = true;
_dispatcher.BeginInvoke(new MouseInputEventHandler(cl.OnMouseEnter), cl, new MouseEventArgs(button, x - cl.Left - mw.Left, y - cl.Top - mw.Top));
}
if ((state & MouseState.Move) > 0)
_dispatcher.BeginInvoke(new MouseInputEventHandler(cl.OnMouseMove), cl, new MouseEventArgs(button, x - cl.Left - mw.Left, y - cl.Top - mw.Top));
if ((state & MouseState.Down) > 0)
_dispatcher.BeginInvoke(new MouseInputEventHandler(cl.OnMouseDown), cl, new MouseEventArgs(button, x - cl.Left - mw.Left, y - cl.Top - mw.Top));
if ((state & MouseState.Up) > 0)
_dispatcher.BeginInvoke(new MouseInputEventHandler(cl.OnMouseUp), cl, new MouseEventArgs(button, x - cl.Left - mw.Left, y - cl.Top - mw.Top));
if ((state & MouseState.Click) > 0)
_dispatcher.BeginInvoke(new MouseInputEventHandler(cl.OnMouseClick), cl, new MouseEventArgs(button, x - cl.Left - mw.Left, y - cl.Top - mw.Top));
}
//向主窗体传OnMouseEvent消息,为了绘制鼠标
if ((state & MouseState.Event) > 0)
_dispatcher.BeginInvoke(new MouseInputEventHandler(mw.OnMouseEvent), mw, new MouseEventArgs(button, x - mw.Left, y - mw.Top));
bReturn = true;
}
else
{
if (cl._EnterFlag)
{
cl._EnterFlag = false;
_dispatcher.BeginInvoke(new MouseInputEventHandler(cl.OnMouseLeave), cl, new MouseEventArgs(button, x - cl.Left, y - cl.Top));
}
}
}
if (bReturn) return;
}
//仅最上层并且可视的窗体接收鼠标消息
if (mw.IsVisible && IsRectContains(x, y, mw.Left, mw.Top, mw.Width, mw.Height))
{
if (!mw.Enabled) return;
if ((state & MouseState.Move) > 0)
_dispatcher.BeginInvoke(new MouseInputEventHandler(mw.OnMouseMove), mw, new MouseEventArgs(button, x - mw.Left, y - mw.Top));
if ((state & MouseState.Down) > 0)
_dispatcher.BeginInvoke(new MouseInputEventHandler(mw.OnMouseDown), mw, new MouseEventArgs(button, x - mw.Left, y - mw.Top));
if ((state & MouseState.Up) > 0)
_dispatcher.BeginInvoke(new MouseInputEventHandler(mw.OnMouseUp), mw, new MouseEventArgs(button, x - mw.Left, y - mw.Top));
if ((state & MouseState.Click) > 0)
_dispatcher.BeginInvoke(new MouseInputEventHandler(mw.OnMouseClick), mw, new MouseEventArgs(button, x - mw.Left, y - mw.Top));
if ((state & MouseState.Event) > 0)
_dispatcher.BeginInvoke(new MouseInputEventHandler(mw.OnMouseEvent), mw, new MouseEventArgs(button, x - mw.Left, y - mw.Top));
return;
}
}
catch (Exception e)
{
throw new Exception(e.Message.ToString(), e);
}
}
}
用户程序的窗体类只要派生于YFWindowBase类,就可以直接支持鼠标和按键功能了。用户代码如下:
//主窗体
internal sealed class MFWindow :YFWindowBase
{
public YFLabel label1;
YFButton button1, button2, button3, button4, button5;
public MFWindow()
{
//标签
label1 = new YFLabel("就绪", 0, Height - 25, Width, 25);
label1.TextAlign = TextAlignment.Left;
label1.BackColor = ColorUtility.ColorFromRGB(189, 235, 255);
label1.BorderStyle = BorderStyle.FixedSingle;
//添加按钮
button1 = new YFButton("触摸屏校准", 30, 35, 90, 40);
button1.MouseClick += new MouseInputEventHandler(button_MouseClick);
button2 = new YFButton("计算器",200, 35, 90, 40);
button2.MouseClick += new MouseInputEventHandler(button_MouseClick);
button3 = new YFButton("简易记事本", 30, 135, 90, 40);
button3.MouseClick += new MouseInputEventHandler(button_MouseClick);
button4 = new YFButton("关于...", 200, 135, 90, 40);
button4.MouseClick += new MouseInputEventHandler(button_MouseClick);
button5 = new YFButton("主菜单", 125, 85, 70, 40);
button5.MouseClick += new MouseInputEventHandler(button_MouseClick);
Children.Add(button1);
Children.Add(button2);
Children.Add(button3);
Children.Add(button4);
Children.Add(button5);
Children.Add(label1);
button3.Enable = false;
//button3.Visible = false;
//设置菜单
Menu.AddItem(new MenuItem("触摸屏校准"));
Menu.AddItem(new MenuItem("-"));
Menu.AddItem(new MenuItem("计算器"));
Menu.AddItem(new MenuItem("简易记事本"));
Menu.AddItem(new MenuItem("-"));
Menu.AddItem(new MenuItem("关于..."));
Menu[3].Enabled = false;
//Menu[3].Visible = false;
}
//按钮事件
void button_MouseClick(object sender, MouseEventArgs e)
{
YFButton button=((YFButton)sender);
switch (button.Text)
{
case "主菜单":
//弹出菜单
this.Menu.Show();
break;
default:
OnMenuClick(new MenuEventArgs(0, button.Text));
break;
}
}
运行后的界面如下:
图1:主界面(按钮即支持鼠标也可以用按键切换输入焦点(right键等同于PC平台上的Tab键),并用OK键触发按键事件)
图2:单击主菜单按钮或单击“Menu”就可以弹出主菜单
图3:不要小看了上图的蓝色小圆,是我费了好大劲才绘制出来的(目前MF仅支持矩形框的填充)。
private void DrawCircle(Color c,int x, int y, int r, DrawingContext dc)
{
Pen p=new Pen(c);
SolidColorBrush b = new SolidColorBrush(c);
int Offset45=(int)(0.707*r);
int Offset30 = r / 2;
int Offset60 = (int)(0.866 * r);
for (int i = Offset45; i < r + 1; i++)
{
dc.DrawEllipse(null, p, x, y, i, i);
}
dc.DrawRectangle(b, null, x - Offset45, y - Offset45,Offset45*2, Offset45*2);
dc.DrawRectangle(b, null, x - Offset60, y - Offset30, Offset60 * 2, Offset30 * 2);
dc.DrawRectangle(b, null, x - Offset30, y - Offset60, Offset30 * 2, Offset60 * 2);
dc.DrawLine(p, x - Offset60, y - Offset30, x - Offset30, y - Offset60);
dc.DrawLine(p, x+ Offset60 , y + Offset30, x + Offset30 ,y + Offset60 );
dc.DrawLine(p, x - Offset60, y + Offset30, x - Offset30 , y + Offset60);
dc.DrawLine(p, x + Offset60, y - Offset30, x + Offset30, y - Offset60);
}
图5:终于在MF上实现了计算器的功能,目前不仅支持鼠标,也可以用光标键和OK键进行输入计算了。
别小看了计算器程序,由于MF仅有数字转字符串功能,没有实现字符串转数字的功能,我自己自定义了一些函数,用了一些特殊的用法才编写完成。
//数字输入的部分代码
if (strInput == "0.") dblInput = 0;
if (strInput != "0.")
{
strInput += b.Text;
if (strInput.IndexOf('.')<1)
{
dblInput *= 10;
dblInput += ToDouble(b.Text);
}
else
{
int index = strInput.Length - strInput.LastIndexOf('.') - 1;
dblInput += ToDouble(b.Text) /System.Math.Pow(10,index);
}
}
else if (b.Text != "0")
{
strInput = b.Text;
dblInput = ToDouble(b.Text);
}
下面是窗体的界面搭建部分,你绝对想不到用这么短的代码就实现了上图的界面布局(看过我以前.Net Micro Framework研究的网友,应该对MF本身提供的控件有印象,正是因为系统的控件不好用,我专门重新写了一套MF控件类)。
YFButton[] button=new YFButton[20];
YFLabel lblInput = null;
string[] strText = new string[] { "7", "8", "9", "/", "CE", "4", "5", "6", "*", "%", "1", "2", "3", "-", "1/x", "0", "+/-", ".", "+", "=" };
public YFCalc(string Title,int Width,int Height,YFWindowBase Parent)
: base(Title, Width, Height, Parent)
{
int x=ClientRect.X,y=ClientRect.Y;
lblInput = new YFLabel("0.", x + 5, y + 5, ClientRect.Width - 10, 20);
lblInput.TextAlign = TextAlignment.Right;
Children.Add(lblInput);
for (int i = 0; i < 20; i++)
{
if(i % 5==0 && i!=0)
{
x = ClientRect.X;
y = y + 32;
}
button[i] = new YFButton("", x + 5, y + 32, 32, 28);
button[i].Text = strText[i];
button[i].MouseClick += new MouseInputEventHandler(button_MouseClick);
x += 37;
Children.Add(button[i]);
}
}
//按钮单击
void button_MouseClick(object sender, MouseEventArgs e)
{
}
其实这段时间以来,我一直在研究MF,虽然目前它还不是很成熟,但是随着研究的深入,越来越对它痴迷,越来越发现很多MF的宝藏(如果你有耐心的话,一定要看看MF底层框架的源码(通过反编译工具Reflector),你会发现很多很有意思的功能)。
MF相对于Windows XP/Vista、Windows CE而言,还只能算一个婴孩,但就是这样,就如一个伟人所说:孩子就是未来的希望。所以有理由相信MF的明天会更好。
|
-
.Net Micro Framework不支持文件系统(目前该项功能正在研发之中),所以无法像Windows和windows ce平台那样把需要永久保存的数据保存到文件之中。内存中保存的数据只要系统一掉电,所有的数据也都消失了,这对一些需要保存参数的应用来说真是不妙。
这几天在研究MF触摸屏功能时就遇到该问题,因为触摸屏校准之后,需要保存校准后的参数,否则MF一重启,难道还需要重新校准不成?
感谢Donald Thompson 和 Rob S. Miles,从他们的大作上找到了问题的解决办法。办法就是把对象保存到Flash(EEPROM)中(有点像对象的二进制序列化)。
下面是我整理的示例代码(实现比较简单,但总觉得不太正规,不知道能存多大,也搞不清楚数据到底存放在什么位置了。):
using System;
using Microsoft.SPOT;
using System.Collections;
using System.Threading;
namespace DataStorage
{
public class Program
{
public static void Main()
{
FlashDatas fd = FlashDatas.Load();
fd.dump();
fd.Flag = "adfg";
//fd.Items.Clear();
//fd.AddItem(new FlashData(55, "1aaa"));
//fd.AddItem(new FlashData(66, "2bbb"));
//fd.AddItem(new FlashData(77, "3ccc"));
//fd.AddItem(new FlashData(88, "4ddd"));
fd.Save();
Thread.Sleep(3000);
}
[Serializable]
private class FlashData
{
public DateTime date;
public int iData;
public string sData;
public FlashData(int iData, string sData)
{
date = DateTime.Now;
this.iData = iData;
this.sData = sData;
}
public override string ToString()
{
return date.ToString() + " " + iData.ToString() + " " + sData;
}
}
[Serializable]
private class FlashDatas
{
public DateTime CreateTime = DateTime.Now;
public string Flag = @"http://blog.csdn.net/yefanqiu/";
public ArrayList Items = new ArrayList();
public void dump()
{
Debug.Print(CreateTime.ToString());
Debug.Print(Flag);
foreach (FlashData Item in Items)
{
if (Item != null)
{
Debug.Print(Item.ToString());
}
}
}
public void AddItem(FlashData Item)
{
Items.Add(Item);
}
//调入数据
public static FlashDatas Load()
{
ExtendedWeakReference ewr = ExtendedWeakReference.RecoverOrCreate(
typeof(FlashDatas), //类型,任意类都可以,其名称起到一个索引作用
0, //ID号,这个数据比较有用,不同ID号代表不同数据
ExtendedWeakReference.c_SurvivePowerdown);//该标志和.c_SurviveBoot 区别不大
ewr.Priority = (Int32)ExtendedWeakReference.PriorityLevel.Important;
FlashDatas data = ewr.Target as FlashDatas;
if (data == null)
{
data = new FlashDatas();
}
return data;
}
//保存数据
public void Save()
{
ExtendedWeakReference ewr = ExtendedWeakReference.RecoverOrCreate(typeof(FlashDatas), 0, ExtendedWeakReference.c_SurvivePowerdown);
ewr.Priority = (Int32)ExtendedWeakReference.PriorityLevel.Important;
ewr.Target = this;
}
}
}
}
以上代码在Digi开发板上测试成功,断电之后,再上电,保存的数据确实没有丢失。
MSDN中相关函数的说明如下:
ExtendedWeakReference Members
The following tables list the members exposed by the ExtendedWeakReference type.
Public Constructors
Public Fields
|
|
Name |
Description |
|
|
|
Contains a flag specifying that the current weak reference will be recoverable after the device reboots. |
|
|
|
Contains a flag specifying that the current weak reference will be recoverable after the device powers down and restarts. |
Public Properties
|
|
Name |
Description |
|
|
Gets the flags specifying the states from which the current weak reference should be recoverable. |
|
|
|
|
|
|
|
|
|
Public Methods
|
|
Name |
Description |
|
|
Flags an ExtendedWeakReference object as a candidate for recovery after the device reboots or powers down and restarts. |
|
|
|
|
|
|
|
Attempts to recover a specific ExtendedWeakReference object, and creates a new instance of this class if the recovery attempt fails. |
|
-
机器人研究一直是我很早以前的梦想,没有想到在深入研究.Net Micro Framework同时能和机器人搭上了联系。
美国iRobot作为一家大型的机器人军工机械厂,其家用机器人吸尘器(电不足时能自动充电的机器人)已经让iRobot公司名满天下。我们研究的机器人就是iRobot公司推出了的新作——一个可以自己编程的机器人移动平台iRobot Create。
由于最近才从微软拿到这个机器人,所以我们第一步就是先要了解这个机器人,然后MF才能作为它的大脑控制该机器人。
让我们先一睹iRobot Create的“芳容”。
iRobot Create 和充电基座(Home Base)
iRobot Create 和BAM蓝牙模块
iRobot Create有六类共31条控制命令,即能控制机器人行走、旋转、播放音乐,也能获知32个内置传感器信息(轮是否抬起、是否是悬崖、前方是否有墙、左右碰撞检测等等)。通过串口和iRobot Create进行通信(默认波特率57600).
//27号信息包
public UInt16 WallSignal; //墙反射的无线信号强度 0 - 4095 高字节在前
//28号信息包
public UInt16 CliffLeftSignal; //左悬崖信号强度
//29号信息包
public UInt16 CliffFrontLeftSignal; //左前悬崖信号强度
//30号信息包
public UInt16 CliffFrontRightSignal; //右前悬崖信号强度
//31号信息包
public UInt16 CliffRightSignal; //右悬崖信号强度
//32号信息包
public bool BpsChange; //波特率发生改变
public bool DI_3; //DI输入
public bool DI_2;
public bool DI_1;
public bool DI_0;
//33号信息包
public UInt16 AI; //模拟入 0 -1023(5V) 高字节在前
//34号信息包
public bool HomeBase; //是否到家
public bool InternalCharger; //是否充电
随便拿出一段代码,你就会发现iRobot Create可获取大量有效的传感器信息(这样的信息包有四十多个)。
为了便于研究iRobot Create,我先用C#编写一个Windows程序(该程序通信控制部分和MF程序没有太大的区别,仅串口操作有细微差别),通过蓝牙模块(串口)和机器人进行通信(也可以用串口线连接,但是这样控制起来不方便)。
在调试过程中发现蓝牙模块的通信性能和串口线相比,有一定区别,那就是延时较大(一般串口线直连15ms延迟就能保证通信收发成功,蓝牙通信一般需要50ms之上延迟,才能保证通信成功)。控制界面如下。
机器人控制本身就是一个很大的课题,所以我们这里也无法一次说个完整和明白,今后我会陆陆续续发些相关文章的。我们今天先说说iRobot Create该充电时是如何找到“家”的(我想大家一定对这个感兴趣)。
iRobot Create毕竟是一个简单的机器人,所以千万不要和它捉迷藏,Homebase一定要放在室内开阔地带(iRobot Create是通过红外信号发现Homebase的所以中间最好不要有遮挡物,否则iRobot Create有可能要找一阵子)。
Homebase有两个红外发射管,可充当位置标记(一个为Green Buoy,一个为Red Buoy)。在iRobot Create头上有一个全方位红外接收头,可以接收到Homebase发出的红外信息。
可收到的红外指令如下:
iRobot Create就是根据以上信息,不断地调整自己的方位,找到回家的路。
|
-
MF的标准模块仅支持按键,并不支持鼠标功能。但是对一些常见应用来说,如果没有鼠标(或触摸屏)用起来就太不习惯了。有什么办法可以让MF支持鼠标功能呢?第一,外部设备必须把鼠标信息传到MF应用程序,应用程序根据这些信息绘制鼠标及执行相应的动作。鼠标信息最少包含三种,鼠标按键状态(按下或放开),鼠标坐标(x,y)。
目前,Spi通道可以非常方便地建立设备和用户程序之间的联系,所以就考虑用Spi来实现该功能。
第一步,还是从我编写的模拟器入手,添加了一个Spi驱动类。
//MouseDevice
public class MouseComponent : SpiDevice
{
public static Int16 State = 0;
public static Int16 X = 0;
public static Int16 Y = 0;
protected override byte[] Write(byte[] data)
{
//------------
//改写坐标值
try
{
//State = (Int16)((data[0] << 8) + data[1]);
//X = (Int16)((data[2] << 8) + data[3]);
//Y = (Int16)((data[4] << 8) + data[5]);
}
catch { }
//------------
//返回当前值
byte[] bytes = new byte[6];
bytes[0] = (byte)(State >> 8);
bytes[1] = (byte)(State & 0xff);
bytes[2] = (byte)(X >> 8);
bytes[3] = (byte)(X & 0xff);
bytes[4] = (byte)(Y >> 8);
bytes[5] = (byte)(Y & 0xff);
return bytes;
}
protected override ushort[] Write(ushort[] data)
{
//------------
//改写坐标值
try
{
//State = (Int16)data[0];
//X = (Int16)data[1];
//Y = (Int16)data[2];
}
catch { }
//------------
//返回当前值
ushort[] Int16s = new ushort[3];
Int16s[0] = (ushort)State;
Int16s[1] = (ushort)X;
Int16s[2] = (ushort)Y;
return Int16s;
}
}
第二步:编写鼠标应用程序
为了通用,我封装了一个windowbase基类
//鼠标事件
public class MouseEventArgs : EventArgs
{
public int X;
public int Y;
public int Button;
public MouseEventArgs()
{
X = 0;
Y = 0;
Button = 0;
State = MouseState.None;
}
public MouseEventArgs(int x, int y)
{
X = x;
Y = y;
Button = 0;
State = MouseState.None;
}
public MouseEventArgs(int x, int y, int button)
{
X = x;
Y = y;
Button = button;
State = MouseState.None;
}
}
//窗体基类
internal class WindowBase : Window
{
protected YFWinApp m_app;
Thread MouseThread;
private ushort state=0, x = 0, y = 0;
SPI _spi=null;
protected WindowBase(YFWinApp app)
{
m_app = app;
this.Visibility = Visibility.Visible;
this.Width = SystemMetrics.ScreenWidth;
this.Height = SystemMetrics.ScreenHeight;
Buttons.Focus(this);
//SPI的pin定义
_spi = new SPI(new SPI.Configuration((Cpu.Pin)127, true, 0, 0, false, false, 4000, SPI.SPI_module.SPI1));
x =(ushort)( this.Width/2);
y =(ushort)( this.Height/2);
MouseThread = new Thread(new ThreadStart(MouseInfo));
MouseThread.Start();
}
protected override void OnButtonDown(ButtonEventArgs e)
{
this.Close();
m_app.GoHome();
}
//获得鼠标信息
private void MouseInfo()
{
ushort[] bout = new ushort[3];
ushort[] bin = new ushort[3];
ushort mX, mY, mState;
while (true)
{
//----------------------------------
//通过spi通道获取鼠标信息 这部分信息解析和模拟器相对应
_spi.WriteRead(bout, bin);
mState = bin[0]; //鼠标的状态 1- 按下 0 - 放开
mX = bin[1]; //鼠标X坐标
mY = bin[2]; //鼠标Y坐标
//----------------------------------
if (x != mX|| y != mY)
{
x = mX; y = mY;
OnMouseMove(new MouseEventArgs(mX, mY, mState));
}
if (state != mState)
{
state = mState;
if (state == 1)
{
OnMouseDown(new MouseEventArgs(mX, mY, mState));
}
else if(state==0)
{
OnMouseUp(new MouseEventArgs(mX, mY, mState));
OnMouseClick(new MouseEventArgs(mX, mY, mState));
}
}
}
}
//鼠标移动
protected virtual void OnMouseMove(MouseEventArgs e)
{
Debug.Print("MouseMove:" + e.X.ToString() + "," + e.Y.ToString() + "," + e.Button.ToString());
}
//鼠标单击
protected virtual void OnMouseClick(MouseEventArgs e)
{
Debug.Print("MouseClick:" + e.X.ToString() + "," + e.Y.ToString() + "," + e.Button.ToString());
}
//按下
protected virtual void OnMouseDown(MouseEventArgs e)
{
Debug.Print("MouseDown:" + e.X.ToString() + "," + e.Y.ToString() + "," + e.Button.ToString());
}
//抬起
protected virtual void OnMouseUp(MouseEventArgs e)
{
Debug.Print("MouseUp:" + e.X.ToString() + "," + e.Y.ToString() + "," + e.Button.ToString());
}
//绘制鼠标
public override void OnRender(DrawingContext dc)
{
if (state == 1)
{
Pen pp=new Pen(ColorUtility.ColorFromRGB(255,255,0));
dc.DrawLine(pp, x, y - 5, x, y + 5);
dc.DrawLine(pp, x-5, y, x+5, y);
}
int[] points = { x, y, x+10, y+4, x+5,y+5, x+4,y+10};
Pen p = new Pen(Color.White, 1);
dc.DrawPolygon(null, p, points);
}
//窗体刷新
protected void Refresh()
{
this.Left = this.Left;
this.UpdateLayout();
}
}
下面是我们实际运行的效果图,已经非常完美的支持鼠标了(并且模拟器可两种方式提供鼠标信息,一种是鼠标直接在LCD屏上操作,一种是通过扩展面板上的滑块进行移动)。
直接用外部鼠标操作。
通过滑块进行鼠标操作。
|
-
上次编写俄罗斯方块,好像是N年前的大学时代,使用TC2.0在DOS平台上开发的,由于当时的代码是保存在3.5寸软盘上的,到现在那些程序早已经作了古,没有了踪影。俄罗斯方块其实编写并不太容易,要考虑的东西还是蛮多的,以前想在PPC上用.Net精简框架集实现,后来起了个头,没有太大的动力,也就作罢了。现在对MF充满了激情,所以从零做起(没有参考任何现成代码,只不过还是依照我早期的思路编写而成),花费我大半天的时间才编写完毕(在编写过程中,还发现MF对二维数组支持不大好,我后面会提到)。
好了,让我们在.Net Micro Framework模拟器(由于我这里没有带LCD屏的MF开发板,所以很遗憾,不知道在实际的设备上该程序的性能会怎样)上体验一下俄罗斯方块游戏吧。
进入MF Windows(姑且这么叫吧),单击“开始”菜单按钮(MF平台不支持鼠标,是单击模拟器上的Menu按钮,或在键盘敲“空格”或“Windows”键),我们用光标键选中“俄罗斯方块”游戏,按OK键(或按键盘上的“回车”键)进入游戏。
通过开始菜单进入“俄罗斯方块”游戏
编写的俄罗斯方块游戏可是具备经典版的所有功能哟!可以翻转,可以移动,可以得分,可以快速降落等等!要认真玩,否则很快就“Game Over”的。
在编写过程中发现MF对二维数组支持不是太好。
下面的代码编译时会报错:
bool[,] bFlag=new bool[10,20];
所以在MF中二维数组必须这样声明才行:
bool[][] bShowFlag = new bool[22][]; //主显示区含边框
for (int i = 0; i < 22; i++)
{
bShowFlag[i] = new bool[12];
}
附:MF俄罗斯方块核心代码
//俄罗斯方块(叶帆工作室:http://blog.csdn.net/yefanqiu)
public class ElsGame
{
int Current_Y = -2, Current_X =3;
int intGrade = 0;
bool[][] bShowFlag = new bool[22][]; //主显示区含边框
bool[][] bMetaShowFlag = new bool[4][]; //下一个方块显示区
bool[][] bCurrentMetaShowFlag = new bool[4][]; //当前方块显示区
int intNextNo = 0; //下一个方块索引
int intCurrentNo = 0; //当前方块索引
enum MoveWay{Left,Down,Right};
DispatcherTimer GameTimer; //游戏时钟
bool bGameRunFlag = false; //游戏运行标志
public ElsGame(DispatcherTimer GameTimer)
{
this.GameTimer = GameTimer;
Init();
}
//元素
private bool[][] GetElsMeta(int intNo)
{
bool[][] ElsMetaFlag = new bool[4][];
for (int i = 0; i < 4; i++)
{
ElsMetaFlag[i] = new bool[4];
}
switch (intNo)
{
case 0: //田
ElsMetaFlag[1][1] = true;
ElsMetaFlag[1][2] = true;
ElsMetaFlag[2][1] = true;
ElsMetaFlag[2][2] = true;
return ElsMetaFlag;
case 1: //-
ElsMetaFlag[2][0] = true;
ElsMetaFlag[2][1] = true;
ElsMetaFlag[2][2] = true;
ElsMetaFlag[2][3] = true;
return ElsMetaFlag;
case 2: //|
ElsMetaFlag[0][2] = true;
ElsMetaFlag[1][2] = true;
ElsMetaFlag[2][2] = true;
ElsMetaFlag[3][2] = true;
return ElsMetaFlag;
case 3: //┻
ElsMetaFlag[1][2] = true;
ElsMetaFlag[2][1] = true;
ElsMetaFlag[2][2] = true;
ElsMetaFlag[2][3] = true;
return ElsMetaFlag;
case 4: //┣
ElsMetaFlag[2][2] = true;
ElsMetaFlag[1][1] = true;
ElsMetaFlag[2][1] = true;
ElsMetaFlag[3][1] = true;
return ElsMetaFlag;
case 5: //┳
ElsMetaFlag[2][2] = true;
ElsMetaFlag[1][1] = true;
ElsMetaFlag[1][2] = true;
ElsMetaFlag[1][3] = true;
return ElsMetaFlag;
case 6: //┫
ElsMetaFlag[2][1] = true;
ElsMetaFlag[1][2] = true;
ElsMetaFlag[2][2] = true;
ElsMetaFlag[3][2] = true;
return ElsMetaFlag;
case 7: //┖
ElsMetaFlag[3][2] = true;
ElsMetaFlag[1][1] = true;
ElsMetaFlag[2][1] = true;
ElsMetaFlag[3][1] = true;
return ElsMetaFlag;
case 8: //┍
ElsMetaFlag[2][1] = true;
ElsMetaFlag[1][1] = true;
ElsMetaFlag[1][2] = true;
ElsMetaFlag[1][3] = true;
return ElsMetaFlag;
case 9: //┒
ElsMetaFlag[1][1] = true;
ElsMetaFlag[1][2] = true;
ElsMetaFlag[2][2] = true;
ElsMetaFlag[3][2] = true;
return ElsMetaFlag;
case 10: //┙
ElsMetaFlag[1][3] = true;
ElsMetaFlag[2][1] = true;
ElsMetaFlag[2][2] = true;
ElsMetaFlag[2][3] = true;
return ElsMetaFlag;
case 11: //┕
ElsMetaFlag[1][1] = true;
ElsMetaFlag[2][1] = true;
ElsMetaFlag[2][2] = true;
ElsMetaFlag[2][3] = true;
return ElsMetaFlag;
case 12: //┎
ElsMetaFlag[1][2] = true;
ElsMetaFlag[1][1] = true;
ElsMetaFlag[2][1] = true;
ElsMetaFlag[3][1] = true;
return ElsMetaFlag;
case 13: //┑
ElsMetaFlag[2][3] = true;
ElsMetaFlag[1][1] = true;
ElsMetaFlag[1][2] = true;
ElsMetaFlag[1][3] = true;
return ElsMetaFlag;
case 14: //┚
ElsMetaFlag[3][1] = true;
ElsMetaFlag[1][2] = true;
ElsMetaFlag[2][2] = true;
ElsMetaFlag[3][2] = true;
return ElsMetaFlag;
}
return null;
}
//翻转
private int GetTurnMeta(int intNo)
{
switch (intNo)
{
case 0: //田
return 0;
case 1: //-
return 2;
case 2: //|
return 1;
case 3: //┻
return 4;
case 4: //┣
return 5;
case 5: //┳
return 6;
case 6: //┫
return 3;
case 7: //┖
return 8;
case 8: //┍
return 9;
case 9: //┒
return 10;
case 10: //┙
return 7;
case 11: //┕
return 12;
case 12: //┎
return 13;
case 13: //┑
return 14;
case 14: //┚
return 11;
}
return intNo;
}
//初始化
public void Init()
{
if (!bGameRunFlag)
{
//主显示区
for (int i = 0; i < 22; i++)
{
bShowFlag[i] = new bool[12];
for (int j = 0; j < 12; j++)
{
if (i == 21 || j == 0 || j == 11)
{
bShowFlag[i][j] = true;
}
else
{
bShowFlag[i][j] = false;
}
}
}
//辅助显示区
for (int i = 0; i < 4; i++)
{
bMetaShowFlag[i] = new bool[4];
for (int j = 0; j < 4; j++)
{
bMetaShowFlag[i][j] = false;
}
}
//方块初始化
Microsoft.SPOT.Math.Randomize();
intCurrentNo = Microsoft.SPOT.Math.Random(15);
bCurrentMetaShowFlag = GetElsMeta(intCurrentNo);
intNextNo = Microsoft.SPOT.Math.Random(15);
bMetaShowFlag = GetElsMeta(intNextNo);
//时钟开始
GameTimer.Start();
//积分清零
intGrade = 0;
//游戏运行标志
bGameRunFlag = true;
}
}
//下降
public bool DoDownMove()
{
if (IsMove(Current_X, Current_Y, bCurrentMetaShowFlag, MoveWay.Down) && bGameRunFlag)
{
Current_Y++;
MoveBlock(Current_X, Current_Y, bCurrentMetaShowFlag, MoveWay.Down);
}
else
{
//时钟恢复正常
GameTimer.Interval = new TimeSpan(0, 0, 1);
//判断是否得分及游戏是否结束
int intRet=GetGameState() ;
if (intRet == -1)
{
GameTimer.Stop();
bGameRunFlag = false;
//主显示区
for (int i = 0; i < 22; i++)
{
bShowFlag[i] = new bool[12];
for (int j = 0; j < 12; j++)
{
if (i==1 || i == 20 || j == 1 || j == 10)
{
bShowFlag[i][j] = true;
}
else
{
bShowFlag[i][j] = false;
}
}
}
}
else
{
intGrade += intRet;
Current_Y = -3;
Current_X = 3;
NextBlock();
}
}
return true;
}
//判断游戏状态
private int GetGameState()
{
bool bFlag = false;
int intNum = 0;
//判断游戏是否结束
for (int i = 1; i < 11; i++)
{
if (bShowFlag[1][i])
{
return -1;
}
}
//判断得分
for (int i = 1; i < 21; i++)
{
bFlag = true;
for (int j = 1; j < 11; j++)
{
if (!bShowFlag[i][j]) bFlag = false;
}
if (bFlag)
{
//移动数组
for (int e = i; e>0; e--)
{
for (int k = 1; k < 11; k++)
{
bShowFlag[e][k] =bShowFlag[e-1][k];
}
}
intNum++;
}
}
switch (intNum)
{
case 1:
intNum = 100;
break;
case 2:
intNum = 300;
break;
case 3:
intNum = 500;
break;
case 4:
intNum = 800;
break;
}
return intNum;
}
//左移
public bool DoLeftMove()
{
if (!bGameRunFlag) return false;
if (IsMove(Current_X, Current_Y, bCurrentMetaShowFlag, MoveWay.Left))
{
Current_X--;
MoveBlock(Current_X, Current_Y, bCurrentMetaShowFlag, MoveWay.Left);
return true;
}
return false;
}
//右移
public bool DoRightMove()
{
if (!bGameRunFlag) return false;
if (IsMove(Current_X, Current_Y, bCurrentMetaShowFlag, MoveWay.Right))
{
Current_X++;
MoveBlock(Current_X, Current_Y, bCurrentMetaShowFlag, MoveWay.Right);
return true;
}
return false;
}
//方块翻转
public bool DoTurn()
{
if (!bGameRunFlag) return false;
#region //判断是否能翻转
int mX=0, mY = 0;
bool bFlag = true;
//翻转后的标志
bool[][] bFlags = GetElsMeta(GetTurnMeta(intCurrentNo));
SetBlock(Current_X, Current_Y, bCurrentMetaShowFlag, false);
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
mX = Current_X + j + 1;
mY = Current_Y + i + 1;
if (mX > -1 && mX < 12 && mY > -1 && mY < 22)
{
if (bShowFlag[mY][mX] && bFlags[i][j])
{
bFlag = false;
}
}
}
}
SetBlock(Current_X, Current_Y, bCurrentMetaShowFlag, true);
#endregion
if (bFlag)
{
SetBlock(Current_X, Current_Y, bCurrentMetaShowFlag, false);
intCurrentNo = GetTurnMeta(intCurrentNo);
bCurrentMetaShowFlag = GetElsMeta(intCurrentNo);
SetBlock(Current_X, Current_Y, bCurrentMetaShowFlag, true);
return true;
}
return false;
}
//设置方块
private void SetBlock(int X, int Y, bool[][] bFlags, bool bFlag)
{
略 ;-)
}
//下一个方块
private void NextBlock()
{
intCurrentNo = intNextNo;
bCurrentMetaShowFlag = bMetaShowFlag;
intNextNo = Microsoft.SPOT.Math.Random(15);
bMetaShowFlag = GetElsMeta(intNextNo);
}
//移动方块
private void MoveBlock(int X, int Y, bool[][] bFlags,MoveWay mw)
{
switch (mw)
{
case MoveWay.Left:
SetBlock(X+1, Y, bCurrentMetaShowFlag, false);
SetBlock(X, Y, bCurrentMetaShowFlag, true);
break;
case MoveWay.Down:
SetBlock(X, Y - 1, bCurrentMetaShowFlag, false);
SetBlock(X, Y, bCurrentMetaShowFlag, true);
break;
case MoveWay.Right:
SetBlock(X-1, Y, bCurrentMetaShowFlag, false);
SetBlock(X, Y, bCurrentMetaShowFlag, true);
break;
default:
break;
}
}
//是否可以移动
private bool IsMove(int X, int Y, bool[][] bFlags, MoveWay mw)
{
略 ;-)
}
//绘制
public void Draw(DrawingContext dc)
{
int BarTop = 26;
int BarHeight = SystemMetrics.ScreenHeight - BarTop * 2;
dc.DrawRectangle(new SolidColorBrush(Colors.Gray), new Pen(Colors.Gray), 0, BarTop, SystemMetrics.ScreenWidth, BarHeight);
//主显示区
dc.DrawRectangle(new SolidColorBrush(Colors.Black), new Pen(Colors.White), 79, BarTop + 3, 92, 182);
//辅助显示区
dc.DrawRectangle(new SolidColorBrush(Colors.Black), new Pen(Colors.White), 222,120, 38, 38);
//积分区
dc.DrawRectangle(new SolidColorBrush(Colors.White), new Pen(Colors.Blue), 205, 180, 74, 20);
dc.DrawText(intGrade.ToString(), YFWinApp.YFFont,ColorUtility.ColorFromRGB(0,120,0), 224 + 17 - intGrade.ToString().Length * 3, 184);
//显示
DrawEls(80, BarTop + 4, 9, 9, dc);
//结束
if (!bGameRunFlag)
{
dc.DrawText("Game Over!", YFWinApp.YFFont, Colors.Red,96, 110);
}
}
//显示方块
private void DrawEls(int X,int Y,int Width,int Height,DrawingContext dc)
{
SolidColorBrush sb=new SolidColorBrush(Colors.Green);
Pen pen=new Pen(Colors.Blue);
//主显示区
for (int i = 0; i < 20; i++)
{
for (int j = 0; j < 10; j++)
{
if(bShowFlag[i+1][j+1])
dc.DrawRectangle(sb, pen, X +j * Width, Y + i * Height, Width, Height);
}
}
//辅助显示区
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if(bMetaShowFlag[i][j])
dc.DrawRectangle(sb, pen,223 + j * Width, 121 + i * Height, Width, Height);
}
}
}
}
|
-
上次用MF做了一个Windows桌面,感觉还不错,所以就想做一做如俄罗斯方块、贪吃蛇之类的小游戏,不过每次用模拟器运行MF程序,都需要打开VS2005的源码程序,然后单击启动。VS2005是个庞大的家伙,启动它至少要半分钟,所以很是麻烦。模拟器有没有可能直接运行MF程序啊(或者说不需安装VS2005及MF开发包,在其它windows上仅借助模拟器就可以运行)?就像一些游戏模拟器一样直接加载相应的文件就可以使游戏运行起来。
既然VS2005能启动运行,我想我们也可以,唯一不同的是模拟器加载的参数不同而已。
这是VS2005启动模拟器时候的命令行参数:
CommandLine = "C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Tools\Microsoft.SPOT.Emulator.Sample.SampleEmulator.exe" "/waitfordebugger" "/load:C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Assemblies\Microsoft.SPOT.TinyCore.pe" "/load:C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Assemblies\Microsoft.SPOT.Hardware.pe" "/load:D:\SELF\MF\YFWinMF\YFWinMF\bin\Release\YFWinMF.pe" "/load:C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Assemblies\Microsoft.SPOT.Graphics.pe" "/load:DC
根据这个信息,我做了一个批处理文件(MF.bat),内容如下:
Microsoft.SPOT.Emulator.Sample.SampleEmulator.exe "/load:C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Assemblies\Microsoft.SPOT.TinyCore.pe" "/load:C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Assemblies\Microsoft.SPOT.Hardware.pe" "/load:C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Assemblies\Microsoft.SPOT.Graphics.pe" "/load:YFWinMF.exe"
注意,我们去掉了"/waitfordebugger"参数,此外最后一个"/load:DC参数我也直接去掉了。此外YFWinMF.exe程序就是VS2005编译后的MF的可执行文件。
运行该批处理试一下,哈!模拟器可以单独启动并且开始运行了YFWinMF.exe程序了。下一步我们要对模拟器进行移植(需要提醒的是,由于模拟器也是用.Net 2.0开发的,所以windows试验平台至少要安装.Net 2.0)。
经过测试发现,模拟器需要如下文件方可移植成功。
此外还需要这些核心文件:
很让人不解的是,模拟器核心库必须要求这些文件在“C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Assemblies\”目录,也就是说我上面的批处理文件的路径必须与此一样(也可以不一样,不过C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Assemblies\目录下也必须存在这些文件),否则就提示****.pe文件不存在。为了便于移植,我又编写了一个批处理文件(init.bat),可以方便地创建C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Assemblies\目录及下面的文件,内容如下:
md "C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Assemblies"
copy *.* "C:\Program Files\Microsoft .NET Micro Framework\v2.0.3036\Assemblies"
MF用户程序有三个文件组成,YFWinMF.exe YFWinMF.exe.manifest YFWinMF.pe。
好了,我们准备工作就绪,把相关文件拷贝到其它机器(windows 2000以上,安装了.Net 2.0),先执行我的Init.bat批处理文件,建立Assemblies文件,然后再执行MF.bat批处理文件。
模拟器的功能还可以进一步扩展,如修改LCD的分辨率(颜色数目前仅支持16位,设置其它值会有问题),这样我们的模拟器就像PPC一样,也可以横竖屏切换了(这个功能我后续会做),下图就是修改LCD分辨率后运行的效果图:
此外在绘图时,还有一点需要注意,MF的窗体类没有Refresh函数(用UpdateLayout命令不好使),这样在配合按键时会显得很迟钝(原先我还以为是模拟器的性能问题呢),经过试验,我自定义了一个Refresh函数,代码如下:
//窗体刷新
private void Refresh()
{
this.Left = this.Left;
//this.UpdateLayout();
} 修改后,那响应真叫一个棒。OK,以后的工作有的做了,各种基于MF的小游戏和小应用就应该源源不断的出来了。
|
-
今天是 2007年最后一天,还有几个小时就迎来充满期待的2008年了。元旦放假至今不到48个小时的时间里,我至少有30多个小时坐在电脑前研究MF相关技术,在缺少必要的硬件的情况下,利用现有的资料,扩充了现有的模拟器功能。也希望像我这样的穷人们,借助模拟器,能体验一下以前只有拥有硬件才能调试的功能。
以前为了介绍MF图形方面的功能,我做了一个“北京2008奥运场馆查询”程序,现在看来那个程序,已经不足以展示MF整体功能的强大。既然Windows 2000 /XP/vista,Windows CE(Windows Mobile)程序都有Windows桌面和开始菜单,那么我们的.Net Micro Framework也应该有一个。岂能光说不练,下面就是我们MF Windows的桌面和开始菜单。
看了这个截图,也许有的网友会认为,这有什么难,这不是从windows mobile系统上做了一个截图嘛。实话实说,这样认为还真是想简单了,除了windows图标和小喇叭外,其它部分全部用代码实现,光字体就用了两种不同大小的字库,此外时钟也不仅仅做个样子,也是能正常刷新显示的。开始菜单我们也有,不仅能选择,还能执行对应的功能,不要急,下面我们一一说起。
在上几篇blog上我说了,我做的模拟器又扩展了两个键(菜单键和返回键),别说,这个程序在微软的模拟器上运行起来还真有问题,因为它不支持菜单键,无法弹出菜单来。
菜单上项目也不仅仅是摆设,和模拟器新增的功能结合在一起,做了一个功能测试大荟萃。让我们先看看GPIO测试的功能在“windows桌面”是怎样的?
菜单上选中“GPIO测试”项,按一下键盘上的回车或者单击模拟器上的“OK”键,就进入上图界面。单击模拟右下角的“>>>”项,展开模拟器上的GPIO测试面板。
单击模拟上的I输入开关,桌面上显示的输入信息也同步发生了变化,Q输出是有用户程序控制的,灯会一一亮起或关灭,桌面上的显示也和模拟器像对应(插一句,千万不要以为上面模拟器和模拟LCD中的程序为同一个,两者彼此独立,用户程序通过正常的指令和模拟器交互(当然同样的代码对实际硬件也一样))。
下面我们再看看“AD测试”功能。
通过开始菜单,我们进入“AD测试”模块,相应的,我们单击“<<<”旁边的“GPIO”字符,弹出选择菜单,我们单击“AD”,模拟器呈现AD模拟数据操作面板。
移动滑块,我们发现桌面中的数据同步发生了改变,不好意思,模拟出部分被弹出菜单给挡住了,我们再来一张截图。
QW0是由用户程序控制的,看的出来,模拟器忠实的获取了该数据并显示了出来。说到这里我还得多说几句,其实模拟器的部分程序更像运行在实际硬件系统中的IO驱动程序,spi读写指令操作的是字节数组,所以上传的数据是否浮点型、是否整形等等,全都需要约定,这也决定了不同的厂家对同样的物理信号进行采集,传到MF系统中的数据有可能不同,不过通过特定的换算,最终的结果一定是差不多的。
下面说一下I2C总线,I2C我的理解和串口通信、CAN口通信差不多,都是两个相对独立的设备进行数据交互,I2C和CAN通信一样,需要设置设备地址,此外还需设置一下时钟主频。下图是测试程序界面。
前三个数据是用户程序进行修改的,由于有时间差,二者数据刷新时刻不同,所以不一致,后五个数据是模拟输入的,可以看出桌面上的显示已经可以同步更新了。用这种方式模拟I2C,我也觉得有些不妥,以后有更好的方案我再修改。
细心的用户一定会发现MF“Windows 桌面”右上角的时间在不断的变化,第一幅图是2007年12月31日21:39:23,最后一幅图的时间是22:27:33,也就是说,写这篇文章大概花了我50分钟,这是我在word写这篇文章的用时,接着再花一些时间写到我blog上,我想所有的一切完毕后,我想大概就可以聆听2008年的第一声钟声了。
祝大家新年快乐!!!
附:部分核心代码
public override void OnRender(DrawingContext dc)
{
//绘制背景
dc.DrawRectangle(new SolidColorBrush(ColorUtility.ColorFromRGB(41,113,189)),null , 0, 0, Width, Height);
//菜单条
LinearGradientBrush lgb = new LinearGradientBrush(ColorUtility.ColorFromRGB(51,160,241), ColorUtility.ColorFromRGB(16,75,161), 0, 0, Width, 0);
dc.DrawRectangle(lgb, null, 0, 0, Width, 26);
//开始菜单
dc.DrawImage(Resources.GetBitmap(Resources.BitmapResources.Win), 0, 0);
//分割线
dc.DrawLine(new Pen(Color.White), 26, 0, 26, 25);
//显示“开始”
dc.DrawText("开始", YFFont, Color.White, 29, 7);
//显示小喇叭
dc.DrawImage(Resources.GetBitmap(Resources.BitmapResources.Sound), 320 - 75, 6);
//状态区
dc.DrawRectangle(new SolidColorBrush(ColorUtility.ColorFromRGB(189, 235, 255)), new Pen(Color.Black), 0, Height-26, Width, 26);
//标识
dc.DrawText("叶帆工作室荣誉出品", YFFont, Colors.Gray, 200, 220);
switch (txtState.TextContent)
{
case "GPIO测试":
string strPace = " ";
dc.DrawText("输入: I0 I1 I2 I3 I4 I5 I6 I7", YFFont, Colors.Black , 40, 50);
dc.DrawText((input[0].Read() ? "1" : "0") + strPace + (input[1].Read() ? "1" : "0") + strPace + (input[2].Read() ? "1" : "0") + strPace + (input[3].Read() ? "1" : "0") + strPace + (input[4].Read() ? "1" : "0") + strPace + (input[5].Read() ? "1" : "0") + strPace + (input[6].Read() ? "1" : "0") + strPace + (input[7].Read() ? "1" : "0"), YFFont, ColorUtility.ColorFromRGB(255, 255, 0), 78, 75);
dc.DrawText("输出: Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7", YFFont, Colors.Black, 40, 100);
dc.DrawText((output[0].Read() ? "1" : "0") + strPace + (output[1].Read() ? "1" : "0") + strPace + (output[2].Read() ? "1" : "0") + strPace + (output[3].Read() ? "1" : "0") + strPace + (output[4].Read() ? "1" : "0") + strPace + (output[5].Read() ? "1" : "0") + strPace + (output[6].Read() ? "1" : "0") + strPace + (output[7].Read() ? "1" : "0"), YFFont, ColorUtility.ColorFromRGB(255, 255, 0), 78, 125);
break;
case "AD测试":
dc.DrawText("AD: AW0=" + ReadWriteAD((Int16)QAW).ToString() + " QW0=" + QAW.ToString(), YFFont, Colors.White, 60, 100);
break;
case "I2C测试":
//I2C读写
byte[] bytRData = new byte[8];
byte[] bytWData = new byte[3];
bytWData[0] = (byte)IntI2CNum;
bytWData[1] = (byte)(IntI2CNum * 2);
bytWData[2] = (byte)(IntI2CNum * 3);
I2CDevice.I2CTransaction[] i2c = new I2CDevice.I2CTransaction[2];
i2c[0] = I2CBus.CreateReadTransaction(bytRData);
i2c[1] = I2CBus.CreateWriteTransaction(bytWData);
I2CBus.Execute(i2c, 100); //执行
string strPace1 = " ";
dc.DrawText("I2C(字节0-7):", YFFont, Colors.Black, 50, 80);
string strInfo = bytRData[0].ToString() + strPace1 + bytRData[1].ToString() + strPace1 + bytRData[2].ToString() + strPace1 + bytRData[3].ToString() + strPace1 + bytRData[4].ToString() + strPace1 + bytRData[5].ToString() + strPace1 + bytRData[6].ToString() + strPace1 + bytRData[7].ToString();
dc.DrawText(strInfo, YFFont, ColorUtility.ColorFromRGB(255, 255, 0), 50, 105);
break;
default:
//.Net Micro Framework
dc.DrawRectangle(new SolidColorBrush(Color.White), new Pen(Color.White), 55, 90, Width - 110, 50);
dc.DrawText(".Net Micro Framework", Resources.GetFont(Resources.FontResources.MF), Colors.Black, 60, 105);
break;
}
//菜单显示
if (bShowMenu)
{
menu.DrawMenu(dc);
}
}
//按键信息
protected override void OnButtonDown(ButtonEventArgs e)
{
switch (e.Button)
{
//按下确定键
case Button.Select:
if (bShowMenu)
{
txtState.TextContent = menu.Text;
bShowMenu = false;
this.UpdateLayout();
}
break;
//按下左键
case Button.Left:
break;
//按下右键
case Button.Right:
break;
//按向上
case Button.Up:
if (bShowMenu)
{
menu.SelectIndex--;
this.UpdateLayout();
}
break;
//按向下
case Button.Down:
if (bShowMenu)
{
menu.SelectIndex++;
this.UpdateLayout();
}
break;
//按下菜单
case Button.Menu:
bShowMenu = !bShowMenu;
if (bShowMenu) menu.SelectIndex = 0;
this.UpdateLayout();
break;
//按下返回键
case Button.Back:
txtState.TextContent = "就绪";
break;
}
}
------------------------------------
附记:本来打算这篇文章作为2007年底最后一篇文章,没有想到从22点半到零点,竟然无法上传成功,只好作为2008年第一篇文章了,这也不错。
|
|
|
|