Labview学习笔记_第1页
Labview学习笔记_第2页
Labview学习笔记_第3页
Labview学习笔记_第4页
Labview学习笔记_第5页
已阅读5页,还剩45页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、reference死锁问题LabVIEW中的引用经常需要和“InPlaceElementStructure己合使用。InPlaceElementStructure对一个引用的数据进行处理时,为了保证多线程安全,它会锁住引用指向的数据;其它线程若需对同一数据做操作,必须能这个InPlaceElementStructure中所有代码执行完毕才可,这样就避免了多线程读写同一内存数据所产生的竞争问题。举例来说,下面这段程序的执行时间是1秒:而下面这段程序的执行时间则是2秒:j引SingkData.viBlockDiagram£ileEditViewProjectQperateleakWird

2、ojjelpli_空牌向阿圜115Pt4p闷_L因为第二段程序中的两个InPlaceElementStructure必须顺序执行。有了锁住”这个操作,就有不小心造成死锁的可能。比如对于同一数据的引用,千万不能嵌套使用InPlaceElementStructure否则就会死锁:在上面这个示例中,程序运行至内层的InPlaceElementStructure就会停在这里等外层InPlaceElementStructure运行结束,释放它锁住白数据;而对于外层InPlaceElementStructure来说,它内部的全部代码要运行结束,它才结束。因而它们相互等待,造成了死锁。PackedProje

3、ctLibraries2与Library的比较ackedProjectLibrary从名字上来看,就是被包装好了的ProjectLibrary0ProjectLibrary是编程时候由程序员创建出来的。比如下图这个工程,我在里面创建了一个叫做“MyAlgorithmLibrary.lvlib的工程库。它包含两个VI,其中一个是私有的ProjectExplorerMyAlgoriu£ileEditViewProjectOperateToolsWindItemsFiles71瓦ProjectMyAlgorithmLibrary.lvproj3日MyComputftirAlgorithmL

4、ibrary.lvlib-r<.FactcriaLvii、工廿PrivateviDependencies-笃Build5pecifications:$M/PackedLibraryPackedProjectLibrary并不是手工创建的,他是通过一个项目的生成规范,从ProjectLibrary编译而来的。比如上图的项目,我创建了一个PackedLibrary类型的生成规范。我在这个生成规范中指定把“MyAlgorithmLibrary.lvlib编译成PackedProjectLibrary。rTMyPacHtEdLiibiraryPrope-ties编译的结果是在我指定的路径下生成了

5、一个名为“MyAlgorithmLibrary.lvlibp的文件。它的后缀名仅比PackedLibrary多了一个字母p。双击这个文件,可以打开它,看到他里面包含的VI:如果需要在其它项目中使用到这个PackedProjectLibrary,我们可以直接把它加到另一个项目中去,下图是一个演示项目:PackedProjectLibrary看上去和ProjectLibrary非常相似,用法也完全相同。PackedProjectLibrary与ProjectLibrary 都是将功能相关的一组VI封装起来的方法; 库中的VI可以具有层次机构; 库中的VI都带有名字空间,名字空间是带有后缀名的库名;

6、 都可以方便的放在项目管理器里使用尽管它们十分相似,PackedProjectLibrary与ProjectLibrary相比,还是有一些明显区别的: PackedProjectLibrary是通过编译生成的; PackedProjectLibrary中的VI是编译后产生的,它们不能被修改; PackedProjectLibrary包含有私有VI,但用户无法看到也不能使用它们; PackedProjectLibrary把VI,.lvlib以及其它用到的文件都打成一个压缩包,用户在磁盘上就只能看到一个.Ivlibp文件,看不到VI文件; PackedProjectLibrary很适合作为最终产品

7、发布给用户使用; 在项目中使用PackedProjectLibrary可以缩短编译时间,因为PackedProjectLibrary中的VI是已编译好的,不会再随项目编译一遍。(这一条先这样写上,但我还需要再深入研究一下)LabVIEW中LVClass数据转换成XML格式的问题前一段时间,一个同事的程序出了问题。他在程序中把一个LVClass类型的数据转换成XML格式,再保存成文件。但是从文件中把数据转回成LVClass时,却出了问题:在调用“UnflattenXML这个函数时,程序有时出错,有时又不出错。他的程序中使用了大量的LVClass,并且它们之间有着复杂的继承与包含关系,以至于花了两

8、三天的事件,才找出问题所在。其实是个简单的问题,只是在设计程序时他没有意识到。我做了一个简化的程序,可以重现这个问题:首先,给一个子类的对象设置一些数据。然后把它当做父类类型的数据,平化成XML文本,存盘:关闭LabVIEW,然后重新打开LabVIEW。再编写一个反向程序,把XML数据转换成父类类型的数据::romXMLFileviUnflatt«nFrormXML=OBd|!ReadFromXMLFil虱Wring)-errorout国|发现UnflattenFromXML函数返回一个错误,value中是一个空的数据。错误产生原因如下:在把子类数据转换成父类数据类型,这个类型虽然是

