I’m drawing some lines and text on a TImage canvas. In general it all works, but it has one bug that is eluding me.
Occasionally a drawing operation is causing the TImage canvas to go transparent as I can see the colour of the TScollBox that is hosting the TImage. The TImage is still there as the mouse events for it still work (popup menu, etc), but all subsequent drawing operations can’t be seen. I’ve checked the Transparent property though and it’s false.
What else can I check / do to track this bug down?
This is a VCL application. I can actually replicate the issue now, but I can’t track down why it’s doing it.
Most of the drawing is for an audio graph, time scale and some marker lines. These are updated in response to user interaction and it never seems to have a problem with that.
A progress marker is updated when a 100ms progress timer fires and that does not cause an issue. The whole image is redrawn each time.
Where it dies is when I have the audio file playing back and it finishes. There’s an event triggered by the audio playback process that also calls the graph update to clean up the window.
If I remove the call to the update when the audio playback stops there is no issue, but then there’s a marker left over from the last progress event.
It’s like there’s a memory corruption occurring when the audio playback stop event calls the update which clobbers the TImage somehow.
Possibly related, would I be better off adding the progress marker as another layer on top of the graph’s TImage so I’m not redrawing it all the time when playback is in progress?
Maybe a long shot, but have you checked to make sure that the event is being fired in the main thread? If it’s not, that could certainly explain strange behaviour in the VCL.
OK, this is getting into unfamiliar territory for me. I just tried using GetCurrentThread and got the same number for the event that the audio playback fires and the one in the form that triggers the update.
Something I also just tried was posting a custom message within the same form. That seems to decouple the end-of-play event from the graph update and no more disappearing TImage.
Just to be sure, you could call the following RunningInMainThread function to check that it’s definitely running in the main thread:
function CurrentThreadID: TThreadID;
begin
Result := TThread.Current.ThreadID;
end;
function RunningInMainThread: Boolean;
begin
Result := CurrentThreadID = MainThreadID;
end;
If it’s not running outside of the main thread, the only ideas that come to mind is a lack of remaining stack,. but that seems unlikely and should cause a stack overflow exception if that occurs.
If your event handler is calling back into the 3rd party DLL, maybe it’s not liking that for some reason and that’s then causing unexpected behaviour.
The custom message is a reasonable solution, so you probably don’t need to persist, but I’m curious as to what the actual problem is nonetheless
Ah, I wasn’t using the ThreadID correctly before. The thread in the audio playback is indeed different to the main thread where the call to the graph update is.
I guess this means I’ve just been lucky until now that I haven’t had more issues. I’ll have to look into how to implement a better (thread correct) solution.
The custom message should work fine. Alternatively, you can just use TThread.Queue to run your code in the main thread.
TThread.Synchronize can be used if you need the graph to be updated before the event handler returns but that probably isn’t needed if the custom message is already working fine.
Cleaning up the graph isn’t time / sequence critical so the message works there as a fix for now.
I have had bug reports from users that show spurious GDI exceptions that could well be a symptom of a similar situation in other parts of my application.
So it seems like I need to look at the way those audio library events interact with my code and the GUI as that could be the root cause.
Wandering back on the canvas drawing, is there a way to overlay another TImage on the base one so I can draw the moving progress bar on that instead and not have to redraw the base image every 100ms ?
I looked at using a second TImage with the transparent property set, but the scrollbox wants to put the images end to end and not let me place it on top.
Am I barking up the wrong tree? Should I be looking at drawing the layers as separate bitmaps then copying them to the TImage in the order I want, omitting any that are not required?
Oh, for the thread issue I just changed the lower level audio event to use Synchronize and moved the event processing itself up a level (in the class I was casting to) so that it’s running in the main thread. Seems to be working well as I reverted back to using a direct call instead of the custom message and the updates are perfectly fine now. Checking the thread IDs in various places confirmed it. I learned a lot ! Thank you.
Why are you redrawing the TImage every 100ms. You should only need to draw the new updated area. With regards to the progressbar you could save what is behind it, when updating paint back what is behind it, and the draw the new progress bar.
I am not sure that Synchronize is the best option. Try dragging the window around while you are recording or playing back. I think that the Synchronize may cause the realtime thread to drop audio data, when the main thread is under heavy usage - like redrawing the window and other tasks.
Not in so far as running VCL code outside of the main thread - it will always run the code in the main thread.
IMO, Synchronize has a greater potential for inducing a deadlock if you’re also using mutexes, etc for thread synchronisation, but I think that it’s fairly safe to say that you’re not doing thread synchronisation at this point, so it’s probably not something that you need to be concerned about right now.
They’re functionally quite similar, but implemented in different ways with some specific idiosyncrasies that you probably don’t need to worry about. Generally, I’d say that TThread.Queue and TThread.Synchronize are nicer but it most likely won’t make any signficant difference for you which way you go.