4

Is there a way to place an image in the form background and be able to tile it or center it ?

Also I need to place other components on top of the image.

I tried rmControls but I cannot place anything on top of the image.

Jlouro
  • 4,515
  • 9
  • 60
  • 91

2 Answers2

9

You can paint your image in an OnPaint handler for the form. Here's a simple example of tiling:

procedure TMyForm.FormPaint(Sender: TObject);
var
  Bitmap: TBitmap;
  Left, Top: Integer;
begin
  Bitmap := TBitmap.Create;
  Try
    Bitmap.LoadFromFile('C:\desktop\bitmap.bmp');
    Left := 0;
    while Left<Width do begin
      Top := 0;
      while Top<Height do begin
        Canvas.Draw(Left, Top, Bitmap);
        inc(Top, Bitmap.Height);
      end;
      inc(Left, Bitmap.Width);
    end;
  Finally
    Bitmap.Free;
  End;
end;

In real code you would want to cache the bitmap rather than load it every time. I'm sure you can work out how to adapt this to centre a bitmap.

The output looks like this:

enter image description here

However, since this is the background to the form, it's much better to do the painting in a handler for WM_ERASEBACKGROUND. That will also make sure that you won't have any flickering when you resize. Here's a more advanced version of the program that demonstrates this, together with a stretch draw option.

procedure TMyForm.FormCreate(Sender: TObject);
begin
  FBitmap := TBitmap.Create;
  FBitmap.LoadFromFile('C:\desktop\bitmap.bmp');
end;

procedure TMyForm.RadioGroup1Click(Sender: TObject);
begin
  Invalidate;
end;

procedure TMyForm.FormResize(Sender: TObject);
begin
  //needed for stretch drawing
  Invalidate;
end;

procedure TMyForm.PaintTile(Canvas: TCanvas);
var
  Left, Top: Integer;
begin
  Left := 0;
  while Left<Width do begin
    Top := 0;
    while Top<Height do begin
      Canvas.Draw(Left, Top, FBitmap);
      inc(Top, FBitmap.Height);
    end;
    inc(Left, FBitmap.Width);
  end;
end;

procedure TMyForm.PaintStretch(Canvas: TCanvas);
begin
  Canvas.StretchDraw(ClientRect, FBitmap);
end;

procedure TMyForm.WMEraseBkgnd(var Message: TWmEraseBkgnd);
var
  Canvas: TCanvas;
begin
  Canvas := TCanvas.Create;
  Try
    Canvas.Handle := Message.DC;
    case RadioGroup1.ItemIndex of
    0:
      PaintTile(Canvas);
    1:
      PaintStretch(Canvas);
    end;
  Finally
    Canvas.Free;
  End;
  Message.Result := 1;
end;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • No problem in the center bit. But how do I alternate between those without closing the application? – Jlouro Feb 28 '13 at 11:37
  • When you switch from one to the other, call `MyForm.Invalidate` to force a paint cycle. – David Heffernan Feb 28 '13 at 11:38
  • @David. Having problem to put imagem in center of the form and strech it. It stays in the top and small. – DRokie Feb 28 '13 at 11:56
  • @DRokie Use `StretchDraw`, like this: `Canvas.StretchDraw(ClientRect, Bitmap)` – David Heffernan Feb 28 '13 at 11:59
  • Also if I do the load off the image in the onPaint it very smooth, but if I load the image before in the form create to avoid reloading it, it flicks, and the myForm.DoubleBuffered does not help. – DRokie Feb 28 '13 at 11:59
  • @DRokie I see no such flickering. And what's more, I avoid `DoubleBuffered` like the plague. It's not needed. It's the wrong way to fix flickering. – David Heffernan Feb 28 '13 at 12:01
  • Ok. How do you load the image them ? – DRokie Feb 28 '13 at 12:12
  • try calling InvalidateRect( MyForm.handle, nil, false ) instead of MyForm.invalidate, the standard Delphi Invalidate (delphi 7 at least) calls invalidaterect with erasebackground set to true. We've seen flickering alot more on our windows 7 machine that we ever did on XP. – Dampsquid Feb 28 '13 at 12:13
  • @David. I have just changed the formStyle to MDI and it those not repaint the background, only when I manually resize it. How can I force it ? – Jlouro Feb 28 '13 at 12:37
  • Where exactly do you want the background in that case? In the MDI client area? If so then that's going to be a different question. It's going to need a different answer. – David Heffernan Feb 28 '13 at 13:22
  • In the client area? The container that holds the MDI children? – David Heffernan Feb 28 '13 at 13:40
  • OK, I'll write you another answer. – David Heffernan Feb 28 '13 at 14:04
  • Sorry off-topic but who is Mr Snoodle haha :) –  Sep 28 '13 at 19:41
6

In the comments to my first answer you ask about how to paint to the client area of an MDI form. That's a bit more difficult because you there is no ready OnPaint event that we can hang off.

Instead what we need to do is to modify the window procedure of the MDI client window, and implement a WM_ERASEBKGND message handler.

