一、引言
本文所讨论的答题卡设计器和其他的图形设计器类似,也是通过选中某个图形元素,在绘画区域根据需要的尺寸和位置画出相应的图形。但和其他的图形设计器不同的是答题卡设计器所提供的图形元素都是答题卡中的基本元素。因此,它是为设计答题卡专门设计的。图1为答题卡设计的主界面。
图1
二、图形元素基类库
从图1可以看出,在设计器中有一个用于选择图形元素的面版,如图2所示。
图2
通过用鼠标选中这些图形元素,就可以画出任意大小的图形来。而这些可视化的图形元素都是通过继承一系列基类实现的。从本质上说,每一个图形元素都是TPanel的子类,这些基类都在ToolLibrary.pas文件中,TMyContainer是这些基类中最顶层的类。它的定义如下: TMyContainer类的定义:
TMyContainer = class(TPanel) // 最上层的基类
protected
MinWidth, MinHeight: Integer;
BlockName: TBlockName;
public
zb_label: TLabel;
public
function GetMinWidth: Integer; // 图形元素的最小宽度
function GetMinHeight: Integer; // 图形元素的最小高度
procedure ShowAllBlock(flag: boolean); virtual; // 显示图形元素中所有的拖动快
procedure SetNewPosition; virtual; // 设置图形元素的位置
procedure ShowBorder(flag: boolean = true); virtual; // 是否显示TPanel的边框
end;
这个类有三个virtual过程,这些过程并未做具体实现,但在TMyContainer的子类中必须实现。TMyContainer还定义了所有图形元素都需要的字段,如图形元素的最小宽度和高度。这些字段在不同的图形元素中的值不同,因此,需要在TMyContainer的子类中为其赋值。
虽然所有的图形元素类都是从TMyContainer中继承的,但是在设计器中还存在另外一类窗口。这类窗口也就是在选中某个图形元素后四周出现的8个用于拉伸图形元素的小方块。如图3所示。
图3
这些窗口的类都从TSizeBlock类继承,这个TSizeBlock类也是从TPanel类继承的。本文在下面的部分将由TSizeBlock的子类所创建的对象称为尺寸块。TSizeBlock的定义如下: TSizeBlock = class(TPanel)
private
procedure CreateShape; // 用于画尺寸块的外边框
procedure SetSizeBlock; // 设置Shape的事件
protected
Shape: TShape;
OldX, OldY: integer;
MoveFlag: boolean;
MoveWinControl: TMyContainer; // 用于保存图形元素对象的字段
protected
procedure SetNewPosition; virtual;
procedure SizeBlockMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer); virtual;
procedure SizeBlockMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer); virtual;
procedure SizeBlockMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer); virtual;
public
Constructor Create(AOwner: TComponent); override;
public
procedure SetMoveWinControl(mw: TWinControl); virtual; // 设置某个移动块所属的图形元素
end;
TSizeBlock和TMyContainer类似,也定义了尺寸块所必须的字段。本文的后面将会讲到,尺寸块分为水平、垂直和四个角的尺寸块。在移动这些尺寸块时都会发生一些动作。因此,在这个尺寸块的父类TSizeBlock中定义了一些用于动作事件的Virtual过程,如SizeBlockMouseUp等。这些过程在子类中必须实现。在这个类中的大多数方法或过程都被实现。下面是TSizeBlock类的实现代码。
constructor TSizeBlock.Create(AOwner: TComponent);
begin
inherited;
width := 8; // 尺寸块的宽度为8
height := 8; // 尺寸块的高度为8
color := clRed; // 颜色为红色
MoveFlag := false;
CreateShape;
SetSizeBlock;
end;
// 建立尺寸块最外面的框
procedure TSizeBlock.CreateShape;
begin
Shape := TShape.Create(nil);
Shape.Parent := self;
Shape.Left := 0;
Shape.Top := 0;
Shape.Width := Width;
Shape.Height := Height;
Shape.Pen.Mode := pmMask;
end;
procedure TSizeBlock.SetMoveWinControl(mw: TWinControl);
begin
MoveWinControl := (mw as TWidgetContainer);
SetNewPosition;
end;
procedure TSizeBlock.SetSizeBlock;
begin
Shape.OnMouseDown := SizeBlockMouseDown;
Shape.OnMouseMove := SizeBlockMouseMove;
Shape.OnMouseUp := SizeBlockMouseUp;
end;
procedure TSizeBlock.SizeBlockMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
OldX := X;
OldY := Y;
MoveFlag := true;
MoveWinControl.ShowAllBlock(false); // 当鼠标按下时,隐藏所有的尺寸块
MoveWinControl.ShowBorder;
MoveWinControl.zb_label.Visible := true;
end;
procedure TSizeBlock.SizeBlockMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
begin
MoveWinControl.zb_label.Caption := 'X:' + inttostr(MoveWinControl.Left) + ' Y:' + inttostr(MoveWinControl.Top);
end;
procedure TSizeBlock.SizeBlockMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
MoveFlag := false;
MoveWinControl.ShowBorder(false); // 当放开鼠标时,显示所有的尺寸块
MoveWinControl.SetNewPosition;
MoveWinControl.ShowAllBlock(true);
CardChanged; // 当答题卡变化时发生(在这个事件里将生成用于预览答题卡的图)
MoveWinControl.Parent.Repaint;
MoveWinControl.zb_label.Visible := false;
end;
上面已经实现了两个位于基类库最上层的类。下面来实现具体的图形元素和尺寸块。对于这个基类库,最重要的就是TWidgetContainers类,所有的答题卡的基本组件都是从这个类继承的,TWidgetContainers类中定义了这些基本组件中需要的所有功能。这个类的定义代码如下: TWidgetContainer = class(TMyContainer)
private
ws: TWidgetStatus;
OldX, OldY: integer;
flag: boolean;
SizeBlocks: array[1..8] of TSizeBlock;
protected
SettingForm: TComponentClass;
bg: TPaintBox;
relative_x, relative_y: integer; //相对的位置, 默认为0
public
MyCanvas: TCanvas; // 用于画当前图形元素的画布
protected
function GetWidgetStatus: TWidgetStatus;
procedure SetWidgetStatus(ws: TWidgetStatus = [wsNone]); // 设置尺寸块的状态(尺寸块是否被显示)
procedure CreateTestControl;
procedure SetEvent;
procedure BringAllToFront; // 将所有的尺寸块放到图形元素前面
procedure Init; virtual;
procedure DrawAll; virtual;
private
procedure SetAllCursor; // 设置在拖动时的鼠标指针
procedure SetAllWidget(ws: TWidgetStatus = [wsNone]);
protected
procedure WidgetContainerDblClick(Sender: TObject); // 图形元素的双击事件
procedure WidgetContainerMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer); // 图形元素的鼠标移动事件
procedure WidgetContainerMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer); virtual; // 图形元素的鼠标按下事件,这个事件需要在子类中实现
procedure WidgetContainerMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer); virtual; // 图形元素的鼠标抬起事件,这个事件需要在子类中实现
procedure BGPaint(Sender: TObject); virtual; // 用于向目标介质(可能是图象,也可以是画布)画这个图形元素
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
public
Constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
public
procedure FreeBlock(); // 释放所有的尺寸块
procedure ShowAllBlock(flag: boolean = true); override; // 显示所有的尺寸块
procedure ShowBorder(flag: boolean = true); override;
procedure SetNewPosition; override;
procedure DrawWidgetToCanvas(Canvas: TCanvas); //将当前图形元素画到画布上
procedure DrawWidget;
procedure Redraw;
procedure Save(fn, name: String); virtual; // 保存当前的图形元素的配置。fn:配置文件名、name:当前图形元素的名子
procedure LoadFromFile(ini: TIniFile; section: string); virtual; // 从文件中装载使用save方法保存的答题卡中的图形元素
procedure Copy(var newwidget: TWidgetContainer); virtual; // 复制当前的图形元素,和clone一样。
public
property WidgetStatus: TWidgetStatus read GetWidgetStatus write SetWidgetStatus default [wsNone];
end;
在这个类中,最核心的过程是DrawAll。这个过程是一个Virtual方法,需要在TWidgetContainer的子类中实现这个方法。这个方法的功能是根据实际情况来画相应的答题卡组件。在移动答题卡组件或改变这些组件的大小时,需要隐藏尺寸块。这一功能将由ShowAllBlock方法实现。这个方法的实现代码如下:
procedure TWidgetContainer.ShowAllBlock(flag: boolean = true);
var
i: integer;
begin
inherited;
for i := 1 to 8 do
begin
if SizeBlocks[i] <> nil then
begin
SizeBlocks[i].Visible := false;
if wsAll in WidgetStatus then
begin
SizeBlocks[i].Visible := flag; // 隐藏或显示尺寸块
end;
end;
end;
end;
TWidgetContainer还提供了保存、装载和复制当前图形元素的功能。这三个功能分别使用Save、LoadFromFile和Copy方法完成。这三个方法的实现代码如下:
procedure TWidgetContainer.Save(fn, name: String);
var
ini: TIniFile;
begin
ini := TIniFile.Create(fn); // 打开fn所指的文件
ini.WriteString(name, 'classname', self.ClassName); // 向文件中写入图形元素的类名
ini.WriteInteger(name, 'width', width); // 写入宽度
ini.WriteInteger(name, 'height', height); // 写入高度
ini.WriteInteger(name, 'top', top); // 写入左上角纵坐标Y
ini.WriteInteger(name, 'left', left); // 写入左上角横坐标X
ini.Free;
end;
procedure TWidgetContainer.LoadFromFile(ini: TIniFile; section: string);
begin
width := ini.ReadInteger(section, 'width', 40); // 读出宽度
height := ini.ReadInteger(section, 'height', 40); // 读出高度
top := ini.ReadInteger(section, 'top', 0); // 读出左上角纵坐标Y
left := ini.ReadInteger(section, 'left', 0); // 读出左上角横坐标X
end;
// 复制当前的图形元素的字段值
procedure TWidgetContainer.Copy(var newwidget: TWidgetContainer);
begin
newwidget := (self.ClassType.NewInstance as TWidgetContainer).Create(nil);
newwidget.Parent := self.Parent;
newwidget.Left := self.Left - 40; // 将新的图形元素的位置和老的图形元素位置错开
newwidget.Top := self.Top - 40;
newwidget.Width := self.Width;
newwidget.Height := self.Height;
newwidget.WidgetStatus := [wsNone];
end;
下面的内容介绍一下各种尺寸块类的实现,先看看水平尺寸块类的实现。水平尺寸块分为左和右两个类。这两个类的定义如下:
//水平方向的拖动块(左侧)
THorizontalLeftSizeBlock = class(TSizeBlock)
protected
procedure SetNewPosition; override;
procedure SizeBlockMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer); override;
public
Constructor Create(AOwner: TComponent); override;
end;
//水平方向的拖动块(右侧)
THorizontalRightSizeBlock = class(TSizeBlock)
protected
procedure SetNewPosition; override;
procedure SizeBlockMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer); override;
public
Constructor Create(AOwner: TComponent); override;
|