Recent

Author Topic: Solved - Problem with OnPaint automatically re-drawing  (Read 2063 times)

Wilko500

  • Jr. Member
  • **
  • Posts: 63
Solved - Problem with OnPaint automatically re-drawing
« on: January 11, 2021, 11:38:12 pm »
This is a follow up to my previous post on Drawing to dynamically created forms https://forum.lazarus.freepascal.org/index.php/topic,52620.msg388311.html#msg388311

I have now progressed my project to the point where I can create and draw to dynamic forms but . . . I don't think I fully understand the OnPaint method and its implications.  In the attachment the first page created is Page-0 and will be one of many pages, listed in the Results ListView.  All the other pages will be stacked under Page-0.  In my VB6 version which I am trying to port to FPC I am able to click on any page in the ListView and it would be made visible thus I can quickly work through pages until I find the one I require which can then optionally be printed.

The problem is that If I "change" the displayed page the OnPaint method is invoked and the whole process of drawing is started over. This involves re-reading the input data file and recalculating all pages.  Definitely not what I want.

In my earlier post(s) I learned that drawing on canvas is not persistent unless it is drawn in the OnPaint event.  What I thought would happen is that the persistent canvass would retain is drawings even if form moved (as is the case in VB6).   Evidently my understanding of OnPaint is flawed. 

I am hoping that there is some kind of workaround to resolve my issue.

Thanks Richard
« Last Edit: January 17, 2021, 11:35:11 pm by Wilko500 »
MacBook Pro mid 2015 with OS Monterey 12.7.2
FPC 3.3.1 Lazarus _3_0

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Problem with OnPaint automatically re-drawing
« Reply #1 on: January 12, 2021, 12:06:13 am »
As you have learned, the TPaintBox has no storage its simply a canvas for you to draw on with no retention..

This OnPaint will get called when ever any area of this needs to be repainted.

VB is not that much different other than maybe you were using a Bitmap to retain the image which you can do here also but it will chew up memory as you keep adding pages

 You need to store your data that needs to be displayed in some container and use the OnPaint handler only to display it, nothing else..

you should not be doing anything in the Paint handler other than processing information you already have in memory.


 I don't know what your pages look like when display with the actual data so its hard to say what choice to offer you as the data storage media.
The only true wisdom is knowing you know nothing

Handoko

  • Hero Member
  • *****
  • Posts: 5129
  • My goal: build my own game engine using Lazarus
Re: Problem with OnPaint automatically re-drawing
« Reply #2 on: January 12, 2021, 03:43:41 am »
One can use TImage if he doesn't want to manage the image buffer manually. So the drawing procedure does not have to put in the OnPaint event. For example, the code of drawing something can be put on a button's OnClick event.

That can be done on TImage because it has it's own image buffer, which most other components do not. Components that do not have its own image buffer have advantages, like already mention, memory efficient. For example TCheckbox doesn't need to store the image because it can simple draw a rectangle and the check sign.

Wilko500

  • Jr. Member
  • **
  • Posts: 63
Re: Problem with OnPaint automatically re-drawing
« Reply #3 on: January 13, 2021, 10:30:58 pm »
Attached is a typical results page. Just lines and text.

Jamie
Quote
VB is not that much different other than maybe you were using a Bitmap to retain the image which you can do here also but it will chew up memory as you keep adding pages

In VB6 I was not using any bitmap.  As far as I can tell VB6 holds the image in memory by default.  Forms have an AutoRedraw property that automatically refreshed the "drawing" from its own copy in memory.  I find it rather strange that FPC does not do something similar but having said that I am very much a novice and clearly have much to learn about FPC and how it should be used.

So I need a new strategy and have read about TImage but in most examples I have read the need to place drawing code in the OnPaint event left me perplexed because it would leave me using TImage but with exactly the same problem I have now.

Handoko, I think your suggestion is pointing me where I want to go.  I'd rather not manage image buffering (until I'm much better at FPC) and I would like to have my drawing code NOT in the OnPaint event. So I have two more questions.

