Solved

Reminder with delphi

Posted on 2006-11-07
5
731 Views
Last Modified: 2010-04-05
Hi,
is it possible to write a reminder with delphi. It will work like this. user will create reminders like
12/11/2006  12:12:25  Meeting with Rob
and all these will be kept in the DB.
When the time comes, the system will pop up and will remind the user.

ignore the db implementation, all i need is the timer-schedule module.
Thanks!
0
Comment
Question by:bilgehanyildirim
5 Comments
 
LVL 9

Expert Comment

by:sun4sunday
Comment Utility
You can use a timer and trigger the query in a specific time intervel and check any reminder exists. If exists pop up the reminder.
Make sure that all the reminders are set in a time interval of few minutes. Atleast 10 or 15 or 30 minutes. (don't set the timer for the user in seconds) If you are providing the time(split into 30 minutes), then you have to do a timer with 30 minutes time interval and when it reach 30 minutes do the query and reset the timer.


If you really want the time interval very less like (1 minutes) store one hour reminders in locally (may be in array) and check the array each minutes. Once one hour reach, do the query and fill the next hours reminder. Also when any reminder add/edit, query the reminder for that hour to refelct the updates of the reminder

sun4sunday
0
 
LVL 17

Expert Comment

by:Wim ten Brink
Comment Utility
You might just add a task to Windows Task Scheduler. See http://windowssdk.msdn.microsoft.com/en-us/library/ms725268.aspx for a start. The API for Delphi can be found at http://cc.borland.com/Item.aspx?id=16007 in case you think it is a usable solution.
The other solution is indeed to use a timer, checking at regular intervals if it's about time or not. In general, you can check every second if you like, to get your triggered at exactly the right time, but in that case you have to quickly check the time in your code and exit the code if it's not yet time immediately. Else it will eat too many precious clock cycles.

To check for the proper time, retrieve the earliest time from your database and keep comparing to that time. Don't start checking for any of the other times!!! At the beginning of your application, read the earliest reminder and store it in-memory. Then you can close the database connection as far as the application is concerned since you won't need further access until that time has been reached. Once the time is reached, determine the next earliest reminder and start processing the current reminder. (Open and close the database if need be.) Once the current reminder is processed, your application can wait until it's time for the next reminder.

If you add reminders to the database, you might want to send a message to your application that it should update the earliest reminder time that it has stored. Yet whatever you do, avoid database access in your timer event since it will slow down your process enormously.

If done correctly, you can check every second and still use 0% of the CPU according to the task manager. If you keep checking with the database, your CPU usage might be around the 10% all the time, unless you check a lot less often...
0
 
LVL 11

Expert Comment

by:calinutz
Comment Utility
As for the implementation of the timer module I guess you would have to do tne next steps:
declare variables for the on timer event:
var
 h,m,s,ms:Word;
begin
// here check the current time:
DecodeTime(Now,h,m,s,ms);

// all you need to do next is to retrieve from the database the times that fit the current time and execute the reminders.
retrieval from database could look like:
Select * from remindertable where hour(remtimer)=h and minute(remtimer)=m
// or you could build the time using the variables and compare the time directly.
// the time can be built as string if you want to:
// timex:=IntToStr(h)+':'+IntToStr(m)+':'+IntToStr(s);


Or variations of this depending on the database used.
Also do not forget to place some Application.ProcessMessages in the timer, in order to unfreeze the application while stuff is executed.


regards
0
 
LVL 11

Expert Comment

by:calinutz
Comment Utility
Also you might be interested in using the nice conversion tools offered by delphi:
StrToTime(string)  -> time
TimeToStr(datetime) ->string

Also for the same purpose check the datetime routines of delphi. A lot of nice and handy functions there.
0
 
LVL 17

Accepted Solution

by:
TheRealLoki earned 500 total points
Comment Utility
Here's a simple demo I wrote for you using a thread and a list that can be loaded from any source.
All you need to do is populate the list as what happens in "Button1Click"
and to display a message just do so in the "Message_EventFired" procedure
If you wish to update the database that the event has ended/rearmed, you can do so in "ThreadScheduledEventChanged"
I am deliberately using a windows message for notification that the event has fired, so that it does not block the thread. I am also allowing the main list/DB to be updated by using a synchronized event so that the DB can be kept the same as the thread's list of events, at the same time.

hth, Loki


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Spin, StdCtrls, ComCtrls, contnrs, CheckLst;

const
  WM_EventFired = WM_user + 1;
  WM_EventChanged = WM_user + 2;

type TRearmUnitOfMesure = (ruom_Seconds, ruom_Minutes, ruom_Hours, ruom_Days);

type TScheduledEvent = class(TObject)
  public
    Description: string; // Description should be unique
    Enabled: boolean;
    StartTime: TDateTime;
    Rearm:  record
              Enabled: Boolean;
              Interval: integer;
              UnitOfMeasure: TRearmUnitOfMesure;
            end;
    Constructor Create;
    Destructor Destroy; override;
    procedure Duplicate(Source: TScheduledEvent);
  end;

type TScheduledEventList = class(TObject)
  private
    fItems: TObjectList;
    function GetItem(index: integer): TScheduledEvent;
    function GetCount: integer;
  public
    property Item[index: integer]: TScheduledEvent read GetItem;
    property Count: integer read GetCount;
    function Add: TScheduledEvent;
    procedure Delete(index: integer);
    function IndexOfDescription(Description_: string): integer;
    Constructor Create;
    Destructor Destroy; override;
    procedure Duplicate(Source: TScheduledEventList);
  end;

type TOnScheduledEventChanged = procedure(Sender: TObject; OriginalDescription: string; ScheduledEvent: TScheduledEvent) of object;

type TScheduledEventsThread = class(TThread)
private
  HandleToSendMessagesTo: HWnd;
  EventList: TScheduledEventList;
  EventThatHasChanged: TScheduledEvent;
  fOnScheduledEventChanged: TOnScheduledEventChanged;
  OriginalDescription: string;
  procedure FireEvents;
// synchronized events
  procedure NotifyThatScheduledEventChanged;
public
  Constructor Create(HandleToSendMessagesTo_: HWnd; Source_ScheduledEventList: TScheduledEventList; OnScheduledEventChanged_: TOnScheduledEventChanged);
  procedure Execute; override;
end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    lDescription: TEdit;
    dtpStartDate: TDateTimePicker;
    dtpStartTime: TDateTimePicker;
    Label1: TLabel;
    cbRearm: TCheckBox;
    gbRearm: TGroupBox;
    rbRearm_Seconds: TRadioButton;
    rbRearm_Minutes: TRadioButton;
    rbRearm_Hours: TRadioButton;
    rbRearm_Days: TRadioButton;
    seRearm_Interval: TSpinEdit;
    Memo1: TMemo;
    cbEnabled: TCheckBox;
    CheckListBox1: TCheckListBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure cbRearmClick(Sender: TObject);
    procedure ThreadScheduledEventChanged(Sender: TObject; OriginalDescription: string; ScheduledEvent: TScheduledEvent);
  private
    { Private declarations }
  public
    { Public declarations }
    MainThreadEventList: TScheduledEventList;
    ScheduledEventsThread: TScheduledEventsThread;
    procedure DisplayEvents;
    Procedure Message_EventFired(var Msg:TMessage);Message WM_EventFired;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ TScheduledEvent }