9、父类的,但其数据仍然是子类的。再转换成XML格式,XML格式中记录的仍然是子类的数据。在反向过程中,UnflattenFromXML拿到的数据是子类的,但它企图转换时,却发现内存中没有子类的类型信息,因此它也就不知道如何转换这个数据,所以报错。如果这个程序稍微改动一下,把XML数据直接转换成子类的数据,就不会出错了:实际上,子类的数据总是可以用父类来表示的。因此这个XML数据亦可以直接被转换成父类的类型,但前提是,一定要保证子类的类型别家在到内存中去了。只要在程序中放置一个子类的对象,自然就可以把子类加载至内存。像下面这个程序就可以正常工作:FromXMLFileviUnflatt«

10、nFromXMLvdlueRadFromXMLFil而ring)子物星类型父会灾据芟型errorout二1=I这个实验反映出两个问题:1 .把XML中的内容如果是属于某个LVClass类型的数据,把这些数据转换回LVClass数据时,那个LVClass一定要已经存在于内存才行。2 .在之前的一篇文章LvClass的一个效率问题”中提到过:当子类被加载如内存时,它所有的父类也会被加载入内存。但反过来并不成立。因为一个类有哪些父类是确定的,父类的地址就记录在子类中。但一个类并不知道他会有多少子类,任何人都可以从它派生出不同的子类来,因此它在装入内存时,不可能把自己的子类也都装进来。LabVIEW中

11、实现链表、树等数据结构LabVIEW自带的数据结构只有数组和队列。多数情况下,这两种数据结构足够开发者使用了。但是,我平时使用C+和C#S言更多一些,所以编写程序时常常会想到使用其它编程语言中常见的数据结构比如链表(List)、树(Tree)等。LabVIEW中也可以编程实现这些数据结构,一个比较直观易懂的编程方法是基于LabVIEW中的类和引用来实现各类数据结构。我在我和LabVIEW一书的第13.3.5节中介绍了一个简单的链表容器的实现方法,它是基于LvClass编写的,数据流驱动的一种容器。但是正如我在书中提到的,它虽然和有一些和文本编程语言中的链表相类似的地方,但本质并不相同。文本编程

12、语言中的链表,树等数据结构离不开引用(或指针),节点之间是通过引用来相互关联的。LabVIEW可以为数据创建引用,因此也可以方便的实现与文本语言中功能相同的数据结构。这里插一段,介绍一下数据结构和数据容器的关系,我自己理解是这样的:数据结构侧重于数据的存储方式,比如如何排序;数据结构在加上与此结构相关的操作方法,比如添加删除数据等方法,就构成了一个数据容器。脱离了操作方法,单纯的数据结构用处非常有限。因此,我文章中在提到数据结构或者数据容器时,指的都是同一回事:数据结构和相关的方法。为了介绍如何在LabVIEW中实现一个数据结构,我打算以双向链表为例,讲解一下如何编写它。双向链表中每个节点都会

13、记录上一个节点和下一个节点的位置。因此,在双向链表中,可以从一个节点直接跳转到它的上一个或下一个节点上去,也就是正向或反向遍历整个链表。可以直观的想到,使用LvClass实现这样的节点,只要为这个节点创建一个类ListNode,并且这个类有两个成员变量,它们的类型都是ListNode的引用,分别用于指向前一个和后一个节点就可以了:ListNodeDMa;intPrevious:LirtNodeNext:ListNode这样的设计在文本编程语言中是没有问题的,但在LabVIEW中行不通。其它编程语言中,程序运行时,才会对类的对象进行初始化。LabVIEW中,VI一打开,它上面的控件和常量就需要被

14、初始化了。某个对象在初始化时,它的成员变量也要被初始化,若它的成员变量的类型还是这个类,这以初始化的过程就陷入了死锁:类需要它的成员变量先初始化;它成员变量需要这个类先初始化。基于同样的原因,一个类的成员变量的数据类型也不可以是这个类的子类:子类初始化需要先对它的父类进行初始化。但是,一个类的成员变量的数据类型可以是这个类的父类:父类在初始化的时候,不需要理会它的任何子类。既然父类初始化时,不依赖于子类的初始化;而子类的对象又可以被当做父类的类型来保存,咱们就可以利用这一特性在LabVIEW中实现可以数据结构的节点了。只不过LabVIEW实现链表的节点要多一个步骤:我们需要为ListNode类

15、再定义一个父类ListNodeVirtual。这个父类不做任何实质性的工作,它仅用于保存相邻节点的引用。ListNodeVirtud;intPrevious:LktNodeVirtualNest:ListrJodEVirtual以上两个类是针对链表节点的双向链表本身也需要做成一个类:DoubleLinkedList类,这个类中封装有链表的属性和方法。比如它需要一个指向链表表头的引用,需要有为链表添加删除数据的方法,为遍历链表中的数据,还需要有一个迭代器作为演示,我只实现了链表的几个简单功能。演示程序工程结构如下:赤ProjectExpllorer-List.lvprqj£ileEdi

