Jacek Laskowski 57 Posted May 28, 2020 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
David Heffernan 2353 Posted May 28, 2020 (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 May 28, 2020 by David Heffernan Share this post Link to post
Vincent Parrett 764 Posted May 28, 2020 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
David Heffernan 2353 Posted May 29, 2020 (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 May 29, 2020 by David Heffernan Share this post Link to post
Jacek Laskowski 57 Posted May 29, 2020 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
David Heffernan 2353 Posted May 29, 2020 You'd have to post the message to the parent. But why bother. Just use TThread.Queue. Share this post Link to post
Jacek Laskowski 57 Posted May 29, 2020 (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 May 29, 2020 by Jacek Laskowski Share this post Link to post
David Heffernan 2353 Posted May 29, 2020 (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 May 29, 2020 by David Heffernan Share this post Link to post
Guest Posted May 29, 2020 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
Jacek Laskowski 57 Posted May 29, 2020 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
David Heffernan 2353 Posted May 29, 2020 (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 May 29, 2020 by David Heffernan Share this post Link to post
Jacek Laskowski 57 Posted May 29, 2020 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
David Heffernan 2353 Posted May 29, 2020 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
Anders Melander 1815 Posted May 29, 2020 The Old New Thing: Paint messages will come in as fast as you let them Share this post Link to post