External App: Check if an Outlook Folder exists - delphi

SOLUTION BELOW
I've been looking all over the net to find a solution for this, but it seems quite hard to get an answer for this in Delphi...
Skip this if you're familiar with Outlook
Some explanation before:
The Contacts Folder in Outlook is organized like a foldertree in Windows. The Contacts are stored in the Contacts Folder itself or within subfolders.
My Code does add Contacts from an external Database into the Outlook contacts Database. To prevent double entries the programm is supposed to check all contacts and see if it can find an 'older' version of the contact entry and update it, or if not, create a new one.
Therefore I wrote a recursion which loops through the folders and checks the contacts.
Within a folder you can get the subfolder by (besides Next, Previous and Last)
Contacts:= Contacts.Folders.Getfirst
//The now selected Folder is the first subfolder within the previous selected one
If I am trying to get any property of this Subfolder like 'Items.Count' or anything else, an error occurs because this folder doesn't exist.
Therefore I want to check if the Folder exists or not, and skip to loop through this subfolder because otherwise the loop would break here and the program stops.
Skip until here if you're familiar with Outlook workings
THE PROBLEM:
In Debugger this Contacts/Folder Variable (an OleVariant, Pointer to the now selected Folder) contains values similar to this: '$0074974C'.
If there is no subfolder this value returns '$00000000'. This seems to be a pointer.
How should I check if a folder exists or not?
const
olFolderContacts = $0000000A;
var
outlook, NameSpace, Contact, ContactsRoot, Contacts: OleVariant;
begin
Outlook := CreateOleObject('Outlook.Application');
NameSpace := Outlook.GetNameSpace('MAPI');
ContactsRoot := NameSpace.GetDefaultFolder(olFolderContacts);
Contacts:= ContactsRoot;
//We're now in the Contacts Folder
Contacts:= Contacts.folders.getfirst;
//First Subfolder
What didn't work:
Check if
Contacts = '$00000000' (As string)
Contacts = '$00000000' (As OleVariant)
var
val:TVarRec;
code:
val:=Contacts;
string(Contacts.VWideChar) = '$00000000'
var
vntNothing: OLEVariant;
code:
TVarData(vntNothing).VType := varDispatch;
TVarData(vntNothing).VDispatch := Nil;
Contacts = vntNothing
Contacts = unassigned
...
...
In VBA this problem has a simple solution
if Contacts = Nothing
But there is no 'Nothing' in Delphi...
Ideas?

You could first check the count on the Folders collection:
if Contacts.Folders.Count = 0 then
or
Contacts := Contacts.Folders.GetFirst;
if VarIsClear(Contacts) then

You could try this:
if IUnknown(Contacts) = nil then
//

var
x: string;
in code:
x:= format('%p%',[Pointer(TVarData(contacts).VDispatch)]);
if x = '00000000' then
'New Contact'
else
'open folder and search within this one'
Co-worker had the solution.. Thanks for your time :)

Related

Why this code do not work in second time click button

I write this code for button click event . the first time I click the button every thing work correctly but when click for the second time on button it raise an error. whats the problem?
procedure TfrmMain.Button1Click(Sender: TObject);
var
B : Boolean;
begin
DM.tblTemp.DisableControls;
B:= DM.tblTemp.Locate('FoodName', DM.tblAsli.FieldByName('FoodName').AsString,[]) ;
if B then
begin
DM.tblTemp.Edit;
DM.tblTemp.FieldByName('Number').AsInteger:= DM.tblTemp.FieldByName('Number').AsInteger + 1;
DM.tblTemp.Post;
end
else
begin
DM.tblTemp.insert;
DM.tblTemp.FieldByName('FoodName').AsString := DM.tblAsli.FieldByName('FoodName').AsString;
DM.tblTemp.FieldByName('UnitPrice').AsInteger := DM.tblAsli.FieldByName('FoodPrice').AsInteger;
DM.tblTemp.FieldByName('Number').AsInteger := 1;
DM.tblTemp.Post;
end;
TotalPrice:= TotalPrice + DM.tblTemp.FieldByName('TotalPrice').AsInteger;
DM.tblTemp.EnableControls;
end;
the Error is
Row cannot be located for updating. Some values may have been changed
since it was last read
DM is data madual
tmbTbl is ADOTable
It is a pity you haven't said which DBMS you are using (e.g. Sql Server or MS Access,
nor told us a full list of the table's column types and indexes, if any.
The most likely answer to your q is that the variable B is set to False the first time
because the call to tblTemp.Locate fails to locate the Food name in question, so the Insert branch executes and the food's data is
added to the table but the second time the Edit branch executes and the error occurs,
though you have not said where, exactly. My guess would be on the call to .Post, because
the error message is one the ADO layer, which sits between the DBMS provider and your app,
emits when it attempts to post a change to the table but cannot identify which table row to update.
As I mentioned in a comment, the fix to this is usually at add a primary key index to the table, and I gather from your latest comment that this has succeeded for you. For the record and benefit of future readers it would be helpful if you could confirm which DBMS you are using and which Ole Driver you are using in your connection string.
Fwiw, I've tested your code using a table on MS Sql Server and MS Access and do not get the
error with either database.
Btw there is an obvious q, "How is it that tblTemp.Locate succeeds, but ADO is unable to
identify the correct record to post the update?" The answer is that tblTemp.Locate works in
a different way than identifying the relevant row to post the update.

