2

There is a FRAME (not a form) and a thread. How to refer to Frame's control from the thread? For example I want to disable a button from a thread. But I don't have a pointer to the button, no global variables in the frame.

Thanks!

maxfax
  • 4,281
  • 12
  • 74
  • 120
  • You do it exactly the same way as you would without threading, except that you put everything that deals with the ListView control in one method without any parameters, and call it from your thread, as Synchronize(MethodName). – Warren P Aug 04 '11 at 23:42
  • If to use PostMessage how to set a handle of a frame? Thanks! – maxfax Aug 05 '11 at 02:00
  • 1
    When you create your thread, make a property in the thread object and set it `MyThread.NotificationHandle := MyForm.Handle;` – Warren P Aug 05 '11 at 02:12
  • @Warren P, thanks for help, +1!!! I know about a handle of forms. What about a handle of a frame? It is what I want to know. – maxfax Aug 05 '11 at 03:09
  • Did you even try to see if a Frame has a property called Handle? Come on MaxFax, don't be lazy! – Warren P Aug 05 '11 at 11:39
  • I know it exists :) I can do like this TFrame3(Owner).Handle, it works. I am just asking, I can be wrong :) – maxfax Aug 05 '11 at 19:19

2 Answers2

8

You should not in fact, call any method or modify any property of an VCL control at all, or anything visible to the user (the User interface of your application, which means VCL controls normally in Delphi, whether in a frame or not) directly from a background thread.

You can however send an event or notification to the main thread, either using PostMessage, or TThread.Synchronize or TThread.Queue.

Rather than having a reference to the frame or the control in your thread object, it might be better to just pass the handle of the form that contains your frame or other controls, to the thread, and use a user-message (WM_USER+10001) like this.

I prefer PostMessage to TTHread.Synchronize or Queue, because it's really simple and it works great. It's not exactly a cross-platform-friendly technique since it's tied to the Win32 API.

You should call synchronize like this:

  TMyThread = class(TThread)
  private
    FFrame: TFrame;
    ...
  public
    constructor Create(AFrame: TFrame); 
    ...
  end;

  constructor TMyThread.Create(AFrame: TFrame);
  begin
    FFrame := AFrame;
    inherited Create;
  end;

  // do not call directly, only using Synchronize
  procedure TMyThread.AMethodWithNoParameters; 
  begin
     FFrame.Button1.Enabled := not FBusy;
  end;

  procedure TMyThread.DoWork; // called from Execute.
  begin
    FBusy := true; 
    Synchronize(AMethodWithNoParameters);
    Sleep(100); //dummy;
    FBusy := false; 
    Synchronize(AMethodWithNoParameters);
  end;
Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146
Warren P
  • 65,725
  • 40
  • 181
  • 316
  • Many thanks!!! I use TThread.Synchronize. Ho to do Button1.Enable:=False using Synchronize? How to refer to the button? – maxfax Aug 04 '11 at 21:17
  • 1
    @maxfax Just expose to the thread those parts of the UI that need to be exposed. – David Heffernan Aug 04 '11 at 22:27
  • @David Heffernan, thanks and +1! Is it how? I couldn't find exactly about controls. – maxfax Aug 04 '11 at 22:55
  • what exactly I need is to set a text of items/subitems of a list view. – maxfax Aug 04 '11 at 23:07
  • @Warren P! Thanks for explanation! The most interesting for me and what I really asked about: "FUserInterfaceForm", it is easy for forms. What about a frame? A haven't somethings as Form's FUserInterfaceForm. How to refer to the frame? Thanks!!!! – maxfax Aug 05 '11 at 03:06
  • @maxfax - the easiest solution is to pass the frame instance into a constructor of TMyThread *you'll have to add a new constructor to your TMyThread object for that*. – Lieven Keersmaekers Aug 05 '11 at 06:03
  • @Lieven, please answer, I cannot imagine how to do this exactly – maxfax Aug 05 '11 at 06:38
  • 1
    @Warren P - I hope you don't mind me having added an example of a constructor passing in a Frame instance. – Lieven Keersmaekers Aug 05 '11 at 06:45
  • FFrame.Button1 - does not work! :( FFrame does not have those objects... FFrame isn't the frame that I need, not mine. – maxfax Aug 05 '11 at 07:47
  • Do I need to Create(AFrame: TFrame)? If to do this on fram's button click - ok. But if from procedure XXX; begin end; - how? – maxfax Aug 05 '11 at 08:34
  • maxfax you do have to learn a little Delphi on your own. Change TFrame to TThisIsTheClassOfMyFrame. Of course the simple TFrame class that comes with delphi doesn't have all your button1 objects etc. – Warren P Aug 05 '11 at 11:31
2

As quite rightly pointed out, you cannot call any members of any visual component in a background thread.

To disable the button from inside the thread code you have to have a reference to the button OR a reference to an event which you can assign the thread object - you can then fire the thread inside the queued or synchronized procedure, like so :-

    type
      test=class(tthread)
         ondisablebutton:tnotifyevent;

{...}

then, when in the procedure which you encapsulate with tthread.synchronize you can call the event, not forgetting to test if it is assigned....

procedure test.synchronisedprocedure;
begin
  if assigned(ondisablebutton) then
    ondisablebuttone(self);
end;

When you create the thread object you have designed, you then have to assign the ondisablebutton to a procedure of the form containing the button which looks like thus :-

procedure form1.threadwantstodisablebutton(sender:tobject);
begin
  button1.enabled:=false;
end;

your thread creation then needs an extra line :-

  mythread:=test.create;
  test.ondisablebutton:=form1.threadwantstodisablebutton;

like so, obviously you have to have access to form1 (or the form containing the button) where you are defining and creating your thread, which is not necessarily good design but it works.

paul
  • 21
  • 1