搬家第42天-citect2018应用cicode和citectVBA制作报表1:动态ODBC连接、添加纪录、条件查询、定制报表列
在www.52plc.net论坛上,施耐德王工给了一个报表的例子程序,不过是用旧版本的citect开发的。最近在自学citect2018,用这个版本自己也做了报表功能,把它记录在博客里面,以后的工作也许用得上。做好的程序保存在我的百度网盘,程序名称是citect2018prj13,打开的网址前面的博客有介绍。
练习项目程序基于citect2018,数据库选用的是access2013,操作系统是windows7 32位简体中文旗舰版SP1,没有连接实际的PLC,使用内部变量模拟赋值。这个例子需要实现以下功能:
1. ODBC连接中Access数据库的路径动态生成。练习的项目程序使用的路径可能发生变动,后面维护的人不一定知道需要设置,需要程序自动的创建。
2. 自动添加纪录到数据库。这个没啥说的。
3. 条件查询。可以查询日报表、月报表、年报表。
4. 定制需要显示的列。数据库记录的字段较多,有时候不是所有的数据都需要查询出来,定制哪些列需要显示就很重要。
5. 导出定制后的数据到excel文件。自动排版,做好表头。
6. 自动删除旧的数据。因为数据库使用的是ACCESS2013,查询资料知道这种数据库文件最大只能是2G,容量有限,需要自动的判断文件大小,然后删除旧的数据,避免记录不断累加之后文件大小超限。
确定好前述6个功能后,就开始逐步实施了。
1. 创建一个新的包含工程,使用默认的配置。
2. 新建内部变量如下图
前面10个变量是报表中需要显示的变量。rowcount变量存放表格控件的行数,colcount变量存放表格控件的列数,processvalue变量存放进度条数值,processvisible变量存放表格控件的可见性。
2. 设置项目程序安全性。administrators角色“允许执行”修改为true。
新建一个用户admin,属于administrators用户组,设置密码123
3. 打开设置编辑器,添加Security区域,其下添加BlockExec参数,参数值为0。这里设置是为了后面cicode代码中exec函数有执行权限。
4. 在项目路径下新建一个access2013数据库,名字叫做report,新建一张表mytable,表中个字段按照下图定义。也可以用前面练习的数据库文件。
一共13个字段。在文件-选项中勾选数据库关闭时压缩。
5. 新建一个cicode文件,写自动创建ODBC连接的程序CreateODBCLink(),由于代码与博客网页后台的一些字符有冲突,这里是用截图展示,如下:
这个截图很长,安利一下截图工具FScapture,挺好用的。
这里是通过先生成一个注册表文件,然后生成一个批处理文件,批处理文件来执行注册表文件,没有提醒注册表操作,静默完成。执行外部/打开程序/文件的cicode函数Exec需要权限,也就是前面设置第2和第3步的作用。这一点困扰了我一段时间,后来在施耐德王工的指导下解决了,再次表示非常感谢。
cicode编程对于文件的操作,这里的思路是先判断项目路径下有没有一个叫做CreateODBC.reg的注册表文件,使用的函数是FileExist(文件名),因为项目路径不固定,又使用了cicode函数PathToStr("[run]")来获取。如果这样的文件存在,因为不知道具体的内容和作用是不是我们想要的那个,干脆先删除他,然后再创建一个,使用FileOpen(文件名,"a+")打开,由于前面已经删除了这个文件,所以这个函数会自动创建一个新的。要注意新文件的创建可能需要时间,所以先不要急着写数据,文件没有完全创建好,写数据是没有意义的。这里是用了一个空循环等待文件创建完毕,代码如下
WHILE NOT FileExist(regfile) DO
//循环等待批处理文件生成,但是一个空文件
END
写注册表文件普通的数字字符问题不大,要注意回车换行以及双引号怎么体现。回车换行通过FileWrite(sfile,"^r^n"),sfile是打开文件的句柄;双引号通过^"在表达。这里也绊住了我一段时间,好在解决了。注册表文件的内容可以先通过手动创建一个ODBC链接,然后打开注册表,看看操作系统在哪里做了修改,写了什么内容,然后原样的创建一个注册表文件。这里项目路径就可以通过前面的函数获取,从而实现了ODBC中ACCESS数据库路径的动态化了。
通常双击一个注册表文件运行时,系统会好心的提醒涉及注册表修改,但这里我希望不要问我,这个文件就是我自己写的,很安全。解决的思路是再生成一个批处理文件,静默的运行注册表文件。批处理文件内容是
reg import 注册表文件的路径
注册表文件一样的先判断是否存在,存在先删除它,不存在就创建它。一样的等待创建完毕后再写入内容。注册表路径是动态的,前面的代码已经获取了,这里只是需要将这个动态获取的路径写进批处理文件就是了。
由于执行批处理文件的函数Exec是需要获取权限的,前面虽然做了设置,admin用户是有资格执行的,但需要先登录。
Login("admin","123")执行登录功能。先判断是不是登录成功了,再执行批处理文件。判断是不是登陆完成了使用
WHILE UserInfo(1)<>"admin" DO
//等待登录成功
END
刚开始我也觉得这个创建动态ODBC连接应该不难,但还是在授权、写文件、等操作是否完成 等步骤上栽了跟头。最后就是执行一下这个批处理文件。打开控制面板-管理工具-odbc连接,就能看到效果了。如果运行过一遍批处理文件,项目程序移动了位置,再次运行,因为连接名一致,新的ODBC配置会自动覆盖以前的配置。
6. 新建一个cicode函数,用于变量赋值
FUNCTION Setvalue()
temp1=TimeSec(TimeCurrent())
temp2=TimeSec(TimeCurrent())+10
temp3=TimeSec(TimeCurrent())+20
temp4=TimeSec(TimeCurrent())+30
press1=TimeSec(TimeCurrent())+5
press2=TimeSec(TimeCurrent())+15
Press3=TimeSec(TimeCurrent())+25
press4=TimeSec(TimeCurrent())+35
flow1=TimeSec(TimeCurrent())+8
flow2=TimeSec(TimeCurrent())+18
END
由于没有连接实际的PLC,这里就模拟一下数据变化。
7. 新建一个cicode函数,用于添加纪录,再次截图。
这里通过ODBC连接上access数据库然后插入记录。
8. access数据库文件大小上限是2G,删除记录后,如果不压缩数据库,文件大小是不会变化的。刚开始的思路是通过连接access数据库,写脚本来压缩数据库,后来发现没有任何效果。后来又想在access数据库文件中写宏命令,access文件一运行,就执行宏命令压缩数据库,结果微软不允许这么干。最后想到设置report.mdb在关闭数据库后自动压缩,删除记录后打开关闭access文件一次来变相实现压缩ACCESS数据库。
9. 在项目文件夹下新建一个带有宏命令的excel文件compaceDB.xlsm。打开这个excel文件,启用开发工具,在visual basic编辑环境下新建一个模块,写入以下代码
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
这两个句子就是声明调用系统函数查找窗口和窗口命令传递。
在compaceDB.xlsm文件的book打开事件中插入以下代码:
这里就是在后台打开report.mdb文件,等待完全打开后再关掉它,给了30秒的时间。保存后需要退出重新打开一次,确保系统允许宏运行,然后再次关掉。
10. 在项目文件夹下新建一个批处理文件killexcel.bat,写以下代码
cmd.exe /c taskkill.exe /f /im excel.exe
这个批处理文件就是强行关掉excel.exe进程。
11. 新建一个citectVBA函数Deleterecord_CompressionDB(),用于连接ACCESS数据库,删除记录。
这里就先判断文件大小,如果超过某个限值,及连接ACCESS数据库,统计表记录数,然后删掉最老的1/5记录。然后运行有宏命令的excel文件,通过这个操作开关一次report.mdb文件,实现压缩数据库的目的,最后运行批处理文件来终止excel进程。如何彻底的终止excel.exe进程消耗了我不少时间,一开始想通过在citectVBA中队object变量操作来实现,始终不行,最后釜底抽薪的方式解决了。
12 变量赋值、数据记录添加、数据库瘦身都是需要按照一定频率进行,前面两个步骤完成后,在项目程序设置-事件那里添加两个事件。变量赋值setvalue按照每秒赋值一次进行,添加纪录addrecord按照5秒一次。数据库瘦身可以定义按照一定周期执行,数据库限值和删除记录数可以根据实际的项目调整。
13.新建两个画面:start和main。start的尺寸是10*10.这个画面主要是用于做初始化的一些操作,由于没有实际的控件,所以把尺寸定义得很小。main画面的尺寸根据显示器分辨率设置。在start画面的“进入页面时”事件中写以下代码:
CreateODBCLink()
PageDisplay("main", "Cluster1")
这样一来,进入start页面就会创建ODBC连接,然后跳转到main画面。
14.项目程序做一下设置向导,使用自定义设置。启动画面设置为start
要选择strat,需要将“仅显示主页面”取消勾选。事件设置勾选前面定义的三个事件。
其他设置使用默认就行,不需要特别设置。
15. main画面需要放置microsoft date and time picker、microsoft datagrid、microsoft form 2.0 checkbox、microsoft form 2.0 optionbutton、microsoft processbar control 6.0、按钮、数字控件。
单选、复选控件不像wincc那样可以在编辑阶段就写caption属性值,可以在脚本里面初始化。
微软日期时间控件默认是没有的,这和wincc不一样,我觉得这个控件很常见,不知道为什么施耐德没有默认包含。此外第三方的空间没法直接使用,强行使用会提示没有注册权限balabala的。这个和wincc相似。我不知道是不是西门子和施耐德和微软钱没有谈拢。
从网上找微软的日期时间控件mscomct2.ocx,放置到系统盘windows\system32(64位操作系统放置到windows\syswow64\)下面。打开资源管理器,找到windows\system32文件夹,使用系统管理员运行cmd.exe,写regsvr32 mscomct2.ocx完成注册(64位操作系统找到windows\syswow64文件夹,同样的操作方法)。其他控件的授权注册,可以参考另一篇博客(http://blog.sina.com.cn/s/blog_724246b90102zb9k.html)
main画面控件和AN编号如下:
控件名称 | AN编号 | 作用 |
microsot日期时间控件 | AN4 | 用户选择日期 |
microsoft单选框 | AN5 | 用户选择日报表 |
microsoft单选框 | AN6 | 用户选择月报表 |
microsoft单选框 | AN7 | 用户选择年报表 |
microsoft复选框 | AN8 | 用户选择显示温度1数据列 |
microsoft复选框 | AN9 | 用户选择显示温度2数据列 |
microsoft复选框 | AN10 | 用户选择显示温度3数据列 |
microsoft复选框 | AN11 | 用户选择显示温度4数据列 |
microsoft复选框 | AN12 | 用户选择显示压力1数据列 |
microsoft复选框 | AN13 | 用户选择显示压力2数据列 |
microsoft复选框 | AN14 | 用户选择显示压力3数据列 |
microsoft复选框 | AN15 | 用户选择显示压力4数据列 |
microsoft复选框 | AN16 | 用户选择显示流量1数据列 |
microsoft复选框 | AN17 | 用户选择显示流量2数据列 |
文本控件 | AN18 | 显示/隐藏数据列 |
microsoft datagrid | AN19 | 显示条件查询结果 |
按钮 | AN20 | “条件查询”按钮 |
按钮 | AN21 | “导出到excel”按钮 |
microsoft processbar | AN22 |
导出到excel进度条 |
文本控件 | AN23 | 显示进度条数值 |
citect中,单选框、复选框上的文字不能在编辑时候设置,这点和wincc不一致,需要改进。
新建一个cicode程序objcheckbox_OptionButton_ini(),用户初始化单选框复选框的文字和颜色。
FUNCTION objcheckbox_OptionButton_ini()
OBJECT objcb1,objcb2,objcb3,objcb4,objcb5,objcb6,objcb7,objcb8,objcb9,objcb10
OBJECT objob1,objob2,objob3,objDTP
objdtp=ObjectByName("AN4")
_OBJECTsetproperty(objdtp,"value",Date())
objcb1=ObjectByName("AN8")
_ObjectSetProperty(objcb1,"CAPTION","温度1")
_ObjectSetProperty(objcb1,"backcolor",15856113)
_ObjectSetProperty(objcb1,"forecolor",16711680)
objcb2=ObjectByName("AN9")
_ObjectSetProperty(objcb2,"CAPTION","温度2")
_ObjectSetProperty(objcb2,"backcolor",15856113)
_ObjectSetProperty(objcb2,"forecolor",16711680)
objcb3=ObjectByName("AN10")
_ObjectSetProperty(objcb3,"CAPTION","温度3")
_ObjectSetProperty(objcb3,"backcolor",15856113)
_ObjectSetProperty(objcb3,"forecolor",16711680)
objcb4=ObjectByName("AN11")
_ObjectSetProperty(objcb4,"CAPTION","温度4")
_ObjectSetProperty(objcb4,"backcolor",15856113)
_ObjectSetProperty(objcb4,"forecolor",16711680)
objcb5=ObjectByName("AN12")
_ObjectSetProperty(objcb5,"CAPTION","压力1")
_ObjectSetProperty(objcb5,"backcolor",15856113)
_ObjectSetProperty(objcb5,"forecolor",16711680)
objcb6=ObjectByName("AN13")
_ObjectSetProperty(objcb6,"CAPTION","压力2")
_ObjectSetProperty(objcb6,"backcolor",15856113)
_ObjectSetProperty(objcb6,"forecolor",16711680)
objcb7=ObjectByName("AN14")
_ObjectSetProperty(objcb7,"CAPTION","压力3")
_ObjectSetProperty(objcb7,"backcolor",15856113)
_ObjectSetProperty(objcb7,"forecolor",16711680)
objcb8=ObjectByName("AN15")
_ObjectSetProperty(objcb8,"CAPTION","压力4")
_ObjectSetProperty(objcb8,"backcolor",15856113)
_ObjectSetProperty(objcb8,"forecolor",16711680)
objcb9=ObjectByName("AN16")
_ObjectSetProperty(objcb9,"CAPTION","流量1")
_ObjectSetProperty(objcb9,"backcolor",15856113)
_ObjectSetProperty(objcb9,"forecolor",16711680)
objcb10=ObjectByName("AN17")
_ObjectSetProperty(objcb10,"CAPTION","流量2")
_ObjectSetProperty(objcb10,"backcolor",15856113)
_ObjectSetProperty(objcb10,"forecolor",16711680)
objob1=ObjectByName("AN5")
_ObjectSetProperty(objob1,"CAPTION","当日报表")
_ObjectSetProperty(objob1,"backcolor",15856113)
_ObjectSetProperty(objob1,"forecolor",16711680)
objob2=ObjectByName("AN6")
_ObjectSetProperty(objob2,"CAPTION","当月报表")
_ObjectSetProperty(objob2,"backcolor",15856113)
_ObjectSetProperty(objob2,"forecolor",16711680)
objob3=ObjectByName("AN7")
_ObjectSetProperty(objob3,"CAPTION","当日报表")
_ObjectSetProperty(objob3,"backcolor",15856113)
_ObjectSetProperty(objob3,"forecolor",16711680)
END
在main的“进入页面时”事件添加以下代码
objcheckbox_OptionButton_ini()
rowcount=0
processvisible=1
进度条和进度条数值显示的控件隐藏条件设置为processvisible,这样一来就能初始化单选框和多选框控件,进度条和进度条数值默认不显示。
为了让单选框唯一,相互排斥,写以下代码:
FUNCTION main_AN5_change(OBJECT ob)
OBJECT ob2,ob3
ob2=ObjectByName("AN6")
ob3=ObjectByName("AN7")
IF _ObjectGetProperty(ob,"value")=-1 THEN //选择日报表
_ObjectSetProperty(ob2,"value",0)
_ObjectSetProperty(ob3,"value",0)
END
END
FUNCTION main_AN6_change(OBJECT ob)
OBJECT ob1,ob3
ob1=ObjectByName("AN5")
ob3=ObjectByName("AN7")
IF _ObjectGetProperty(ob,"value")=-1 THEN //选择月报表
_ObjectSetProperty(ob1,"value",0)
_ObjectSetProperty(ob3,"value",0)
END
END
FUNCTION main_AN7_change(OBJECT ob)
OBJECT ob1,ob2
ob1=ObjectByName("AN5")
ob2=ObjectByName("AN6")
IF _ObjectGetProperty(ob,"value")=-1 THEN //选择年报表
_ObjectSetProperty(ob1,"value",0)
_ObjectSetProperty(ob1,"value",0)
_ObjectSetProperty(ob2,"value",0)
END
END
单选框和复选框被选中其value属性值是-1,不是1,需要注意。
做到这里,这个练习的功能才刚刚实现了一半,最关键的个性化定制查询和结果导出功能,后面的博客继续记录。