16、tyiewProjectOperateTookWindowHe箱片可。2x|)拿胃|陶工HemsFiles:3,幻一ProjectListIvprojEj-国MyComputerif-ListMcdeVirtualilvdass百.ListNode.IvcIass!h,H解ListNode.ctl*Raddata.viL3Iht|I-息Writedata.viI-Readprevious.virir=ik,a.匕/Writeprevious-viIh'虔,Readnext,vi«X-岫加next.viECoubleLinkedLitt.IvqlaiI-f1DoubleLink

17、edLittctl:/.AppendafterEnumerator.viI阿GetAllCata.vil“至,EnumeratorgoNertiVi例一EnumeratorValue.vi-g.ResetEnurnerator.vir-,Demovil-哲DependencieslGuildSpeqifiqListNode的成员变量包括一个数据,和两个指向前后节点的引用:DoubleLinkedList类的成员变量包括指向链表头节点的引用,迭代器指向的节点的引用,并记录了链表长度卜面看一下链表中几个主要方法是如何实现的首先是AppendafterEnumerator”这个方法,它是链表里最复杂

18、的一个方法。它的输入是链表中一个新的节点,它把这个新节点添加在链表迭代器指向的那个节点的后面。在给链表添加数据时,会遇到两种情况。首先,这个链表是一个空链表,那么被添加的节点就是这个链表的首节点,链表的迭代器也应当指向这一唯一的节点。我设计的这个链表是一个环状链表。当链表中只有一个节点的时候,这个链表的上一个和下一个节点都是它自己。如果链表不是空的,就把新节点插在迭代器指向的节点的后面。因此:新节点的前一节点指向的应当是迭代器指向的那个节点;新节点的后一节点是迭代器指向节点原来的后一节点。迭代器指向节点的新的后一节点应当是这个新节点;原来迭代器的后一节点的前一节点也应当换成这个新的节点。最后,

19、我把迭代器也指向了这个新的节点,这样连续添加新节点时,它们会按照先后顺序插入链表。我的演示程序还用到了其它几个方法。ResetEnumerator”负责把迭代器复位,也就是指向链表的头节点:ListinfiistenumeratorListoutfirstenumeratorerrorout.卜力kosnEnumeratorgoNext.vi用于让迭代器向后移动一个节点:erroroiitListoutEnumeratorValue.vi返回迭代器指向的那个节点:Listinerrorin(noerror)LustoutpJcaterrorout使用这几个方法就可以搭建出一个简单的演示程序来看

20、一下链表如何工作了。下面这个演示程序中,分两部分:第一部分是左面那个循环,每次循环迭代就会创建出一个新的ListNode对象,它的数值是当前迭代的次数;右半部分使用链表的迭代器遍历链表中的节点。在这个演示程序中,迭代器移动次数比链表长度多了两次,因为链表是环状的,转着圈访问,链表中的头两个元素会被读出两遍。程序运行后,data显示了迭代器每一步所指向的节点的值:data回调VILabVIEW界面程序最常用的结构就是循环事件结构。用事件结构截获用户在界面上对控件的操作,然后做出相应处理。在文本语言中,常用的事件处理方法与LabVIEW是不同的。文本语言常常使用回调函数来处理界面事件。比如:某个按

21、钮按下时,需要做一个fft运算。那么就写一段函数来完成这个fft运算,再把这个函数与按钮按下事件关联起来。开发语言通常已经做好了对事件的监控,一旦发现按钮按下事件产生了,就去调用与它关联的fft运算函数。这个有开发者编写,被系统调用的函数就叫做回调函数。LabVIEW也可以采用与文本语言相类似的方法来处理事件:不是在事件结构内处理,而是在程序开始时,就为某事件注册一个回调VI。在回调VI内编写相应代码,一旦事件发生,这段代码就会被执行。与事件结构相比,回调VI编写起来稍微麻烦一点;但它的好处是,它和主VI是平行运行的。如果事件处理过程比较耗时,把它放在事件结构中会阻塞整个程序,使得程序界面暂时

22、失去响应;而把它放在回调VI中,则不会影响程序其它部分的运行。比如下面这个例子。程序界面上有两个仪表盘:左面那个始终在运转,每10秒钟旋转一圈;右边那个,由按钮控制,按下按钮才旋转一圈。若把旋转右表这个工作放到事件结构的按钮按下处理分支中去做,它势必会打断左表的旋转,因此,考虑把它放到回调VI中去做。这是主程序界面:两个表盘,和一个控制右表旋转的按钮程序的代码也比较简单。先看代码的右半部份:这是一个典型的循环事件结构,用来控制左表的旋转。但是注意,右表的控制并不是在这个结构中实现的。再看程序左半部分:它为按钮右表旋转一圈”的值改变事件注册了一个回调VI0注册回调VI用的是节点“Register

