• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 16543
  • Last Modified:

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.
0
Esopo
Asked:
Esopo
  • 14
  • 11
  • 9
  • +2
4 Solutions
 
Russell LibbySoftware Engineer, Advisory Commented:
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
0
 
JaccoCommented:
I had this error once when I was using Canvas.TextWidth and Canvas.TextHeight to measure things. The solution was to create a separate Canvas myself like this:

MyCanvas := TCanvas.Create;
MyCanvas.Handle := GetDC(0);
MyCanvas.Font := Font;
MeasuredWidth := MyCanvas.TextWidth('the text to measure');
MyCanvas.Free;

Regards Jacco
0
 
EsopoAuthor Commented:
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.
0
Cloud Class® Course: Amazon Web Services - Basic

Are you thinking about creating an Amazon Web Services account for your business? Not sure where to start? In this course you’ll get an overview of the history of AWS and take a tour of their user interface.

 
sftwengCommented:
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".
0
 
ricswikaCommented:
I've gotten this error with poorly designed 3rd party components, by setting a property before Parent has been assigned. Tracing their code revealed an attempt to access the canvas, which won't work when is no parent control has been assigned. The lame workaround I found was to set Visible false when Parent isn't assigned, which prevented entering the branch of code that accesses the canvas.

This doens't sound like your probelm, but I thought I'd post this to possible help others.
0
 
EsopoAuthor Commented:
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?
0
 
sftwengCommented:
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.
0
 
EsopoAuthor Commented:
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?
0
 
sftwengCommented:
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.
0
 
EsopoAuthor Commented:
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?
0
 
sftwengCommented:
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.
0
 
Russell LibbySoftware Engineer, Advisory Commented:
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
0
 
EsopoAuthor Commented:
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.
0
 
sftwengCommented:
If you run the program in debug mode, you can open a Threads debug window.
0
 
Russell LibbySoftware Engineer, Advisory Commented:

Alan, nice to see you again ;-). (haven't seen you around recently.)

Kind Regards,
Russell
0
 
EsopoAuthor Commented:
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?
0
 
sftwengCommented:
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.
0
 
sftwengCommented:
Thanks, Russell, I've been pre-occupied with some personal issues, most of which are resolved.

Alan
0
 
Russell LibbySoftware Engineer, Advisory Commented:
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

0
 
EsopoAuthor Commented:
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.
0
 
EsopoAuthor Commented:
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?
0
 
Russell LibbySoftware Engineer, Advisory Commented:

Should have also mentioned that the clipped block of code comes from graphics.pas, my bad on that ;-)

Russell


0
 
Russell LibbySoftware Engineer, Advisory Commented:
Check the call stack to see where the call originated from...

Russell
0
 
EsopoAuthor Commented:
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.
0
 
EsopoAuthor Commented:
>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
0
 
Russell LibbySoftware Engineer, Advisory Commented:
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


0
 
EsopoAuthor Commented:
>check GetLastError
How do I do that?
0
 
Russell LibbySoftware Engineer, Advisory Commented:

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.

0
 
Russell LibbySoftware Engineer, Advisory Commented:
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


0
 
EsopoAuthor Commented:
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.
0
 
sftwengCommented:
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.
0
 
sftwengCommented:
What is unit ConnProp?
0
 
EsopoAuthor Commented:
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.
0
 
sftwengCommented:
Esopo, sometimes the best alternative is a redesign and rewrite. If that solves the problem, then move on. After having worked in this business for about 40 years, I'm convinced I'd have twice as many grey hairs if I had tried to solve every lurking problem. Hmm. not a bad idea actually - then I'd have almost twice as much hair. ;-)
0
 
EsopoAuthor Commented:
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.
0
 
sftwengCommented:
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.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 14
  • 11
  • 9
  • +2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now