CorelDraw插件开发教程(6):循环
Category:COREL插件CorelDraw插件开发教程(6):循环
刘肖健
浙江工业大学工业设计研究院
8. 双循环
有了画一行圆的经验,我们就可以画很多行的圆,把整个页面铺满。下面是界面上增加的部分。
从下往上把整个页面铺满,圆的半径和刚才的算法一样,按页面宽度除以圆的个数来计算,每行圆左右相切,且第一个和最后一个与页面两侧边缘相切。
由于页面高度不一定是圆直径的整数倍,所以页面最上端可能会留出一段空白,而页面下边缘则会与最下一行圆相切。
程序代码如下:
Private Sub DrawCirclePage_Click()
Dim i As Integer, j As Integer, nRows As Integer
Dim s1 As Shape, x As Double, y As Double, r AsDouble
r = 0.5 * ActivePage.SizeWidth / nCircles3.Value ‘圆半径
nRows = Int(ActivePage.SizeHeight / (2 * r)) ‘计算行数
For j = 1 To nRows ‘第二层循环:每次画一行圆
‘圆心y坐标
y = ActivePage.BottomY + (j – 1) * 2 * r + r
‘第一层循环:每次画一个圆
For i = 1 To nCircles3.Value
‘圆心x坐标
x = ActivePage.LeftX + (i – 1) * 2 * r +r
Set s1 = ActiveLayer.CreateEllipse2(x, y, r,r, 90#, 90#, False)
‘填RGB随机色
s1.Fill.UniformColor.RGBAssign (i /nCircles3.Value) * 255, (1 – i / nCircles3.Value) * 255, (1 – i /nCircles3.Value) * 255
Next
‘第一层循环结束
Next
End Sub
上述代码如果是从刚才的代码直接拷贝过来的,记得要把所有的“nCircles2.Value”都改为“nCircles3.Value”。
nRows是一个整数,是圆的行数,同步页面高度除以圆直径再取整获得。函数Int()是对括号中的数值进行取整,即抹掉小数点后的数位,不做四舍五入。带四舍五入的取整函数是CInt()。
圆的色彩每列都是一样的,因为给圆赋色的那句代码里面只有i没有j,就是说,色彩只与圆心x坐标有关,而在y坐标上没有变化。
圆心x坐标是i的函数:
x = ActivePage.LeftX + (i – 1) * 2 * r + r
圆心y坐标是j的函数:
y = ActivePage.BottomY + (j – 1) * 2 * r + r
我们想让色彩在两个方向收都有变化,赋色语句可以改为(注意第二个色彩分量):
s1.Fill.UniformColor.RGBAssign (i / nCircles3.Value)* 255, (j / nRows) * 255, (1 – i /nCircles3.Value) * 255
9. 条件循环:次数不确定的循环
现在来做这样一个任务:画一行顺序相切的圆,撑满页面,圆的半径值在一个范围内变化。
这个循环和刚才的几个程序的不同之处是循环次数不确定,循环终止的条件是到达右页面边界。我们采用Do While语句来实现这个任务。
9.1 Do While条件循环
在页面下端建立一个新的按钮控件,命名为DrawRndRadiumCircles。
代码如下:
Private Sub DrawRndRadiumCircles_Click()
Dim i As Integer, s1 As Shape, x As Double, y As Double,r As Double
Dim w As Double: w = ActivePage.SizeWidth ‘页宽
‘生成第一个圆的半径
r = minR.Value * w + Rnd * (maxR.Value – minR.Value)* w
‘生成第一个圆的圆心坐标
x = ActivePage.LeftX + r: y = ActivePage.BottomY +0.5 * ActivePage.SizeHeight
Set s1 = ActiveLayer.CreateEllipse2(x, y, r, r, 90#,90#, False)
Do While x + r < ActivePage.RightX
x = x + r ‘此时x为上一个圆的右界坐标
‘生成第一个圆的半径
r = minR.Value * w + Rnd * (maxR.Value -minR.Value) * w
‘此时x为新圆的圆心坐标(注意两个r不一样)
x = x + r
Set s1 = ActiveLayer.CreateEllipse2(x, y, r,r, 90#, 90#, False)
Loop
End Sub
最大最小半径用的还是原来面板上的半径上下限两个输入框:
上述代码的运行逻辑可表示为如下图所示:
几点说明:
1)第一个圆单独画,没有放在循环里,是因为需要确定第一个圆的圆心作为后续圆心计算的基准;
2)Do While…Loop的用法:While后面的条件满足时,执行Do While和Loop之间的语句,否则结束跳出;
3)While条件是“x + r < ActivePage.RightX”,即当前圆心的x坐标加上一个半径(圆的右边界x坐标)仍在页面内,未超出右边界;页面右边界为ActivePage.RightX;
4)Do While循环内,当前圆心的x坐标是通过旧圆心x坐标加上上一个圆的半径和本次要画的新圆半径得到,使用了两次x=x+r语句:第一次使用时r表示上一个圆的半径,将其叠加到x上;第二次使用时r已经是新圆半径了,将其再次叠加到x上;注意:VB语言中的等号表示“赋值”而不是相等,x=x+r表示把变量x的值加上r后重新赋给x。
上述代码运行结果:
9.2 用条件判断完善图形绘制
这个结果好像跟我们想的不太一样,因为右侧最后一个圆跑出了页面。这是因为画最后一个圆时并未考虑将其与右边界相切。如果顾及这点,则最后一个圆的半径就不是随机的了,而是根据右边界的位置计算出来的。
因此,要想正确地画出最后一个圆,需要做三件事:
1) 判断什么时候是最后一个圆;
2) 计算该圆的半径和圆心坐标;
3) 画圆。
由于画最后一个圆的流程与其他不一样,所以要在Do While…Loop循环里设置一个If…Then判断机制,如果是最后一个圆,则按上述流程画,否则还是按刚才的流程。
判断最重要。判断是否最后一个圆的条件是:页面右侧的剩余空隙不大于最大圆的直径。也就是说,如果剩余的空隙只放一个圆,则它的半径不会比最大半径maxR.Value大;否则至少还能放两个圆,当前要画的就不是最后一个了。
页面右侧剩余空隙的计算方法:
Dim span As Double ‘页面右侧空隙
span=ActivePage.RightX – (x+r)
其中x+r是当前圆的右边界,x是圆心横坐标,r是半径。
判断语句可以写为:
If span < 2 * maxR.Value * wThen
因此,程序可以改为:
Private Sub DrawRndRadiumCircles_Click()
Dim i As Integer, s1 As Shape, x As Double, y AsDouble, r As Double
Dim w As Double: w = ActivePage.SizeWidth ‘页宽
‘页面右侧空隙
Dim span AsDouble
‘生成第一个圆的半径
r = minR.Value * w + Rnd * (maxR.Value – minR.Value)* w
‘生成第一个圆的圆心坐标
x = ActivePage.LeftX + r: y = ActivePage.BottomY +0.5 * ActivePage.SizeHeight
Set s1 = ActiveLayer.CreateEllipse2(x, y, r, r, 90#,90#, False)
Do While x + r < ActivePage.RightX
‘计算空隙
span =ActivePage.RightX – (x + r)
If span< 2 * maxR.Value * w Then
x = x +r
r =span / 2
x = x +r
Set s1= ActiveLayer.CreateEllipse2(x, y, r, r, 90#, 90#, False)
Exit Do
Else
‘此时x为上一个圆的右界坐标
x = x + r
‘生成第一个圆的半径
r = minR.Value * w + Rnd * (maxR.Value -minR.Value) * w
‘此时x为新圆的圆心坐标(注意两个r不一样)
x = x + r
Set s1 = ActiveLayer.CreateEllipse2(x, y, r,r, 90#, 90#, False)
End If
Loop
End Sub
两点说明:
1)程序在开始部分定义了一个span变量,用于表达空隙,这样就不用每次用到“空隙”这个值都重新计算一遍了,以节省计算时间;
2)最后一个圆做完后,增加了一个Exit Do语句,强迫跳出DoWhile循环,因为已经是最后一个圆了;这句没有也可以,但是加上可以以防万一造成死循环。
程序执行结果:
如果需要画圆结束后统计一下画了多少个圆,可以设置一个计数变量。程序修改如下:
Private Sub DrawRndRadiumCircles_Click()
Dim i As Integer, s1 As Shape, x As Double, y AsDouble, r As Double
Dim w As Double: w = ActivePage.SizeWidth ‘页宽
‘页面右侧空隙
Dim span As Double
‘计数变量
Dim n As Integer
‘生成第一个圆的半径
r = minR.Value * w + Rnd * (maxR.Value – minR.Value)* w
‘生成第一个圆的圆心坐标
x = ActivePage.LeftX + r: y = ActivePage.BottomY + 0.5* ActivePage.SizeHeight
Set s1 = ActiveLayer.CreateEllipse2(x, y, r, r, 90#,90#, False)
n = 1
Do While x + r < ActivePage.RightX
‘计算空隙
span = ActivePage.RightX – (x + r)
If span < 2 * maxR.Value * w Then
x = x + r
r = span / 2
x = x + r
Set s1 = ActiveLayer.CreateEllipse2(x, y, r,r, 90#, 90#, False)
n = n +1
Exit Do
Else
‘此时x为上一个圆的右界坐标
x = x + r
‘生成第一个圆的半径
r = minR.Value * w + Rnd * (maxR.Value -minR.Value) * w
‘此时x为新圆的圆心坐标(注意两个r不一样)
x = x + r
Set s1 = ActiveLayer.CreateEllipse2(x, y, r,r, 90#, 90#, False)
n = n +1
End If
Loop
MsgBox “共计” & n& “个圆。“
End Sub
计数语句出现在三个地方:
1)开始时设置初值n=1,此时已经画好了第一个圆;
2)正常画圆时:n=n+1;
3)画最后一个圆时:n=n+1。
最后会弹出对话框:
Msgbox()函数的详细用法请查阅VBA帮助文档。
上述代码的第三行:
Dim w As Double: w = ActivePage.SizeWidth ‘页宽
VB语言允许在一行里写多个语句,语句之间用英文冒号隔开即可。
修改后的流程图如下:
应该指出的是,最后一个圆的半径最大值虽然保证了在maxR.Value之内,但是不能保证其最小值在minR.Value之上。要实现这点,需要采用一些不同的机制。因为如果最后的空隙小于最小圆直径,则倒数第二个圆就要重新生成了,而且这种情况下两个圆也不能100%保证其大小都在上下限之内。
这道题留着慢慢思考,这里给出一种处理思路:
1)如果空隙span小于2*minR.Value*w,则删掉上一个圆;
2)重新计算span,即:span=span+2*r;
3)判断新span的大小,并作出相应的动作:
4)如果span大于2*minR.Value*w且小于2*maxR.Value*w,则画一个圆,半径r=span/2;
5)如果span大于2*maxR.Value*w,则画两个圆;为了保证两个圆的半径都合法,可以令其中一个圆的半径为最小值r=minR.Value*w,另一个半径则为r=span/2-minR.Value*w。
上述步骤中还有一种情况没提到:有没有可能新的空隙span小于2*minR.Value*w呢?答案是不可能,因为我们删掉的圆的直径已经大于这个值了,这个直径加入新span后,只会令其更大。
写完这个程序,我们大概就能明白了,为什么前面说写代码不需要很高的数学能力,但是需要缜密的逻辑头脑。一个设计师可以大言不惭地承认自己数学差,但是要承认自己逻辑也差就有点难为情了。写代码确实能训练一个人有条有理地思考,和规划各种复杂行为。