23、EventCallback它在函数选板“Connectivly>-ActiveX上。这个节点主要是为了给ActiveX、.NET控件的事件注册回调VI。事件结构无法截获ActiveX、.NET控件的事件,因此只能通过回调VI的方式来处理这些控件的事件。但是这个节点也可以用于给LabVIEW自带的控件注册回调VI。注册回调VI节点,有三个输入参数从上至下分别是:事件的发出者、回调VI、用户自定义数据。在我们这个例子中,需要截获的是按钮右表旋转一圈”的值改变事件,因此需要把右表旋转一圈”控件的引用作为第一个参数传递给注册回调VI节点。指定好事件的发出者,接下来需要选择事件的类型,鼠标点击注册

24、回调VI节点的第一个参数的接线方块,发现右表旋转一圈”按钮的所有事件都已经列在这里了,选择俏改变”事件。第三个参数是用户自定义数据,可以是任意类型的数据,在回调VI中需要用到的数据都可以通过它来传递。因为我打算在回调VI中对控件右表”做修改,因此,在这里把右表”的引用作为数据传递给回调VI。第二个参数是回调VI的引用,如果已经写好了回调VI,把引用传进去就行了。我还没有编写回调VI,因此可以在参数的接线端上点击鼠标右键,选择“CreateCallbackVI创建一个空白的回调VI。回调VI中写一小段代码,让右表旋转一圈,整个程序就完成了。这时,左右表可以各自运行,互不影响。LvClass的一个

25、效率问题前几天,听到了一个客户的抱怨:他编写了一个LabVIEW程序,每次打开主程序就要花费几分钟的时间,这有点令他忍无可忍。我没有见过他的源程序,不过据帮他检查过程序的同事讲,他的问题很可能是使用了大量的LvClass造成的。在他的项目中,包含有上百个类(LvClass)o我以前也听说过LvClass在效率上可能会有些问题,听到了这个消息后,我自己做了一个实验。LabVIEWScripting中有一个属性节点可以用来查看内存中所有的VI,我就利用这个VI来查看一个程序到底在装入些什么,令它启动如此之慢。假设不存在子VI,如果打开某个不在LvClass中的VI(即使这个VI是属于某个lvlib

26、的),只有这个VI会被装入内存。但是,打开某一个LvClass中的VI,我发现不但这个VI会被装入内存,它所在的类中的所有其它的VI也都被调入内存。如果这个类还有父类和祖先类,那么所有父类、祖先类中的VI统统都会被调入内存。总结一下就是这样:当一个VI被装入内存1 .它的所有子VI都会被装入内存;2 .它所在的类中的所有的VI都会被装入内存;3 .它所在的类的父类中的所有的VI都会被装入内存以上3条可以是递归发生的,比如一个主VIA被装入内存,它的子VIB也会被装入内存,和B同属一个类的VIC也要被装入内存,C中有个子VID,D属于类E,E有个父类F,F中有个方法VIGo尽管G的功能和程序A八

27、杆子都打不着了,但也会被装进来。这大概就是那个用户遇到的问题,表面上他的程序不算太大,但是程序开始启动时,却需要把多于程序本身数倍的不相关的VI都装入内存,这一过程会每次都浪费他几分钟的时间。鉴于LvClass的这一特性,设计使用它的时候一定要格外小心,否则很可能会造成程序效率的低下。我想到了几点需要注意的地方:1 .如果仅需要对一些VI进行封装,那么应当使用lvlib,而不是lvclass。两者封装的主要区别是,lvclass可以封装对象的属性(也就是模块用到的数据)。2 .类中的VI必须是高内聚的,类中的方法共同完成某一基本功能,不可再分割。应用程序一旦用到这个类中的某个VI,就意味着程序

28、将会使用到类中几乎全部的VI;而不是一个应用程序可能只使用这个类中的某几个VI。3 .继承关系应当尽量简单。没有必要的时候尽量不使用继承。LabVIEW不支持接口,不应创建一个纯虚类,然后当作接口来用。4 .尽量不要嵌套调用。比如在一个类的VI中又去调用另一个类中的VI。5 .打算使用多态这个特性时要注意,多态使得应用程序在运行时,根据对象的类型选择对应的处理方法。但有些选择应当是程序编译时就做出的,它们不适合套用在多态特性上。举一些例子: INI文件读写这个模块比较适合做成类,每个INI文件对应一个类的实例。它有丰富的数据(文件的内容);它的方法有限,基本上只需要打开、读条目、写条目、保存关

29、闭,这四个方法,并且一般的应用程序都会同时使用到这四个方法。 复杂仪器的驱动程序不适合做成类。因为驱动程序会提供非常多的功能,示波器有各种触发模式。而一个应用程序通常只用到多种模式中的某一种就够用了。 某测试程序可以生成测试报告给用户。用户可以选择几种不同的报告类型。生成报告的模块可以用Ivclass来设计。因为生成不同类型的报告的方法问,可重用代码很多,可以为它们设计一个基类。并且,是程序运行时,才选择生成报告类型的。 某一测试程序,可以支持多种型号的仪器。因为不同用户使用不同的硬件。对不同型号仪器的支持不适合使用Ivclass来设计,因为测试程序发布给用户时,用户的硬件设备是固定的。对仪器

