delphi – 命令历史记录单元的意外行为
我写了这样的模块来存储我的绘图应用程序中的图片的最后更改“在Delphi中
unit HistoryQueue; interface uses Graphics; type myHistory = class constructor Create(Size:Integer); public procedure Push(Bmp:TBitmap); function Pop():TBitmap; procedure Clean(); procedure Offset(); function isEmpty():boolean; function isFull():boolean; function getLast():TBitmap; protected historyQueueArray: array of TBitmap; historyIndex,hSize:Integer; end; implementation procedure myHistory.Push(Bmp:TBitmap); var tbmp:TBitmap; begin if(not isFull) then begin Inc(historyIndex); historyQueueArray[historyIndex]:=TBitmap.Create; historyQueueArray[historyIndex].Assign(bmp); end else begin Offset(); historyQueueArray[historyIndex]:=TBitmap.Create; historyQueueArray[historyIndex].Assign(bmp); end; end; procedure myHistory.Clean; var i:Integer; begin { for i:=0 to hSize do begin historyQueueArray[i].Free; historyQueueArray[i].Destroy; end; } end; constructor myHistory.Create(Size:Integer); begin hSize:=Size; SetLength(historyQueueArray,hSize); historyIndex:=-1; end; function myHistory.isEmpty: boolean; begin Result:=(historyIndex = -1); end; function myHistory.isFull: boolean; begin Result:=(historyIndex = hSize); end; procedure myHistory.Offset; {to handle overflow} var i:integer; begin //historyQueueArray[0]:=nil; for i:=0 to hSize-1 do begin historyQueueArray[i]:=TBitmap.Create; historyQueueArray[i].Assign(historyQueueArray[i+1]); end; end; function myHistory.Pop: TBitmap; var popBmp:TBitmap; begin popBmp:= TBitmap.Create; popBmp.Assign(historyQueueArray[historyIndex]); Dec(historyIndex); Result:=popBmp; end; function myHistory.getLast: TBitmap; {this function I use when I need refresh the cnvas when I draw ellipse or rect,to get rid of traces and safe previous changes of the picture} var tBmp:TBitmap; begin tBmp:= TBitmap.Create; tBmp.Assign(historyQueueArray[historyIndex]); Result:=tBmp; end; end. 这就是我如何使用它 procedure TMainForm.FormCreate(Sender: TObject); var cleanBmp:TBitmap; begin {...} doneRedo:=false; redomode:=false; undomode:=false; //init arrays picHistory:=myHistory.Create(10); //FOR UNDO tempHistory:=myHistory.Create(10); //FOR REDO cleanbmp:=TBitmap.Create; cleanbmp.Assign(imgMain.Picture.Bitmap); picHistory.Push(cleanbmp); cleanbmp.Free; {...} end; procedure TMainForm.btnUndoClick(Sender: TObject); var redBmp:TBitmap; begin undoMode:=true; //if there were some changes if(not picHistory.isEmpty) then begin redBmp:=TBitmap.Create; redBmp.Assign(picHistory.getLast); //clean canvas imgMain.Picture.Bitmap:=nil; //get what was there before imgMain.Canvas.Draw(0,picHistory.Pop); //and in case if we will not make any changes after UNDO(clicked one or more times) //and call REDO then tempHistory.Push(redBmp);//we save what were on canvas before UNDOand push it to redo history redBmp.Free; end; end; procedure TMainForm.btnRedoClick(Sender: TObject); var undBmp:TBitmap; begin redoMode:=true; if(not tempHistory.isEmpty) then begin doneRedo:=True; undBmp:=TBitmap.Create; undBmp.Assign(tempHistory.getLast); imgMain.Picture.Bitmap:=nil; MainForm.imgMain.Canvas.Draw(0,tempHistory.Pop); //same history (like with UNDO implementation) here but reverse picHistory.Push(undBmp); undBmp.Free; end; end; {...} procedure TMainForm.imgMainMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X,Y: Integer); var bmp:TBitmap; begin //if mouse were down and then it's up this means we drew something //and must save changes into history to be able to make UNDO {...} bmp:=TBitmap.Create; try bmp.Assign(imgMain.Picture.Bitmap); picHistory.Push(bmp); //if there are some changes added after redo then we clean redo history if (doneRedo) then begin tempHistory.Clean; doneRedo:=false; end; finally bmp.Free; //sor of refresh imgMain.Canvas.Draw(0,picHistory.getLast); end; {...} 但问题是它的工作方式与我的预期无关.一个例子: 如果我按下撤销按钮一次 – 没有任何反应.两次 – 它会立刻做到它应该做的事情. 如果我绘制一个椭圆,然后单击撤消一次并开始绘制新的 – 最后绘制的椭圆只是消失! 这是elipse draw方法,如果它有助于找出问题 procedure TMainForm.ellipseDraw(X,Y: Integer); begin imgMain.Canvas.Pen.Color:=useColor; imgMain.Canvas.Brush.Color:=scndColor; imgMain.Canvas.Pen.Width:=size; if(mouseIsDown) then begin imgMain.Canvas.Draw(0,picHistory.getLast); //there gonna be no bizzare traces from figures imgMain.Canvas.Ellipse(dX,dY,X,Y); end; end; 解决方法
回答
这确实是你的代码所做的: >在imgMainMouseUp中,将当前图片添加到撤消列表中,然后 针对此特定问题的解决方案是将先前的位图添加到撤消列表而不是当前位图. 奖金 为了解决有关泄漏的David’s comment,您的实现泄漏了Bitmaps,因为: >例程Pop和getLast返回一个新的本地创建的Bitmap.这将其破坏的责任放在调用者的例程上.您的MainForm代码不会破坏这些位图,因此它们是内存泄漏.解决方案是简单地返回数组中的项,而不是创建新的Bitmap. 除了这些泄漏之外,您的代码还存在更多问题.这里有一些修复和提示: >由于动态数组是从零开始的,因此isFull例程在应该的时候不会返回True.它应该实现为Result:= historyIndex = hSize – 1; 总而言之,您的历史课最好看起来像: uses SysUtils,Graphics; type TBitmapHistory = class(TObject) private FIndex: Integer; FStack: array of TBitmap; procedure Offset; public procedure Clear; function Count: Integer; constructor Create(ACount: Integer); destructor Destroy; override; function Empty: Boolean; function Full: Boolean; function Last: TBitmap; function Pop: TBitmap; procedure Push(ABitmap: TBitmap); end; implementation { TBitmapHistory } procedure TBitmapHistory.Clear; var I: Integer; begin for I := 0 to Count - 1 do FreeAndNil(FStack[I]); FIndex := -1; end; function TBitmapHistory.Count: Integer; begin Result := Length(FStack); end; constructor TBitmapHistory.Create(ACount: Integer); begin inherited Create; SetLength(FStack,ACount); FIndex := -1; end; destructor TBitmapHistory.Destroy; begin Clear; inherited Destroy; end; function TBitmapHistory.Empty: Boolean; begin Result := FIndex = -1; end; function TBitmapHistory.Full: Boolean; begin Result := FIndex = Count - 1; end; function TBitmapHistory.Last: TBitmap; begin if Empty then Result := nil else Result := FStack[FIndex]; end; procedure TBitmapHistory.Offset; begin FStack[0].Free; Move(FStack[1],FStack[0],(Count - 1) * SizeOf(TBitmap)); end; function TBitmapHistory.Pop: TBitmap; begin if not Empty then begin Result := Last; Dec(FIndex); end; end; procedure TBitmapHistory.Push(ABitmap: TBitmap); begin if Full then Offset else Inc(FIndex); FStack[Findex].Free; FStack[FIndex] := TBitmap.Create; FStack[Findex].Assign(ABitmap); end; 备注: >在Contnrs单元中还存在一个专门的类TObjectStack,您可以覆盖/利用它.>您的MainForm代码也存在问题,但我礼貌地将此由您解决. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |