《GOF设计模式》—备忘录(MEMENTO)—Delphi源码示例:图形编辑器

来源:互联网 发布:java上机考试题库 编辑:程序博客网 时间:2024/04/25 22:54

示例:图形编辑器
说明:
考虑一个图形编辑器,它支持图形对象间的连线。用户可用一条直线连接两个矩形,而当用户移动任意一个矩形时,这两个矩形仍能保持连接。在移动过程中,编辑器自动伸展这条直线以保持该连接。
我们可用备忘录(Memento)模式实现移动操作取消。

界面:
 clip_image002
object Form1: TForm1
  Left = 192
  Top = 113
  Width = 400
  Height = 270
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  OnMouseDown = FormMouseDown
  OnMouseUp = FormMouseUp
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 40
    Top = 184
    Width = 75
    Height = 25
    Caption = '创建图形'
    TabOrder = 0
    OnClick = Button1Click
  end
  object Button2: TButton
    Left = 144
    Top = 184
    Width = 75
    Height = 25
    Caption = '取消移动'
    TabOrder = 1
    OnClick = Button2Click
  end
end


代码:
clip_image002[4] 
unit uGraphic;

interface

uses
    Windows,SysUtils,Classes,Graphics,Contnrs;

type
    TGraphic = class;

    {约束信息}
    TConstraintInfo = record
        StartConnection,EndConnection: TGraphic;
        StartPosition,EndPosition: TPoint;
    end;
    PConstraintInfo = ^TConstraintInfo;

    TConstraints = class(TList)
    private
        function GetItems(Index: integer): PConstraintInfo;
    protected
        procedure Notify(Ptr: Pointer; Action: TListNotification); override;
    public
        procedure Add(const AStartConnection,AEndConnection: TGraphic); overload;
        function IndexOf(const AStartConnection,AEndConnection: TGraphic): Integer; overload;
        procedure Assign(const AConstraints: TConstraints);
        //---
        property Items[Index: integer]: PConstraintInfo read GetItems;
    end;

    TGraphic = class
    private
        FCanvas: TCanvas;
        FPosition: TPoint;
        procedure Clear;
    public
        constructor Create(ACanvas: TCanvas; APosition: TPoint);
        //---
        function GetRect: TRect;
        procedure Draw;
        procedure Move(p: TPoint);
        //---
        property Position: TPoint read FPosition;
    end;
    TGraphics = class(TObjectList)
    private
        function GetItems(Index: Integer): TGraphic;
    public
        function GetGraphic(const APosition: TPoint): TGraphic;
        //---
        property Items[Index: Integer]: TGraphic read GetItems;
    end;

    TMemento = class
    end;
    TConstraintSolverMemento = class(TMemento)
    private
        FConstraints: TConstraints;
    public
        constructor Create(AConstraints: TConstraints);
        destructor Destroy; override;
    end;

    {约束解释器,采用单件模式}
    TConstraintSolver = class
    private
        FCanvas: TCanvas;
        FConstraints: TConstraints;
        FIsDraw: boolean;
        procedure DrawLine(pInfo: PConstraintInfo);
        procedure ClearLines;
    public
        constructor Create;
        destructor Destroy; override;
        //---
        class function Instance: TConstraintSolver;
        //---
        procedure Solve();
        //---
        procedure AddConstraint(const AStartConnection,AEndConnection: TGraphic);
        procedure RemoveConctraint(const AStartConnection,AEndConnection: TGraphic);
        //---
        function CreateMemento(): TMemento;
        procedure SetMemento(m: TMemento);
        //---
        property Canvas: TCanvas write FCanvas;
    end;

    {命令,采用命令模式}
    TCommand = class
    public
        procedure Execute(); virtual; abstract;
        procedure Unexecute(); virtual; abstract;
    end;
    TMoveCommand = class(TCommand)
    private
        FTarget: TGraphic;
        FDelta: TPoint;
        FState: TMemento;
    public
        constructor Create(ATarget: TGraphic; ADelta: TPoint);
        destructor Destroy; override;
        //---
        procedure Execute(); override;
        procedure Unexecute(); override;
    end;
    TSelectCommand = class(TCommand)
    private
        FGraphics: TGraphics;
        FCurGraphic: TGraphic;
        FPosition: TPoint;
    public
        constructor Create(AGraphics: TGraphics);
        //---
        procedure Execute; override;
        procedure Unexecute; override;
        //---
        property Position: TPoint write FPosition;
        property CurGraphic: TGraphic read FCurGraphic write FCurGraphic;
    end;

    TGraphicManipulator = class
    private
        FSelectCommand: TSelectCommand;
        FMoveCommand: TMoveCommand;
        procedure ClearMoveCommand;
    public
        constructor Create(AGraphics: TGraphics);
        destructor Destroy; override;
        //---
        procedure MouseDown(X,Y: Integer);
        procedure MouseMove(X,Y: Integer);
        procedure MouseUp(X,Y: Integer);
        //---
        property MoveCommand: TMoveCommand read FMoveCommand;
    end;