30、的选择应当是程序发布时就决定好的,而不应等到程序每次运行起来后判断。如何在程序中同时弹出多个子VI的界面,各自运行互不影响回答网友一个问题:我设计了一个labview界面子VI,我想在主VI中多次调用该界面VI(同时执行,单独分配内存),并显示出多窗口,该如何设置?我尝试将子VI属性设置成可重入,仍无法解决。”这个问题其实挺常见的。若需要子VI打开多份实例,子VI必须是可重入的。所以第一步要把子VI设置为可重入。但仅仅这样还不够,主程序运行到子VI处,把子VI打开后,会一直等在这里,知道子VI运行结束,才继续执行主VI后续的代码。主VI既然已经停在这里了,自然不会再继续去打开其它子VI。解决的

31、办法是在调用子VI的地方,改为动态调用,并且不等待子VI运行结束。这样一来,主程序运行到这里,将子VI调起后,立即执行后续代码,又可以去调用其它的子VI了。需要注意的是,用于子VI是可重入的,需要给“OpenVIReference!'数设置一个值为“8的Options参数。主VI程序代码如下:口也8:PTe:ppreferreentrantrur>-LabVIEW2009新功能幅引用以前版本的LabVIEW虽然也有多种方法可以让数据以引用的方式在程序间传递,但是用起来都有些麻烦。LabVIEW有了构建数据传引用的节点,大大简化了传引用的程序代码。新添的有关传引用的两个节点在函数选

32、板“Programir->ApplicationControl->MemoryControl中,分别是“NewDataValueReferenceDeleteDataValueReference:'NNewDataValueReferences于创建一个数据的引用,“DeleteDataValueReference可以从引用中取回原来的数据。引用最主要应用于多线程程序中。如果两个线程同时对同一份数据进行修改,则必须使用传引用的机制。否则,使用值传递的方式,数据在数据线分叉的地方,就会编程独立的两份,之后在两个线程内分别修改的是两份完全独立的数据,没办法对同一份数据进行修改的

33、。比如下图这个程序,程序输入了一个数组,然后需要在两个并行的子VI中同时对这个数组中的数据进行修改。每个子VI可能修改了数组不同的元素,程序运行结束产生的数组应当把两个子VI中的修改都包含进来。因此,程序一开始需要数组数据生成一个引用,然后把引用分别传递到两个子VI中去。两个子VI都运行结束后,在从引用中取回数据。dataindataoutLabVIEW中已有的函数还都是为值传递设计的,所以使用值传递少不了把数据取出、放回的过程。这以过程中,可能又会产生数据拷贝,效率会比较差。好在我们可以使用“InPlaceElementStructur钻构来处理从引用中取出、放回数据的过程<配合了“I

