Solved

Threading and 'Canvas does not allow drawing' exception.

Posted on 2006-11-23
3
564 Views
Last Modified: 2010-04-05
I must be missing something here.

I made the following small demo application just in case I was doing something wrong in the other, but seems that maybe my understanding of threads and events might be the issue.

basically what I am trying to do is make a client server application using indy tcpclient/server where on the client side I have a thread that is trying to read data from the server, and when success, fire an event letting the main vcl trhead of the application know that there is data available that can be read. so the client will read up the data and display it.

problem is that when firing the event, a 'Canvas does not allow drawing' exception is being thrown.

AFAIK, such events like the one in the demo app are executed from the main vcl thread. is this not so? because if I know right, I cannot explain why I am getting this exception.

The way I used to to these client/server app back in the old days was using a timer and readln(terminator,5); (checked my old sources to see how I done it) but it's just not elegant enough for me anymore :D

But since I have to move on with the project, I'll do it with the timer, but until then I'll start up a new thread about the best way to approach this.

So points go to the one who can explain why that exception is being thrown. (I repeat, indy solution goes to another thread)

code follows

unti1.dfm
object Form1: TForm1
  Left = 192
  Top = 114
  Width = 870
  Height = 640
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 280
    Top = 96
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
  object client: TIdTCPClient
    MaxLineAction = maException
    ReadTimeout = 0
    Host = 'localhost'
    Port = 488
    Left = 112
    Top = 104
  end
  object server: TIdTCPServer
    Active = True
    Bindings = <
      item
        IP = '0.0.0.0'
        Port = 488
      end>
    CommandHandlers = <>
    DefaultPort = 0
    Greeting.NumericCode = 0
    MaxConnectionReply.NumericCode = 0
    OnExecute = serverExecute
    ReplyExceptionCode = 0
    ReplyTexts = <>
    ReplyUnknownCommand.NumericCode = 0
    Left = 200
    Top = 112
  end
  object IdAntiFreeze1: TIdAntiFreeze
    IdleTimeOut = 50
    OnlyWhenIdle = False
    Left = 112
    Top = 64
  end
end


--------------------------------
unit1.pas
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, IdTCPServer, IdBaseComponent, IdComponent, IdTCPConnection,
  IdTCPClient, StdCtrls, unit2, IdAntiFreezeBase, IdAntiFreeze;

type
  TForm1 = class(TForm)
    client: TIdTCPClient;
    server: TIdTCPServer;
    Button1: TButton;
    IdAntiFreeze1: TIdAntiFreeze;
    procedure Button1Click(Sender: TObject);
    procedure serverExecute(AThread: TIdPeerThread);
  private
    { Private declarations }
    r:TReader;
    procedure OnCommandAvailable(Sender:TObject; name:string);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  client.connect;
  r:=treader.create(client);
  r.OnCommandAvailable:=OnCommandAvailable;
  client.WriteLn('test string');
end;

procedure TForm1.OnCommandAvailable(Sender: TObject; name: string);
begin
  showmessage(name+' received.');
  showmessage(client.readln+' read.');
end;

procedure TForm1.serverExecute(AThread: TIdPeerThread);
begin
  AThread.Connection.WriteLn('something else');
end;

end.


---------------------

unit2.pas
unit Unit2;

interface

uses
  Classes, IdTcpConnection;

type
  TOnCommandAvailable=procedure(Sender:TObject; name:string) of object;

  TReader = class(TThread)
  private
    FOnCommandAvailable: TOnCommandAvailable;
    con:TIdTcpConnection;
    { Private declarations }
  public
    constructor create(c:TIdTcpConnection); reintroduce;
  protected
    procedure Execute; override;
  published
    property OnCommandAvailable:TOnCommandAvailable read FOnCommandAvailable write FOnCommandAvailable;
  end;

implementation

{ Important: Methods and properties of objects in visual components can only be
  used in a method called using Synchronize, for example,

      Synchronize(UpdateCaption);

  and UpdateCaption could look like,

    procedure Reader.UpdateCaption;
    begin
      Form1.Caption := 'Updated in a thread';
    end; }

{ Reader }

uses sysutils;

constructor TReader.create(c: TIdTcpConnection);
begin
  inherited Create(false);
  FreeOnTerminate:=true;
  con:=c;
end;

procedure TReader.Execute;
var s:string;
begin
  { Place thread code here }
  try
    while not terminated do
    begin
      s:=con.ReadLn(' ');
      if assigned(OnCommandAvailable) then
        OnCommandAvailable(self, s);
    end;
  except
    on e:exception do
    begin
      s:=e.message;  // put the breakpoint here.
      OnCommandAvailable(self, s);
    end;
  end;
end;

end.
 

0
Comment
Question by:2266180
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 2
3 Comments
 
LVL 21

Accepted Solution

by:
ziolko earned 125 total points
ID: 18003875
if assigned(OnCommandAvailable) then
        OnCommandAvailable(self, s);

is called in thread's Execute so this event is triggered by thread, it's the same as calling ShowMessage() inside Execute,
pass to TReader main form's handle instead event handler and use PostMessage()
is the first solution that comes into my mind.

ziolko.
0
 
LVL 28

Author Comment

by:2266180
ID: 18003917
indeed, that works.
so actually the event is being executed in the context of the thread that called the event and not the main vcl. Makes sense especially since you can make an app with no vcl in it. don't know why it was the othre way around in my head. now that I think of how it could be implemented in a generic class, it looks stupid.
oh well, thanks for the heads up :)

0
 
LVL 21

Expert Comment

by:ziolko
ID: 18006730
event is executed by thread because:
r.OnCommandAvailable:=OnCommandAvailable;
is nothing more than copying pointer to class procedure, so

if assigned(OnCommandAvailable) then
        OnCommandAvailable(self, s);

executes procedure at given pointer which still belongs to vcl thread.

ziolko.
0

Featured Post

Free Tool: Subnet Calculator

The subnet calculator helps you design networks by taking an IP address and network mask and returning information such as network, broadcast address, and host range.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
CheckListBox usage 3 96
update joined tables 2 72
DBCtrlGrid, Delphi, Scroll 8 82
How to save the image in the .cds File ClientDataSet? 1 40
In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Exchange organizations may use the Journaling Agent of the Transport Service to archive messages going through Exchange. However, if the Transport Service is integrated with some email content management application (such as an antispam), the admini…

739 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question