2005年06月07日

/*——————————————————————————-
* 函数名称:Boolean gf_check_identity(string as_identity)
* 函数功能:验证身份证号输入的正确性(可以对身份证号码的第18位校验位进行判断.)
* 参数说明:string as_identity    身份证号
* 返 回 值:True  成功
*           False 失败
* 调用举例:gf_check_identity(‘513101198201130014′)  
* 修改日期:2005-05-09
* 修 改 人:tecsun
*——————————————————————————–*/
string ls_identity_no
string ls_year,ls_month,ls_day,ls_date
string ls_today
long ll_year
long ll_identity_no_len  

ls_identity_no = as_identity
ls_today = string(today(),’yyyy/mm/dd’)

ll_identity_no_len=LEN(ls_identity_no)

if ls_identity_no = ”  then
MessageBox("系统提示","身份证号码不能为空!!",StopSign!,ok!)
return False
elseif ll_identity_no_len <> 15 AND ll_identity_no_len <> 18 then
MessageBox("系统提示","身份证号码位数不正确,请检查输入情况!!",StopSign!,ok!)
return False
end if

if ll_identity_no_len = 15 then //身份证为 15 处理,  认为15位的年 = 19**
ls_year = mid(ls_identity_no, 7, 2)
ls_month = mid(ls_identity_no, 9, 2)
ls_day = mid(ls_identity_no, 11, 2)
ls_year = ‘19′ + ls_year//year is only 20 century
ls_date = ls_year +’/’ + ls_month +’/’ + ls_day
else
ls_year = mid(ls_identity_no, 7, 4)

if Left(ls_year,2) <> ‘19′ and left(ls_year,2) <> ‘20′ then
  MessageBox("系统提示","身份证号码中的出生年份不正确, 请您重新输入! ",StopSign!,ok!)
  return False
end if

ls_month = mid(ls_identity_no, 11, 2)
ls_day = mid(ls_identity_no, 13, 2)
ls_date = ls_year +’/’ + ls_month +’/’ + ls_day
end if

if ls_month > ‘12′ then
MessageBox("系统提示","身份证号码中的出生月份不能大于12, 请您重新输入 ",StopSign!,ok!)
return False
elseif ls_month <= ‘00′ then
MessageBox("系统提示","身份证号码中的出生月份不能小于01, 请您重新输入 ",StopSign!,ok!)
return False
end if

if ls_day <= ‘00′ then
MessageBox("系统提示","身份证号码的出生日小于01, 请您重新输入 !",StopSign!,ok!)
return False
end if

CHOOSE CASE ls_month
CASE ‘01′,’03′,’05′,’07′,’08′,’10′,’12′//大月的处理
  if ls_day > ‘31′ then
   MessageBox("系统提示","身份证号码的出生日大于31, 请您重新输入 !",StopSign!,ok!)
   return False
  end if

CASE ‘04′,’06′,’05′,’09′,’11′ //小月的处理
  
  if ls_day > ‘30′ then
  MessageBox("系统提示","身份证号码的出生日大于30(本月无31日), 请您重新输入 !",StopSign!,ok!)
  return False
  end if
  
CASE ‘02′  //平年和闰年的处理
  
  ll_year = long(ls_year)
  
  if (mod(ll_year,4) = 0 and Mod(ll_year,100)<>0) or (Mod(ll_year,400) = 0) then   //闰年,二月份不能多于29天
   if ls_day > ‘29′ then
    MessageBox("系统提示","闰年的二月份没有" + ls_day + "日,请重新输入出生日期!",StopSign!,ok!)
    return False
   end if
  else//平年二月份不能大于28天
   if ls_day > ‘28′ then
    MessageBox("系统提示","平年的二月份没有" + ls_day + "日,请重新输入出生日期!",StopSign!,ok!)
    return False
   end if
  end if
  
END CHOOSE

if ls_date > ls_today then
MessageBox("系统提示","身份证号码的出生日期小于登记日期,不合理请您重新输入 !",StopSign!,ok!)
return False
end if

/*==========================验证身份证的最后一位检验码==================================================+/
根据〖中华人民共和国国家标准GB11643-1999〗中有关公民身份号码的
规定,公民身份号码是特征组合码,由十七位数字本码和一位数字校验
码组成。排列顺序从左至右依次为:六位数字地址区位码,八位数字出
生日期码,三位数字顺序码和一位数字校验码。

地址区位码:表示居民常住户口所在县(市、区)的行政区划代码。

出生日期码:表示居民出生的年、月、日,其中年份用四位数字表示,
年、月、日之间不用分隔符。

数字顺序码:表示同一地址区位码所标识的区域范围内,对同年同月同
日出生的人员编定的顺序号。奇数分给男性,偶数分给女性。

数字校验码:根据前面十七位数字码,按照ISO 7064:1983.MOD 11-2校
验码计算出来的检验码。由(1,2,3,4,5,6,7,8,9,X)构成。

下面举例说明该计算方法并用代码实现。

公式:Ax=(∑(Ai×Wi))(mod 11)…………………………(1)

i—-表示身份证号码字符从右至左包括校验码在内的位置序号;
Ai—-表示第i位置上的数字号码;
Ax—-数字校验码的值;
Wi—-示第i位置上的加权因子;
公式:Wi=(2^(i-1))(mod 11)…………………………(2)

设某男性公民身份号码为51310119820113001,首先得到各个变量:

i18 17 16 15 14 13 12 11 10 9  8  7  6  5  4  3  2  1

Ai5  1  3  1  0  1  1  9  8  2  0  1  1  3  0  0  1  Ax
由公式(2)可得:
Wi7  9  10 5  8  4  2  1  6  3  7  9  10 5  8  4  2  1

Ai×Wi  35 9  30 5  0  4  2  9  48 6  0  9  10 15 0  0  2  A1

按照公式(1)计算:

∑(Ai×Wi)=(35+9+30+5+0+4+2+9+48+6+0+9+10+15+0+0+2)=184
184÷11=16+8/11
∑(Ai×Wi)(mod 11)=8

然后根据计算的结果,从下表中查出相应的校验码,X表示结果为10:

∑(Ai×WI)(mod 11)0 1 2 3 4 5 6 7 8 9 10
校验码字符值Ai1 0 X 9 8 7 6 5 4 3 2

根据上面的方法,查出计算结果为8的校验码为4所以该人员的公民身份
号码应该为513101198201130014。


/+===============================================================================================================*/
if ll_identity_no_len = 18 then //18位的校验码的检验
  