implementation

var
    FConstraintSolver: TConstraintSolver;

procedure TGraphic.Draw;
begin
    with FCanvas do
    begin
        with Pen do
        begin
            Color := clYellow;
            Style := psSolid;
            Width := 1;
            Mode := pmXor;
        end;
        //---
        Rectangle(self.GetRect);
    end;
end;

procedure TGraphic.Clear;
begin
    Draw;
end;

constructor TGraphic.Create(ACanvas: TCanvas; APosition: TPoint);
begin
    FCanvas := ACanvas;
    FPosition := APosition;
end;

procedure TGraphic.Move(p: TPoint);
begin
    if (FPosition.X <> 0) or (FPosition.Y <> 0) then
        Clear;
    //---
    with FPosition do
    begin
        X := X + p.X;
        Y := Y + p.Y;
    end;
    //---
    Draw;
end;

constructor TMoveCommand.Create(ATarget: TGraphic; ADelta: TPoint);
begin
    inherited Create;
    //---
    FTarget := ATarget;
    FDelta := ADelta;
    FState := nil;
end;

destructor TMoveCommand.Destroy;
begin
    if FState <> nil then
        FState.Free;
    //---
    inherited;
end;

procedure TMoveCommand.Execute();
var
    ASolver: TConstraintSolver;
begin
    ASolver := TConstraintSolver.Instance;
    //---
    if FState <> nil then
        FState.Free;
    FState := ASolver.CreateMemento();
    //---
    FTarget.Move(FDelta);
    //---
    ASolver.Solve;
end;

procedure TMoveCommand.Unexecute();
var
    ASolver: TConstraintSolver;
begin
    ASolver := TConstraintSolver.Instance;
    //---
    FDelta.x := -FDelta.x;
    FDelta.y := -FDelta.y;
    FTarget.Move(FDelta);
    //---
    ASolver.SetMemento(FState);
    ASolver.Solve;
end;

constructor TConstraintSolverMemento.Create(AConstraints: TConstraints);
begin
    inherited Create;
    //---
    FConstraints := TConstraints.Create;
    FConstraints.Assign(AConstraints);
end;

destructor TConstraintSolverMemento.Destroy;
begin
    FConstraints.Free;
    //---
    inherited;
end;

constructor TConstraintSolver.Create;
begin
    if FConstraintSolver = nil then
    begin
        FConstraintSolver := Self;
        FConstraints := TConstraints.Create;
    end
    else
        abort;
end;

destructor TConstraintSolver.Destroy;
begin
    FConstraintSolver := nil;
    FConstraints.Free;
    //---
    inherited;
end;

procedure TConstraintSolver.Solve();
    //---
    procedure _RefreshPositions;
    var
        i: Integer;
    begin
        with FConstraints do
        begin
            for i := 0 to Count - 1 do
            begin
                with Items[i]^ do
                begin
                    StartPosition := StartConnection.Position;
                    EndPosition := EndConnection.Position;
                end;
            end;
        end;
    end;
    //---
    procedure _DrawLines;
    var
        i: Integer;
    begin
        with FConstraints do
        begin
            for i := 0 to Count - 1 do
                DrawLine(Items[i]);
        end;
        //---
        FIsDraw := True;
    end;