constructor TScheduledEvent.Create;
begin
  inherited;
  Description := 'new event';
  Enabled := False;
  StartTime := Now + (1/24/60); // default to in 1 minute
  Rearm.Enabled := False;
  Rearm.Interval := 10;
  Rearm.UnitOfMeasure := ruom_Days
end;

destructor TScheduledEvent.Destroy;
begin
  inherited;

end;

procedure TScheduledEvent.Duplicate(Source: TScheduledEvent);
begin
  self.Enabled := Source.Enabled;
  self.Description := Source.Description;
  self.StartTime := Source.StartTime;
  self.Rearm.Enabled := Source.Rearm.Enabled;
  self.Rearm.Interval := Source.Rearm.Interval;
  self.Rearm.UnitOfMeasure := Source.Rearm.UnitOfMeasure;
end;

{ TScheduledEventList }

function TScheduledEventList.Add: TScheduledEvent;
begin
  result := TScheduledEvent.Create;
  fItems.Add(result);
end;

constructor TScheduledEventList.Create;
begin
  inherited;
  fItems := TObjectList.Create;
end;

procedure TScheduledEventList.Delete(index: integer);
begin
  fItems.Delete(index);
end;

destructor TScheduledEventList.Destroy;
begin
  fItems.Clear;
  fItems.Free;
  inherited;
