颜卓灵 音乐:表格(grid)控件的使用(转载)

来源:百度文库 编辑:九乡新闻网 时间:2024/04/27 19:40:07
表格(grid)控件的使用(转载)(2010-01-16 23:11:06)转载 标签:

电脑

网格

this

箭头键

小数位

杂谈

分类: vf学习

一、 概述
  网格(Grid),是Visual Foxpro 3.0下一个功能极为强大的容器控件。从外观上看,它非常类

似于我们熟悉的Browse窗口,但实际上它提供了比Browse窗口更为丰富的控制方式。如作为

一个容器,它的每一列均可容纳不同的控件,这样就提供了比Browse更方便的输入方式;又如

它能设置列对象的动态字体和颜色,这样就使得每一行、每一列甚至每一单元格都可定制自

己的显示风格,从而提供了比Browse更丰富的显示效果。笔者在对网格控件的使用过程中觉

得,如果恰当地运用网格的属性以及网格控件提供的事件与方法,可以完成一些看起来非常难

以实现的编辑功能。下面从一个具体例子出发,讨论VFP下网格的使用方法与技巧。

  二、输入焦点的自动转移
  为叙述方便起见,首先利用VFP的项目管理器建立一个测试项目,然后以该项目为原型分

析本文中关于网格使用技巧方面的例子。我们将项目命名为Test,然后在该项目下新建一个

数据库,也命名为Test,再在数据库Test建立一个表Test1,该表有两个字段,1)Name:字符型,宽

度为10; 2)Value:数值型,宽度为10,小数位数为1。
  下面先建立Test1表的输入表单Test11,并在表单的"数据环境"中将表Test1添加进去。然

后在表单上放置一网格,并将网格的数据源(RecordSource属性)设为Test1,同时设置网格为两

列,分别绑定Test1表的Name和Value字段。这样我们就可以开始讨论具体问题及其解决方案

了。
  1. 问题的提出
假定Test1表的Name字段的内容为在一般情况下固定不变的一些数据(这种情况还是比较常

见的,如Name字段表示项目指标,Value字段表示对应该指标的数值,Name字段的数据一旦一

次输入后,一般情况下是不用改变的),而需要改变的是对应Name的Value值,为使在网格输入

时Name字段的值不被用户修改,可将网格的第一列Column1的Enabled属性设为.F.,但这样

做以后,问题就出现了。
我们希望用户在Value字段列中的某一行输完数据(即到达字符最大宽度或输入一位小数)后,

或输入数据并按下回车键或TAB键后,焦点能自动转到下一行,以便输入下一个数据。而事实

上很遗憾,焦点根本不动,仍停留在原输入数据上,用户必须按下箭头键才能输入下一行数据

。显然,出现该问题的原因与Column1的Enabled属性为.F.有关,但即使Enabled=.T.(这时需要

设定Column1的ReadOnly属性为.T.),焦点也将移到同一行的第一列,而不会移到下一行的同

一列。事实上,在Foxpro的Browse窗口中,也存在同样的问题。
  2. 解决方法
  解决此问题的关键在于利用网格提供的BeforeRow ColChange或AfterRowColChange事

件。 其中BeforeRow ColChange事件当用户更改活动的行或列,而新单元还未获得焦点时发

生,也可以在表格列中当前对象和数据库中任何规则的 Valid 事件之前发生;

AfterRowColChange事件当用户移到表格的另一行或列时,新单元获得焦点以及新行或列中

对象的 When 事件发生后发生。而在上述问题中,用户输完数据后正好发生这两个事件,因此

解决上述焦点移动问题的关键就在于编写其事件代码,当然选用BeforeRowColChange事件或

AfterRowColChange事件均可,在本例中我们采用了BeforeRowColChange事件。
  在BeforeRowColChange事件代码中,首先判断目前活动列是否为第二列,如果是而且输入

或修改了数据,就使用Keyboard命令模拟按下箭头键,这样焦点就自动转到了下一行。下面是

