VFP学习、开发漫谈 (12)
今天与大伙聊一聊有关“表单集”的使用问题。处理表单集要比处理单个表单复杂,并且表单的某些属性,如:WindowType、WindowState等,与单个表单的对应属性不同,所以能用一个表单处理的,就尽可能不用表单集。
比如,我在设计较复杂的查询表单时,通过在表单上添加一个 PageFrame,在 Page1 上设置查询条件,在 Page2 上显示查询结果,这样就可以不使用表单集了。
我在开发应用程序时,经常使用表单集。使用表单集的最大好处是:所有表单共享一个数据环境,数据处理起来比较方便。并且将相关功能的表单归集在一起,修改起来也比较方便。
比如,我要设计一个采购计划表单,如下所示:
该表单集包含2个表单:frmPlan(主表单)和 frmPlanEdit(副表单)。主表单的上半部分显示计划的整体信息,下半部分是一个表格,用于显示计划中的材料明细,这是一个典型的“一对多”表单。在向计划添加材料时,若直接在表格中输入,由于版面所限,很难在表格中设计一个友好的输入界面。若采用表单集,在另一个表单中输入材料(上图中右边窗口),就容易多了。该方法可以连续输入,且输入的每一条记录,都及时显示在主表单中,利于掌握输入进度。
初始化时,仅显示主表单,而隐藏副表单(设置表单的 Visible=.f.)。在关闭主表单时,释放表单集,但在关闭副表单时,应该取消关闭操作,改为隐藏表单。
要实现在关闭主表单时释放表单集,可在主表单的 Destroy 事件中输入:THISFORMSET.Release。
要实现在关闭副表单时不释放表单,而是隐藏表单,可在副表单的 QueryUnload 事件中,输入:
NODEFAULT
THIS.Hide
表单的 QueryUnload 事件比较特殊。当执行命令 THISFORM.Release 时并不激活 QueryUnload 事件,只有在单击窗口右上角的“关闭”按钮时,才激活该事件。在 QueryUnload 事件中输入NODEFAULT 可以阻止窗口被释放。
一般情况下,我们在操作副表单时,不希望用户去操作主表单。有用户可能会说:“这还不简单!将副表单设为模式表单,即:WindowType=1就行了”。如果你这样认为,就错了。表单集中的所有单个表单,都是无模式的,它会忽略 WindowType 设置。表单集也有 WindowType 属性,可将整个表单集设为模式。这样设置后,可以在表单集中的单个表单之间切换,但不可以切换到其他表单。所以,我一般通过设置主表单的 Enabled=.f.,来禁用主表单。当关闭副表单时,再将主表单的 Enabled 设为 .t.
当主表单和副表单同时显示在屏幕上时,调整二者之间的位置很重要。为了防止主表单最大化时,遮挡住副表单,我一般设置副表单的 AlwaysOnTop=.t.,使其显示在顶层,同时将副表单移至屏幕左上角(THISFORMSET.frmPlanEdit.Move(0,0)),主表单居中(AutoCenter=.t.)。如果要将主表单oFrm移至屏幕右下角,可输入:oFrm.Move(_Screen.Width-oFrm.Width-8,_Screen.Height-oFrm.Height-32)。
我们知道,对于单个表单若要接收参数,需将 LPARAMETERS 语句放在表单的 Init 事件中,若表单要返回值,需要设置表单为模式表单,同时在表单的 Unload 事件中返回值。调用表单时,使用“DO FORM 表单名 WITH 参数 TO 变量”命令格式。但使用表单集时,若表单要接收参数,必须将 LPARAMETERS 放在表单集的 Init 事件中。同理,若要返回值,必须将“Return <值>”放在表单集的 Unload 事件中(需设置表单集为模式)。
再看一个问题:保持表单集的 WindowType=0(无模式),设置表单的 WindowState=2(最大化),运行表单,我们发现表单并没有最大化。
现在,我们将表单集的 WindowType 设为 1(模式),再运行一下表单,我们发现表单被最大化了。
结论:当表单集为非模式时,若要将表单最大化,可在表单的 Init 事件中输入“ZOOM WINDOW (THIS.Name) MAX”。
在 VFP下建立一个“窗口”菜单,对于大多数用户来说应该不难。操作步骤:
1. 指定菜单的 Prompt 为“窗口(\<W)”,Result 为“Submenu”,单击“Create”打开建立子菜单窗口
2. 选择菜单“View”-“Menu Option”,在 Name 文本框中输入“_MWINDOW”,单击“OK”按钮
3. 单击“Insert Bar”按钮,选择其中的“Cascade(层叠窗口)”、“Arrange All(平铺窗口)”等,单击“Insert”
上述操作的关键之处是:将菜单的 POPUP 命名为 _MWINDOW,这样,所有打开的窗口以列表的形式自动显示在“窗口”菜单的下方。
采用上述方法建立的窗口菜单有一个缺陷:表单集中被隐藏的表单,也显示在窗口菜单中,这显然不符合我们的要求。如何在窗口菜单中仅显示可见窗口呢?
我的解决方案稍显苯拙,期待高手们能给出更好的解决方法:
1. 利用菜单设计器在主菜单中添加“窗口(\<W)”
2. 将窗口菜单的 POPUP 命名为“_win”
3. 通过“Insert Bar”按钮,为窗口菜单添加 2 个菜单项,一个是“层叠窗口(\<C)”,另一个是“平铺窗口(\<A)”
4. 在过程文件中,添加一个自定义函数 ActivateMenu():
程序代码:
* 功能:激活指定的窗口 * 参数:tcCaption - 窗口标题 FUNCTION ActivateWindow(tcCaption) LOCAL cWName,oForm * 提取窗口标题 tcCaption = SUBSTR(tcCaption,AT(' ',tcCaption)+1) * 找到对应的窗口名称 FOR EACH oForm IN _Screen.Forms IF oForm.BaseClass = 'Form' AND oForm.Visible AND oForm.Enabled AND oForm.Caption == tcCaption cWName = oForm.Name EXIT ENDIF NEXT * 根据名称激活窗口 IF !EMPTY(cWName) ACTIVATE WINDOW (cWName) TOP IF WMINIMUM(cWName) ZOOM WINDOW (cWName) NORM ENDIF ENDIF ENDFUNC
5. 建立一个基于 Custom 的类,命名为:SetMenu
6. 为类新建一个方法:AddMenu,用于添加窗口菜单项,该方法包含的代码如下:
程序代码:
* SetMenu.AddMenu * 添加分隔线 IF CNTBAR('_win') = 2 DEFINE BAR 3 OF _win PROMPT '\-' ENDIF * 标记当前窗口菜单项 LOCAL i,cPrompt,lFlag,nBar FOR i = 4 TO CNTBAR('_win') cPrompt = PRMBAR('_win',i) cPrompt = SUBSTR(cPrompt,AT(' ',cPrompt)+1) IF cPrompt == THISFORM.Caption SET MARK OF BAR i OF _win TO .t. lFlag = .t. ELSE SET MARK OF BAR i OF _win TO .f. ENDIF NEXT * 菜单项不存在时,添加之 IF !lFlag nBar = CNTBAR('_win') + 1 cPrompt = '\<' + TRAN(nBar-3) + ' ' + THISFORM.Caption DEFINE BAR nBar OF _win PROMPT cPrompt ON SELECTION BAR nBar OF _win ActivateWindow(PROMPT()) SET MARK OF BAR nBar OF _win .t. ENDIF
7. 为类再新建一个方法:RemoveMenu,用于移除窗口菜单项,包含的代码如下:
程序代码:
* SetMenu.RemoveMenu * 找到移除位置 nBar LOCAL nCntBar,i,nBar,cPrompt nCntBar = CNTBAR('_win') FOR i = 4 TO nCntBar cPrompt = PRMBAR('_win',i) IF SUBSTR(cPrompt,AT(' ',cPrompt)+1) == THISFORM.Caption nBar = i EXIT ENDIF NEXT * 移除菜单项,并调整其他菜单项之序号 IF !EMPTY(nBar) FOR i = nBar TO nCntBar-1 && 将后面的菜单项序号前移 cPrompt = PRMBAR('_win',i+1) cPrompt = SUBSTR(cPrompt,AT(' ',cPrompt)+1) DEFINE BAR i OF _win PROMPT '\<'+TRAN(i-3)+' '+cPrompt ON SELECTION BAR i OF _win ActivateWindow(PROMPT()) NEXT RELEASE BAR nCntBar OF _win && 移除最后一个菜单项 ENDIF * 移除分隔条 IF CNTBAR('_win') = 3 RELEASE BAR 3 OF _win ENDIF
8. 在类的 Init 事件中,输入以下代码,当表单激活时添加菜单,当隐藏表单时移除菜单:
程序代码:
* SetMenu.Init * 当激活表单时添加窗口菜单项,隐藏表单时移除窗口菜单项 IF TYPE("CNTBAR('_win')") = 'N' BINDEVENT(THISFORM,'Activate',THIS,'AddMenu') BINDEVENT(THISFORM,'Hide',THIS,'RemoveMenu') ENDIF
9. 在类的 Destroy 事件中,输入以下代码,当释放表单时,移除菜单项:
程序代码:
* SetMenu.Destory * 释放菜单项 IF THISFORM.Visible AND TYPE("CNTBAR('_win')") = 'N' THIS.RemoveMenu ENDIF
10.将类添加到所有表单中。在我开发的应用程序中,由于表单均基于自定义类,所以我只需将 SetMenu 类添加到表单类即可。
特别说明:
1.之所以将代码封装在类中,是为了使用方便,无需对表单做任何修改,只需将类添加到表单即可。
2.第4步中之所以对 SCREEN 中 Form 的基类进行判断,是因为工具栏也是窗口,但其基类是 Toolbar 而不是 Form。另外,只激活可见的、没有禁用的窗口。
3.第8步和第9步中,对“_win”菜单是否存在进行了判断,这是为了在开发环境中对表单进行调试时系统不报错。
[ 本帖最后由 liuxingang28 于 2014-3-21 15:19 编辑 ]