end;

procedure TScheduledEventList.Duplicate(Source: TScheduledEventList);
var
  i: integer;
begin
  fItems.Clear;
  for i := 0 to pred(Source.Count) do
    with self.Add do Duplicate(Source.Item[i]);
end;

function TScheduledEventList.GetCount: integer;
begin
  result := fItems.Count;
end;

function TScheduledEventList.GetItem(index: integer): TScheduledEvent;
begin
  result := TScheduledEvent(fItems[index]);
end;

function TScheduledEventList.IndexOfDescription(Description_: string): integer;
var
  i: integer;
begin
  result := -1;
  i := 0;
  while (result = -1) and (i < self.Count) do
  begin
    if Item[i].Description = Description_ then
      result := i
    else inc(i);
  end;
end;

{ TScheduledEventsThread }

constructor TScheduledEventsThread.Create(HandleToSendMessagesTo_: HWnd; Source_ScheduledEventList: TScheduledEventList; OnScheduledEventChanged_: TOnScheduledEventChanged);
begin
  inherited Create(true); // create in a suspended state
  HandleToSendMessagesTo := HandleToSendMessagesTo_;
  fOnScheduledEventChanged := OnScheduledEventChanged_;
  EventList := TScheduledEventList.Create;
  EventList.Duplicate(Source_ScheduledEventList);
  FreeOnTerminate := True;
  Resume;
end;

procedure TScheduledEventsThread.Execute;
var
  i: integer;
  NextTime: TDateTime;
begin
  while not Terminated do
  begin
// determine soonest event
    NextTime := 0;
    for i := 0 to pred(Eventlist.Count) do
    begin
      if EventList.Item[i].Enabled then
      begin
        if ( (NextTime = 0) or (EventList.Item[i].StartTime < NextTime) ) then
          NextTime := EventList.Item[i].StartTime;
      end;
    end;
    if (NextTime = 0) then Sleep(500) // just wait half a second
    else
    begin
      while ( (not Terminated) and (Now < NextTime) ) do
      begin
        sleep(500); // wait for half a second...
      end;
      if ( (not Terminated) and (Now > NextTime) ) then
      begin // an event is ready!
        FireEvents;
      end;
    end;
  end;
end;

procedure TScheduledEventsThread.FireEvents;
var
  i: integer;
  PS: PString;
begin
  for i := 0 to pred(Eventlist.Count) do
  begin
    if ( (EventList.Item[i].Enabled) and (EventList.Item[i].StartTime < Now) ) then
    begin
        New(PS);
        PS^ := EventList.Item[i].Description;
        PostMessage(HandleToSendMessagesTo, WM_EventFired, Integer(PS), i);
        if EventList.Item[i].Rearm.Enabled then
        begin
          case EventList.Item[i].Rearm.UnitOfMeasure of
            ruom_Seconds: EventList.Item[i].StartTime := EventList.Item[i].StartTime + (1/24/60/60 * EventList.Item[i].Rearm.Interval);
            ruom_Minutes: EventList.Item[i].StartTime := EventList.Item[i].StartTime + (1/24/60 * EventList.Item[i].Rearm.Interval);
            ruom_Hours:   EventList.Item[i].StartTime := EventList.Item[i].StartTime + (1/24 * EventList.Item[i].Rearm.Interval);
            ruom_Days:    EventList.Item[i].StartTime := EventList.Item[i].StartTime + (1 * EventList.Item[i].Rearm.Interval);
          end;
        end
        else
        begin
          EventList.Item[i].Enabled := False;
        end;
{ now notify by synchronize that this event has changed.
This is so that both the main vcl and the threads list ae identical as often as possible
We are passing the "Original Description" in case the thread event has changed it,
although this does not actually happen in this demo.
It allows the main thread to locate the Event that has changed easily.}
        OriginalDescription := EventList.Item[i].Description;
        EventThatHasChanged := TScheduledEvent.Create;
        EventThatHasChanged.Duplicate(EventList.Item[i]);
        Synchronize(NotifyThatScheduledEventChanged);
        EventThatHasChanged.Free;
    end;
  end;