long Ai=0
string code,old_id
int j
old_id=trim(sle_1.text)

for j=17 to 1 step -1
Ai=Ai+(long(mid(old_id,j,1)))*(mod(2^((18)-j),11))
next

choose case mod(Ai,11)
  case 0
   code=’1′
  case 1
   code=’0′
  case 2
   code=’X’
  case else
   code=string((12)-mod(Ai,11))
end choose

if right(as_identity,1) <> code then
  messagebox("系统提示","最后一位校验位数字不正确",StopSign!,ok!)
  return False
end if

end if

return True

2005年04月18日

 从客户端获得服务器的时间

方法一,从系统表中取时间


datetime ldt_dt

//ORACLE:
select sysdate into:ldt_dt from sys.dual;

//SQLServer、Sybase:
select getdate() into:ldt_dt from tablename;

不用和表相关的方法:

datetime ldt_1
declare cur cursor for select getdate();
open cur;
fetch cur into :ldt_1;
close cur;

再来一个

SELECT getdate()
FROM "sys"."dummy"  ;


PB中的套接字是通过Winsock.pbl库来提供的,它封装了套接字编程中用到的数据结构和过程,在功能上类似于VB中的Winsock控件。

  Winsock.pbl中定义了两种类型的Socket:流式Socket和数据报式Socket。流式Socket需要连接到另一个处于监听状态的流式Socket后才能进行通信,是基于连接的,其可靠性高;数据报式Socket无需建立连接,源主机发出的报文在网络中经过存储转发后到达目的主机,效率高但可靠性低。编程时,根据应用环境和需求选择其中一种,若通信子网相当可靠,可考虑采用数据报式Socket。



图1

  用PB编写WinSock TCP/IP应用程序的第一步是将Winsock.pbl加到应用程序中,然后声明如下全局变量:

  Winsock ws

  Boolean b—tcp—active

  //用于检验ws是否初始化成功

  PowerObject gpo—null//全局空对象

  在应用程序的Open事件加入下列代码:

  ws=Create Winsock

  //初始化Winsock的一个实例

  SetNull(gpo—null)//ws的函数中用到空对象gpo—null

  在应用程序的Close事件加入下列代码:



  图2

  Destroy ws//销毁ws对象

  完成以上工作后,就可以着手编程了,下面介绍如何利用Socket进行通信。

  1.用数据报式Socket向本机的7号端口发送数据

  TCP和UDP协议规定了传输层端口的长度为16比特,因此TCP和UDP软件可以使用216个不同的端口进行通信。尽管如此,编程时最好不要使用前1024个端口,因为这个范围内很多是专用端口,如21为FTP端口。本例中用到的7号端口很特殊,它回显接收到的任何数据,常用于端口检测。下面就向本机的7号端口发送数据报:

  DGSock=Create Socketdgram

  //创建数据报式Socket对象

  ulAddr=ws.inet—addr(″127.0.0.1″)

  //将本机IP地址转换为32位的ulong类型

  buf=Blob(″These data is send through datagram~r ~n″)//要发送的数据

  DGSock.sendto(buf,Len(buf),0,ulAddr,7)

  //向ulAddr主机的7号端发送数据报

  buf=Blob(Space(Len(buf)))

  //清空buf缓冲区

  DGSock.recv(buf,Len(buf),0)

  //接收数据报

  MessageBox(′Data Received′,String(buf))

  //显示接收到的数据

  DGSock.closesocket()//关闭Socket

  Destroy DGSock

  从上面的演示可以看出,发送到本机7号端口的数据报立即被反弹回来。
2.用流式Socket 开发网络聊天程序

  网络聊天程序通常包含两个部分:服务程序和客户程序。服务程序一直处于监听状态,当听到客户程序的呼叫时,就创建一个Socket对它进行响应。下面用流式Socket开发一个两节点聊天程序:

  (1)编写服务程序

  服务程序界面如图1所示。在主窗口的Open事件中创建流式Socket的一个实例:

  sSock=Create SockStream//sSock为实例变量

  在“监听”按钮的Clicked事件中加入下列代码:

  ulAddr=ws.inet—addr(″202.140.1.20″)

  //将服务器地址转为ulong类型

  sSock.bind(ulAddr,2000)//将流式Socket绑定到ulAddr地址的2000号端口上

  sSock.listen(5)//监听上述地址和端口,参数为请求队列长度,最大值为5

  uiSockType=sSock.accept(ulClientAddr,iClientPort)

  //接受客户请求,参数填入了客户Socket的地址和端口,返回值为客户Socket类型

  sAccept=Create Socket

  //创建一个Socket响应客户请求

  ulParam=1

  sAccept.initsocket(uiSockType)

  //与客户Socket类型相同

  sAccept.ioctlsocket(ws.FIONBIO,ulParam)

  //异步模式

  Timer(0.5)

  //启动定时器,以0.5秒的间隔接收数据

  在Timer事件中加入下列代码来处理到达的数据:

  buf=Blob(Space(256))//定义缓冲区大小

  sAccept.recv(buf,Len(buf),0)

  //接收到达的数据

  mle—1.Text=mle—1.Text+Trim(String(buf))

  //显示消息

  在“发送”按钮的Clicked事件中加入下列代码:

  buf=Blob(mle—2.Text+″~r~n″)

  //将mle—2中的内容放入发送缓冲区

  sAccept.send(buf,Len(buf),0)

  //将buf中的内容发给对方

  mle—2.Text=″ ″

  //清除已发送的内容

  在“退出”按钮的Clicked事件中加入下列代码:

  sAccept closesocket()//关闭Socket

  Destroy sAccept

  sSock.closesocket()

  Destroy sSock//清除Socket

  (2)编写客户程序

  设计如图2所示的窗口,其Open事件的代码为:

  sClient=Create SocketStream

  //创建流式Socket

  ulParam=1

  //1表示异步模式(即非阻塞模式)

  Timer(0.5)//启动定时器,以0.5秒的间隔检查是否有数据到达

  sClient.ioctlsocket(ws.FIONBIO, ulParam)

  //将sClient设置为异步模式

  在“连接” 按钮的Clicked事件中加入下列代码:

  ulAddr=ws.inet—addr(″202.140.1.20″)

  //服务器地址

  If sClient.wsconnect(ulAddr,2000)=-1 Then//连接到服务器的2000号端口

  MessageBox(′Socket′,″连接服务器失败″)

  End If

  Timer事件和“发送”按钮的Clicked事件的代码与服务程序相同,只需将套接字对象sAccept改为sClient即可。

  声明:缺省情况下创建的流式Socket对象使用同步模式,可根据需要将其转换成异步模式。在同步模式下,一些Winsock函数调用在完成处理之前不会把控制权还给程序,导致程序无响应。例如,在数据到达之前,recv()调用将一直处于等待状态。在上面的服务程序中,用于监听客户连接的Socket使用了同步模式,响应客户请求的Socket使用了异步模式,客户程序中的Socket也使用了异步模式。

  运行服务程序,点击“监听”进入等待状态;运行客户程序,点击“连接”进行呼叫。建立连接后,就可以聊天了。在mle—2中输入消息,点击“发送”就可传给对方,对方发过来的消息显示在mle—1中。