BeforeRowColChange事件代码:
  LPARAMETERS nColIndex
  IF This.ActiveColumn =2
  IF ThisForm.ModifyData.and.!MDown()
  ThisForm.ModifyData=.F.
  KEYBOARD '{dnarrow}'
  ENDIF
  ENDIF
  这里ModifyData变量是在Form中新增的属性,如果ModifyData属性为.T.,表明输入或修

改了数据,为.F.则相反。可以看出,通过上述代码,我们实现了希望达到的效果,但仍存在一些

问题,下面进一步讨论。
3. 问题讨论
  (1) ModifyData属性的设置
  怎样设置ModifyData属性呢?我们只要在网格Column2列Text1对象(即输入Value字段值

的文本框)的InteractiveChange事件中编写如下代码即可:
  ThisForm.ModifyData=.T.
  (2) 使用箭头键的问题
  从表面上看,似乎实现了上述代码后,就解决了我们提到的问题,事实上也确实解决了,但

却带来了新的问题。在测试中,我们发现,当输入了数据但不按回车或TAB键,而是按上箭头

键(UpArrow)时,焦点不动,按下箭头键(DownArrow)时,焦点向下移动了两行。原因很简单,正

是"KEYBOARD '{dnarrow}'"命令带来的问题,在这两种情况下,是不应该再按DownArrow键

的。要解决此问题,可以在Column2列Text1对象的KeyPress事件中编写如下代码:
  LPARAMETERS nKeyCode, nShiftAltCtrl
  IF nKeyCode=24 OR nKeyCode=5 && Down Arrow Or Up Arrow
  thisform.ModifyData=.F.
  ENDIF
  这里判断如果在数据输入时按了箭头键,则置ModifyData属性为.F.,从而在

BeforeRowColChange事件中不再按DownArrow键,也就解决了上述问题。
  (3) 鼠标移动焦点的问题
  同使用箭头键的问题一样,如果输入了数据但不按回车或TAB键,而是用鼠标将焦点转到

另一行的话,这时焦点会自动多移一行,从而使输入很不方便。解决此问题的第一个想法是仿

上述方法编写Column2列Text1对象的MouseDown事件代码,经过实验发现这种方法是不行的

,因为文本框的MouseDown事件是在网格BeforeRowColChange事件后发生的。但编写网格的

MouseDown事件代码是否可行呢?同样不行,因为此时根本不发生此事件(焦点在文本框上,容

器内的控制响应了此事件而容器本身则不会获得此事件)。经过反复的分析和实验,我们发现

,解决此问题的方法却异乎寻常的简单,正如我们在前面网格BeforeRowColChange事件代码中

所写的一样,使用了MDOWN()函数。如果调用 MDOWN( )时有鼠标键按下,则该函数返回

"真"(.T.)。调用 MDOWN( )时若没有鼠标键按下,则返回"假"(.F.)。由于用户用鼠标改变

输入焦点时正好鼠标键按下,所以MDOWN函数返回.T.,这时将不执行"KEYBOARD

'{dnarrow}'"命令,从而不会出现焦点自动多移一行的问题。
  解决了上述三个问题后,可以说比较完满地达到了我们的要求。但解决该问题,还有其他

一些方案,这些方案是否可行,存在什么问题,我们在下面进一步加以讨论。
  4. 其他问题讨论
  (1) 使用ActivateCell方法
  从原理上讲,不用Keyboard命令,而是用网格的ActivateCell方法也能达到同样的效果。

ActivateCell方法用于激活表格控制中的一个单元。在上述网格的BeforeRowColChange事件

代码中,将"KEYBOARD '{dnarrow}'"改为如下代码:
  =This.ActivateCell(This.ActiveRow+1,This. ActiveColumn)
  也能实现同样的功能,而且更为灵活。我们发现,这样做后,确实能够实现与

KEYBOARD命令同样的效果。但存在一个令人费解的问题,即在网格没有进行垂直卷滚时,

没有任何问题;而在网格垂直卷滚后,这时第一行不再可见,则输完后焦点不动,无法移到下

