Link to home
Start Free TrialLog in
Avatar of ChLa
ChLaFlag for United States of America

asked on

main loop ?

I have used While-Do loops for things that need to finish before anything else happens.
I am trying to use a loop to control a mode of the program. Depending on the mode in the program, a different loop would be run to look for certain conditions. Perhaps this was a bad idea.
It is interesting how a Delphi program can run without any main control loop, or main procedure. I am still adjusting to this. What I am trying to do is have several main procedures. Depending on the mode in the program, a unique main loop would run.
I was thinking it would be neater to have several smaller procedures, instead of putting everything into one big main procedure. The program has unique modes and it's operation is somehwat different in each mode.
What I discovered is that while a While-Do loop in running noting else can happen. The program more or less locks up until the loop is exited. On-Click events for example don't happen. Anything else inthe procedure before or after the loop doesn't happen until the loop is finished. I didn't expect this. I though it would run in parallel. I think I have done that with timer loops.
Is this just a bad programming idea ? Or is there some other type of loop I might use ? How do I make a loop run while everything else is still working ?
ASKER CERTIFIED SOLUTION
Avatar of Ephraim Wangoya
Ephraim Wangoya
Flag of United States of America 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 ChLa

ASKER

Application.ProcessMessages;  more or less works. I am getting flickering images. And the program won't close on it's own. I also found that the processes stop while a menu is dropped down. Interesting. Gives me some things to think about.
I've never used threads. Can you show me a simple example ?
Here is a simple example

it just adds numbers to a memo
unit Unit3;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TMyProcess = procedure(const Value: Integer) of Object;

  TMYThread = class(TThread)
  private
    FCurrent: Integer;
    FMax: Integer;
    FMyProcess: TMyProcess;
    procedure DoProcess;
  public
    constructor Create(const AMax: Integer);
    procedure Execute; override;
  end;

  TForm3 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    procedure DoProcess(const Value: Integer);
  public
    { Public declarations }
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}


{ TMYThread }

constructor TMYThread.Create(const AMax: Integer);
begin
  FreeOnTerminate := True;
  FMax := AMax;
  inherited Create(False)
end;

procedure TMYThread.DoProcess;
begin
  if Assigned(FMyProcess) then
    FMyProcess(FCurrent);
end;

procedure TMYThread.Execute;
var
  I: Integer;
begin
  while (FCurrent < FMax) and not Terminated do
  begin
    Inc(FCurrent);
    Synchronize(DoProcess);
    Sleep(10);  //just for effect
  end;
end;

{ TForm3 }

procedure TForm3.Button1Click(Sender: TObject);
var
  MyThread: TMyThread;
begin
  MyThread := TMYThread.Create(500);
  MyThread.FMyProcess := DoProcess;
end;

procedure TForm3.Button2Click(Sender: TObject);
begin
  ShowMessage('I can show dialog while thread is running');
end;

procedure TForm3.DoProcess(const Value: Integer);
begin
  Memo1.Lines.Add(IntToStr(Value));
end;

end.

Open in new window

Here are the files
Sample.zip
looks like you aren't sure anymore what the output of your program is
and probably not really sure about the flow either :)
Hey ChLa.

To me it looks you come from functional programming and just stepped into object oriented programming, is this correct?

Can you give a global idea of the functionality you want to achieve. What are the different loops for, what kind of conditions are you looking for?
Avatar of fromer
fromer

"It is interesting how a Delphi program can run without any main control loop, or main procedure."?

here is delphi's main loop...
 
procedure TApplication.Run;
begin
  FRunning := True;
  try
    AddExitProc(DoneApplication);
    if FMainForm <> nil then
    begin
      case CmdShow of
        SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized;
        SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
      end;
      if FShowMainForm then
        if FMainForm.FWindowState = wsMinimized then
          Minimize else
          FMainForm.Visible := True;
      repeat
        try
          HandleMessage;
        except
          HandleException(Self);
        end;
      until Terminated;
    end;
  finally
    FRunning := False;
  end;
end;

Open in new window


      repeat
        try
          HandleMessage;
        except
          HandleException(Self);
        end;
      until Terminated;

This block deals with the external messages (Windows messages), even deal with the exceptions you do not handle..






var
  terminateloop: boolean = False;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  terminateloop := True;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  condition: boolean;
begin
  condition := False;
  repeat
    //Code
    Application.ProcessMessages;
  until (condition) or (terminateloop) or (ect);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  condition: boolean;
begin
  condition := False;
  while (not condition) or (not terminateloop) do
  begin
    //Code
    Application.ProcessMessages;
  end;
end;

Open in new window


As ewangoya suggested, perhaps the best solution to your problem is to run your loops in a thread (assuming the code you want to run is thread safe)?, this way the application's main loop should still work as normal.


What I discovered is that while a While-Do loop in running noting else can happen.

What did you expect?  Were you expecting to enter one While . . . do loop and then enter each of the others while that first one (and all of the others) was looping?  

Unless you process the Windows Messages somewhere in the loop, you have a close circle (i.e. a loop) that executes until the While condition is met.  

Depending on the mode in the program
If you want to, in effect, select which loop processes, how are you indicating that selection (i.e. the "mode in the program")?  Are you passing a commandline parameter or what?  Whatever the mechanism, you can either put it in the "main loop" (ref: response #35735752) or you can have the "main loop" call a driver procedure that sets things up and uses the "mode in the program" to call an appropriate procedure to do the processing you desire.

Out of curiosity, what language did you used to use? ;-)

first, and that is good as well for ewangoya, you should not use ProcessMessages in loops as it will eat up your CPU. Yes, that is still better than nothing, messages will be treated but more or less your application will do nothing else.

You should use instead HandleMessage , as it is the case in the Run; loop. The major difference is all explained in Delphi help, but here it is : HandleMessage will treat ONE message at a time, and PUT THE APP IN IDLE MODE if none was found, until one message comes to the application. While ProcessMessages (with a plural 's') will treat ALL of the pending messages, but not go Idle after. It is more used generally sporadically in big long functions, to limit the 'freezing'

while condition do
 begin
   //do your process codes
   Application.HandleMessage;
 end;

second, as Diver, I would like to know what those 'modes' are about. If they are too different, you should consider create 2 applications. Or just maybe it would be enough to create two sets of Events for those responses that are different depending on the cases. Playing around with main application loop and windows messages in Delphi is better left to people who know what they are doing (I don't mean to be offending, that is just an emphasized advice), and when there is no other solution (or just not convenient). As you have seen already, it has side effects like the closing of the application, and that is just the tip of the iceberg of problems you'll end up dealing with.

Tell us more about your real need, and we will tell you which solution is best : threading, or switching events, or whatever else
@epasquier,

Please not, I used the phrase process the Windows Messages and NOT ProcessMessages. ;-)  

@Diver : I have noted, your comment was clear and exact, as usual.
@epasquier,

:-> ;-)
Avatar of ChLa

ASKER

I started Pascal with the orange cartridge...Action on the Atari 800.Transitioning to object oriented has not been without a few bumps. I don't program professionally, or full time. I do it 60% to solve unique personal problems, or to create special solutions for my business, and 40% for fun. I've written many successful programs in Delphi. For a few of these I used timers. For example, one program takes data from a Kurz thermo-anemometer via RS-485. Another grabs data once a second. I put a few things in those timers and it runs like a parallel process.Everything else still works and yet I have a small loop running and doing things. A few nights ago when i was thnking about how to continue with my program, it seemed a small loop would keep the program modular while doing things differently in each mode. It seemed like a good idea at the time and I was thinking a While-Do loop would run in parallel. It is great to be able to get some feedback on such things, instead of working in a vacuum.
Now I think it was a dumb idea and I know I can do what i want with events. It's just a different way of thinking. I usually find the Delphi help files are hard to use and don't tell me what I need to know. Usually when I use the Delphi help files it tells me how to do something in Visual Basic. Experts Exchange has saved me a lot of time and enabled me to be very successful. And of course I am always learning. I should close this question now. I am not sure if something like this helps others, or embarasses me. I haven't yet not tried not to published one of my questions. I have been assuming that someone edits them and removes the bad ones. I think this is probably a bad one. Better to look stupid than not to learn, or continue with a bad idea.
The more I program with Delphi, the more amazing it seems. Event programming works. But it's really different.
Actually, i got into Delphi by accident. I wanted to order Borland C++. At the time I had only used C. They were out of C and mentioned Delphi. When I heard it was Pascal I got it.
Avatar of ChLa

ASKER

My recent applications:
1. Copies every other file from one directory to the other. I use it with 7000 files at a time. It has a left and right, source and destination filelist boxes. It lets me see source and destination directories at the same time.
2. Renames files to have same a chosen name and extension, but with an incrementing padded number.
First creates a 30 FPS file stream from a 60., Second renames so they are sequential, otherwise premier couldn't deal with it.
The success of these two, first time I have deal with files, made me realize I need a program for putting away images. I have files of 200 images. I take some and put them in one folder, and some into another. I have folders for different subjects. Because I deal with so many files and it is so time consuming, I usually just add a leetter or two so it will go into the directory. For some folders the files have the same name and a padded number. Then I have to rename each file before moving it to the new folder. The first idea for the third program is it would show me the image of the first and last selected file, so I could easily edit my selection, I can click onthe last file inthe destination and it puts the name and next number into the name and starting number, and padding, so the named files can be automatically renamed, and then moved to the destination directory.
Then I realized how easy it is to add the functions of the last two programs, and more. When it copies it won't change the creation date. It can rename and number files onthe right also. So I could take a folder with 500 pictures of raccoons and name them Raccoon0001 through Raccoon0500, leaving the extension as it is. I can take a messy folder, sort by creation date, and then rename them, then add to them.
Generally when i write programs for myself, I make them easy for me to use and failsafe. This is why i have changed the names of buttons, in the past. The modes of this program are copy selected from left to right, move selected left to right, rename selected on right, rename selected on left, create folder, and create 3-D folder group, In each mode I would want things to go a bit differently.
But I can definently do this without needing a loop. It was just an idea.
Can i chose not to publish all this ?
Avatar of ChLa

ASKER

The interesting thing is that with a program like this, there is always a user input before something new is to happen. So it is easy to make things happen when desired. But what if there are no human events ?
ChLa :
No you didn't look stupid. I think most experts here where puzzled by your approach of the problem, they didn't know what you really wanted to do so they tried to answer how to solve your 'faulty' approach.
But at least you thought of some original way to solve a problem, you were not afraid to look at something mechanism you are not too familiar with, and that kind of thinking is what makes a good programmer. Some times when searching, you go in the wrong direction and you have to recognize that and go back to continue your search without looking back. Again, what you say about your question and how we answered is not stupid, it just show us that we helped you see that your original idea was not the best option considering Delphi philosophy.

I'm sure you have learned a few things already :
a) technically, Delphi is event (or message) driven, and those events NEED to be treated, so the main Loop that do that is paramount to Delphi applications functioning properly, even if most Delphi programmers do not even have to know anything about it, to make excellent, complex applications.
b) about ExpertExchange, the more you talk about your needs and not your imagined solution, the more and quicker we can find what is the best way to achieve your goals
About your application 'modes', the easiest way would be to put a radiogroup to let you choose the mode, and when an event occurs that could be handled differently depending of it, you test the selected index of the radiogroup and do the switching.
At least that is the easiest way of doing so.

But what if there are no human events ?
There HAS TO BE something that happened before that leads your program toward the 'correct' action. So when that event occurs, you put a flag somewhere and act differently on subsequent events.
In fact, what triggers the switching is not different from the user selecting the mode. You would even often create your 'automated' application first as if it was a fully interactive application, and only then you 'simulate' user actions when triggers occurs. By that, I mean for example that it is the APPLICATION that will set the selected index in your Mode radiogroup. All of the application will remain untouched, and will not bother if the radio group has been humanly selected or by a programmed event. And with that approach, you will end up with an automated application (maybe your main goal) that can also be used manually if something went wrong during automated process (fallback use of the application). That is also easier to debug and to improve your app functionalities, by allowing unitary, step by step, testing
Avatar of ChLa

ASKER

I am now using  Application.ProcessMessages.
It seems odd that items inside a procedure, before a while-do, do not show until after the loop is done. I wanted to have the message "Working...Please Stand By" show as the loop started, and then "Done" when it is finished. It was most convenient to have it inside the procedure. By following the line with Application.ProcessMessages, the message shows up before the loop starts. So it is very useful. I am awarding points to ewangoya for his quick reply and useful line.
Thank you.
using application.processmessages may cause your app to act totally different
here is a sample

set
timer1 to 600
timer2 to 700
and timer3 to 800

without application.processmessages
flow : timer1, timer2, timer3

with application.processmessages
flow : timer3, timer2, timer1



unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons, ExtCtrls;

type
  TfrmTestEE = class(TForm)
    btnStart: TButton;
    btnStop: TBitBtn;
    timerJob1: TTimer;
    timerJob2: TTimer;
    timerJob3: TTimer;
    memInfo: TMemo;
    chkDoProcess: TCheckBox;
    procedure timerJob1Timer(Sender: TObject);
    procedure btnStartClick(Sender: TObject);
    procedure chkDoProcessClick(Sender: TObject);
  private
    fStep: Integer;
    fDoProcess: Boolean;
    procedure SetStep(const Value: integer);
    procedure AddMsg(Msg: string);
  protected
    property Step: integer read fStep write SetStep;
    property DoProcess: Boolean read fDoProcess write fDoProcess;
  public
    constructor Create(AOwner: TComponent); override;
  end;

var
  frmTestEE: TfrmTestEE;

implementation

uses DateUtils;

{$R *.dfm}

procedure TfrmTestEE.btnStartClick(Sender: TObject);
begin
  timerJob1.Enabled := False;
  timerJob2.Enabled := False;
  timerJob3.Enabled := False;
  timerJob1.Enabled := True;
  timerJob2.Enabled := True;
  timerJob3.Enabled := True;
end;

procedure TfrmTestEE.chkDoProcessClick(Sender: TObject);
begin
  fDoProcess := chkDoProcess.Checked;
end;

constructor TfrmTestEE.Create(AOwner: TComponent);
begin
  inherited;
  fStep := 0;
  fDoProcess := chkDoProcess.Checked;
end;

procedure TfrmTestEE.AddMsg(Msg: string);
begin
  memInfo.Lines.Add(Msg);
end;

procedure TfrmTestEE.SetStep(const Value: integer);
begin
  fStep := Value;
  AddMsg(Format('Step %d', [fStep]));
end;

procedure TfrmTestEE.timerJob1Timer(Sender: TObject);
  procedure WaitSecs(Secs: Integer);
  var time: TDateTime;
  begin
    time := now;
    repeat
      Sleep(100);
      if DoProcess then
        Application.ProcessMessages;
    until Abs(SecondsBetween(Now, time)) > Secs;
  end;
var
  I: Integer;
begin
  TTimer(Sender).Enabled := False;
  try
    for I := 1 to 3 do
    begin
      AddMsg(Format('Start phase %d timer job Start step %s : %d', [I, TTimer(Sender).Name, Step]));
      Step := Step +1;
      WaitSecs(3);
      Step := Step +1;
      AddMsg(Format('End phase %d timer job Start step %s : %d', [I, TTimer(Sender).Name, Step]));
    end;
  finally
    //TTimer(Sender).Enabled := True; // don't restart
  end;
end;

end.

Open in new window