How to save an email attachment to file

I would to save an email attachment to a file using a TIdImap4 object of Indy Ver.10.
I get the UID of the email, then I use this code:
lMsg := TIdMessage.Create(Self);
lImap.UIDRetrieveStructure(lUid, lMsg);
lMsg.MessageParts.CountParts;
if lMsg.MessageParts.AttachmentCount > 0 then
for lJ := 0 to lMsg.MessageParts.Count - 1 do
if (lMsg.MessageParts[lJ] is TIdAttachment) and
SameText(lMsg.MessageParts[lJ].Name, 'MyAttachment') then
lImap.UidRetrievePartToFile(lUid, lJ, lDimAllegato, lFileName, Trim(lMsg.MessageParts[lJ].ContentTransfer))
This worked until lMsg.MessageParts[lJ].ContentType = 'Text/Plain' and
lMsg.MessageParts[lJ].ContentTransfer = '7bit', now UidRetrievePartToFile() returns False and no file is created. I suppose because
lMsg.MessageParts[lJ].ContentType = 'application/octet-stream' and
lMsg.MessageParts[lJ].ContentTransfer = 'base64'.
I'm not skilled on this topic, what I need to change in code in order to save this type of attachment?
I also tried with: TIdAttachment(lMsg.MessageParts[lJ]).SaveToFile(lFileName)
and similar, but the file created was always empty.
Using UIDRetrieveStructure() with a TIdMessage is going to fill the TIdMessage.MessageParts with a lot of TIdttachment objects, never any TIdText objects, and not all of the objects are going to represent actual attachments. You are using the TIdAttachment indexes as the APartNum parameter of UIDRetrievePartToFile(), which might not be accurate.
And you can't use TIdAttachment.SaveToFile() when using UIDRetreiveStructure(), because no actual data has been downloaded, only the structure of the email, which then allows you to download the data for the specific elements you want.
I suggest you use the other overloaded version of UIDRetrieveStructure() that fills a TIdImapMessageParts instead. Amongst other things, TIdImapMessagePart gives you an exact ImapPartNumber that you can then give to UIDRetrievePartToFile() (as well as the ContentTransferEncoding):
lParts := TIdImapMessageParts.Create(nil);
try
lImap.UIDRetrieveStructure(lUid, lParts);
for lJ := 0 to lParts.Count - 1 do
begin
if (lParts[lJ] is the desired attachment) then
begin
lImap.UidRetrievePartToFile(lUid, lParts[lJ].ImapPartNumber, lDimAllegato, lFileName, lParts[lJ].ContentTransferEncoding);
end;
end;
finally
lParts.Free;
end;

TDelphiTwain component, corrupts delphi form (dfm file)

