| 
				 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; 
由于答题卡设计器中的组件比较多,因此,在文章中不再一一阐述。 			
				 |