附件 1: fangz20705_01.jpg (2005-4-16 08:47, 8.34 K,下载次数: 1)


附件 2: fangz20705_02.jpg (2005-4-16 08:47, 7.89 K,下载次数: 1)

系统注册登记表是Windows 9x中所有配制信息的中央仓库。它除了保存操作系统自身的软件、硬件及环境信息外,另一个用途就是保存应用程序中用户定制的配制信息。把应用程序保存到系统注册登记表中的方法是使应用程序的适用性提高的最佳方法之一。

  在PowerBuilder应用程序中,通过使用PB内嵌的注册函数对系统注册登记表的访问,可以方便地设置和获取应用程序及用户设置的信息:如进入应用程序时,通过访问系统注册登记表来获取主窗口的原有大小及位置信息,而退出程序时可保存用户改变了的窗口大小及位置信息;可以将用户的设置保存到系统注册登记表中,在必要时从系统注册登记表中取出;亦可创建一个.REG文件(注册登记)来安装自己的应用程序,以及实现存取ODBC驱动信息等等。

  PB注册(Registry)函数

  在PowerBuilder6.0中内嵌的注册函数有5条,分别是RegistrySet、RegistryGet、RegistryDelete、RegistryKeys、和RegistryValues,需要注意的是,这组函数只能在MicrosoftWindows系统中使用,下面是这五个函数的详细用法:

  1.RegistrySet(key,valuename,valuetype,value)
key String 类型,指定键名
valuename String 类型,指定要访问值或创建新值的名称
valuetype RegistryValueType枚举类型,指定值的数据类型
value 要设置的值,其数据类型应与valuetype参数指定的类型相匹配
返回值 函数执行成功时返回1,发生错误时返回-1

  RegistrySet()的功能是在系统注册登记表中设置或创建指定键和值,如果要设置的键名或要设置的值不存在,则先建立再赋值。

  如下面的代码在系统注册登记表中设置或创建了应用程序MyApp运行时读写文件或访问图形及其他对象时的路径:

RegistrySet("HKEY_LOCAL_MACHINE\SOFTWARE\PB6\MYAPP\Application",&

"Path",RegString!,"C:\MyApp")

  2.RegistryGet(key,valuename,valuetype,valuevariable)

 

Key String 类型,指定键名
Valuename String 类型,指定要访问值的名称
Valuetype RegistryValueType枚举类型,指定值的数据类型
Valuevariable 用于保存键值的变量,其数据类型应与valuetype参数指定的类型相匹配
返回值 函数执行成功时返回1,发生错误时返回-1

  RegistryGet()的功能是从系统注册表得到指定键的值。如下面的代码在系统注册登记表中获取应用程序MyApp读写文件或访问图形及其他对象时的路径于变量ls_Path中:

String ls_Path

RegistryGet("HKEY_LOCAL_MACHINE\SOFTWARE\PB6\MYAPP\Application",&

"Path",RegString!,ls_Path)

  3.RegistryDelete(key,valuename)

 

Key String 类型,指定键名,将删除该键或该键的某个值
Valuename String 类型,指定要删除的值的名称
返回值 函数执行成功时返回1,发生错误时返回-1

  RegistryDelete()的功能是删除系统注册登记表中的一个键或键的某个值。如想删除键、键的值、以及其下的所有子键,将valuename参数指定为空字符串。

  如下面的代码删除系统注册登记表中应用程序MyApp读写文件或访问图形及其他对象时的路径:

li_return = RegistryDelete("HKEY_LOCAL_MACHINE\

SOFTWARE\PB6\MYAPP\Application","Path")

  4.RegistryKeys(key,subkeys)

Key String 类型,指定键名
Subkeys 字符串数组变量,用于保存各子键
返回值 函数执行成功时返回1,发生错误时返回-1

  RegistryKeys()的功能是从系统注册登记表中得到指定键的有效子键。

  如下面的代码在系统注册登记表中得到应用程序MyApp的所有子键:

String ls_subkeylist[]

li_return = RegistryKeys("HKEY_LOCAL_MACHINE\

SOFTWARE\PB6\MYAPP",ls_subkeylist)

  5.RegistryValues(key,valuename)

Key String 类型,指定键名
Valuename 字符串数组变量,用于保存各值名
返回值 函数执行成功时返回1,发生错误时返回-1

  RegistryValues()的功能是从系统注册登记表中得与指定键相关的一组值名。如下面的代码得到为用程序MyApp定义的子键Application的值名,并将它保存在动态数组ls_valuearray中:

String ls_valuearray[]

li_return = RegistrySet("HKEY_LOCAL_MACHINE

\SOFTWARE\PB6\MYAPP\Application",ls_valuearray)

  程序实例

  下面以一个简单的实例来说明PB注册函数在应用程序中使用。



使用Application画板,创建一个新库Myapp.pbl和应用程序对象Myapp;

使用Window画板,按图1在窗口w_login上放置一个静态文本st_1,在静态文本的属性页中将其Text属性置为"学年度设定:";放置一个编辑掩码控件em_1,在其Mask属性页中,置Mask属性为"####",选中SpinControl属性,置Min属性为"1999";放置一个组框gb_1,在组框的属性页中将其Text属性置为"考试种类选择:";在组框内放置两个单选钮rb_1和rb_2,在单选钮rb_1的属性页中将其Text属性置为"正考",选中Checked属性,在单选钮rb_2的属性页中将其Text属性置为"补考";放置三个命令按钮cb_save、cb_show和cb_delete,在命令按钮cb_save的属性页中将其Text属性置为"保存",在命令按钮cb_show的属性页中将其Text属性置为"显示",在命令按钮cb_delete的属性页中将其Text属性置为"删除"。

给cb_save命令按钮的Clicked事件输入如下脚本代码:
String ls_Login_key

Integer li_return1,li_return2

ls_Login_key = "HKEY_CURRENT_USER\Software\PB6\MYAPP\Login"

li_return1 = RegistrySet(ls_Login_key,

"Year",RegString!,em_1.text)

IF rb_1.checked THEN

li_return2 = RegistrySet(ls_Login_key,

"Examin",RegString!,rb_1.text)

END IF

IF rb_2.checked THEN

li_return2 = RegistrySet(ls_Login_key,

"Examin",RegString!,rb_2.text)

END IF

IF li_return1 = 1 AND li_return2 = 1 THEN

MessageBox("保存信息显示","Login子键的值已保存!")

ELSE

MessageBox("保存信息显示","Login子键的值未能保存!")

END IF

RETURN

给cb_show命令按钮的Clicked事件输入如下脚本代码:
String ls_Login_key,ls_Year,ls_Examin

ls_Login_key = "HKEY_CURRENT_USER\Software\PB6\MYAPP\Login"

IF RegistryGet(ls_Login_key,

"Year",RegString!,ls_Year) = 1 &

AND RegistryGet(ls_Login_key,

"Examin",RegString!,ls_Examin) = 1 THEN

MessageBox("Login子键信息显示",

" 年度:"+ls_Year+char(13)&

   +"考试种类:"+ls_Examin)

ELSE

MessageBox("Login子键信息显示","获取Login子键信息失败!")

END IF

RETURN


给cb_delete命令按钮的Clicked事件输入如下脚本代码:
String ls_Login_key

ls_Login_key = "HKEY_CURRENT_USER

\Software\PB6\MYAPP\Login"

IF RegistryDelete(ls_Login_key,"") = 1 THEN

MessageBox("删除信息显示","Login子键已删除!")

ELSE

MessageBox("删除信息显示","未能删除Login子键!")

END IF

RETURN

使用Application画板,为Myapp应用对象的Open事件输入如下脚本代码:
Open(w_login)

运行程序,在图1窗口中输入年度1999和选择补考,之后按"保存"按钮,然后在按"显示"按钮,可以看到显示出了正确结果(图2),如果在保存之前或删除之后按"显示"按钮,则出现(图3)结果,说明要找的子键的值不存在。
图2和图3差不多,只是一个保存,一个失败。


当按下"保存"按钮后,点击按钮【开始】→【运行】→键入regedit→【确认】,进入系统注册登记表编辑程序,可以查看到HKEY_CURRENT_USER\Software\PB6\MYAPP\Login子键下的信息如(图4)所示。从上实例可以看出,用PB注册函数访问系统注册表是十分简单容易的。


附件 1: pb119_1.gif (2005-4-17 16:53, 3.2 K,下载次数: 1)


附件 2: pb119_2.gif (2005-4-17 16:53, 1.33 K)


附件 3: pb119_4.gif (2005-4-17 16:53, 7.56 K)



public function boolean wf_appendfromxls (datawindow dw_acton, string str_filename);OLEObject ExcelServer
long excelok
string str_savename
ExcelServer = CREATE OLEObject
ExcelOK = ExcelServer.ConnectToNewObject( "excel.application" )
if excelok < 0 then
messagebox("连接excel失败,检查你的系统是否安装了office",string(excelok))
return false
else
ExcelServer.Workbooks.Open(str_filename)
str_savename="c:\temp.txt"
excelserver.activeworkbook.saveas(str_savename,3)
excelserver.displayalerts=false
ExcelServer.quit()
   ExcelServer.DisconnectObject()
   DESTROY ExcelServer
dw_acton.ImportFile(str_savename)
filedelete(str_savename)
   return true
end if
end function

  
public function boolean wf_appendfromtxt (datawindow dw_acton, string str_filename, string str_filesegment, string str_columnsegment);integer int_filehandle,int_filenum,i,j
string str_text,str_row,str_row_1,str_data[],str_columndatatype
long lng_row,lng_column_count,lng_column_count1,lng_column_count2
int_filenum = FileOpen(str_filename, StreamMode!, Read!, LockRead!)
If int_filenum > 0 Then
   FileRead (int_filenum, str_text)
str_text=trim(str_text)+str_filesegment
do while pos(str_text,str_filesegment) <> 0
  str_row=left(str_text,pos(str_text,str_filesegment) – 1)
  str_text=right(str_text,len(str_text) – pos(str_text,str_filesegment) – 1)
  if trim(str_row)=” then
   fileclose(int_filenum)   
  end if
  str_row_1=trim(str_row)+str_columnsegment
  i=1
  do while pos(str_row_1,str_columnsegment)<>0
   str_data[i]=left(str_row_1,pos(str_row_1,str_columnsegment) – 1)
   str_data[i]=trim(str_data[i])
   str_row_1=right(str_row_1,len(str_row_1) – pos(str_row_1,str_columnsegment))
   i++
  loop
  lng_row=dw_acton.insertrow(0)
  lng_column_count1=long(dw_acton. O B J E C T.datawindow.column.count)  
      lng_column_count2=upperbound(str_data[])
  lng_column_count=min(lng_column_count1, lng_column_count2)
  for j = 1 to lng_column_count
    str_columndatatype=dw_acton.describe("#"+string(j)+".coltype")
    choose case left(str_columndatatype,5)
    case "date"
     dw_acton.setitem(lng_row,j,date(str_data[j]))
    case "datet"
     dw_acton.setitem(lng_row,j,datetime(blob(str_data[j])))
    case "decim"
     dw_acton.setitem(lng_row,j,dec(str_data[j]))
    case "int"
     dw_acton.setitem(lng_row,j,integer(str_data[j]))
    case "long"
     dw_acton.setitem(lng_row,j,long(str_data[j]))
    case "number"
     dw_acton.setitem(lng_row,j,dec(str_data[j]))
    case "time"
     dw_acton.setitem(lng_row,j,time(str_data[j]))     
    case "char("
      dw_acton.setitem(lng_row,j,str_data[j])
    case "times"   
     end choose
  next   
loop
fileclose(int_filenum)
return true
else
messagebox("提示", "打开文件失败")
return false
end If

end function

2005年04月06日


许多情况下,文本文件中的数据排列顺序与数据库表中列的顺序一致,并且其数据类型与数据库表中对应列的一致,要做的就是将该文件中的内容存入对应数据库表中。例如在点对点通讯的情况下,一方把数据库表中的数据以文本文件格式存储并传送给另一方,另一方要做的工作就是将收到的数据存入相同的数据库表中。PowerBuilder有多种方式与文本文件打交道,比如使用ODBC的文本文件驱动器、采用DDE(动态数据交换)方式、采用OLE方式或使用Cursor将文本文件逐行逐列读取并写入数据库表等。但在这种情况下,还有一种更快捷的方法:使用ImportFile()函数将文本文件内容直接倒入数据库表中。该函数用法为: datawindowname.ImportFile(filename{,startrow{,endrow& {,startcolumn{,endcolumn{,dwstartcolumn}}}}}) ImportFile()函数要求数据窗列的数据类型和列的排列顺序必须与文本文件中的数据相匹配。在存储文本文件时要注意接收数据库对表的格式要求,比如在SYBASE中,表格的列与列间是采用Tab键区分的,所以如果要用ImportFile()函数将文件内容写入SYBASE表中,就要将相应文本文件的列与列间加入Tab键——这在文件形成时即可加入。下面的代码将文件infor.txt写入数据窗dw_infor,并将与dw_infor对应的数据库表更新:
int impt
if (FileLength("c:\data\infor.txt")>0) then
impt=dw_infor.ImportFile("c:\data\infor.txt")
if impt>0 then
dw_infor.settrans(sqlca)
update(dw_infor)
commit;
If sqlca.sqlcode=-1 then
messagebox("SQLERROR",sqlca.sqlerrtext)
endif
else
messagebox("注意","文件infor.txt写入失败!")
endif
endif
此外,ImportFile()函数还支持从文本文件的指定开始行列到结束行列读取并写入数据;并且支持.dbf(dBase)文件的读写,这给原有台式数据库数据文件向新的分布式数据库表中转换带来了极大的方便。


依据条件改变数据颜色是许多场合都要用到的重要功能,数据颜色的改变不仅引人注目,而且能起到暗示作用,清楚地告诉用户价位的涨跌或状态的改变等。大多数证券期货实时行情显示软件都提供了这种功能。在当前价位比其前一价位高时,当前价位数据颜色变红,表示价位上涨;反之,颜色变绿,表示价位下跌;若当前价位与其前一价位相等,则数据颜色不变。PowerBuilder没有提供解决这一问题的捷径,但我们仍可利用动态数据窗来实现。先考虑一下实现的步骤,在Retrieve前需要把有关列的数据先保存起来;Retrieve后我们获得了相应列的新数据;我们需要将上述二者作一比较,以确定颜色的变化。值得指出的是,由于动态数据窗函数dwModify()只能用描述数据窗的模式串作参数,不能接收变量作参数,故我们得想法把比较的结果传递给数据窗。为解决这个问题,可以在定义数据窗时多定义几个空列,这几列不与数据库表中的列相对应,它们作为存放比较结果的缓冲区。原则上若需要N列实时地变色,则需要N列缓冲区,就应该多定义N个空列。下面给出了一个例子具体说明。 这段程序写在某窗口的Timer事件中,该窗口内有数据窗dw_infor,其"buy"、"sell"列分别表示买价和卖价,需要实时地变颜色。为此,我们在数据窗dw_infor中多定义了"buybuf"和"sellbuf"两列,分别存放"buy"列和"sell"列Retrieve前后数据比较的结果。
//Red=255;Green=65280
int i,infor_rownum decimalbuy_old[],sell_old[],buy_new[],sell_new[]
dw_infor.SetRedraw(false)
infor_rownum=dw_infor.RowCount()
FOR i=1 To infor_rownum
  buy_old[i]=dw_infor.GetitemNumber(i,"buy")
  sell_old[i]=dw_infor.GetitemNumber(i,"sell")
NEXT
dw_infor.retrieve()
FOR i=1 TO infor_rownum
  buy_new[i]=dw_infor.GetitemNumber(i,"buy")
  sell_new[i]=dw_infor.GetitemNumber(i,"sell")
NEXT
FOR i=1 TO infor_rownum
  dw_infor.Setitem(i,"buybuf",buy_new[i]-buy_old[i])
  dw_infor.Setitem(i,"sellbuf",sell_new[i]-sell_old[i])
NEXT
dw_infor.dwModify("buy.color=’0~tif(buybuf>0,255,if(buybuf<0,65280,0))’")
dw_infor.dwmodify("sell.color="0~tif(sellbuf>0,255,if(sellbuf<0,65280,0))’")
dw_infor.setredraw(true)
我们看到,程序在Retrieve前后分别将"buy"和"sell";列的数据写进与其类型匹配的数组中,然后将比较的结果分别写入"buybuf"和"sellbuf"列,最后用函数dwModify()改变有关列的颜色。记住在该窗口的Open事件中设置事务对象并激活Timer事件。此外,还有一些方法可以改变颜色,比如先在某些需要变颜色的行或列设置带颜色的长方形,同时将其上面的数据窗中的数据设置成透明的,当条件改变时,可以通过改变数据窗后的长方形的颜色来实现。



有时需要限制一个PowerBuilder应用同时运行的实例(Instance)个数或仅让应用运行一次,我们可以通过调用WindowsSDK函数或使用PowerBuilder的Handle()函数来实现。
   先谈调用SDK函数的方法。为了调用SDK函数,需要在ApplicationPainter的菜单项Declare\GlobalExternalFunctions中定义:
   FunctionuintGetModuleHandle(stringModuleName)Library"Kernel.exe" FunctionuintGetModuleUsage(uintModuleHandle)Library"Kernel.exe"

下面这段程序写在Application的Open事件中。它先通过调用SDK函数GetModuleHandle()获得指定应用程序的句柄,然后调用GetModuleUsage()函数确定应用程序同时运行的实例个数。
uint IApplHandle
int App_num
IApplHandle=GetModuleHandle("ocmis.exe")
if IApplHandle>0 then
   App_num=GetModuleUsage(IApplHandle)
   if App_num>1 then
      Messagebox("注意","本程序已经运行!",Stopsign!)
      return
   endif
endif
Open(w_main)
  若需要限制应用同时运行的实例个数,比如仅允许同时运行N个实例,那么将上述程序中的语句 “if App_num>1 then”改为“if App_num>N then”即可。
  采用Handle()函数的方法更简洁一些,代码如下:
int hand
hand=Handle(this,TRUE)
If hand>0 then
   Messagebox("注意","本程序已经运行!",Stopsign!)
   Halt
else
Open(w_main)
endif

2005年03月08日

说起产品UI设计通常的认识就是“图形界面的设计”而产品的交互设计往往被忽略!一个好的交互设计对产品的成功起着很关键的作用。UI所做的就是用户最先接触到的东西,也是一般性的用户唯一接触到的东西。用户对于界面视觉效果和软件操作方式的易用性的关心,要远远大于他对底层到底用什么样的代码去实现的关心。如果说程序是一个人的肌肉和骨骼,那么UI设计就是人的外貌和品格!都是一个成功软件产品必不可少的重要组成部分!对我而言程序懂得不多所以只是从UI设计与软件产品整体的关系和如何才能使软件产品得到最佳的UI设计角度来谈。

  现在我们的软件产品存在的一些问题有技术方面的问题,但是更多的问题来源于各个部门、各个项目小组的之间的配合。我们现有的开发流程一般都是由市场部门提出客户需求,产品设计人员提出产品设计报告,开发部门设计开发计划,由各个小组分别开发一个模块,最后整合成为一个完整的软件产品。在这些流程之间UI设计应该参与那一个部分,每一个部分应该做到什么地步才可以使产品得到最好的UI设计效果呢?下面我们会在每一个部分具体分析。

  首先分析一下现在的问题所在,在一些软件业比较发达的国家软件产品的UI设计过程贯穿了软件开发的自始至终,而且是必不可少的。而在中国产品UI设计并没有被广泛接受,就算是已经有了UI设计师的一些企业也没有对产品的UI有着足够的重视,一般来讲他们大都会把重点放在如何使用代码实现所需要的功能,在我看来这只是一个成功软件产品的一个部分。一个优秀软件产品的开发过程应该是由四个部分组成:
1.软件产品的设计(业务建模)
2.系统的设计(技术建模)
3.分单元的开发(把软件各个部分拆分分单元编写代码)
4.测试(分为单元测试、系统集成测试和产品功能测试),这些是由软件研发部门做的工作。
除去以上软件开发过程的四个部分还有用户需求和用户验收测试,这两个过程是由市场部门和产品用户一起完成。所以说用代码实现产品功能(coding过程)只是软件开发的一个步骤。现在我们回到UI设计的角度来看,作为UI设计人员我们需要全程参与到软件开发过程中,而不只是在某一个步骤参与,现在在大多数软件企业里UI设计师只是在产品的coding过程的时候才实质性的参与到软件开发过程里,而在其它几个步骤里只是参加甚至根本没有参加(在这里我要强调“参与”和“参加”是两个词的不同概念,“参与”指的是完全加入到开发行列开始进入设计阶段,而“参加”指的只是旁听会议或者提出一些简单的意见并没有开始进入设计阶段),这样就会大大降低软件产品的开发效率使开发成本成倍上升甚至导致整个产品的不成功!这并不是危言耸听,下面我们分析一下在一个软件产品的开发过程中UI设计应该怎么做、做到什么地步才能避免上边提到的那些问题?

  下面我会根据软件开发的过程解释上边的问题,刚才我提过软件开发过程的几个步骤,
1.产品建模
2.技术建模
3.分模块开发
4.测试,那么我们也分为这四个部分进行讨论:

一.产品建模时期:

  我们首先来了解一下“输入”和“输出”,在UI设计里是很重要的两个概念,经常会有人过来对我说“我们有一个软件产品需要美化一下”然后再也没有什么深入的解释了,仅仅这句话我的工作就要开始了,然而这个软件是给谁用的?是干什么的?我们却一无所知!成功的UI设计首先要有完整的“输入”,怎么才能叫做完整的“输入”呢?也就需要UI设计师从整个软件产品的策划阶段就开始介入,在产品用户(也就是客户)向市场部门或者产品部门提出产品需求的时候就要开始参与到产品策划开发过程中来,这一部分对于UI设计师而言就是第一个输入阶段,并且在这个阶段里UI设计师也需要提出一些对产品交互设计的意见,以便产品部门在做产品设计的时候更多的考虑到产品的交互性和功能的简单表现原则,有很多软件在设计阶段就被加入了许多并不是用的附加功能,其实一个好的软件设计就是要用最简单的结构实现用户的想法,一些可有可无的功能看上去很花哨往往会影响用户的判断能力,这些就是产品优化的一些概念了在此我需要简单的提一下如果想要深入研究可以看一些有关于产品优化的书籍甚至是心理学的书籍,有很多人认为软件的优化就是代码的优化(用最少的代码实现产品功能),在我看来这只是程序的优化是针对程序员而言的而不是整个软件产品的优化,产品优化包含了交互设计在现在的多数软件企业没有专门做这一部分的交互设计师所以往往这一部分被忽略,我认为这一部分应该又UI设计师承担起来,从文章的开头我就说过UI设计不只是图形界面的设计,就算是有企业里边有这样的优化人员或者交互设计师他们也要和UI设计师一起配合完成产品交互设计,作为UI设计师产品的交互性和易用性是在做设计的时候必须考虑的!
  言归正传,产品设计人员经常不会过多考虑简单易用原理也就是产品出来用什么样的组合形式体现给用户,这也是UI设计师考虑最多的事情,所以UI设计师一定要在产品建模期间参与设计,给产品设计师一些意见。作为一名优秀的UI设计师我们还要在了解了产品的需求之后更深入了解这个产品的使用环境和用户群体的使用习惯。我们还需要了解市场上的同类软件产品的设计方案,研究他们的优缺点,以便在我们设计的时候吸取它们的长处避免它们的错误。在产品建模之后一般的都会由产品设计人员给客户做一次功能设计讲解,往往这样的讲解只是文字性质的需要让客户想象着理解,这就会造成很大的隐患有的客户根本无法理解你的讲解甚至对这样的讲解根本不认真听,因为他们根本不懂,在讨论过程中他们经常会同意产品设计人员的一切设计想法但是产品测试的时候他们又会提出种种不满意,我想这是一般的软件公司都会遇到的也是最最头疼的事情,但这并不能怪客户我说过客户只会关心视觉效果和软件的操作而并不会去关心我们是怎么实现这一切的。这种情况带来的直接后果就是产品的反复修改开发成本成倍上升,怎么避免呢?这就要*UI设计师了,俗话说“眼见为实,耳听为虚”,所以需要UI设计师做出一个产品整体效果的demo。这个demo用图片的形式表现就可以,我们只需要将要体现的产品界面做一个拼凑就可以了,因为这并不是产品的最后样子,只是协助产品设计人员给客户讲解产品设计。产品建模时期UI设计师要了解客户的要求想法和产品设计人员对产品功能的要求深入了解产品,采集用户的使用需求、使用环境和使用习惯,了解市场同类产品的设计分析它们的优缺点。协助产品设计人员完成产品建模过程并制作产品展示demo模拟用户对主要功能的操作过程和界面呈现,生成交互原型(基本上产品的交互性和易用性问题都需要在产品建模的时期解决)。如果时间允许我们甚至可以提出一份“UI设计分析报告”,这份报告可以附在产品设计说明后,更有效的帮助客户了解我们的产品设计并且帮助开发人员更好的遵循UI的整体要求来完成开发工作。这个时期的关键是“交互设计”。

二.技术建模时期:

  在这个时期作为UI设计师我们已经了解了软件产品的功能需求并且拿到了一份产品设计人员的产品设计说明,可以进入界面样式的设计过程了。这个时候我们应该考虑更多的应该是产品的整体风格和界面的设计,通常我们也会做出几份方案给客户选择。有些客户会要求产品遵循一个整体的VI设计标准,那么我们就需要按照一个整体的已定的风格去设计软件的界面,要与客户公司的企业形象吻合。在这个时期软件的UI设计进入到了美术设计阶段,我们需要制定整个软件的风格,塑造软件的整体形象,并且具体的描述每一个界面中的元素和布局、文字字体等信息。在这个阶段我也不应过多的说什么,主要是每个UI设计师各自发挥你们的艺术专长用最简洁、最漂亮的界面表现软件产品。需要注意的就是在我们设计整体风格的时候一定要深入了解这个产品的理念,看看它是干什么用的。不同的产品要有不同的风格,这里边有很多的细节注意,不同的产品、同类的产品不同的内容、不同的传播介质,这些都会决定UI设计的风格。1.不同的产品:比如一个游戏产品就需要将界面做的花哨一些或者用大的图片充斥;如果要是一个应用软件就需要突出使用方便和强大的功能设计要简洁。2.同类不同的内容:比如一个可爱的游戏产品(像是卡通类游戏)就需要将界面做的活泼生动可爱一点;如果是一个角色扮演的战斗类游戏(像是枪战闯关类游戏)就要做的酷一点深沉一些。3.不同的传播介质:我们要做的软件产品有的需要在网络上传播那么就需要我们考虑到网络速度的问题;有的就是利用光盘当作介质那么这样的软件就可以做一些比较花哨的效果。所以说不同的产品还需要单独考虑,这也需要UI设计师多多了解产品,保持与客户交流。还需要重点注意的就是我们在做图形化设计的过程中千万要贯彻在前一个阶段做好的交互设计,始终注意产品的交互性和易用性。在设计过程中我们一定要做出每种结构每一个步骤的效果图,不能只提供图标、按钮、背景图等图片,这样的话程序员根本不知道往那放这些东西,在这个时期我们就要最终确定软件界面的呈现形式。技术建模一般是由高级程序员完成的,他们会将整个软件开发分为一个一个功能模块,分配给一个一个的开发小组。但是这些负责技术建模的高级程序员考虑更多的往往是如何将整个设计用代码实现、怎么才能更有效的复用以前已有的模块等等,而不是软件是什么模样会有什么样的风格,所以作为UI设计师我们必须主动出击,多多和他们交流以保证我们的想法能够完整的实现,如果有技术实现的问题我们还要及时做出修改。有时候我们还需要根据客户或者产品的特定需求做一些延伸性的设计(也叫UI产品设计的外延),包括:软件的安装导航界面、产品的演示宣传动画、一些附带的桌面壁纸或者屏幕保护、代表软件的卡通小精灵、有时还会被要求设计软件的logo和广告banner等等。技术建模时期的关键是“风格和界面设计”。

三.分模块开发时期:

  这个时期软件开发过程进入实现阶段,也是需要人力最多的时期,这样就会分散UI设计师的精力。软件会被切分为若干个小的模块进行代码编写,最后整合成一个完整的软件产品。对于一个程序员来讲他们大多根本不会考虑到产品应该是什么样子应该有什么整体风格,他们所考虑的只是如何用代码实现设计的要求,而且在现在的软件企业多都实现了模块的复用,这样会大大节约人力成本,那么程序员只是对原有模板进行修改使之适应新的软件产品,这样就会对UI设计的最终贯彻和实现带来很大的麻烦。做出的每一个模块虽然已经能够使用但是都是“各自为政”没有统一,因此我们也需要主动的协助和监督程序员完整的实现UI设计的要求,如果有技术无法实现的问题需要及时沟通改正设计方案。有的时候有些模块需要有单独的风格,比如一些已有的软件产品需要集合到某一个新的产品中去,这样就会加大了设计师的设计难度,我们必须要在保证产品整体风格不变的情况下将原有产品的设计风格集合进去,使之更加适合新的产品表现形式。如果我们仍旧保持原有产品的风格那么当各个模块集合起来之后往往会使新的产品感觉很松散,进入每一个功能都会觉得是另外一个软件,使人对软件的印象不深刻。在这个阶段我们还是要主动一些,跟进各个模块界面的实现。现在很多软件企业都存在很多UI设计师和程序员的协作问题,不是程序员做不到UI设计的要求,就是UI设计师坚持一些自己的想法不能改动,还有的时候经常会有人过来没头没尾的说帮我做点东西吧!当软件集成到一起再一看,就是很多不同风格的东西堆砌到一起,从头到尾都不舒服,领导或者客户看了以后极度不满狂批一阵,最后得出结果UI设计做的不到位。有人说UI设计师就要背着软件不成功的黑锅,因为人们根本看不见代码怎么写的,功能是怎么实现的,他们只知道对软件的样子和使用进行评论。让一个用户评论一个软件他们只会说这个软件好用看上去也不错挺漂亮的,但是做为一个普通用户决不会有人说这个软件程序写的不错。这么一看我们会联想到现在软件开发之中主要的冲突在UI设计师和程序员之间,其实这只是表面的表现形式。实质上这个现象体现了现在软件企业的一个通病就是这个开发组之间的协作关系混乱,程序员和UI设计师之间是平级协作关系,程序员是不会对产品负责的,这样看来UI设计师只应该听项目经理的,无论对设计做什么样的改动或者增添什么样的东西,都应该由开发项目经理和产品经理协商之后决定,只有他们可以对最终的产品负责。这样也可以避免很多程序员和UI设计师之间的争执和矛盾。但是现在大多说软件企业的产品经理和开发项目经理没有做到这一点,他们也根本不了解UI设计师和程序员的工作,也无法把握他们的工作量,这样无序的管理会造成很麻烦的后果。其实可以建立一些合理的流程管理制度,就算企业没有作为UI设计师也可以自己起草一份适合自己和企业的“UI设计需求申请单”,里边应该列出我们需要的“输入”内容、工作时间、最终的“输出”结果等等栏目(可以自己根据要求灵活决定)。这样形成一个有参与人、有依据、有存底的工作流程,出现问题或者争执的时候我们有据可依,这只是一个习惯性的东西因不同的企业而议不一定都要建立需求单。在分模块开发时期UI设计师应该做的是,在模块开发的前期做出产品每个模块的效果demo(可以用图片的形式表现)要求程序员按照demo的样式进行模块开发,协助和监督程序员严格按照UI设计要求生成最终产品,把握各个模块的统一,经常了解程序员的工作进展及时对不合理或者难以实现的设计进行讨论设计出新的方案。分模块开发时期的关键是“协助和监督程序员生成最终产品”。

四.测试时期的输入和输出:

  软件产品的测试会分为三个测试阶段,第一个是分模块开发完成之后每一个模块进行的单元测试;第二个是将各个单元集成为一个整体的产品进行集成测试;第三个就是整个产品在交付使用前进行的整体测试。在测试过程中UI设计师的任务相对会轻松一些,我们只需要跟着测试人员走几遍流程,如果在其中发现没有按照UI设计要求的部分及时要求改正就好了。我们还会经常遇到客户在测试过程中突然觉得那里不合适需要修改,这也是最最头疼的事情了,有的时候他们说的并不一定对,只要我们设计的每一个步骤都有一定的道理能够说服他们就一切ok了。如果他们执意要修改设计方案,那么我们没办法只能按照客户需求修改。但是如果前边按照本文的流程走下来我想这样的可能性不大就算是修改也不会是大动干戈。在修改过程中我们还是需要先做出效果图,让客户确定再具体实施,这样也会避免很多麻烦的。测试时期的关键是“检查整个产品发现问题及时改正”。

  如今软件的越来越多的考虑到人的因素,“以人为本”的设计理念贯穿了整个软件产品开发的始终,因此软件产品的UI设计过程最重要的两个部分就是行为和构造,也就是交互设计和界面设计。上面我们按照软件开发的四个阶段,逐个的分析了每个时期UI设计的任务。由此我们可以看出UI设计并不完全是一个美术设计的过程,还有很重要的一个部分就是交互性和易用性的设计。我们要时刻把自己放在软件的用户角度来考虑,设计出最简单易用,界面友好的软件产品。