Link to home
Start Free TrialLog in
Avatar of Esopo
EsopoFlag for United States of America

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.
Avatar of Russell Libby
Russell Libby
Flag of United States of America image

Common issues:

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
SOLUTION
Avatar of Jacco
Jacco
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Esopo

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.
Avatar of sftweng
sftweng

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
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Esopo

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?
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.
Avatar of Esopo

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(Provincia:integer);
var
ContCiudad:integer;

begin
ContCiudad:=1;

FrmPrincipal.CB1.Clear;
while Telefonos[Provincia,ContCiudad,0]<>'' do
begin
  FrmPrincipal.CB1.Items.Add(Telefonos[Provincia,ContCiudad,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?
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.
Avatar of Esopo

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?
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
Avatar of Esopo

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.
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
Avatar of Esopo

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?
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
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(ReqState: 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.CreateRes(@SNoCanvasHandle);
    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

Avatar of Esopo

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.
Avatar of Esopo

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.CreateRes(@SNoCanvasHandle);
 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
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Esopo

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.
Avatar of Esopo

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=TotalIntentos then
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/TBitmapCanvas by checking Self.ClassName)

Russell


Avatar of Esopo

ASKER

>check GetLastError
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


Avatar of Esopo

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.
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?
Avatar of Esopo

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.
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Esopo

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.
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.