gspears060598
asked on
Accessing Outlook Calendar from Delphi
Hello,
I have a small app that worked fine in Delphi5. I have ported it to TurboDelphi, and the routine that accesses the appointments in Outlook Calendar no longer works. Specifically, the call to the Item.GetNext for Outlook generates an EOleSysError with an errorcode of 1. try..except blocks DO NOT trap the error. I have gotten the following explanation, but I am having difficulty implementing it....
"Delphi freaks out if anything other than S_OK is returned by a COM method. In this case the error is non-critical (>0) and should be ignored. There are a few other places in OOM that cause the same problem in Delphi:
MailItem.Unread, MailItem.Move, etc.
Normally, you could just wrap the call in to a try..except block, but the exception will prevent you from getting the return value. Either import the type library without using the safecall convention (you would need to handle all HResults explicitly) or call IDspatch.GetIDsOfNames/Inv oke explicitly."
I will provide sample code. 500 points to anyone who can make it work without generating an untrappable error. I would ideally like to eliminate variants as well if possible, as they are really slow. I would prefer not to use the sagecall/HResults approach as there are other portions of code that access Outlook as well, and I don't want to have to change this app any more than I have to....
-------------------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----
procedure TForm1.Button4Click(Sender : TObject);
var
i: Integer;
Outlook, Namespace, Appointment, Calendar: variant;
Created: Boolean;
Accept: Boolean;
MyItems: Variant;
begin
Created := False;
try
Outlook := GetActiveOleObject('outloo k.applicat ion');
except
Outlook := CreateOleObject('outlook.a pplication ');
Created := True;
end;
Namespace := Outlook.GetNamespace('MAPI ');
Calendar := Namespace.GetDefaultFolder (olFolderC alendar);
try
Appointment := Calendar.Items.GetFirst;
except
Memo1.Lines.Add('No appointments found');
end;
while VarIsEmpty(Appointment) = False do
begin
Memo1.Lines.Add(Appointmen t.Subject) ;
try
Appointment := Calendar.Items.GetNext; // THIS LINE errors out when no more appts exists
except
on E: EOleSysError do
begin
if E.Errorcode > 0 then
ShowMessage(IntToStr(E.Err orCode));
end;
end;
end;
if Created then
Outlook := unassigned;
I have a small app that worked fine in Delphi5. I have ported it to TurboDelphi, and the routine that accesses the appointments in Outlook Calendar no longer works. Specifically, the call to the Item.GetNext for Outlook generates an EOleSysError with an errorcode of 1. try..except blocks DO NOT trap the error. I have gotten the following explanation, but I am having difficulty implementing it....
"Delphi freaks out if anything other than S_OK is returned by a COM method. In this case the error is non-critical (>0) and should be ignored. There are a few other places in OOM that cause the same problem in Delphi:
MailItem.Unread, MailItem.Move, etc.
Normally, you could just wrap the call in to a try..except block, but the exception will prevent you from getting the return value. Either import the type library without using the safecall convention (you would need to handle all HResults explicitly) or call IDspatch.GetIDsOfNames/Inv
I will provide sample code. 500 points to anyone who can make it work without generating an untrappable error. I would ideally like to eliminate variants as well if possible, as they are really slow. I would prefer not to use the sagecall/HResults approach as there are other portions of code that access Outlook as well, and I don't want to have to change this app any more than I have to....
--------------------------
procedure TForm1.Button4Click(Sender
var
i: Integer;
Outlook, Namespace, Appointment, Calendar: variant;
Created: Boolean;
Accept: Boolean;
MyItems: Variant;
begin
Created := False;
try
Outlook := GetActiveOleObject('outloo
except
Outlook := CreateOleObject('outlook.a
Created := True;
end;
Namespace := Outlook.GetNamespace('MAPI
Calendar := Namespace.GetDefaultFolder
try
Appointment := Calendar.Items.GetFirst;
except
Memo1.Lines.Add('No appointments found');
end;
while VarIsEmpty(Appointment) = False do
begin
Memo1.Lines.Add(Appointmen
try
Appointment := Calendar.Items.GetNext; // THIS LINE errors out when no more appts exists
except
on E: EOleSysError do
begin
if E.Errorcode > 0 then
ShowMessage(IntToStr(E.Err
end;
end;
end;
if Created then
Outlook := unassigned;
ASKER
That is a fair question....
I know that I have an appointment. The problem is that the COM interface appears to have changed somewhere between D7 and TurboDelphi. The Items.GetNext will fail when there is no next item/appointment available, and this failure is NOT caught within a try...except block. I cannot simply count the appointments and use a for...next loop because while this will work in the code shown, this will NOT work when you sort/restrict the appointments. For example, if I want all appointments between T1 and T2, (dates) sorted by start date, INCLUDING RECURRING APPOINTMENTS, then the count ALWAYS returns 2 Billion (whatever that specific maxint value is.) This is known, expected behaviour when you use the "include recurring" functionality.
I have been able to get this to work. Here is the code. I will withdraw this question.
In the following code, PM1 is a memobox....
procedure TForm1.BitBtn1Click(Sender : TObject);
var
Outlook, Namespace, Appointment, Calendar: variant;
Recip: Variant; // MultiCalendar Access
fldr: Variant; // MultiCalendar Access
Created: Boolean;
MyItems, ItemCollection: Variant;
Msg: string;
DateRangeStart, DateRangeEnd: TDateTime;
Save_Cursor: TCursor;
RestrictDateFilter: string;
FilterStartDate: string;
FilterEndDate: string;
CurrentDate: TDateTime;
ApptDate: TDateTime;
WeekDayNo: Integer;
StartDateYear, EndDateYear: string;
NeedDateHeader: Boolean;
RP: OLEVariant;
RecurType: string;
sd, ed: string;
T1, T2: DWord;
begin
if MessageDlg('This will dump the raw Outlook appointments to the log. Continue?',
mtConfirmation, [mbYes, mbNo], 0) = mrNo then
exit;
// Get the dump date range....
DateRangeStart := trunc(now - 365);
DateRangeEnd := trunc(now + 365);
sd := DateToStr(DateRangeStart);
ed := DateToStr(DateRangeEnd);
if InputQuery('Debug Dump', 'Start date:', sd) = False then exit;
if InputQuery('Debug Dump', 'End date:', ed) = False then exit;
try
DateRangeStart := StrToDate(sd);
DateRangeEnd := StrToDate(ed);
except
MessageDlg('You have entered an invalid date. Please check the date format', mtError, [mbok], 0);
exit;
end;
MessageDlg('NOTE: This will probably take 15 to 20 seconds.... please be patient....',
mtInformation, [mbOK], 0);
// If here, then we are ready to go....
PageControl.ActivePage := LogTab;
PM1.Clear;
////// START OUTLOOK ////////////////////////// ////////// ////////// ////
try
Outlook := GetActiveOleObject('outloo k.applicat ion');
except
Outlook := CreateOleObject('outlook.a pplication ');
Created := True;
end;
Namespace := Outlook.GetNamespace('MAPI ');
Calendar := Namespace.GetDefaultFolder (olFolderC alendar);
if VarIsEmpty(OutLook) then
PM1.Lines.Add('Unable to access Outlook')
else
PM1.Lines.Add('Outlook accessed');
if VarIsEmpty(NameSpace) then
PM1.Lines.Add('Unable to access MAPI Namespace')
else
PM1.Lines.Add('MAPI Namespace accessed');
if VarIsEmpty(Calendar) then
PM1.Lines.Add('Unable to access Outlook calendar folder')
else
PM1.Lines.Add('Outlook calendar folder accessed');
PM1.Lines.Add('Accessing Outlook Calendar for raw data dump');
Application.ProcessMessage s;
Application.ProcessMessage s;
PM1.Lines.Add('Total appointment entries found in calendar: ' + IntToStr(Calendar.Items.Co unt));
PM1.Lines.Add('Filtering dates between ' + DateToStr(DateRangeStart) + ' and ' + DateToStr(DateRangeEnd));
PM1.Lines.Add(' ');
Save_Cursor := Screen.Cursor;
Screen.Cursor := crHourglass; { Show hourglass cursor }
MyItems := Calendar.Items;
MyItems.Sort('[Start]', False);
MyItems.IncludeRecurrences := True;
// Set the filter dates... SECONDS can NOT be shown...
FilterStartDate := FormatDateTime('mmmm dd, yyyy', DateRangeStart);
FilterStartDate := FilterStartDate + ' 12:00 AM';
FilterEndDate := FormatDateTime('mmmm dd, yyyy', DateRangeEnd);
FilterEndDate := FilterEndDate + ' 11:59 PM';
RestrictDateFilter := ('[Start]>' + CHR(34) + FilterStartDate + CHR(34) + 'and ' +
'[Start]<' + CHR(34) + FilterEndDate + CHR(34));
PM1.Lines.Add('Filter is set to');
PM1.Lines.Add(RestrictDate Filter);
Application.ProcessMessage s;
PM1.Lines.Add('Preparing to restrict and sort data');
Application.Processmessage s;
ItemCollection := MyItems.Restrict(RestrictD ateFilter) ;
ItemCollection.Sort('[Star t]', False);
PM1.Lines.Add('Restriction and sorting complete');
PM1.Lines.Add('Beginning to iterate through data');
PM1.Lines.Add('----------- ---------- ---------- --');
Application.Processmessage s;
// Try to read the first appoint, or error message if no appointments
T1 := GetTickCount;
try
Appointment := ItemCollection.GetFirst;
except
PM1.Lines.Add('No appointments found');
MessageDlg('Unable to retrieve any appointments in this time frame.',
mtError, [mbOK], 0);
if Created = True then
begin
Application.ProcessMessage s;
Outlook.Quit;
end;
Screen.Cursor := Save_Cursor; { Always restore to normal }
exit;
end;
CurrentDate := Trunc(StartDate.Date);
PM1.Lines.Add('----------- ---------- ---------- ---------- ---');
PM1.Lines.Add('');
while VarIsClear(Appointment) = False do
begin
Application.ProcessMessage s;
PM1.Lines.BeginUpdate;
PM1.Lines.Add('*** Subject: ' + Appointment.Subject);
StatusBar.Panels[0].Text := Appointment.Subject;
Application.ProcessMessage s;
// Get the date of the appt..
ApptDate := Trunc(Appointment.Start);
PM1.Lines.Add(' Start: ' + DateTimeToStr(Appointment. Start) + ' End: ' + DateTimeToStr(Appointment. end));
// Now get recurrance type
RP := Appointment.GetRecurrenceP attern;
if Appointment.IsRecurring = True then
recurType := IntToStr(RP.RecurrenceType )
else
recurType := 'Non-recurring';
PM1.Lines.Add(' Recurrency: ' + recurType);
// Get privacy code
PM1.Lines.Add(' Status: ' + IntToStr(Appointment.Sensi tivity));
// get all day status
PM1.Lines.Add(' All day event: ' + BoolToStr(Appointment.AllD ayEvent));
PM1.Lines.EndUpdate;
try
Appointment := ItemCollection.GetNext;
except
// break;
end;
Application.ProcessMessage s;
end;
T2 := GetTickCount;
PM1.Lines.Add('');
PM1.Lines.Add('Processing took: ' + IntToStr(T2 - T1) + ' ms');
Screen.Cursor := Save_Cursor; { Always restore to normal }
PM1.Lines.BeginUpdate;
PM1.Lines.Add(' ');
PM1.Lines.Add('----------- ---------- ---------- ---------- ---------' );
PM1.Lines.Add('All appointments from Outlook have been processed.');
PM1.Lines.EndUpdate;
if Created = True then
begin
// Outlook := unassigned
///else
PM1.Lines.Add('Closing Outlook');
Application.ProcessMessage s;
Outlook.Quit;
end;
Application.ProcessMessage s;
// Build the email subject
EmailSubject := 'Raw Log Dump';
Application.ProcessMessage s;
MessageDlg('Processing completed', mtInformation, [mbOK], 0);
end;
I know that I have an appointment. The problem is that the COM interface appears to have changed somewhere between D7 and TurboDelphi. The Items.GetNext will fail when there is no next item/appointment available, and this failure is NOT caught within a try...except block. I cannot simply count the appointments and use a for...next loop because while this will work in the code shown, this will NOT work when you sort/restrict the appointments. For example, if I want all appointments between T1 and T2, (dates) sorted by start date, INCLUDING RECURRING APPOINTMENTS, then the count ALWAYS returns 2 Billion (whatever that specific maxint value is.) This is known, expected behaviour when you use the "include recurring" functionality.
I have been able to get this to work. Here is the code. I will withdraw this question.
In the following code, PM1 is a memobox....
procedure TForm1.BitBtn1Click(Sender
var
Outlook, Namespace, Appointment, Calendar: variant;
Recip: Variant; // MultiCalendar Access
fldr: Variant; // MultiCalendar Access
Created: Boolean;
MyItems, ItemCollection: Variant;
Msg: string;
DateRangeStart, DateRangeEnd: TDateTime;
Save_Cursor: TCursor;
RestrictDateFilter: string;
FilterStartDate: string;
FilterEndDate: string;
CurrentDate: TDateTime;
ApptDate: TDateTime;
WeekDayNo: Integer;
StartDateYear, EndDateYear: string;
NeedDateHeader: Boolean;
RP: OLEVariant;
RecurType: string;
sd, ed: string;
T1, T2: DWord;
begin
if MessageDlg('This will dump the raw Outlook appointments to the log. Continue?',
mtConfirmation, [mbYes, mbNo], 0) = mrNo then
exit;
// Get the dump date range....
DateRangeStart := trunc(now - 365);
DateRangeEnd := trunc(now + 365);
sd := DateToStr(DateRangeStart);
ed := DateToStr(DateRangeEnd);
if InputQuery('Debug Dump', 'Start date:', sd) = False then exit;
if InputQuery('Debug Dump', 'End date:', ed) = False then exit;
try
DateRangeStart := StrToDate(sd);
DateRangeEnd := StrToDate(ed);
except
MessageDlg('You have entered an invalid date. Please check the date format', mtError, [mbok], 0);
exit;
end;
MessageDlg('NOTE: This will probably take 15 to 20 seconds.... please be patient....',
mtInformation, [mbOK], 0);
// If here, then we are ready to go....
PageControl.ActivePage := LogTab;
PM1.Clear;
////// START OUTLOOK //////////////////////////
try
Outlook := GetActiveOleObject('outloo
except
Outlook := CreateOleObject('outlook.a
Created := True;
end;
Namespace := Outlook.GetNamespace('MAPI
Calendar := Namespace.GetDefaultFolder
if VarIsEmpty(OutLook) then
PM1.Lines.Add('Unable to access Outlook')
else
PM1.Lines.Add('Outlook accessed');
if VarIsEmpty(NameSpace) then
PM1.Lines.Add('Unable to access MAPI Namespace')
else
PM1.Lines.Add('MAPI Namespace accessed');
if VarIsEmpty(Calendar) then
PM1.Lines.Add('Unable to access Outlook calendar folder')
else
PM1.Lines.Add('Outlook calendar folder accessed');
PM1.Lines.Add('Accessing Outlook Calendar for raw data dump');
Application.ProcessMessage
Application.ProcessMessage
PM1.Lines.Add('Total appointment entries found in calendar: ' + IntToStr(Calendar.Items.Co
PM1.Lines.Add('Filtering dates between ' + DateToStr(DateRangeStart) + ' and ' + DateToStr(DateRangeEnd));
PM1.Lines.Add(' ');
Save_Cursor := Screen.Cursor;
Screen.Cursor := crHourglass; { Show hourglass cursor }
MyItems := Calendar.Items;
MyItems.Sort('[Start]', False);
MyItems.IncludeRecurrences
// Set the filter dates... SECONDS can NOT be shown...
FilterStartDate := FormatDateTime('mmmm dd, yyyy', DateRangeStart);
FilterStartDate := FilterStartDate + ' 12:00 AM';
FilterEndDate := FormatDateTime('mmmm dd, yyyy', DateRangeEnd);
FilterEndDate := FilterEndDate + ' 11:59 PM';
RestrictDateFilter := ('[Start]>' + CHR(34) + FilterStartDate + CHR(34) + 'and ' +
'[Start]<' + CHR(34) + FilterEndDate + CHR(34));
PM1.Lines.Add('Filter is set to');
PM1.Lines.Add(RestrictDate
Application.ProcessMessage
PM1.Lines.Add('Preparing to restrict and sort data');
Application.Processmessage
ItemCollection := MyItems.Restrict(RestrictD
ItemCollection.Sort('[Star
PM1.Lines.Add('Restriction
PM1.Lines.Add('Beginning to iterate through data');
PM1.Lines.Add('-----------
Application.Processmessage
// Try to read the first appoint, or error message if no appointments
T1 := GetTickCount;
try
Appointment := ItemCollection.GetFirst;
except
PM1.Lines.Add('No appointments found');
MessageDlg('Unable to retrieve any appointments in this time frame.',
mtError, [mbOK], 0);
if Created = True then
begin
Application.ProcessMessage
Outlook.Quit;
end;
Screen.Cursor := Save_Cursor; { Always restore to normal }
exit;
end;
CurrentDate := Trunc(StartDate.Date);
PM1.Lines.Add('-----------
PM1.Lines.Add('');
while VarIsClear(Appointment) = False do
begin
Application.ProcessMessage
PM1.Lines.BeginUpdate;
PM1.Lines.Add('*** Subject: ' + Appointment.Subject);
StatusBar.Panels[0].Text := Appointment.Subject;
Application.ProcessMessage
// Get the date of the appt..
ApptDate := Trunc(Appointment.Start);
PM1.Lines.Add(' Start: ' + DateTimeToStr(Appointment.
// Now get recurrance type
RP := Appointment.GetRecurrenceP
if Appointment.IsRecurring = True then
recurType := IntToStr(RP.RecurrenceType
else
recurType := 'Non-recurring';
PM1.Lines.Add(' Recurrency: ' + recurType);
// Get privacy code
PM1.Lines.Add(' Status: ' + IntToStr(Appointment.Sensi
// get all day status
PM1.Lines.Add(' All day event: ' + BoolToStr(Appointment.AllD
PM1.Lines.EndUpdate;
try
Appointment := ItemCollection.GetNext;
except
// break;
end;
Application.ProcessMessage
end;
T2 := GetTickCount;
PM1.Lines.Add('');
PM1.Lines.Add('Processing took: ' + IntToStr(T2 - T1) + ' ms');
Screen.Cursor := Save_Cursor; { Always restore to normal }
PM1.Lines.BeginUpdate;
PM1.Lines.Add(' ');
PM1.Lines.Add('-----------
PM1.Lines.Add('All appointments from Outlook have been processed.');
PM1.Lines.EndUpdate;
if Created = True then
begin
// Outlook := unassigned
///else
PM1.Lines.Add('Closing Outlook');
Application.ProcessMessage
Outlook.Quit;
end;
Application.ProcessMessage
// Build the email subject
EmailSubject := 'Raw Log Dump';
Application.ProcessMessage
MessageDlg('Processing completed', mtInformation, [mbOK], 0);
end;
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Maybe a silly question, but is it a particular reason you don't check if there is an appointment to display before trying to get it ?
if Calendar.Items.count > 0 then
Appointment := Calendar.Items.GetFirst
else
Memo1.Lines.Add('No appointments found');
for j:= 1 to calendar.items.count do
begin
Appointment := Calendar.Items[j];
Memo1.Lines.Add(Appointmen
end;
It's the same checks to do with MailItem.Unread, MailItem.Move, etc.
I believe it's easier to check if there is at minimum one item to display, move or read before working with it. with item.count you are always sure that you neve go out of bounds.