Since I have many Pages to draw, an array of TForm, I will also need an array of TImage?  or do I add the TImage to my form instance when it is created so that each form has its own TImage?  Secondly I presume that I create instances of TImage, draw on them and have OnPaint doing a refresh of the TImage.  I think I'll create a new project and see if I can make progress with TImage.

Thank you for your comments and suggestions

Richard


MacBook Pro mid 2015 with OS Monterey 12.7.2
FPC 3.3.1 Lazarus _3_0

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Problem with OnPaint automatically re-drawing
« Reply #4 on: January 13, 2021, 11:10:00 pm »
VB does have cached canvases and it does use a Bitmap, you simply aren't addressing one directly, in other words it's being done for you..

A Timage is about as close as it gets that way but I would suggest a different approach.

store all the lines of text in a TStringList object for each page. That object can load the data once or you process it and store the net results in that object.. It is a class with array of strings that you can store your text in..

During the OnPaint cycle you simply reference this object and draw your header lines etc..

This will result in a super fast response..


 Other way would be to use a TTreeView and customDraw it all, it has all those lines already and you only need to load the tree once with the data
.

The only true wisdom is knowing you know nothing

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: Problem with OnPaint automatically re-drawing
« Reply #5 on: January 14, 2021, 01:44:07 am »
If all your forms have in them is an image and your images are all "static", that is generated at time of creation and simply shown, I would simply do this process:
  • Create a "sample" (not auto-created) form with a TImage in it at design time.
  • Each time you need it at run-time, create an instance of that sample form and draw in its Image.Picture.Bitmap (for example) what you need.
Note that when I say "form" it may be any other container control, like a TTabSheet in a TPageControl, a TFrame, etc.

If all you want is to have a series of images which the user can select and see, like the canonical example of an "image gallery" browser, that's the simplest way to do it, IMHO.

So to your questions:

Since I have many Pages to draw, an array of TForm, I will also need an array of TImage? or do I add the TImage to my form instance when it is created so that each form has its own TImage?

Add the image to each form or use a "sample" form class.  You don't even need an array of forms: the Application object already has one which you can iterate looking for the "show image" form class, and each form would contain its own TImage object, so no need to keep a separate reference.

Quote
Secondly I presume that I create instances of TImage, draw on them and have OnPaint doing a refresh of the TImage.

No, that's the beauty (the reason d'etrê) of TImage: Whatever you load or draw in its Picture.Bitmap (for example) is kept (and shown) there for eternity (OK, until freed ;)); no need to redraw it and no need for OnPaint handlers.
« Last Edit: January 14, 2021, 01:52:52 am by lucamar »
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: Problem with OnPaint automatically re-drawing
« Reply #6 on: January 14, 2021, 09:08:09 am »
Back in old times canvas drawing wasn't double buffered by default. Mostly because it would have consumed too much system resources and all possible usage cases weren't predictable. It was up to programmer himself to manage back buffer, so optimizing resource usage of his program was also his task, not OS's task. As result, it was always needed to redraw picture on screen, when it was "damaged" somehow. It actually happens not only when you change something by yourself, but also when other window overlaps part of window and when part of your window goes offscreen. OnPaint message is fired every time it happens. And Canvas.ClipRect property allows you to get "damaged" part of canvas to be redrawn.

Please note! Some changes were made to this process since Vista, that implemented "indirect" painting on screen. It was exact moment, when built-in double buffering was implemented to OS itself. You no longer paint on screen itself. You paint on some back buffer, that is managed by window manager, i.e. DWM. Actual drawing is performed via hardware accelerated D3D. I'm not 100% sure, what has changed. In what cases OnPaint is fired and in what cases it no longer does.

And it's up to you to optimize redrawing process. Bitmap is usually used for this purpose. Problem is - you don't know, what size your window will have and resizing bitmap may be slow. Creating one big screen size back buffer - is always the fastest way. But it consumes the most resources. It's not problem now, but it was back in old times.