begin
    ClearLines;
    _RefreshPositions;
    _DrawLines;
end;

procedure TConstraintSolver.AddConstraint(const AStartConnection,
    AEndConnection: TGraphic);
begin
    FConstraints.Add(AStartConnection,AEndConnection);
end;

procedure TConstraintSolver.ClearLines;
    //---
    procedure _ClearLine(pInfo: PConstraintInfo);
    begin
        DrawLine(pInfo);
    end;
var
    i: Integer;
begin
    if FIsDraw then
    begin
        with FConstraints do
        begin
            for i := 0 to Count - 1 do
                _ClearLine(Items[i]);
        end;
        //---
        FIsDraw := false;
    end;
end;

procedure TConstraintSolver.RemoveConctraint(const AStartConnection,
    AEndConnection: TGraphic);
var
    AIndex: Integer;
begin
    AIndex := FConstraints.IndexOf(AStartConnection,AEndConnection);
    if AIndex >= 0 then
        FConstraints.Delete(AIndex);
end;

function TConstraintSolver.CreateMemento(): TMemento;
begin
    Result := TConstraintSolverMemento.Create(FConstraints);
end;

procedure TConstraintSolver.DrawLine(pInfo: PConstraintInfo);
begin
    with FCanvas do
    begin
        with Pen do
        begin
            Color := clYellow;
            Style := psSolid;
            Width := 1;
            Mode := pmXor;
        end;
        //---
        with pInfo^ do
        begin
            MoveTo(StartPosition.X,StartPosition.Y);
            LineTo(EndPosition.X,EndPosition.Y);
        end;
    end;
end;

procedure TConstraintSolver.SetMemento(m: TMemento);
begin
    if m is TConstraintSolverMemento then
    begin
        ClearLines;
        self.FConstraints.Assign(TConstraintSolverMemento(m).FConstraints);
    end;
end;

class function TConstraintSolver.Instance: TConstraintSolver;
begin
    if FConstraintSolver = nil then
        FConstraintSolver := TConstraintSolver.Create;
    //---
    Result := FConstraintSolver;
end;

function TConstraints.GetItems(Index: integer): PConstraintInfo;
begin
    Result := Get(Index);
end;

function TConstraints.IndexOf(const AStartConnection,AEndConnection: TGraphic):
    Integer;
var
    i: Integer;
begin
    for i := 0 to Count - 1 do
    begin
        with Items[i]^ do
        begin
            if (StartConnection = AStartConnection) and (EndConnection = AEndConnection) then
            begin
                Result := i;
                Exit;
            end;
        end;
    end;
    //---
    Result := -1;
end;

procedure TConstraints.Assign(const AConstraints: TConstraints);
var
    i: Integer;
    pInfo: PConstraintInfo;
begin
    Self.Clear;
    //---
    with AConstraints do
    begin
        for i := 0 to Count - 1 do
        begin
            New(pInfo);
            pInfo^ := Items[i]^;
            self.Add(pInfo);
        end;
    end;
end;

procedure TConstraints.Notify(Ptr: Pointer; Action: TListNotification);
begin
    if Action = lnDeleted then
        Dispose(Ptr);
end;

function TGraphic.GetRect: TRect;
begin
    with FPosition do
        Result := Rect(X - 10,Y - 10,X + 10,Y + 10);
end;

function TGraphics.GetGraphic(const APosition: TPoint): TGraphic;
var
    i: integer;
begin
    for i := 0 to self.Count - 1 do
    begin
        if PtInRect(self.Items[i].GetRect,APosition) then
        begin
            Result := self.Items[i];
            Exit;
        end;
    end;
    //---
    Result := nil;
end;

function TGraphics.GetItems(Index: Integer): TGraphic;
begin
    Result := TGraphic(inherited Items[Index]);
end;

procedure TConstraints.Add(const AStartConnection,
    AEndConnection: TGraphic);
