【问题标题】:Delphi: Can't Draw On Panel's CanvasDelphi:无法在面板的画布上绘图
【发布时间】:2015-02-13 01:26:14
【问题描述】:

我正在尝试在此面板的过程中在 TPanel 的画布上绘制图像。当我在 Panel 的 Paint Method 中执行此操作时,它工作得很好,但是当我尝试在另一个过程或构造函数中绘制 Canvas 时,什么也没有发生。

这就是我在绘制过程中在面板的画布上绘制的方式:

procedure TFeld.Paint;
var bmp : TBitmap;
begin
  inherited;
  bmp := TBitmap.Create;
  try
    bmp.LoadFromFile('textures\ground.bmp');
    self.Canvas.Draw(0,0,bmp);
  finally
    bmp.Free;
  end;
end;

这就是我尝试在另一个过程中使用它的方式:

procedure TFeld.setUnsichtbar;
var bmp : TBitmap;
begin
  bmp := TBitmap.Create;
  try
    bmp.LoadFromFile('textures\invisible.bmp');
    self.Canvas.Draw(0,0,bmp);
  finally
    bmp.Free;
  end;
end;

但是 Panel 的 Canvas 仍然是我在 Paint 过程中应用的图像。

我已经尝试将绘图从 Paint 过程移到 Constructor 中,但没有成功。

路径也正确,切换路径,现在面板有了“invisible.bmp”图像。

全班/单位:http://pastebin.com/YhhDr1F9

知道为什么这不起作用吗?

【问题讨论】:

  • 因为所有的绘图都需要在OnPaint 中完成,而在其他地方尝试这样做是行不通的,因为当Paint 事件发生时,它会撤消您在窗口或其他地方所做的一切面板上漆。这相当于“医生,我这样做的时候很痛。”医生回答“所以不要再那样做了。”。当您在OnPaint 以外的地方绘制时它不起作用,解决方案是停止在OnPaint 以外的地方绘制
  • 我会从 Petzold 的书开始,了解 InvalidateRect 和 WM_PAINT

标签: image delphi canvas draw


【解决方案1】:

查看您的整个班级,我假设您希望根据特定条件控制在哪个时间显示哪个图像。对吧?

如果是这种情况,您首先需要让您的班级拥有一个用于存储图像数据的字段。在上面的示例中,您仅使用 bmp 文件,因此 TBitmap 就足够了。但是,如果您使用的是其他图片类型,则可能需要使用 TPicture 字段,因为这个字段可以将所有支持的图片图像加载为您也尝试使用组件的 TImage。

然后您更改组件的 Paint 方法以使用上述字段来获取图片数据,而不是像现在这样每次都创建本地图片数据变量。
实际上,您现在所做的事情很糟糕,因为每次渲染组件时,您都在强制应用程序将图像数据从文件读取到内存中。这可能会导致糟糕的性能。

最后,当您想要更改组件上显示的图片时,只需将不同的图片加载到您的图片字段中。

因此,通过上述更改,您的类应该如下所示:

type 
  TFeld=class(TPanel)
  protected
    procedure Paint;override;
    procedure BitmapChange(Sender: TObject);
  private
    zSichtbar : Boolean;
    zPosHor,zPosVer : Integer; // Position im Array
    zEinheit  : TEinheit;
    Bitmap: TBitmap; //Field for storing picture data
  public

//    hImage    : TImage;

    constructor Create(pX,pPosHor,pY,pPosVer,pHoehe,pBreite:integer;pImgPath:String;pForm:Tform); virtual;
    destructor Destroy;
    procedure ChangeImage(pImgPath: String);
  end;

...

implementation

constructor TFeld.Create(pX,pPosHor,pY,pPosVer,pHoehe,pBreite:integer;pImgPath:String;pForm:Tform);
begin
  inherited create(pForm);

  ...

  //Creater internal component for storing image data
  Bitmap := TBitmap.Create;
  //Assign proper method to Bitmaps OnChange event
  Bitmap.OnChange := BitmapChange;
  //Load initial image data
  Bitmap.LoadFromFile(pImgPath);

  ....

end;

destructor Destroy;
begin
  //We need to destroy the internal component for storing image data before 
  //we destory our own component in order to avoid memory leaks
  Bitmap.Free;
end;

procedure TFeld.Paint;
begin
  inherited;
  //Use local image field to tell the Paint method of what to render
  self.Canvas.Draw(0,0,Bitmap);
end;

procedure TFeld.BitmapChange(Sender: TObject);
begin
  //Force redrawing of the component on bitmap change
  self.Invalidate;
end;

procedure TFeld.ChangeImage(pImgPath: String);
begin
  //Load different image into image field
  Bitmap.LoadFromFile(pImgPath);
end;

编辑:添加必要的代码以在位图更改后强制重新绘制组件。

【讨论】:

  • 你的ChangeImage方法在改变图片后应该包含Self.Invalidate,这样Paint就会被调用来反映变化。
  • 正确的方法是为TBitmap.OnChange事件分配一个事件处理程序并让它调用Invalidate()。这说明了位图的任何更改。
  • @RemyLebeau 你也是正确的。我已经相应地编辑了我的答案
  • @KenWhite 你是对的。我完全忘记了这一点。很明显,我更习惯于使用游戏引擎,因为每次都使整个场景无效。
  • 非常感谢,这完美解决了问题,现在一切正常。再次感谢! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-24
  • 1970-01-01
  • 2017-06-30
相关资源
最近更新 更多