end;
可以看到,THorizontalLeftSizeBlock和THorizontalRightSizeBlock都是从TSizeBlock继承的,并分别实现了SetNewPosition和SizeBlockMouseMove方法。这两个方法的实现代码如下:
// THorizontalLeftSizeBlock类的实现代码
procedure THorizontalLeftSizeBlock.SetNewPosition;
begin
// 设置左尺寸块在纵向的中间位置
Top := MoveWinControl.Top + MoveWinControl.Height div 2 - Height div 2;
Left := MoveWinControl.Left - Width div 2; // 设置左尺寸块在横向的中间位置
end;
procedure THorizontalLeftSizeBlock.SizeBlockMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
begin
inherited;
if MoveFlag = false then exit;
if MoveWinControl.Width + OldX - X <= MoveWinControl.MinWidth then
begin
exit;
end;
MoveWinControl.BlockName := [bnLeft]; // 设置当前移动的是左尺寸块
MoveWinControl.Width := MoveWinControl.Width + OldX - X;
self.Left := self.Left + X - OldX;
MoveWinControl.Left := MoveWinControl.Left + X - OldX; // 向左移动相应的位置
end; // THorizontalRightSizeBlock类的实现代码
procedure THorizontalRightSizeBlock.SetNewPosition;
begin
// 设置右尺寸块在纵向的中间位置
Top := MoveWinControl.Top + MoveWinControl.Height div 2 - Height div 2;
// 设置右尺寸块在横向的中间位置
Left := MoveWinControl.Left + MoveWinControl.Width - Width div 2;
end;
procedure THorizontalRightSizeBlock.SizeBlockMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
begin
inherited;
if MoveFlag = false then exit;
if MoveWinControl.Width + X - OldX <= MoveWinControl.MinWidth then
begin
exit;
end;
MoveWinControl.BlockName := [bnRight]; // 设置当前移动的是右尺寸块
MoveWinControl.Width := MoveWinControl.Width + X - OldX;
self.Left := self.Left + X - OldX; // 向右移动相应的位置
end;
从上面的代码中可以看到,在.SizeBlockMouseMove过程中根据尺寸块的类型来设置图形元素的相应位置,其他移动块的实现和这两个类的实现类似。
三、答题卡组件类
前面介绍了如何实现答题卡组件类的基类,现在将讨论如何实现具体的组件。首先介绍一个最重要的组件类:准考证类。有很多读者都参与过标准化的考试,在这种考试中所使用的答题卡都会有一个填写准考证的地方。这个部分可以用笔填写阿拉伯数字的准考证号,在下面是0 至 9的数字。考生需要根据准考证号涂相应的数字。准考证组件如图3所示。由于不管是准考证,还是答题区,都有涂数字或字母的区域(它们的区别只是数字的多少不同,如答题区一般是A、B、C、D四个区域,而准考证号是0至9,10个数字。因此,可以将这个特性提炼出来形成一个类:TInfoCollection。这个类的定义如下:
//信息采集区
TInfoCollection = class(TWidgetContainer)
private
movement: integer;
public
Color_Border: TColor; // 涂点边框的颜色
Color_Text: TColor; // 涂点文字的颜色
Width_SmallRegion, Height_SmallRegion: integer;
GridToRegionX, GridToRegionY: integer;
GridHeight: integer;
TextSize: integer; // 涂点文字的大小
VerticalSpace: integer;
HorizontalSpace: integer;
title: string;
Cols: integer; // 每个涂点区的列数
Rows: integer;
Left_SmallRegion, Top_SmallRegion: integer;
TextList: array of String;
TextStyle: integer; // 涂点的类型。0: A-D 1: 0-9 2: 0-9、X
ShowGrid: Boolean; // 是否显示网格
GridLineWidth: integer; // 网络线宽度
private
procedure Init; override;
procedure SetAttr; virtual;
//画信息采集小方框
procedure DrawSmallRegion(left, top: integer; text: String); // 画每一个涂点
procedure DrawCol(left, top: integer); // 画一列涂点
procedure DrawGrid(); // 使用多列涂点组成一个网络
protected
procedure DrawAll; override; //画信息采集区
public
// 覆盖TWidgetContainer类的保存、装载和复制当前图形元素的方法
procedure Save(fn, name: String); override;
procedure LoadFromFile(ini: TIniFile; section: string); override;
procedure Copy(var newwidget: TWidgetContainer); override;
end;
在TInfoCollection类中覆盖了TWidgetContainer类的DrawAll过程,用于按行和列画涂点。DrawAll过程的实现代码如下:
procedure TInfoCollection.DrawAll;
var
i: integer;
c: integer;
rect: TRect;
begin
inherited;
SetAttr; // 设置当前答题卡组件的属性
// 开始化涂点外围的边框
for i := 1 to Cols do
begin
// 设置每一列的坐标
rect.Left := (i - 1) *(Width_SmallRegion) - HorizontalSpace;
rect.Top := Top_SmallRegion;
rect.Right := rect.Left + Width_SmallRegion + 2 * HorizontalSpace;
rect.Bottom := rect.Top + Rows * Height_SmallRegion;
if i mod 2 = 1 then // 根据奇偶列来确定绘制颜色
MyCanvas.Brush.Color := rgb(255, 200, 200)
else
MyCanvas.Brush.Color := clWhite;
DrawCol(Left_SmallRegion + (i - 1) *(Width_SmallRegion + HorizontalSpace), Top_SmallRegion);
end;
DrawGrid; // 画当前涂点区的所有的涂点
end;
在DrawAll过程中调用了DrawGrid和DrawCol过程,用来画所有的涂点。实质上,还有一个最核心的过程:DrawSmallRegion,这个过程来完成一个基本涂点的绘制工作。这个方法的实现代码如下:
procedure TInfoCollection.DrawSmallRegion(left, top: integer; text: String);
var
new_left, new_top: integer;
text_left: integer;
textrect: TRect;
textrect1: TRect;
abc: integer;
begin
// 设置画笔的宽度
MyCanvas.Pen.Width := GridLineWidth;
// 设置每个涂点的相应坐标
new_left := relative_x + left;
new_top := relative_y + top;
textrect.Left := new_left;
textrect.Top := new_top;
textrect.Right := new_left + Width_SmallRegion;
textrect.Bottom := new_top + Height_SmallRegion;
abc := Height_smallregion * 2 div 3;
textrect1.Left := new_left;
textrect1.Top := new_top - 2;
textrect1.Bottom := textrect.Bottom + 2;
textrect1.Right := textrect.Right;
MyCanvas.FillRect(textrect1); // 为当前涂点设置相应的背景色
// 开始画当前的涂点
MyCanvas.MoveTo(textrect.Left, textrect.Top);
MyCanvas.LineTo(textrect.left, textrect.bottom);
MyCanvas.MoveTo(textrect.Left, textrect.Top);
MyCanvas.LineTo(textrect.Left + abc, textrect.Top);
MyCanvas.MoveTo(textrect.Left, textrect.bottom);
MyCanvas.LineTo(textrect.Left + abc, textrect.bottom);
MyCanvas.MoveTo(textrect.right, textrect.Top);
MyCanvas.LineTo(textrect.right, textrect.bottom);
MyCanvas.MoveTo(textrect.right, textrect.Top);
MyCanvas.LineTo(textrect.right - abc, textrect.Top);
MyCanvas.MoveTo(textrect.right, textrect.bottom);
MyCanvas.LineTo(textrect.right - abc, textrect.bottom);
text_left := new_left + (Width_SmallRegion - MyCanvas.TextWidth(text)) div 2;
MyCanvas.Font.Color := Color_Border;
textrect.Top := new_top + 1;
MyCanvas.Font.Charset := GB2312_CHARSET;
// 向每个涂点中写相应的字符,如0 - 9,或A-D
if TextSize > 12 then
MyCanvas.TextOut(text_left , new_top - 2, text)
else
MyCanvas.TextOut(text_left + 1 , new_top - 1 , text);
end;
在实现完TInfoCollection后,就可以实现准考证类了,这个类的类名为TZKZ。这个类的定义如下:
//准考证号
TZKZ = class(TInfoCollection)
private
Top_Title: integer;
private
procedure Init; override;
procedure SetAttr; override;
procedure DrawTitle; // 输出准考证区域上方的文本,默认为准考证号
protected
procedure DrawAll; override; // 除了继承TInfoCollection中的DrawAll功能外,还负责画Title
public
procedure Save(fn, name: String); override; // 保存当前准考证区域
end;
在TZKZ类中的核心过程是DrawTitle,这个过程负责画涂点上方的网络线和文本。DrawTitle过程的实现代码如下: procedure TZKZ.DrawTitle;
var
i: integer;
textrect: TRect;
width: integer;
title_height: integer;
begin
// 设置上方文本的颜色、尺寸、网络线的颜色、尺寸等信息
MyCanvas.Font.Color := clBlack;
MyCanvas.Font.Size := TextSize + 2;
MyCanvas.Pen.Color := Color_Border;
MyCanvas.Pen.Width := GridLineWidth;
MyCanvas.Brush.Color := clWhite;
width := Cols *(Width_SmallRegion + HorizontalSpace);
title_height := 2 * Height_SmallRegion;
textrect.Top := relative_y + Top_Title + title_height;
textrect.Bottom := textrect.Top + Top_SmallRegion - GridToRegionY;
// 画纵向的网格线
for i := 0 to Cols do
begin
textrect.Left := relative_x + i *(Width_SmallRegion + HorizontalSpace);
textrect.Right := Width_SmallRegion + HorizontalSpace + textrect.Left;
MyCanvas.MoveTo(textrect.Left, textrect.Top);
MyCanvas.LineTo(textrect.Left, textrect.Bottom);
end;
// 画横向的网格线
MyCanvas.MoveTo(relative_x, textrect.Top);
MyCanvas.LineTo(relative_x + width, textrect.Top);
MyCanvas.MoveTo(relative_x, relative_y);
MyCanvas.LineTo(relative_x, textrect.Top);
MyCanvas.MoveTo(relative_x + width, relative_y);
MyCanvas.LineTo(relative_x + width, textrect.Top);
MyCanvas.MoveTo(relative_x, relative_y);
MyCanvas.LineTo(relative_x + width, relative_y);
textrect.Top := relative_y + (title_height - MyCanvas.TextHeight(title)) div 2;
textrect.left := relative_x;
textrect.Right := relative_x + width;
textrect.Bottom := relative_y + title_height;
// 画上方的文本
DrawText(MyCanvas.Handle, PChar(title), Length(title), textrect, DT_CENTER);
end;
除了准考证组件,还有一个答题区组件,这个答题区组件的类名为TQuestionRegion。实现方法和TZKZ类似。 要想让读卡机或其他读卡设备识别答题卡,需要使用某种机制来定位每一个涂点。本文使用的是一种比较简单的方法:同步头。所谓同步头,就是在答题卡左侧或其他侧面的等距黑色小块,如图4显示了一个横向的同步头。
图4
实现横向同步头的类为TTBT_Horizontal,这个类继承于一同步头的基类TTBT。这两个类的定义如下:
TTBT类的定义:
//同步头蕨类
TTBT = class(TWidgetContainer)
protected
TBT_Count: integer;
public
TBT_Height: integer;
TBT_Vertical_Space: integer;
TBT_Number: integer;
private
procedure Init; override;
public
procedure Save(fn, name: String); override; // 保存当前同步头
procedure LoadFromFile(ini: TIniFile; section: string); override; // 装载被保存的同步头
end;
TTBT_Horizontal类的定义:
TTBT_Horizontal = class(TTBT) // 横向同步头
private
procedure DrawSingleTBT(left: integer); //画一个同步头
protected
procedure DrawAll; override; //画所有的同步头
public
procedure Save(fn, name: String); override;
end;
在TTBT_Horizontal类中,核心的过程为DrawSingleTBT和DrawAll,这两个过程分别用于画单独的小黑块和整个同步头。这两个过程的实现代码如下: procedure TTBT_Horizontal.DrawSingleTBT(left: integer);
var
r: TRect;
begin
inherited;
r.Left := left + relative_x;
r.Top := relative_y;
r.Right := left + TBT_Height + relative_x;
r.Bottom := bg.Height + relative_y;
MyCanvas.Brush.Color := clBlack; // 设置同步头颜色为黑色
MyCanvas.Pen.Color := clBlack; // 设置同步头边框颜色为黑色
MyCanvas.Rectangle(r); // 画黑色框
MyCanvas.FillRect(r); // 用黑色填充同步头
end;
procedure TTBT_Horizontal.DrawAll;
var
n: integer; //同步头数
h: integer;
begin
n := 0;
h := TBT_Height;
// 根据同步头的长度计算同步头数,并使用DrawSingleTBT过程画每一个同步头
while h < bg.Width do
begin
inc(n);
DrawSingleTBT(h - TBT_Height);
h := h + TBT_Vertical_Space + TBT_Height;
end;
TBT_Count := n; // 记录同步头数
end;
由于答题卡设计器中的组件比较多,因此,在文章中不再一一阐述。
|