I have downloaded opensource delphi twain component (TDelphiTwain).
The interesting thing is, that when placed and saved on the form it creates bad dfm entry for itself.
object DelphiTwain: TDelphiTwain
OnSourceDisable = DelphiTwainSourceDisable
OnSourceSetupFileXfer = DelphiTwainSourceSetupFileXfer
TransferMode = ttmMemory
SourceCount = 0
Info.MajorVersion = 1
Info.MinorVersion = 0
Info.Language = tlDanish
Info.CountryCode = 1
Info.Groups = [tgControl, tgImage, tgAudio, MinorVersion]
Info.VersionInfo = 'Application name'
Info.Manufacturer = 'Application manufacturer'
Info.ProductFamily = 'App product family'
Info.ProductName = 'App product name'
LibraryLoaded = False
SourceManagerLoaded = False
Left = 520
Top = 136
end
The problem is with the line:
Info.Groups = [tgControl, tgImage, tgAudio, MinorVersion]
There are only three possible elements:
tgControl, tgImage and tgAudio
It adds MinorVersion everytime I Save the form.
When the app is run I get the error that there is invalid property for Info.Groups.
When i rmeove the bad part manually and without leaving dfm file the app starts ok.
I looked in the internet and there was one inquire regarding these strange issue, unfortunately it hasn't been resolved.
I think that there is some sort of memory corruption. In the post in teh internet, strange signs were displayed ...
Has anyone worked with that component or could give me some hint how this could be fixed?
The error seems to be in TTwainIdentity.GetGroups where result is not initialized. You can try to change the code by replacing
Include(Result, tgControl);
with
Result := [tgControl];
You have to recompile the package to make this change work inside the IDE.
I don't know the component, but I think the problem lies in the TTwainIdentity.GetGroups method. It starts like this:
begin
Include(Result, tgControl);
This means that it assumes that Result is initialized to an empty set. However, Result may contain garbage, and not necessarily an empty set. Change this method to look like this:
function TTwainIdentity.GetGroups(): TTwainGroups;
{Convert from Structure.SupportedGroups to TTwainGroups}
begin
Result := [tgControl];
if DG_IMAGE AND Structure.SupportedGroups <> 0 then
Include(Result, tgImage);
if DG_AUDIO AND Structure.SupportedGroups <> 0 then
Include(Result, tgAudio);
end;
Some result types will not throw a compiler warning about not being initialized, but that doesn't mean they are empty. Same goes, for instance, for strings.
See also: http://qc.embarcadero.com/wc/qcmain.aspx?d=894
But still, it is odd that this happens. Apparently, Delphi tries to find the name of the given item in the set and accidentally finds the name of another property. It seems to me that quite some checks in writing the dfm are missing if this happens. :)

Delphi: ResolveToDataset issue

I am using a TClientDataset with the following options for the provider:
ResolveToDataSet = True
Options = [poPropogateChanges, poUseQuoteChar]
UpdateMode = upWhereKeyOnly
AfterUpdateRecord = DataSetProvider1AfterUpdateRecord
The provider is connected to a TIBCQuery which manages the generator for the NO_INVOICE key.
On AfterUpdateRecord the following code is done (as found in many places in groups to really propagate the key change when posting to the database)
DeltaDS.FieldByName(ClientDataSet1NO_INVOICE.FieldName).NewValue
:= SourceDS.FieldByName(ClientDataSet1NO_INVOICE.FieldName).NewValue
The following code is then used to add a record:
ClientDataSet1.Params[0].AsInteger := -1;
ClientDataSet1.Open;
ClientDataSet1.Edit;
ClientDataSet1NO_INVOICE.AsInteger := -1;
ClientDataSet1NO_STORE.AsInteger := 1;
ClientDataSet1.Post;
ClientDataSet1.ApplyUpdates(-1);
If I call ClientDataSet1.Refresh after the ApplyUpdate, the underlying TIBCQuery is reopened with the original param of -1 and not with the new key... even if the ClientDataSet1NO_INVOICE.AsInteger shows up the new value assigned after merging records...
The use of Refresh here is only to simplify this example... The problems happens when we insert a record, apply updates and edit the record again.
Do I miss something with the usage of the ResolveToDataset option or should I explicitly reopen the query with the new param?
I never had this problem before when using ResolveToDataset = False on other projects...

Undelete a contact in outlook

I have an application written in Delphi that adds / updates contacts in outlook. The problem I'm having is that if the contact has been deleted in Outlook, the code still finds the contact and updates it - and the contact still remains deleted. Is there a way I can determine if the contact is deleted or undelete the contact?
Roughly the code looks something like:
OutlookApp := CreateOleObject('Outlook.Application');
Mapi := OutlookApp.GetNameSpace('MAPI');
//.....
try
if ContactOutlookEntryID.AsString <> '' then
aContact := Mapi.GetItemFromID(ContactOutlookEntryID.AsString);
except
end;
//try to locate the contact if they have been synchro'd before
if VarIsEmpty(aContact) then //if not found
aContact := Contacts.Items.Add(2); //add a new contact to outlook
aContact.LastName := ContactSurname.AsString;
//.....
When contacts are deleted they are put in the Deleted Items folder. There is no other "deleted" state other than being in that folder. "Undeleting" is as simple as moving it back out.
There is a Move method on the ContactItem object that you can use to move it back to the default contact folder which you can get with the NameSpace.GetDefaultFolder method.
EDIT
To determine if the contact is in the deleted items folder you can look at the Parent property which should return a MAPIFolder object. You can then compare its EntryID against the one returned by GetDefaultFolder(olFolderDeletedItems).
Keep in mind that this is PST specific - the PST provider does not change the entry id when items are moved to different folders.
Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool

Resources