var
    pInfo: PConstraintInfo;
begin
    New(pInfo);
    with pInfo^ do
    begin
        StartConnection := AStartConnection;
        EndConnection := AEndConnection;
        StartPosition := Point(0,0);
        EndPosition := Point(0,0);
    end;
    //---
    self.Add(pInfo);
end;

constructor TSelectCommand.Create(AGraphics: TGraphics);
begin
    inherited Create;
    //---
    FGraphics := AGraphics;
end;

procedure TSelectCommand.Execute;
begin
    FCurGraphic := FGraphics.GetGraphic(FPosition)
end;

procedure TSelectCommand.Unexecute;
begin
end;

{ TGraphicManipulator }

procedure TGraphicManipulator.ClearMoveCommand;
begin
    if FMoveCommand <> nil then
        FMoveCommand.Free;
    FMoveCommand := nil;
end;

constructor TGraphicManipulator.Create(AGraphics: TGraphics);
begin
    FSelectCommand := TSelectCommand.Create(AGraphics);
    FMoveCommand := nil;
end;

destructor TGraphicManipulator.Destroy;
begin
    FSelectCommand.Free;
    ClearMoveCommand;
    //---
    inherited;
end;

procedure TGraphicManipulator.MouseDown(X,Y: Integer);
begin
    with FSelectCommand do
    begin
        Position := Point(X,Y);
        Execute;
    end;
end;

procedure TGraphicManipulator.MouseMove(X,Y: Integer);
begin

end;

procedure TGraphicManipulator.MouseUp(X,Y: Integer);
    //---
    procedure _HandleMoveCommand(ATarget: TGraphic);
    begin
        ClearMoveCommand;
        //---
        FMoveCommand := TMoveCommand.Create(ATarget,Point(X - ATarget.Position.X,Y - ATarget.Position.Y));
        FMoveCommand.Execute;
    end;
begin
    with FSelectCommand do
    begin
        if CurGraphic <> nil then
        begin
            with CurGraphic do
            begin
                if (Position.X <> X) or (Position.Y <> Y) then
                    _HandleMoveCommand(CurGraphic);
            end;
            //---
            CurGraphic := nil;
        end;
    end;
end;

initialization
    FConstraintSolver := nil;

finalization
    if FConstraintSolver <> nil then
        FConstraintSolver.Free;

end.

unit Unit1;

interface

uses
    Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls,Forms,
    Dialogs,uGraphic,StdCtrls;

type
    TForm1 = class(TForm)
        Button1: TButton;
        Button2: TButton;
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift:
            TShiftState; X,Y: Integer);
        procedure FormMouseUp(Sender: TObject; Button: TMouseButton; Shift:
            TShiftState; X,Y: Integer);
    private
        FGraphics: TGraphics;
        FGraphicManipulator: TGraphicManipulator;
    public
    { Public declarations }
    end;

var
    Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
    FGraphics := TGraphics.Create;
    FGraphicManipulator := TGraphicManipulator.Create(FGraphics);
    TConstraintSolver.Instance.Canvas := self.Canvas;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
    FGraphics.Free;
    FGraphicManipulator.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
    //---
    function _CreateGraphic(APosition: TPoint): TGraphic;
    begin
        Result := TGraphic.Create(self.Canvas,APosition);
        Result.Draw;
        FGraphics.Add(Result);
    end;
var
    AStartConnection,AEndConnection: TGraphic;
begin
    AStartConnection := _CreateGraphic(point(20,20));
    AEndConnection := _CreateGraphic(point(20,100));
    with TConstraintSolver.Instance do
    begin
        AddConstraint(AStartConnection,AEndConnection);
        Solve;
    end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
    with FGraphicManipulator do
    begin
        if MoveCommand <> nil then
            MoveCommand.Unexecute;
    end;
end;

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift:
    TShiftState; X,Y: Integer);
begin
    FGraphicManipulator.MouseDown(X,Y);
end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift:
    TShiftState; X,Y: Integer);
begin
    FGraphicManipulator.MouseUp(X,Y);
end;

end.