一行。由于VFP联机文档中没有关于这方面的详细叙述,解决此问题也颇费周折,最后我们

发现,应该使用网格的RelativeRow属性代替ActiveRow属性,即将上述代码改为如下代码:

=This.ActivateCell(This.RelativeRow+1,This. ActiveColumn)即可。 RelativeRow属性指出表格

控制可见部分中的活动行,如果在表格中滚动,使第一行不再可见,而活动行是表格中第

一个可见行,这时RelativeRow 就为1。现在的问题正是滚动时出现的,用RelativeRow正好解

决。这也说明,使用 ActivateCell方法激活的是相对的行或列。
  (2) 使用文本框的LostFocus事件
  与使用网格的BeforeRowColChange事件相比,还有一种更为简单的方法,即使用文本框的

LostFocus事件。去掉网格的BeforeRowColChange事件代码,然后在文本框的LostFocus事件

里编写如下代码:
  LPARAMETERS nColIndex
  IF ThisForm.ModifyData.and.!MDown()
  ThisForm.ModifyData=.F.
  KEYBOARD '{dnarrow}'
  ENDIF
  也能达到同样的效果,而代码更为简洁。
  (3) 关于TAB键与回车键
  在上述例子中,仅当用户修改了数据才自动移动焦点,但有时也需要在按了TAB键或回车

键后就开始移动焦点,而不用等到对数据进行输入或修改。这时可在文本框的KeyPress事件

中加入如下代码:
  IF nKeyCode=9 OR nKeyCode=13 && Tab Or Enter Key
  ThisForm.ModifyData=.T.
  ENDIF
  当然KeyPress的其他代码仍然是必要的。这时ModifyData属性已失去了其本身的含义,

只是作为一个允许焦点转换的标志而已。
  另外,如果不考虑数据输完后(即到达字符最大宽度或输入一位小数)即进行焦点转移,而

只是在按了TAB键或回车键才移动焦点,代码就变得更简单了,这时只需要编写文本框的

KeyPress事件代码即可,其他代码可全部省略。
  IF nKeyCode=9 OR nKeyCode=13 && Tab Or Enter Key
  KEYBOARD '{dnarrow}'
  ENDIF
三、 网格的自动拆分

  在上面实现焦点自动转移的例子中,主要使用的网格事件为BeforeRowColChange(或

AfterRowColChange),另外还提到网格的

ActiveRow,ActiveColumn,RelativeRow,RelativeColumn等属性,这些都是网格编程中常用的事

件与属性,在下面的例子中,我们将看到,这些属性和事件将会得到进一步的应用。
  在本例中,我们仍然利用上面提到Test项目,并在数据库Test再建立一个新表Test2,该表

有六个字段,1)Name:字符型,宽度为10;2)Value1:数值型,宽度为10,小数位数为1;3)Value2:数

值型,宽度为10,小数位数为1;4)Value3:数值型,宽度为10,小数位数为1;5)Value4:数值型,宽度

为10,小数位数为1;6)Value5:数值型,宽度为10,小数位数为1。
  同上例一样我们先建立一个上述Test2表的输入表单Test2,并在表单的"数据环境"中将

表Test2添加进去。然后在表单上放置一网格,并将网格的数据源(RecordSource属性)设为

Test2,同时设置网格为六列,分别绑定Test2表的各个字段。这样我们就可以开始讨论具体问

题及其解决方案了。
  1. 问题的提出
  我们知道,Foxpro的Browse命令有一个参数,即lock。使用lock参数可指定不需滚动就能

在浏览窗口左分区中看见字段数,这样在进行数据浏览时,如果使用browse lock 1命令就可以

保证当发生水平滚动而使第一列不可见时,仍能在浏览窗口的左分区中看到第一列的内容。

事实上,在很多情况下,第一列往往是整条记录的标识或提示信息,用户在通过网格输入带有

很多字段的数据时,总希望即使发生水平滚动后也能看到第一列的提示,这时lock提供的方式

