Jump to content
Jacek Laskowski

TPainBox, Handle and PostMessage()

Recommended Posts

Is possible to send message to PaintBox?

PaintBox inherits from TGraphicControl:

 

  TGraphicControl = class(TControl)
  private
    FCanvas: TCanvas;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  protected
    procedure Paint; virtual;
    property Canvas: TCanvas read FCanvas;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

As can be seen TGraphicControl receives a WM_PAINT message. But how to send it? Where to get the handle for the control? I need to notify the TPaintBox control of the need to repain from the worker thread.

Share this post


Link to post
Posted (edited)

You never send a WM_PAINT message to a control. You invalidate in. For a plain Win32 control that means calling InvalidateRect. For a VCL control that means calling Invalidate. 

 

You don't want to call that from a thread. Use Synchronize to execute code on the UI thread. 

Edited by David Heffernan

Share this post


Link to post
2 hours ago, David Heffernan said:

You never send a WM_PAINT message to a control. You invalidate in. For a plain Win32 control that means calling InvalidateRect. For a VCL control that means calling Invalidate. 

 

You don't want to call that from a thread. Use Synchronize to execute code on the UI thread. 

Or avoid Synchronize and send custom message to the control using PostMessage(control.handle,...) and call invalidate in the message handler. 

Share this post


Link to post
Posted (edited)
6 hours ago, Vincent Parrett said:

Or avoid Synchronize and send custom message to the control using PostMessage(control.handle,...) and call invalidate in the message handler. 

Use TThread.Queue, not least because the paint box isn't windowed. 

Edited by David Heffernan

Share this post


Link to post
6 hours ago, Vincent Parrett said:

Or avoid Synchronize and send custom message to the control using PostMessage(control.handle,...) and call invalidate in the message handler. 

Thanks, this idea seems interesting to me, because it focuses drawing in one place (several threads can add data). But there is a problem. The control inheriting from TPaintBox does not have Handle.

How to get around it?

Share this post


Link to post
Posted (edited)
4 hours ago, David Heffernan said:

You'd have to post the message to the parent. But why bother. Just use TThread.Queue.  

Calling Queue from the thread is a bad idea here. There are several threads, each of them processes some data, and one visual control displays them. If each thread is forcing a refresh, it will be a heavy load. And if the light gets messages, even from a few threads, it will decide how to refresh itself.

 

ps. How to handle the message the parent got? I don't know who the parent will be?

Edited by Jacek Laskowski

Share this post


Link to post
Posted (edited)
26 minutes ago, Jacek Laskowski said:

Calling Queue from the thread is a bad idea here. There are several threads, each of them processes some data, and one visual control displays them. If each thread is forcing a refresh, it will be a heavy load. And if the light gets messages, even from a few threads, it will decide how to refresh itself.

Dude, it was you that wanted to have the worker thread notify the main thread! That's literally what you asked in the OP.

 

26 minutes ago, Jacek Laskowski said:

How to handle the message the parent got? I don't know who the parent will be?

Why would you opt to send a message to the parent? I only said that because you can't send a message to the paint box since it isn't windowed. Which is why Queue or Synchronize are cleaner.

 

Ultimately, you need to call Invalidate on the paint box, from the main thread. And you need to decide what is going to trigger that to happen.  I gave you some examples, and of course there are other ways. But your have to make you own mind up.

Edited by David Heffernan

Share this post


Link to post
25 minutes ago, Jacek Laskowski said:

Calling Queue from the thread is a bad idea here. There are several threads, each of them processes some data, and one visual control displays them. If each thread is forcing a refresh, it will be a heavy load. And if the light gets messages, even from a few threads, it will decide how to refresh itself.

Right, then let the threads push the data in list/stack..., , then use a timer with with interval fit your needs to call invalidate, the interval here will be (1000/TimerInterval) = your refresh rate per second, the invalidate method will pop the data from the threads and draw, the lists should be thread safe, so you can use FIFO or LIFO or a simple TList<yourData> with CriticalSection/Spinlock, in such case were thread count is controlled and known, i suggest to use SpinLock, just make the protected code part as short as possible, so it should look like this:

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  //////     Right way
  SpinLock.Enter;
  try
    //Get one item or all of them to into local copy
  finally
    SpinLock.Exit;
  end;
  // Draw here


  //////   No so right way
  SpinLock.Enter;
  try
    // Loop though items, get one item and draw
  finally
    SpinLock.Exit;
  end;
end;

 

Share this post


Link to post
33 minutes ago, David Heffernan said:

Dude, it was you that wanted to have the worker thread notify the main thread! That's literally what you asked in the OP. 

There is a big difference between notifying the main thread about the event and calling the drawing method in the main thread (from working thread). In the first case I can decide what I will do, I can even do nothing, in the second case drawing will ALWAYS be forced.

Share this post


Link to post
Posted (edited)

No. That's not what Invalidate does. It marks the paint box as invalid. And then when the message queue is next empty, a paint event gets synthesised. 

 

The thing is, at some point you need to make the main thread wake up and do something. What's your plan for doing that? Perhaps what you want to do is note that an update is required with a flag and then check that on an update timer in the main thread. That would be a sensible approach if the frequency of updates from the threads was very high. 

 

Bear in mind that in the OP you suggested sending a message from the worker thread to the main thread. Have you now decided that you don't want to do that? 

Edited by David Heffernan

Share this post


Link to post

Okay, I didn't know that Invalidate() would send a separate wm_paint message. It changes a lot.


Sending a message from the work thread to the main one was an idea to communicate with the main thread, one of the possible ones. But actually the timer will be better. The data is already written to the appropriate structure, and it is enough to read and draw it periodically.

Thank you all for your comments.

Share this post


Link to post

Invalidate doesn't send a message. It marks the control as needing repainting and then that paint message is synthesised the next time the message queue is emptied. 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×