34、nPlaceElementStructur结构使用后,LabVIEW会尽量使用数组原地址,而不是把从引用中取出的数据复制一份,这样就做到了传引用与效率的兼顾。比如下面两图中的程序,功能是完全相同的,但LabVIEW会对下面一幅图中的程序进行优化,提高效率。dvaluereferenctErrcrin(noerror二sUvsIijisreferenceouterrorout美化程序僚藏程序框图上的大个Cluster在编写某些程序的时候可能会遇到如图1所示的情形:即用到了一个极为复杂的数据类型常量。这个常量由于体积巨大,使得在程序框图无论怎么摆放都让人看起来不太舒服。如何才能把这个程序改造得美观

35、一些呢?图1:体积巨大的常量会有碍观瞻要解决这个问题,只有设法把这个常量在主程序框图上隐藏起来。通常可以用以下两种方法。第一种方法:把这个常数变换成控件,再把控件隐藏起来。这种方法比较简单,但是也有弊病。容易引起误解:控件一般表示有值传入,其他人读程序读到这里就可能搞不清楚这个值是从哪里传来的了;如果要修改常量Cluster中某一个元素的值,操作起来比较麻烦。第二种方法,也就是我向大家推荐的:把它隐藏到更深层的子VI中去。具体操作方法如下:如图2先给这个复杂数据类型建立一个StrictTypeDef。我的建议是为所有程序中用到的Cluster都建立一个StrictTypeDef。这样可以为以后

36、的程序维护省去很多麻烦。图2:StrictTypeDef.然后然后再建立一个新的VI,把我们要隐藏的这个个头巨大的常量摆放在这个VI中,并且连接一个Indicator,以把它的值传出来。VI的接线板采用4-2-2-4格式的,最下层第3个接线端用于传出VI中唯一的数据,如图3所示。图3:用于隐藏个头巨大常量的VI这个VI的图标要做得小巧漂亮,如图4,图标不一定非要做成正方形。只要B&W和256Colors中的图标形状一样,我们就可以画出不规则图标了。详细方法可以参考制作不规则图形的子VI图标。图4:常量数据VI的图标把这个新造出来的常量数据VI拖到程序框图上,把它的输出链接到刚才链接常量

37、的地方,再把位置摆放好。现在我们的程序是不是漂亮多了图5:改造后的程序框图Caption和Label的书写规范LabVIEW控件的Caption和Label的特性和用途很相似,都是给了控件一个有意义的名字。因此,在很多场合没有必要刻意区分他们。Caption和Label的最主要区别在于,Caption可以在程序运行的时候改变;而Label则不可以,一旦程序运行,就固定不变了。鉴于这一点,Caption和Label的用途也略有区别。Label应该是给程序自己用的,比如在程序中需要根据控件的名字找到它,那就得跟据Label来找,而不能用Caption来找;Caption是为了给用户看的,有时控件的

38、名字在运行到不同状态下需要发生改变,此时显示在界面上的就应该是Caption推荐大家按照下面的规范使用Caption和Label。先给VI分一下类:1 .底层VI:用户不会直接使用到的VI,作为subVI随程序一起发布。2 .用户界面VI:VI前面板是给用户看的程序界面的一部分。3 .程序接口VI:VI是提供给用户,在他们编程时,当作API被调用。对于Caption和Label一个共同的书写规范是:使用有意义的文字,在使用英语短语命名时,单词之间用空格分隔,不应该有重名。不同点列于下表:LabelCaption底层VI显示出来使用LabVIEW的默认状态,即Caption为空。用户界面VI隐藏

39、多谛言版本中,只使用英语显示多语百版本中,使用本地化语百程序接口VI隐藏多谛言版本中,只使用英语不用标注控件的默认值显示多语百版本中,使用本地化语百在后面加一括号,括号内标注控件的默认值和数据单位在LabVIEW中实现VI的递归调用LabVIEW中使用递归调用不是很方便。不过递归并不是编程必须程序结构,任何需要使用递归调用的地方,都可以用循环结构来代替。但是在某些情况下,使用递归调用的确可以大大简化程序代码,对缩短编程时间、提高程序可读性都非常有帮助,所以学习一下递归的实现方法还是有好处的。一、为什么VI不能够被静态的递归调用LabVIEW不能通过静态调用的方法(把子VI直接放到另一VI的程序

40、框图上)来实现递归。对于一个非可重入的subVI,在每一个时间,这个subVI这能被运行一次。LabVIEW需要借此来保证多线程时的数据安全。对于被递归调用的代码,是需要在它执行到中间的时候,就再次被调用的。所以默认设置下的VI不能被静态递归调用。对于被设置为可重入的VI,是可以被同时调用多次的,但也不能被静态的递归调用。除非是通过VIServer动态的调用VI,否则,LabVIEW是在一个程序被调入内存,开始运行之前就为它的所有VI分配好内存空间的,包括数据区。如果一个VI不是可重入的,LabVIEW会在这个VI运行时局部变量所在的数据区开辟在这个VI所在的空间内;对于可重入的VI,LabV

41、IEW把它的数据区开辟在调用者VI上,这样就可以保证这个可重入VI在不同的地方被同时调用时使用不同的数据区,以防止多线程运行时数据混乱。因此,可重入VI虽然可以被同时多次调用,但是被调用的次数是运行前就确定的。而递归运算时的调用次数是运行时决定的。这样,如果是静态调用,LabVIEW根本没有办法为提前为参与递归的VI开辟好数据区。二、用动态调用方法实现递归图1是一个采用递归算法计算阶乘的例子,可以点击后面的连接直接下载示例VI:3535080153251.rarkl皿Eiror图1:利用递归结构计算阶乘正如前文说过的,所有的递归都可以使用循环来代替,计算阶乘也可以使用循环结构,但是这里介绍的是

42、使用递归结构的方法。因为n!=n*(n-1)!,所以我们只要编写一个VI实现功能F(n)=n*F(n-1)就可以了。程序中,递归调用VI自身的结构由三个VI动态调用节点实现:OpenVIReference,CallByReferenceNode,CloseReference这三个节点分别负责动态打开一个VI(本例中就是这个VI自身),运行这个VI,再关闭它。使用CallByReferenceNode需要在打开VI句柄的时候就要知道VI连线板(ConnectorPane的布局,因止匕,我们在用OpenVIReference打开VI的时候要提供VI连线板的布局信息,在例子中就是OpenVIRefe

43、rence节点上方的那个常量。三、使用递归时的几点注意事项递归调用的退出或结束条件,本例中当输入数据小于1时,就需要结束递归调用返回最底层的值了。如果递归调用的退出条件设置不当,可能会引起程序死循环甚至崩溃。LabVIEW中也可以实现A调用B,B又调用A这种用多个VI相互调用的递归结参与递归调用的VI必须被设置为可重入。动态调用的需要把VI在运行时调入内存,这个过程是比较耗时的。因此递归结构的运行效率远不如可实现相同功能的循环结构,内存占用也会更大一些。决定使用递归结构之前要考虑到这些因素。在LabVIEW中实现VI的递归调用LabVIEW中使用递归调用不是很方便。不过递归并不是编程必须程序结

44、构,任何需要使用递归调用的地方,都可以用循环结构来代替。但是在某些情况下,使用递归调用的确可以大大简化程序代码,对缩短编程时间、提高程序可读性都非常有帮助,所以学习一下递归的实现方法还是有好处的。一、为什么VI不能够被静态的递归调用LabVIEW不能通过静态调用的方法(把子VI直接放到另一VI的程序框图上)来实现递归。对于一个非可重入的subVI,在每一个时间,这个subVI这能被运行一次。LabVIEW需要借此来保证多线程时的数据安全。对于被递归调用的代码,是需要在它执行到中间的时候,就再次被调用的。所以默认设置下的VI不能被静态递归调用。对于被设置为可重入的VI,是可以被同时调用多次的,但

45、也不能被静态的递归调用。除非是通过VIServer动态的调用VI,否则,LabVIEW是在一个程序被调入内存,开始运行之前就为它的所有VI分配好内存空间的,包括数据区。如果一个VI不是可重入的,LabVIEW会在这个VI运行时局部变量所在的数据区开辟在这个VI所在的空间内;对于可重入的VI,LabVIEW把它的数据区开辟在调用者VI上,这样就可以保证这个可重入VI在不同的地方被同时调用时使用不同的数据区,以防止多线程运行时数据混乱。因此,可重入VI虽然可以被同时多次调用,但是被调用的次数是运行前就确定的。而递归运算时的调用次数是运行时决定的。这样,如果是静态调用,LabVIEW根本没有办法为提

46、前为参与递归的VI开辟好数据区。二、用动态调用方法实现递归图1是一个采用递归算法计算阶乘的例子,可以点击后面的连接直接下载示例VI:3535080153251.rarkl皿Eiror图1:利用递归结构计算阶乘正如前文说过的,所有的递归都可以使用循环来代替,计算阶乘也可以使用循环结构,但是这里介绍的是使用递归结构的方法。因为n!=n*(n-1)!,所以我们只要编写一个VI实现功能F(n)=n*F(n-1)就可以了。程序中,递归调用VI自身的结构由三个VI动态调用节点实现:OpenVIReference,CallByReferenceNode,CloseReference这三个节点分别负责动态打开

47、一个VI(本例中就是这个VI自身),运行这个VI,再关闭它。使用CallByReferenceNode需要在打开VI句柄的时候就要知道VI连线板(ConnectorPane的布局,因止匕,我们在用OpenVIReference打开VI的时候要提供VI连线板的布局信息,在例子中就是OpenVIReference节点上方的那个常量。三、使用递归时的几点注意事项递归调用的退出或结束条件,本例中当输入数据小于1时,就需要结束递归调用返回最底层的值了。如果递归调用的退出条件设置不当,可能会引起程序死循环甚至崩溃。LabVIEW中也可以实现A调用B,B又调用A这种用多个VI相互调用的递归结参与递归调用的V

48、I必须被设置为可重入。动态调用的需要把VI在运行时调入内存,这个过程是比较耗时的。因此递归结构的运行效率远不如可实现相同功能的循环结构,内存占用也会更大一些。决定使用递归结构之前要考虑到这些因素。一个XControl的实例XControl与.ctl用户定义控件相比,其最大的提高就在于它不但可以定义控件的外观,还可以定义控件的行为。在XControl出现之前,同样可以在程序中编写代码,控制程序的行为。在用XControl实现面向组件的编程一文中提到了,这种方法在程序模块划分上有缺陷。如果用户想发布一个带有特定行为的控件也是不可能的,因为控制控件行为的代码,是同其它代码混杂在一起的。利用XCont

49、rol可以解决上面提到的问题,这里以一个例子说明一下如何利用XControl实现一个有特定行为的控件。Windows风格的工具条上的按钮有一个特点,就是当鼠标移动到按钮上方,按钮就会变亮或浮起。LabVIEW中默认的按钮没有这样的特性,但是实现这一点是很容易的。以鼠标移上,按钮变亮为例:在程序中,当按钮的MouseEnter事件发生时,把按钮的颜色设置为浅颜色;当按钮的MouseLeave事件发生时,把按钮的颜色设置为深色即可。现在把界面上的按钮和控制颜色的代码都封装在一个XControl中。这样,其他人在使用这个XControl时,就无需修改他的代码,而直接获得这种颜色变化的特性了。一、简单

50、行为的XControl首先创建一个空的XControl。图1、2:创建一个新的XControl新的XControl中有四个VI。Data.ctl定义XControl的数据类型。比如我们要做一个按钮,数据类型应该是布尔型。如果要作一个工具条,数据类型就应该是布尔型数组了。State.ctl定义XControl内部要用到的一些数据,类似于类的私有变量。我们这个简单的例子用不到任何变量,所以可以不去动它。Init.vi类似于类的构造函数。在我们这个简单的例子中也不需要去改变它。Facade.vi是最主要的VI,XControl的外观和行为都是在这个VI中定义的。Facade.vi的界面就是XCont

51、rol控件的外观。控制控件行为的代码也是放在这个VI的程序框图上。我们要做的是个按钮,所以就在Facade.vi的前面板上放一个按钮。如果希望用户在使用这个XControl时可以调整它的大小,在我们这个简单例子中,只要设置Facade.vi窗口尺寸属性中的在窗口尺寸变化时,按比例调整控件大小”这个选项就可以了。对于复杂的XControl控件,要另写代码,在窗口尺寸变化后重新计算每个控件的大小和位置。信VIProperties区CategoryWindowSieeMininriumPanelSizeMdth0Height0<<SettoCurrsrtV/indaw5i2e回:allo

52、bjectsonfrontpanelasthewindowresizes*Appliesonlyto511glepanepanelsOKCancelHelp图3:窗口尺寸属性设置控制按钮颜色的代码也需要放在Facade.vi中:把前文提到的按钮的MouseEnter和MouseLeave放在这里即可。具体实现方法,可以参考文章结尾给出的范例程序:5280261469140.zip二、有持续运动的XControlFacade.vi不能够持续运行,只有在有事件发生时,LabVIEW才会调用这个VI。处理完这个事件,Facade.vi就会停止运行。不要试图让Facade.vi持续运行,否则会导致整个

53、LabVIEW被挂起。有时候,需要控件能够循环地或者持续一段时间地作一个动作。比如说,需要做一个不停闪烁的小灯。控制灯光闪烁的代码就不能够放在Facade.vi中。实现这种功能的一个方法是:把定时控制小灯颜色的代码放在一个可重入VI中,通过小灯控件的引用参考来定时更改它的颜色属性。在XControl的Init.vi中把这个定时VI动态加载并以异步方式运行;在XControl的Uninit.vi中再把这个定时VI卸载即可。Uninit.vi不是一个必须的XControl功能定义VI(AbilityVI),新建的XControl没有这个VI。可以在工程浏览窗口,鼠标右击这个XControl来为它添

54、加新的功能定义VI。笆5279316579099.zip它只能在LabVIEW8.5下打开。XControl是可以在VI的面板上放多个实例的,每个实例小灯的闪烁频率可能不同。我在这个例子里,每个XControl实例都有自己的一个专用定时VI,因为这些VI是可重入的。定时的方法我采用的是加延时。我做了一下测试,发现现在的XControl有个问题,就是在程序面板上放多个XControl实例之后,定时就变得非常不准确了,小灯闪烁速度明显减慢。这也许是XControl的bug,也许是LabVIEW延时函数的问题。解决这个问题的方法就是使用一个定时VI控制所有的实例,当然这样的实现方法会比较麻烦一些。下

55、载文章中的示例程序:LabVIEW中的泛型容器Google网站里有个Google实验室,有不少Google的产品最初就是放在这个实验室里的。现在NI也有NI实验室了。NI实验室公布出来的项目一般是NI工程师利用额外时间做的一些调查研究。这些项目不是公司的正式产品,但是它们的设计很有创新或者是比较有应用潜力。与其让这些项目被埋没了,不如先看看用户对这些项目的反应,如果相当一部分用户觉得某个项目非常有帮助,或许它就值得我们为其增加投资,把它作成正式产品了。我这里给大家介绍其中的一个项目:LabVIEWGenericContainerMap”。因为这个项目是我设计的,所以对它了解比较多一些。当时,我

56、们打算提出这个项目的时候,主要有两个目的:第一是帮助用户编写有复杂数据结构的应用程序;第二是推进LabVIEW向通用编程语言方向做改进。C+的程序员基本都很喜欢STL这个模板库。程序中常会使用数组、队列、字符用等等数据类型和结构,如果自己设计实现这些数据结构和相关的操作,是相当耗费精力的。好在STL实现了这些数据结构,和它们常用的操作方法。借用STL提供的功能,编程时很多细节方面不需要再去考虑了,这就让工作简化了许多。尝到STL甜头的程序员,在编写程序的时候,已经很难离开STL了。STL中非常重要的一个部分就是容器。容器用于存放数据,程序通过调用容器的结构函数保存数据到容器或者访问容器中的数据。容器也分为不同的类型,如链表、队列等。它们在数据的组织方式上,或存取方式上有所分别,以适用不同的需求。STL中的容器和方法都是泛型的或者说是数据类型无关的,就是说这些容器可以保存和操作任何类型的数据。其它一些常用的编程语言,如JavaC#也都有类似的泛型容器以方便程序员使用。LabVIEW的主要方针是简化工程师们编写程序的难度,以前用La

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论