So, optimal ways to handle OnPaint are:
1) Fast way - you have back buffer bitmap, you draw on, and just need to copy ClipRect part of it to your main window's Canvas. Or just use TImage, that does it automatically.
2) Optimized way - you have list of items, that are identified by their rects. You check, if YourItem.Rect.IntersectsWith(ClipRect) and redraw item, if it does.
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

Wilko500

  • Jr. Member
  • **
  • Posts: 63
Re: Problem with OnPaint automatically re-drawing
« Reply #7 on: January 16, 2021, 12:45:39 am »
Thank you all for very helpful feedback

Jamie
Quote
VB does have cached canvases and it does use a Bitmap, you simply aren't addressing one directly, in other words it's being done for you..
Point taken, I intended to say that I was not deliberately handling the bitmap
Quote
A Timage is about as close as it gets that way but I would suggest a different approach.
Your alternative approach is noted.  However, The original incarnation of this project was written a lifetime ago in Fortran outputting directly to a Calcomp pen plotter via mag tape on mainframe (24 hour turnaround on each run!!! How things have changed).  The suggested elements were, of course, non existent in early Fortran and the working code has been migrated a couple of times largely unchanged. My hope was to avoid a complete re-write at the moment and if/when I have the time/experience I can see many advantages of your suggested alternative approach.

Lucamar
Thank you, TImage does indeed get me quite close to where I want to be.  I have done a bit of testing but still have an issue.  Pic and code attached.  I can't figure out the actual size/placement of the image on the form. It looks like the image does not cover the entire form and only the top left corner is spot on.

Quote
  frm.Image1.Canvas.Brush.Color:=clWhite;
  frm.Image1.Canvas.FillRect(frm.image1.BoundsRect);
This was supposed to clear the image to white and I think the second line may be the problem. I still can't figure out the exact co-ords of the corners though.

I acknowledge that my code may be ineligant but its just for testing.  Suggestions welcome

Richard
MacBook Pro mid 2015 with OS Monterey 12.7.2
FPC 3.3.1 Lazarus _3_0

Handoko

  • Hero Member
  • *****
  • Posts: 5129
  • My goal: build my own game engine using Lazarus
Re: Problem with OnPaint automatically re-drawing
« Reply #8 on: January 16, 2021, 02:42:25 am »
There are several ways to solve it. The easiest is to set the image the same size as the form by using Align := alClient. You can set it using the Object Inspector, or using code:

Code: Pascal  [Select][+][-]
  1. procedure TForm2.FormCreate(Sender: TObject);
  2. begin
  3.   Image1.Align := alClient;
  4. end;
« Last Edit: January 16, 2021, 04:21:04 am by Handoko »

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Problem with OnPaint automatically re-drawing
« Reply #9 on: January 16, 2021, 02:47:18 am »
if you really are going to images with who knows how many pages then set the pixel format to a lower level if it can be like 8 bit.

or if you don't need....
The only true wisdom is knowing you know nothing

circular

  • Hero Member
  • *****
  • Posts: 4195
    • Personal webpage
Re: Problem with OnPaint automatically re-drawing
« Reply #10 on: January 16, 2021, 12:19:59 pm »
Looking at the code of TImage, indeed one can draw directly on its Canvas and it creates a temporary bitmap, as long as it is not within the OnPaint event. Otherwise, if you draw within the OnPaint event of TImage, it is just like painting on the control surface and would not be kept.

I thought the only correct way was to do Image1.Picture.Bitmap.SetSize(...) and then Image1.Picture.Bitmap.Canvas.FillRect(...)
Conscience is the debugger of the mind

Wilko500

  • Jr. Member
  • **
  • Posts: 63
Re: Problem with OnPaint automatically re-drawing
« Reply #11 on: January 17, 2021, 11:34:48 pm »
Quote
Align := alClient
works for me.  From a simple question and some reading around the posted suggestions I have learned more than I expected. My thanks to the Lazarus community. 

So now back to my FlowChart project . . .

Richard

MacBook Pro mid 2015 with OS Monterey 12.7.2
FPC 3.3.1 Lazarus _3_0

 

TinyPortal © 2005-2018