tags:

views:

621

answers:

6

Hi,

My application sits in the system tray when it's not being used.

The user can configure events to occur at particular schedule. For example they may way the task performed mon-fri at 5pm or every wednesday at 3pm or on the 16th of every month at 10am.

Assuming my delphi program is always running, it starts at boot-up, what is the best way in delphi to support the triggering of these scheduled events.

Obviously a TTimer can be used to schedule events based on elapsed time but they don't seem suited to this problem.

Cheers

+6  A: 

I would use the Microsoft Task Scheduler API for that:

http://msdn.microsoft.com/en-us/library/aa383614(VS.85).aspx

There are Delphi Wrappers available for the API if you don't want to do the "dirty work", but I don't know if there's a free one. You might have a look at

If you don't want to use the Microsoft Scheduler, there are things like the CronJob Component available here: http://www.appcontrols.com/components.html. It's shareware, too, but is easy to implement (just an onAlert event).

Mef
How would you let the Windows Task Scheduler trigger the execution of tasks in the already running executable?
mghie
Code the app in such a way that it only allows a single instance. If another instance is launched (which the Task Scheduler approach would attempt to do), this new instance can transfer the relevant information to the alreadu existing instance via some form of IPC (for example using SendMessage) before quitting. It probably doesn't make sense to run multiple instances of the app anyway, so nothing's really lost by adding such a limitation.
Michael Madsen
Hm ok, but it seems cumbersome and error prone solution. I would say that it really depends on the problem at hand. Windows scheduler may be good enough or even perfect solution, but in some cases it is completely inappropriate.
Runner
Well personally I would split my application into two executables: one that does nothing more than executing the tasks that users can define (and this application gets called by the scheduler) and one through which my users can add new tasks, that would be the one the OP constantly has running in his tray. Michaels suggestion is also a good idea, but imo too complicated to implement :-)
Mef
@Michael: That solution starts to look *really* complicated, which I think is a sure sign that the Windows Task Scheduler is just the wrong approach.
mghie
+1  A: 

You need a scheduling component. There are many available, however I can't seem to find any free one. You can always build one yourself, based on TTimer, or try to access theTask Scheduling API.

However, having a timer that executes a task every minute to check if a task is due, is much simpler.

Kornel Kisielewicz
+6  A: 

You can use my CRON compliant Cromis Scheduler. It even supports some things that cron does not. Interval based events for instance and from / to timeframe. I use it in a lot of my software and it has proven itself quite useful. It is free, very lightweight, works in threads and is production tested. If you need any further help just mail me.

The other ways would be:

  1. Use windows scheduling API as already suggested. But this may change between OS-es.
  2. Use JCL that has a scheduler unit (component in the JVCL), but I found that one hard to use from code directly. That is why I wrote my own.
Runner
+1  A: 

You could implement some kind of inter process communication in your program and trigger these events via ipc by a program called from the Windows scheduling service. This approach has the advantage that you don't need to write a user interface to set up the schedule and also that ipc might prove useful in other ways.

dummzeuch
A: 

Since your application is running, you can use the idle event to see if a preset date/time has already elapsed and if so then to perform your timing. The only side effect of this approach is that your event won't fire if the application is busy, but then neither would a timer (for the TTimer to work the message queue needs to be processed, or the application needs to be "Idle" unless you use a threaded timer).

uses
  ...,DateUtils; // contains IncMinute

type
  TForm1 = Class(TForm)
    :
    procedure FormCreate(Sender: TObject);
  private 
    fTargetDate : tDateTime;
    procedure AppIdle(Sender: TObject; var Done: Boolean);
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  fTargetDate := 0.0;
  Application.OnIdle := AppIdle;
  fTargetDate := IncMinute(Now,2);
end;

procedure TForm1.AppIdle(Sender: TObject; var Done: Boolean);
begin
  if (fTargetDate <> 0.0) and (Now >= fTargetDate) then
    begin
      fTargetDate := 0.0;
      ShowMessage('started');
    end;
 end;

EDIT If you have multiple times you need to run something, place in an ordered list by date/time and then only track the NEXT time something will run. As something is run, it is removed for the list (or rescheduled back into the list and the list re-sorted).

skamradt
Note that relying solely on `OnIdle` is dangerous, as this will be called only after the last queued message for one of the windows created by the VCL thread has been processed. If there are no messages, no idle processing takes place. If the application is minimized or has no visible windows and there are no broadcasted messages this could essentially mean never.
mghie
A: 

Another option would be to create a TThread which performs the management of the timer:

type
  TestThreadMsg = class(tThread)
  private
    fTargetDate : tDateTime;
    fMsg : Cardinal;
  protected
    procedure Execute; override;
    procedure NotifyApp;
  public
    constructor Create( TargetDate : TDateTime; Msg:Cardinal);
  end;

implementation:

constructor TestThreadMsg.Create(TargetDate: TDateTime; Msg: Cardinal);
begin
  inherited Create(True);
  FreeOnTerminate := true;
  fTargetDate := TargetDate;
  fMsg := Msg;
  Suspended := false;
end;

procedure TestThreadMsg.Execute;
begin
  Repeat
    if Terminated then exit;
    if Now >= fTargetDate then
      begin
        Synchronize(NotifyApp);
        exit;
      end;
    Sleep(1000); // sleep for 1 second, resoultion = seconds
  Until false;
end;

procedure TestThreadMsg.NotifyApp;
begin
  SendMessage(Application.MainForm.Handle,fMsg,0,0);
end;

Which can then be hooked up to the main form:

const
  WM_TestTime = WM_USER + 1;

TForm1 = Class(TForm)
  :
  procedure FormCreate(Sender: TObject);
  procedure WMTestTime(var Msg:tMessage); message WM_TestTime;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  TestThreadMsg.Create(IncSecond(Now,5),WM_TestTime);
end;

procedure TForm1.WMTestTime(var Msg: tMessage);
begin
  ShowMessage('Event from Thread');
end;
skamradt
Implementing a Thread for a TTimer? Doesn't create TTimer a Thread itself?? So there's no benefit compared to onTimer...
Mef
only this one isn't seconds based, it doesn't fire until a specific time is reached.
skamradt