让 Anchor 属性拥有设计时行为

用过 vfp9 的人都知道这个好用的新属性,宿主控件大小变化时,其内部控件可以用这个属性来自动调整它们的位置和大小,不用再像 vfp6 中要写代码来调整;不过,大家也看到了,这个属性只是一个运行时行为,设计时是不起作用的。

趁着春节休息,想试试是否有办法,能使这个属性在设计时也起作用。这就想起以前有老外写过的一个,设计时可以让内部控件随容器自动调整的示例,网上是找不到了,幸运的是还有咱们万能的百事通 xinjie大笑,要来了这个示例。

研究了半天得出以下结论:

1. 任意控件的某个属性,如果其值是一个表达式(例如:=myfunc() ),vfp 在打开前都会先计算其值;这就给控件设计者一个干涉的机会!(重要:如果这个表达式无法计算(例如:函数不存在或者找不到),vfp 不会报错,仅简单的忽略它),所以,要想达到目的,这个函数必须在 curdir() 指向的路径上,或者 vfp 可以成功调用到这个函数,才会起作用

2. 这个表达式函数可以使用 This 将控件对象传递到表达式指向的函数中,这也是要达到目的的充要条件之一,注意这个 This 指向的对象是在设计时,而非运行时

做以下试验来验证:

1. 新建一个项目
2. 粘贴下列代码并保存为 test.prg

Lparameters toCtrl

m.toCtrl.Caption = Sys(2015)
Return Rgb(255, 0, 0)
3. 再新建一表单,将 BackColor 属性改成 = test(This)

表单背景色和标题是否变了? 

什么! 没变 ???

那就对了,因为没找到 test.prg

先关闭表单 ...

在命令窗口中键入 set deafult to ? 

回车,找到你刚才保存 test.prg 所在的文件夹

重新打开表单

要是你还是没发现有变化,下面的内容可以不用看了 ... 偷笑

如果你幸运的看到类似下面的样子,恭喜你!可以继续


(图一)

这个实验验证了上面的两点:

1. test.prg 必须在当前路径上

2. = test(This) 中的 This 传递给了 test 函数的 toCtrl 参数,它成功的用  toCtrl.Caption = sys(2015) 改变了表单的 Caption


做一个实用的工具

既然可以传入对象,那么就可以获取和设置对象的属性。本文开始时指出,vfp 控件的 anchor 属性仅在运行时才起作用,设计时时这样的:


(图二)


我们把它拖到一个新建的表上,一开始是这样:


(图三)

当我们将这个容器拉大后,仅容器变大了,里面的按钮并未按 anchor 属性设置的值那样自动调整到右下角:


(图四)

我们希望的 anchor 设计时行为,其结果应该是这样:


(图五)


现在,我们来添加一些代码,让它达到我们期望的结果

1. 设计我们的 resizer:新建一个 prg,贴入下列代码,并保存为 resizer.prg

Public goResizer
m.goResizer = NewObject('resizer')

Define Class resizer as Custom
	Dimension aCtrls[1]
	resizing = .f.
	
	Procedure Hook(toCtrl)
		Local cClass, ii
		If This.resizing
			Return
		EndIf
		
		m.cClass = Lower(m.toCtrl.BaseClass)
		If InList(m.cClass, 'container')	&& , pageframe, page, optiongroup, commandgroup, grid
			If Alen(This.aCtrls) == 1
				m.ii = 1
			Else
				m.ii = 1 + Alen(This.aCtrls, 1)
			EndIf
			Dimension This.aCtrls[m.ii, 3]
			This.aCtrls[m.ii, 1] = This.GetFullPath(m.toCtrl)
			This.aCtrls[m.ii, 2] = m.toCtrl.Width
			This.aCtrls[m.ii, 3] = m.toCtrl.Height
			BindEvent(m.toCtrl, 'resize', This, 'resize')
			BindEvent(m.toCtrl, 'destroy', This, 'destroy')
		EndIf
	EndProc
	
	Procedure Destroy
		Dimension This.aCtrls[1]
		This.aCtrls = .f.
	EndProc
	
	Procedure resize
		If This.resizing
			Return
		EndIf
		This.resizing = .t.
		
		Local ii, nw, nh, cc, c1, o1
		Local oo as Container
		Local oForm as Form
		Local array aTemp[1]
		
		AEvents(m.aTemp, 0)
		m.oo = m.aTemp[1]
		m.ii = Ascan(This.aCtrls, This.GetFullPath(m.oo), 1, -1, 1, 1+2+4+8)
		If m.ii > 0
			m.c1 = m.oo.ReadExpression('zzz_hook')
			m.oo.WriteExpression('zzz_hook', '')
			m.nw = m.oo.Width
			m.nh = m.oo.Height
			m.oo.Width  = This.aCtrls[m.ii, 2]
			m.oo.Height = This.aCtrls[m.ii, 3]
			m.cc = Sys(2015)
			m.oo.SaveAsClass(m.cc, m.cc)
			m.oForm = NewObject('Form')
			m.oForm.NewObject(m.cc, m.cc, m.cc)
			With GetPem(m.oForm, m.cc)
				.Move(0, 0, m.nw, m.nh)
				.Visible = .t.
				For each m.o1 in .Controls
					With GetPem(m.oo, m.o1.name)
						.Move(m.o1.Left, m.o1.Top, m.o1.Width, m.o1.Height)
					EndWith
				EndFor
			EndWith
			m.o1 = Null
			m.oForm = Null
			
			This.aCtrls[m.ii, 2] = m.nw
			This.aCtrls[m.ii, 3] = m.nh
			m.oo.Width  = m.nw
			m.oo.Height = m.nh
			m.oo.WriteExpression('zzz_hook', m.c1)
			
			Clear Class (m.cc)
			Erase (m.cc + '.vc?')
		EndIf
		
		This.resizing = .f.
	EndProc
	
	Procedure GetFullPath(toCtrl)
		Local oo, cc
		
		m.oo = m.toCtrl
		m.cc = m.oo.name
		Do while PemStatus(m.oo, 'Parent', 5) and !(m.oo.BaseClass == 'Form')
			Try
				m.oo = m.oo.Parent
				m.cc = m.oo.name + '.' + m.cc
			Catch
			EndTry
		EndDo
		Return Lower(m.cc)
	EndProc
EndDefine

说明一下:

A. 这个 resizer.resize 方法中的变量命名很没规矩,不要学大笑

B. 一开始的试验,验证了属性表达式中调用的函数必须在当前路径下,或者 vfp 可以找到(例如,你可以启动 vfp 后运行一个单独的 prg 来 set default to ...,或用 set proc to ... 或 set path to ... 将这 resizer.prg 放置在可搜索的过程或路径中);我们这里使用另外一种方式,定义一个设计时用的全局变量 goResizer,并让它在 vfp 启动时自动加载到设计环境中,这样就可以不再受 curdir() 的影响,因为它已在内存中了。


(图六)

好了,现在重启 vfp,在 debugger 中应该可以看到已经有一个全局变量 goResizer


(图七)


2. 怎样使用这个 resizer ?

  A. 按图二创建一个容器控件,放两个按钮,下面一个按钮紧靠右下,并设置它的 anchor 为 12

  B. 关键一步:给这个容器添加一个名为 zzz_hook 的设计时钩子属性,属性值为 * =goResizer.hook(This),因为现在我们只希望这个控件在表单设计器上拥有设计时 anchor 行为,而不是在类设计器中,所以在这个控件类的 zzz_hook 属性值前加了一个 * 号注释掉它

(图八)

  C. 将这个容器拖到表单上,见前面的图三,然后去掉 cnt1.zzz_hook 属性值前面的 * 号

  D. 现在再拉动表单上的这个容器调整下大小,看看右下角的按钮是否自动调整位置了? 再改变下这个按钮的 anchor 的属性,看看是否是你预计的结果

  E. 这个 resizer 的 resize 方法,本来几行代码就可以达到目的的,之所以写得又臭又长,其实是为了实现另一个目的:

      你在设计时往这个容器中加几个控件,并分别设置它们的 anchor 属性试试,虽然他们不是 cnt 控件类自带的,但也同样拥有设计时 anchor 行为。


(图九)

(图十)

这个示例仅测试了 vfp anchor 属性实现设计时行为的可能性,应该还有很多未考虑到的方面,resizer.destroy 中就偷懒了,将记录的所有绑定控件的原始属性都清空了,有兴趣的 foxer 可以按这里描述的方法创建自己的 resizer


示例样本:测试前,请注意按本文(图六)设置好开发环境;或者,先手工运行项目中的  resizer.prg

 http://download.csdn.net/detail/dkfdtf/9745745 



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值