The way to do that is to override ClientWndProc in your MDI form:

procedure ClientWndProc(var Message: TMessage); override;
....
procedure TMyMDIForm.ClientWndProc(var Message: TMessage);
var
  Canvas: TCanvas;
  ClientRect: TRect;
  Left, Top: Integer;
begin
  case Message.Msg of
  WM_ERASEBKGND:
    begin
      Canvas := TCanvas.Create;
      Try
        Canvas.Handle := Message.WParam;
        Windows.GetClientRect(ClientHandle, ClientRect);
        Left := 0;
        while Left<ClientRect.Width do begin
          Top := 0;
          while Top<ClientRect.Height do begin
            Canvas.Draw(Left, Top, FBitmap);
            inc(Top, FBitmap.Height);
          end;
          inc(Left, FBitmap.Width);
        end;
      Finally
        Canvas.Free;
      End;
      Message.Result := 1;
    end;
  else
    inherited;
  end;
end;

And it looks like this:

enter image description here


It turns out that you are using an old version of Delphi that does not allow you to override ClientWndProc. This makes it a little harder. You need some window procedure modifications. I've used the exact same approach as is used by the Delphi 6 source code since that's the legacy Delphi that I happen to have at hand.

Your form wants to look like this:

type
  TMyForm = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    FDefClientProc: TFarProc;
    FClientInstance: TFarProc;
    FBitmap: TBitmap;
    procedure ClientWndProc(var Message: TMessage);
  protected
    procedure CreateWnd; override;
    procedure DestroyWnd; override;
  end;

And the implementation like this:

procedure TMyForm.FormCreate(Sender: TObject);
begin
  FBitmap := TBitmap.Create;
  FBitmap.LoadFromFile('C:\desktop\bitmap.bmp');
end;

procedure TMyForm.ClientWndProc(var Message: TMessage);
var
  Canvas: TCanvas;
  ClientRect: TRect;
  Left, Top: Integer;
begin
  case Message.Msg of
  WM_ERASEBKGND:
    begin
      Canvas := TCanvas.Create;
      Try
        Canvas.Handle := Message.WParam;
        Windows.GetClientRect(ClientHandle, ClientRect);
        Left := 0;
        while Left<ClientRect.Right-ClientRect.Left do begin
          Top := 0;
          while Top<ClientRect.Bottom-ClientRect.Top do begin
            Canvas.Draw(Left, Top, FBitmap);
            inc(Top, FBitmap.Height);
          end;
          inc(Left, FBitmap.Width);
        end;
      Finally
        Canvas.Free;
      End;
      Message.Result := 1;
    end;
  else
    with Message do
      Result := CallWindowProc(FDefClientProc, ClientHandle, Msg, wParam, lParam);
  end;
end;

procedure TMyForm.CreateWnd;
begin
  inherited;
  FClientInstance := Classes.MakeObjectInstance(ClientWndProc);
  FDefClientProc := Pointer(GetWindowLong(ClientHandle, GWL_WNDPROC));
  SetWindowLong(ClientHandle, GWL_WNDPROC, Longint(FClientInstance));
end;

procedure TMyForm.DestroyWnd;
begin
  SetWindowLong(ClientHandle, GWL_WNDPROC, Longint(FDefClientProc));
  Classes.FreeObjectInstance(FClientInstance);
  inherited;
end;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Now I have a blank form !? I use D2007. Can not set ClientWndProc to override. ClientRect does no have Width, changed it to Right, and noting show on the form . – Jlouro Feb 28 '13 at 15:58
  • Where is the FBitmap declared ? – Jlouro Feb 28 '13 at 16:07
  • Wherever you want to declare it. Anyway, the inability of override `ClientWndProc` is a problem. There is a way around it. But it will take time. It's a bit galling that 1. You only mentioned MDI after I wrote my first answer and 2. You didn't tell me that you were using such an old Delphi version. These details appear to matter. – David Heffernan Feb 28 '13 at 16:10
  • my problem now is that ClientWndProc is never called – Jlouro Feb 28 '13 at 16:13
  • +1 - you sometimes are a very patient man, David. BTW, that's some... interesting choice for your picture. – Leonardo Herrera Feb 28 '13 at 16:21
  • @Jlouro OK, see my latest update. This will get around the limitations of your legacy Delphi. By the way, I know I've been a bit grumpy. But that has been fun. If you have an old Delphi, always let us know. And if your form is MDI, again let us know because that's also unusual. So, thanks for the fun!!! – David Heffernan Feb 28 '13 at 16:25
  • Thanks. You have not been grumpy. Sorry for the lack of information. I'm glad you enjoyed it. Again Thanks. – Jlouro Feb 28 '13 at 16:36
  • 1
    :) Don't worry it's only him. – Jlouro Feb 28 '13 at 16:39
  • @David Why is Mr. Snoodle crying? He looks pretty happy? – Marjan Venema Mar 01 '13 at 08:52
  • Ah, missed that. Poor thing. – Marjan Venema Mar 01 '13 at 08:54