gpt4 book ai didi

delphi - 命令历史记录单元的意外行为

转载 作者:行者123 更新时间:2023-12-03 15:22:09 28 4
gpt4 key购买 nike

我编写了这样的模块来存储我的绘图应用程序中图片的最后更改“在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,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,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,0, picHistory.getLast);
end;
{...}

但问题是它的工作方式与我预期的不同。一个例子:

如果我按一次撤消按钮 - 什么也不会发生。两次 - 它立即完成它应该做的事情。

如果我画了一个椭圆,然后单击“撤消”一次并开始绘制新的椭圆 - 最后绘制的椭圆就消失了!

这是椭圆绘制方法,如果它可以帮助找出问题

    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,0, picHistory.getLast); //there gonna be no bizzare traces from figures
imgMain.Canvas.Ellipse(dX, dY, X,Y);
end;
end;

最佳答案

回答

If I push undo button once - nothing happens. On twice - it does what it should at once.

这确实正是您的代码所做的:

  • imgMainMouseUp 中,将当前图片添加到撤消列表中,并且
  • btnUndoClick 中,您可以从撤消列表中检索最后一个位图,该位图与当前在图像上看到的位图相同。

针对这个特定问题的解决方案是将前一个位图添加到撤消列表中,而不是当前位图。

奖金

并发送至地址David's comment关于泄漏,您的实现会泄漏位图,因为:

  • 例程PopgetLast返回一个新本地创建的位图。这将销毁它的责任交给了例程的调用者。您的 MainForm 代码不会破坏这些位图,因此它们是内存泄漏。解决方案是简单地返回数组中的项,而不是创建新的 Bitmap。
  • Offset 例程中,您再次创建新的位图并泄漏所有以前的位图。只需将 Queue[I] 分配给 Queue[I + 1]
  • Push 方法中,您忘记释放最后一项。
  • 该类没有析构函数,这再次将销毁所有位图的责任交给了对象的用户,需要调用Clean,但事实并非如此。解决方案是向您的对象添加一个析构函数,该析构函数调用 Clean

除了这些泄漏之外,您的代码还存在更多问题。这里有一些修复和提示:

  • 由于动态数组是从零开始的,因此您的 isFull 例程在应该返回 True 时却没有返回 True。它应该实现为 Result := HistoryIndex = hSize - 1;
  • 您的数组不是队列 ( FIFO ),而是堆栈 ( LIFO )。
  • 您的 Pop 例程不会检查空列表。

总而言之,您的历史课最好看起来像:

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 代码也存在问题,但我礼貌地将其留给您解决。

关于delphi - 命令历史记录单元的意外行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19547895/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com