end;

procedure TScheduledEventsThread.NotifyThatScheduledEventChanged;
begin
  if assigned(self.fOnScheduledEventChanged) then
    fOnScheduledEventChanged(self, OriginalDescription, EventThatHasChanged);
end;

{ TForm1 }

procedure TForm1.Message_EventFired(var Msg: TMessage);
var
  PS:PString;
  S: string;
  index: integer;
begin
  PS:=Pointer(Msg.WParam);
  S := PS^;
  index := Msg.LParam;
  Dispose(PS);

  Memo1.Lines.Add(DateTimeToStr(now) + ' - Event Fired! - ' + S + ' for ' +
    DateTimeToStr(MainThreadEventList.Item[index].StartTime));

end;

procedure TForm1.FormCreate(Sender: TObject);
var
  defaultstart: TDateTime;
begin
  MainThreadEventList := TScheduledEventList.Create;
  defaultstart := now + (1/24/60); // now + 1 minute
  dtpStartDate.Date := Trunc(defaultstart);
  dtpStartTime.Time := Frac(defaultstart);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if assigned(ScheduledEventsThread) then
  begin
    ScheduledEventsThread.Terminate;
    ScheduledEventsThread.WaitFor;
    ScheduledEventsThread := nil;
  end;
  MainThreadEventList.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  with MainThreadEventList.Add do
  begin
    Description := lDescription.Text;
    Enabled := cbEnabled.Checked;
    StartTime := Trunc(dtpStartDate.Date) + Frac(dtpStartTime.Time);
    Rearm.Enabled := cbRearm.Checked;
    Rearm.Interval := seRearm_Interval.Value;
    if rbRearm_Seconds.Checked then Rearm.UnitOfMeasure := ruom_Seconds
    else if rbRearm_Minutes.Checked then Rearm.UnitOfMeasure := ruom_Minutes
    else if rbRearm_Hours.Checked then Rearm.UnitOfMeasure := ruom_Hours
    else if rbRearm_Days.Checked then Rearm.UnitOfMeasure := ruom_Days
  end;
// when changing events from the main vcl, we stop the thread and give it the entire list again
  if assigned(ScheduledEventsThread) then
  begin
    ScheduledEventsThread.Terminate;
    ScheduledEventsThread.WaitFor;
    ScheduledEventsThread := nil;
  end;
  ScheduledEventsThread := TScheduledEventsThread.Create(Form1.Handle, MainThreadEventList, Form1.ThreadScheduledEventChanged);
  DisplayEvents;
end;

procedure TForm1.cbRearmClick(Sender: TObject);
begin
  gbRearm.Visible := cbRearm.Checked;
end;

procedure TForm1.ThreadScheduledEventChanged(Sender: TObject;
  OriginalDescription: string; ScheduledEvent: TScheduledEvent);
var
  index: integer;
begin
  index := MainThreadEventList.IndexOfDescription(OriginalDescription);
  MainThreadEventList.Item[index].Duplicate(ScheduledEvent);
  if MainThreadEventList.Item[index].Enabled then
    Memo1.Lines.Add(DateTimeToStr(now) + ' - ' + originalDescription + ' rearmed to ' +
      DateTimeToStr(MainThreadEventList.Item[index].StartTime))
  else
    Memo1.Lines.Add(DateTimeToStr(now) + ' - ' + originalDescription + ' disabled');

  DisplayEvents;
end;

procedure TForm1.DisplayEvents;
var
  i: integer;
