分享

Visual Foxpro表格的使用方法与技巧

 踏雪_寻梅 2010-11-29
一、 概述
  网格(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事件代码一样。实现了上述代码后,网格在用鼠标拖动水平卷滚条或单击滚动块时均可以自动拆分。
 
 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多