Esopo
asked on
Canvas does not allow drawing...
Dear experts:
I'm back with more of my lack of knowledge in Delphi ;)
For those of you that remember my RAS application, well it's working very well thanks to the EE experts, although I'm having another problem:
Every now and then I get a "canvas does not allow drawing" error (this is what the Delphi debuger sends). I've done some testing and finally spotted a common place where it happens.
I don't know if this info is relevant but I'm giving it anyway in case it helps:
The error occurs when the user tries to dial the phone number under a phone-central without the - 9, - prefix to get a line. So the central returns a beeping similar to the "line bussy" and the application returns a user friendly error (coded by me) telling the line is bussy. Then the application attemps to dial again (since it's default config is to make 3 redial attempts) with the common close-line/dial-again that works like a charm when the phone number was entered correctly. When it tries to dial again, my application crashes with the "canvas does not allow drawing" error.
I read a bit and the explanations for this error that I found (lack of system resources) don't seem to apply to me. I read about a possible solution Canvas.Lock or Canvas.Trylock but I don't know where to place that.
Any ideas or suggestions are very welcome.
Thank you,
Esopo.
I'm back with more of my lack of knowledge in Delphi ;)
For those of you that remember my RAS application, well it's working very well thanks to the EE experts, although I'm having another problem:
Every now and then I get a "canvas does not allow drawing" error (this is what the Delphi debuger sends). I've done some testing and finally spotted a common place where it happens.
I don't know if this info is relevant but I'm giving it anyway in case it helps:
The error occurs when the user tries to dial the phone number under a phone-central without the - 9, - prefix to get a line. So the central returns a beeping similar to the "line bussy" and the application returns a user friendly error (coded by me) telling the line is bussy. Then the application attemps to dial again (since it's default config is to make 3 redial attempts) with the common close-line/dial-again that works like a charm when the phone number was entered correctly. When it tries to dial again, my application crashes with the "canvas does not allow drawing" error.
I read a bit and the explanations for this error that I found (lack of system resources) don't seem to apply to me. I read about a possible solution Canvas.Lock or Canvas.Trylock but I don't know where to place that.
Any ideas or suggestions are very welcome.
Thank you,
Esopo.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Thanks for posting guys,
The thing is, I can't seem to spot which line of my code is causing the problem.
I'd love to post some code, but my guess is if I knew which line of code was the one, I could probably fix it myself with what I've read so far, very similar to rllibby's comment.
I haven't hand coded anything related to canvas. All I've done so far is inserting common objects (buttons, images, etc). Any ideas?
Esopo.
The thing is, I can't seem to spot which line of my code is causing the problem.
I'd love to post some code, but my guess is if I knew which line of code was the one, I could probably fix it myself with what I've read so far, very similar to rllibby's comment.
I haven't hand coded anything related to canvas. All I've done so far is inserting common objects (buttons, images, etc). Any ideas?
Esopo.
If you are using multi-threading, you may be trying to do a GUI update from a thread other than the main one. In those threads, you must use "Synchronize".
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
ricswika,
Thanks for posting, anything counts. I think the Internet lacks enough info on this issue.
I don't know how to trace back to my problem. I get the CPU window, but don't know hot to use it, could anyone post a link to some sort of tutorial or article so I can learn?
sftweng,
Where do I put the Synchronize?
Thanks for posting, anything counts. I think the Internet lacks enough info on this issue.
I don't know how to trace back to my problem. I get the CPU window, but don't know hot to use it, could anyone post a link to some sort of tutorial or article so I can learn?
sftweng,
Where do I put the Synchronize?
Esopo, any procedure call that interfaces with the GUI needs to take place within a Synchronize procedure.
You can see an example here, created within Delphi by going to "File -> New -> Other -> Thread Object":
unit Unit2;
interface
uses
Classes;
type
MyThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
{ Important: Methods and properties of objects in VCL or CLX can only be used
in a method called using Synchronize, for example,
Synchronize(UpdateCaption) ;
and UpdateCaption could look like,
procedure MyThread.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end; }
{ MyThread }
procedure MyThread.Execute;
begin
{ Place thread code here }
end;
end.
You can see an example here, created within Delphi by going to "File -> New -> Other -> Thread Object":
unit Unit2;
interface
uses
Classes;
type
MyThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
{ Important: Methods and properties of objects in VCL or CLX can only be used
in a method called using Synchronize, for example,
Synchronize(UpdateCaption)
and UpdateCaption could look like,
procedure MyThread.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end; }
{ MyThread }
procedure MyThread.Execute;
begin
{ Place thread code here }
end;
end.
ASKER
sftweng,
How can I identify when something is outside the main thread?
I'm used to VB and am in the process of switching to Delphi so there are this things I still don't get.
This is an example of my code:
**********************
procedure MostrarCiudadesProv(Provin cia:intege r);
var
ContCiudad:integer;
begin
ContCiudad:=1;
FrmPrincipal.CB1.Clear;
while Telefonos[Provincia,ContCi udad,0]<>' ' do
begin
FrmPrincipal.CB1.Items.Add (Telefonos [Provincia ,ContCiuda d,0]);
ContCiudad:=ContCiudad+1;
end;
FrmPrincipal.CB1.Items.Add ('Otro');
end;
**********************
It basically loads a list into a combo box. I call it like this:
MostrarCiudadesProv(CBProv .ItemIndex +1);
Within an OnChange event fired by a combo box.
My question is, is this the kind of things that need to be called with Synchronize?
How can I identify when something is outside the main thread?
I'm used to VB and am in the process of switching to Delphi so there are this things I still don't get.
This is an example of my code:
**********************
procedure MostrarCiudadesProv(Provin
var
ContCiudad:integer;
begin
ContCiudad:=1;
FrmPrincipal.CB1.Clear;
while Telefonos[Provincia,ContCi
begin
FrmPrincipal.CB1.Items.Add
ContCiudad:=ContCiudad+1;
end;
FrmPrincipal.CB1.Items.Add
end;
**********************
It basically loads a list into a combo box. I call it like this:
MostrarCiudadesProv(CBProv
Within an OnChange event fired by a combo box.
My question is, is this the kind of things that need to be called with Synchronize?
If you are triggering the procedure call from an OnChange event then it's being done in the main thread so no Synchronize should be necessary.
If you are not explicitly creating TThread objects then this is probably not the problem.
However, all of the ComboBox access in MostrarCiudadesProv would need to be the subject of a Synchronize call if done from any other thread than the main one.
I am assuming that FrmPrincipal is created at design time rather than from within a thread.
If you are not explicitly creating TThread objects then this is probably not the problem.
However, all of the ComboBox access in MostrarCiudadesProv would need to be the subject of a Synchronize call if done from any other thread than the main one.
I am assuming that FrmPrincipal is created at design time rather than from within a thread.
ASKER
Yes FrmPrincipal is the main form created at design time.
I'm not explicitly creating TThread objects.
Would a thread, to be considered other than the main one, have to be created using a TThread object, or are there other ways to have a thread not be the main one?
I'm not explicitly creating TThread objects.
Would a thread, to be considered other than the main one, have to be created using a TThread object, or are there other ways to have a thread not be the main one?
Some components create threads (e.g., the Indy Internet components) but it should be obvious from their documentation that they are doing so. I notice in your original posting that you mentioned "RAS", by which I interpret "Remote Access Services", so there is some possibility that you do have threads. What I cannot tell without looking at the code is whether any of those threads invoke GUI operations.
The main form runs on the main thread (no way around that in the VCL). But there may be other threads to contend with.
Easiest way to check is through the use of the IDE's debug windows (or NT Task manager where applicable) to determine the number of threads in the app. If only one thread, then you need to look at other areas, as the canvas sync'ing won't apply.
Regards,
Russell
Easiest way to check is through the use of the IDE's debug windows (or NT Task manager where applicable) to determine the number of threads in the app. If only one thread, then you need to look at other areas, as the canvas sync'ing won't apply.
Regards,
Russell
ASKER
I'd love to post my whole code but it's very long (about 1500 lines) besides, I don't want you spending that much time browsing through it. I'm not using any thirparty components, my -Remote Access Services- connection is handled completely by my code.
You say two ways to look for extra threads, one is to run the app and check the TaskManager, I'll do that.
The other one is to look in the code for Threads (TThread objects), I'll do that too.
You say two ways to look for extra threads, one is to run the app and check the TaskManager, I'll do that.
The other one is to look in the code for Threads (TThread objects), I'll do that too.
If you run the program in debug mode, you can open a Threads debug window.
Alan, nice to see you again ;-). (haven't seen you around recently.)
Kind Regards,
Russell
ASKER
I did the first to things and got no other process related to my app and also found no Tthread call in my code.
I ran the program in debug mode with the threads debug window and eventually got the error, this is what the threads debug window listed:
Thread ID State Status
Principal1.exe (1920)
2180 S U
1904 S U
2212 S Init
S: Stopped
U:Unknown
Any ideas?
I ran the program in debug mode with the threads debug window and eventually got the error, this is what the threads debug window listed:
Thread ID State Status
Principal1.exe (1920)
2180 S U
1904 S U
2212 S Init
S: Stopped
U:Unknown
Any ideas?
All I can suggest at this point is that you post your "Uses" statements so that we can see whether any of the imported modules might contain multithreaded code.
Thanks, Russell, I've been pre-occupied with some personal issues, most of which are resolved.
Alan
Alan
Also, if an option (you have full source for vcl)...
There is only ONE location that will raise this exception, and if you have the vcl source (depending on delphi package std/pro/enterprise/etc), you can set the "Use debug DCU's" in the compiler options.
What you would be looking for is this:
procedure TCanvas.RequiredState(ReqS tate: TCanvasState);
var
NeededState: TCanvasState;
begin
NeededState := ReqState - State;
if NeededState <> [] then
begin
if csHandleValid in NeededState then
begin
CreateHandle;
if FHandle = 0 then
raise EInvalidOperation.CreateRe s(@SNoCanv asHandle);
end;
if csFontValid in NeededState then CreateFont;
if csPenValid in NeededState then CreatePen;
if csBrushValid in NeededState then CreateBrush;
State := State + NeededState;
end;
end;
By setting a breakpoint on the raise statement, you should be able to check the call stack window to find out where things went wrong.
Hope this helps,
Russell
There is only ONE location that will raise this exception, and if you have the vcl source (depending on delphi package std/pro/enterprise/etc), you can set the "Use debug DCU's" in the compiler options.
What you would be looking for is this:
procedure TCanvas.RequiredState(ReqS
var
NeededState: TCanvasState;
begin
NeededState := ReqState - State;
if NeededState <> [] then
begin
if csHandleValid in NeededState then
begin
CreateHandle;
if FHandle = 0 then
raise EInvalidOperation.CreateRe
end;
if csFontValid in NeededState then CreateFont;
if csPenValid in NeededState then CreatePen;
if csBrushValid in NeededState then CreateBrush;
State := State + NeededState;
end;
end;
By setting a breakpoint on the raise statement, you should be able to check the call stack window to find out where things went wrong.
Hope this helps,
Russell
ASKER
unit Principal;
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Ras, Forms, Dialogs, StdCtrls, ExtCtrls, jpeg, Buttons, FileUtilities;
uses ConnProp, Unit1, Aydua;
> Unit1 and Aydua are forms in my proyect.
unit unit1;
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, jpeg, ExtCtrls;
unit Aydua;
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, jpeg, ExtCtrls, StdCtrls, ComCtrls;
unit FileUtilities;
uses
windows, messages, sysutils, classes, graphics, controls, forms, dialogs, stdctrls;
unit Ras;
uses Windows, Messages, SysUtils;
These are the files in my proyect.
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Ras, Forms, Dialogs, StdCtrls, ExtCtrls, jpeg, Buttons, FileUtilities;
uses ConnProp, Unit1, Aydua;
> Unit1 and Aydua are forms in my proyect.
unit unit1;
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, jpeg, ExtCtrls;
unit Aydua;
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, jpeg, ExtCtrls, StdCtrls, ComCtrls;
unit FileUtilities;
uses
windows, messages, sysutils, classes, graphics, controls, forms, dialogs, stdctrls;
unit Ras;
uses Windows, Messages, SysUtils;
These are the files in my proyect.
ASKER
Russell,
I'm there,
I checked the "Use debug DCU's" and managed to get the exception, the debugger is paused at the line
raise EInvalidOperation.CreateRe s(@SNoCanv asHandle);
what should I do now?
I'm there,
I checked the "Use debug DCU's" and managed to get the exception, the debugger is paused at the line
raise EInvalidOperation.CreateRe
what should I do now?
Should have also mentioned that the clipped block of code comes from graphics.pas, my bad on that ;-)
Russell
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
I think I spotted something. The exception seems to be caused by any line of code after a showmessage call:
showmessage( StatusString(state, error) + chr(10) + 'Consulte la ayuda en las opciones avanzadas');
I say any line of code, cause if comment the line that caused it and when ran again the following line caused it.
showmessage( StatusString(state, error) + chr(10) + 'Consulte la ayuda en las opciones avanzadas');
I say any line of code, cause if comment the line that caused it and when ran again the following line caused it.
ASKER
>I say any line of code, cause if comment the line that caused it and when ran again the following line caused it.
Does that make sense? hehe
I say any line of code after the showmessage one, cause the line that followed was just a call to a method that adds a info to a log file. I just commented it ( // ) and then, when ran the program again, the following line caused it (the line that came right after the comment one):
If IntentoActual=TotalIntento s then
Does that make sense? hehe
I say any line of code after the showmessage one, cause the line that followed was just a call to a method that adds a info to a log file. I just commented it ( // ) and then, when ran the program again, the following line caused it (the line that came right after the comment one):
If IntentoActual=TotalIntento
Can you try converting the ShowMessage call to a MessageBox function?
MessageBox(Handle, PChar(Message information), PChar(Title), MB_OK+MB_ICONINFORMATION);
Perhaps something is interfering with the handle allocation. (Could also check GetLastError when in the breakpoint, and verify the canvas is either TControlCanvas/TBitmapCanv as by checking Self.ClassName)
Russell
MessageBox(Handle, PChar(Message information), PChar(Title), MB_OK+MB_ICONINFORMATION);
Perhaps something is interfering with the handle allocation. (Could also check GetLastError when in the breakpoint, and verify the canvas is either TControlCanvas/TBitmapCanv
Russell
ASKER
>check GetLastError
How do I do that?
How do I do that?
Also thinking out loud...
Are you sure the ShowMessage call is made within the main thread? If it isn't (it uses vcl code), then this would also cause the error you are getting. Try the message box, which is straight api stuff to see if this makes a difference.
Russell
----------
Offtopic:
Alan, good to hear, hope things are "straight" now. Glad to see your expertise back around here.
To check any of the stuff I mentioned...
at the breakpoint.
Right click / "Debug" / "Evaluate/Modify"
enter in the expression, eg: GetLastError
press enter
check the result value
----------------
Russell
at the breakpoint.
Right click / "Debug" / "Evaluate/Modify"
enter in the expression, eg: GetLastError
press enter
check the result value
----------------
Russell
ASKER
I'm having trouble repliclating the error. As stated before it happens almost at will ;)
I'll keep trying and playing around with the showmessage until I get it straight.
Thanks guys, it's been very useful. I'll check this up and come back later.
I'll keep trying and playing around with the showmessage until I get it straight.
Thanks guys, it's been very useful. I'll check this up and come back later.
The fact that the error seems to have a mind of its own (i.e., "happens almost at will") implies to me that there is a threading problem of some kind.
What is unit ConnProp?
ASKER
I'm sorry to have been gone for too long. I wasn't able to get anywhere with this and had to work on more important stuff.
Never got around the canvas error so I fixed my application to work without the code that was generating the error. It now works but without the options it was suppoused to have.
I'm very worried about this canvas thing cause it isn't the first application I get this with, I'll see into this problem some more and then come back to tell you my findings.
Thank you for your time,
Esopo.
Never got around the canvas error so I fixed my application to work without the code that was generating the error. It now works but without the options it was suppoused to have.
I'm very worried about this canvas thing cause it isn't the first application I get this with, I'll see into this problem some more and then come back to tell you my findings.
Thank you for your time,
Esopo.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Thank you all for your help.
I never really found a solution for this, gave it many long hours and eventually had to move on to other stuff. Maybe as Alan says sometimes is best to light the code on fire with a keg + bunch of friends + couple of guitars and go for a second round.
Nevertheless your help is most appreciated, and even when I found no conclution for this problem I did got many possibilities I had no idea about.
Sorry to have kept this Q open for so long.
Esopo.
I never really found a solution for this, gave it many long hours and eventually had to move on to other stuff. Maybe as Alan says sometimes is best to light the code on fire with a keg + bunch of friends + couple of guitars and go for a second round.
Nevertheless your help is most appreciated, and even when I found no conclution for this problem I did got many possibilities I had no idea about.
Sorry to have kept this Q open for so long.
Esopo.
Thanks. Personally, I'd prefer a keg + bunch of critics (probably also friends) + some bagpipes + a whiteboard. Occasionally blowing it all away feels good and breaks fixations.
1.) you may have run out of resources, which means a new DC cannot be
allocated. Usually this is a result of not freeing unused DCs or other
objects
2.) you are attempting to draw to an device context that is not yet valid.
Such as in the create constructor of a component.
3) you are trying to draw on a canvas while your last draw-operation has
not finished yet. In this case you can use Canvas.Lock or Canvas.TryLock to fix it.
This can also be a problem if attempting to draw on a control's canvas before Loaded has been called for the component. A little bit of code would help to further see where the problem might be. But anyways, if you have some drawing code, check the component first to make sure that the ComponentState does not contain the csLoading flag. If it does, the component should not be drawn upon until after this flag is removed (which is when the component is fully loaded). You can also try the Canvas.Lock before drawing/painting, and unlock when done
if Canvas.TryLock then
begin
try
// .. do painting
finally
Canvas.Unlock;
end;
end;
--------------
Regards,
Russell