就非常有用。
  我们也希望能在网格中实现这一点,事实上由于网格提供面板属性(Panel),因此实现该

功能是非常容易的,只要在表单设计时就将网格分为两个面板即可。但这里我们想提出一个

更难一些的问题,即希望当网格的右面板的第一列可见时,左面板消失,因为这时并不需要

显示两个第一列;而当网格的右面板的第一列不可见时,左面板出现并显示第一列的内容

。也就是说,实现网格两个面板的自动拆分。
  2. 解决方法
  解决此问题的关键仍然在于利用网格提供的BeforeRowColChange或AfterRowColChange

事件。下面是网格的AfterRowColChange事件代码:
  LPARAMETERS nColIndex
  IF This.RelativeColumn#This.ActiveColumn
  This.Partition=100
  ELSE
  This.Partition=0
  ENDIF
  我们对上述代码的有关问题做一简单分析。
  (1)Partition属性
  网格的Partition属性指定一个表格是否拆分为两个面板,并且指定相对于表格左边的拆

分位置。 当设置Partition属性值为零时,表示不拆分表格;为非零时表示拆分位置的值(实

际上就是左面板的宽度)。在上述代码中,当网格的RelativeColumn属性不等于ActiveColumn

属性时,表示网格进行了水平滚动使第一列不再可见,从而需要进行窗口拆分;否则就不进行

拆分。
  (2)使用BeforeRowColChange还是使用AfterRowColChange事件?
  本例与上例不一样,使用BeforeRowColChange和AfterRowColChange事件的效果是不同

的。因为进行拆分必须当列变化以后再加以判断,因此应该使用AfterRowColChange事件。如

果使用BeforeRowColChange事件,也能进行拆分,但总是漏掉一列,即当网格的第一列第一次

不可见时,不能进行拆分,按右箭头键再向后移动就可以了。
 3. 问题讨论
  实现了AfterRowColChange事件代码后,当移动箭头键时可以进行网格拆分,但仍存在一

个问题,即当用鼠标拖动水平卷滚条时网格仍不能进行拆分。解决此问题的关键在于网格的

Scrolled事件,此事件当单击水平或垂直滚动条,或移动滚动条中的滚动块时发生。下面是该

事件的代码:
  LPARAMETERS nDirection
  IF nDirection>=4
  IF This.RelativeColumn#This.ActiveColumn
  This.Partition=100
  ELSE
  This.Partition=0
  ENDIF
  ENDIF
  这里nDirection>=4表示进行的卷滚是水平方向的(详细情况可见VFP的帮助),下面几行

代码所做的工作与AfterRowColChange事件代码一样。实现了上述代码后,网格在用鼠标拖动

水平卷滚条或单击滚动块时均可以自动拆分。

 
 KEYBOARD 使用
 在键盘缓冲区中放置指定的字符表达式。

直到 Visual FoxPro 查找键盘输入,字符将一直保存在缓冲区中。在查找键盘输入时,这些

字符将被读出并执行,如同直接从键盘输入一样。您可以使用 KEYBOARD 来创建自动运

行的演示系统来演示您的应用程序。
KEYBOARD cKeyboardValue [PLAIN] [CLEAR]
参数
cKeyboardValue
指定放入键盘缓冲区中的字符表达式。字符表达式可以是字符串、键标记、一组键标记、

或返回字符表达式的用户自定义函数。对于键标记列表,请参见 ON KEY LABEL。 注意:
若 cKeyboardValue 是键标记,则必须把它用大括号和单引号或双引号括起来。例如: 

KEYBOARD "{CTRL+LEFTARROW}"
 
若要在键盘缓冲区中插入一个暂停,您可以包含 PAUSE nSeconds 选项作为 cKeyboardValue

语句部分,其中 nSeconds 指定了要暂停的秒数。例如,下行代码在字符"Hello," 和"World"

之间插入一个 5 秒的暂停:
KEYBOARD "Hello,{PAUSE 5} World"