begin
  CheckListbox1.Items.BeginUpdate;
  try
    CheckListbox1.Items.Clear;
    for i := 0 to pred(MainThreadEventList.Count) do
    begin
      CheckListbox1.Items.Add(DateTimeToStr(MainThreadEventList.Item[i].StartTime) + ' - ' +
        MainThreadEventList.Item[i].Description);
      CheckListbox1.Checked[pred(CheckListbox1.Items.Count)] :=
        MainThreadEventList.Item[i].Enabled;
    end;
  finally
    CheckListbox1.Items.EndUpdate;
  end;
end;

end.


***************************************
** FORM FOLLLOWS
***************************************

object Form1: TForm1
  Left = 249
  Top = 107
  Width = 696
  Height = 480
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  PixelsPerInch = 96
  TextHeight = 13
  object Label1: TLabel
    Left = 8
    Top = 20
    Width = 53
    Height = 13
    Caption = 'Description'
  end
  object Button1: TButton
    Left = 24
    Top = 224
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
  object lDescription: TEdit
    Left = 80
    Top = 20
    Width = 121
    Height = 21
    TabOrder = 1
    Text = 'new event'
  end
  object dtpStartDate: TDateTimePicker
    Left = 80
    Top = 48
    Width = 89
    Height = 21
    CalAlignment = dtaLeft
    Date = 39029.4282815509
    Time = 39029.4282815509
    DateFormat = dfShort
    DateMode = dmComboBox
    Kind = dtkDate
    ParseInput = False
    TabOrder = 2
  end
  object dtpStartTime: TDateTimePicker
    Left = 180
    Top = 48
    Width = 97
    Height = 21
    CalAlignment = dtaLeft
    Date = 39029.4282815509
    Time = 39029.4282815509
    DateFormat = dfShort
    DateMode = dmComboBox
    Kind = dtkTime
    ParseInput = False
    TabOrder = 3
  end
  object cbRearm: TCheckBox
    Left = 80
    Top = 84
    Width = 57
    Height = 17
    Caption = 'Rearm'
    TabOrder = 4
    OnClick = cbRearmClick
  end
  object gbRearm: TGroupBox
    Left = 80
    Top = 104
    Width = 245
    Height = 65
    Caption = 'gbRearm'
    TabOrder = 5
    Visible = False
    object rbRearm_Seconds: TRadioButton
      Left = 8
      Top = 40
      Width = 73
      Height = 17
      Caption = 'Seconds'
      Checked = True
      TabOrder = 0
      TabStop = True
    end
    object rbRearm_Minutes: TRadioButton
      Left = 76
      Top = 40
      Width = 65
      Height = 17
      Caption = 'Minutes'
      TabOrder = 1
    end
    object rbRearm_Hours: TRadioButton
      Left = 140
      Top = 40
      Width = 57
      Height = 17
      Caption = 'Hours'
      TabOrder = 2
    end
    object rbRearm_Days: TRadioButton
      Left = 192
      Top = 40
      Width = 49
      Height = 17
      Caption = 'Days'
      TabOrder = 3
    end
    object seRearm_Interval: TSpinEdit
      Left = 8
      Top = 16
      Width = 57
      Height = 22
      MaxValue = 0
      MinValue = 0
      TabOrder = 4
      Value = 10
    end
  end
  object Memo1: TMemo
    Left = 24
    Top = 292
    Width = 625
    Height = 121
    Font.Charset = ANSI_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'Courier New'
    Font.Style = []
    ParentFont = False
    TabOrder = 6
  end
  object cbEnabled: TCheckBox
    Left = 80
    Top = 176
    Width = 69
    Height = 17
    Caption = 'Enabled'
    Checked = True
    State = cbChecked
    TabOrder = 7
  end
  object CheckListBox1: TCheckListBox
    Left = 340
    Top = 16
    Width = 309
    Height = 253
    Enabled = False
    Font.Charset = ANSI_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'Courier New'
    Font.Style = []
    ItemHeight = 14
    ParentFont = False
    TabOrder = 8
  end
end
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
When you create an app prototype with Adobe XD, you can insert system screens -- sharing or Control Center, for example -- with just a few clicks. This video shows you how. You can take the full course on Experts Exchange at http://bit.ly/XDcourse.

743 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

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now