Creating extended property using EWS and access it from Outlook Add-in - delphi

I am currently working on EWS to have some integration of our company application with Exchange 2010. I am using EWS to create appoinment to Exchange 2010 and it works fine; but recently I tried to add some custom/extended property when creating the appointment, below is my code to add the extended property.
Dim customField As New ExtendedPropertyDefinition(DefaultExtendedPropertySet.PublicStrings, "MyCustomField", MapiPropertyType.String)
appointment.SetExtendedProperty(customField, "CustomFieldValue")
The above codes able to create the custom field to the appointment.
Now here is my problem. When I open up the appointment in Outlook that I created and go to "Developer > Design This Form", then "All Fields" tab, I only see the custom field I created in the "User-defined field in folder" but not in "User-defined field in this item".
I also making an Outlook Add-in to react to the custom field that I created using the EWS when user opens up the appointment in Outlook, when I tried to look for the custom field, couldn't find the custom field, because the custom field is created in "User-defined field in folder" but not in "User-defined field in this item".
This is the codes in the Outlook Add-in and will execute when user opens an apointment in Outlook. But because the custom field is not in "in this item", the .Find() returns Nothing.
Dim appt As Outlook.AppointmentItem
appt = TryCast(inspector.CurrentItem, Outlook.AppointmentItem)
If appt.UserProperties.Find("MyCustomField") Is Nothing Then
'Some action
Else
'Some action
End If
What I want to achieve is to create an appointment with the custom field (extended property) using EWS, and then read the custom field (extended property) in Outlook Add-in when user open the appointment in Outlook.
EDIT:
The value that I assigned to the custom field using EWS is shown in the "User-defined field in folder". How do I retrieve the value from my Outlook Add-in? Maybe I can retrieve the value and add the custom field to the item and with the value?
Thanks.

The answer is here:
http://social.technet.microsoft.com/Forums/en-US/exchangesvrdevelopment/thread/2a98b4ab-0fbc-4863-8303-48711a18a050
Can't access the extended property created by EWS using UserProperties. But can access using PropertyAccessor.
outlookItem.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/yourProp")

I'm posting this as another answer showing some actual (Delphi) code, because that was missing from the first answer.
AAppointmentItem is an OLEVariant
const
GUID_PS_PUBLIC_STRINGS = '{00020329-0000-0000-C000-000000000046}';
cPublicStringNameSpace = 'http://schemas.microsoft.com/mapi/string/' + GUID_PS_PUBLIC_STRINGS + '/';
var
lPropertyAccessor: OleVariant;
lSchemaName, lValue: String;
begin
// Use the PropertyAccessor because Outlook UserProperties() can't access the extended properties created by EWS
// Use the 'string subnamespace of the MAPI namespace' (http://msdn.microsoft.com/en-us/library/office/ff868915.aspx)
// with the PS_PUBLIC_STRINGS GUID from http://msdn.microsoft.com/en-us/library/bb905283%28v=office.12%29.aspx
lPropertyAccessor := AAppointmentItem.PropertyAccessor;
lSchemaName := cPublicStringNameSpace + PROPERTY_TIMETELLID; // Name constants defined elsewhere
try
lSchemaName := cPublicStringNameSpace + PROPERTY_TIMETELLID;
lValue := lPropertyAccessor.GetProperty(lSchemaName);
lEvent.CustSyncTTID := StrToInt(lValue);
except
end;
try
lSchemaName := cPublicStringNameSpace + PROPERTY_TIMETELLSYNCTIME;
lValue := lPropertyAccessor.GetProperty(lSchemaName);
lEvent.CustSyncDate := UTCString2LocalDateTime(lValue);
except
end;
try
lSchemaName := cPublicStringNameSpace + PROPERTY_TIMETELLSYNCID;
lValue := lPropertyAccessor.GetProperty(lSchemaName);
lEvent.CustSyncEntryID := lValue;
except
end;
Note the many try excepts because we are doing late binding; 'early' would've been better
(http://blog.depauptits.nl/2012/04/safely-accessing-named-properties-in.html)
Also, We are retrieving multiple user properties, so GetProperties() is actually better.
FWIW, this was the old code using UserProperties (lProperty is OLEVariant)
lProperty := AAppointmentItem.UserProperties.Find(PROPERTY_TIMETELLID);
if IDispatch(lProperty) <> nil then
lEvent.CustSyncTTID := lProperty.Value;
lProperty := AAppointmentItem.UserProperties.Find(PROPERTY_TIMETELLSYNCTIME);
if IDispatch(lProperty) <> nil then
lEvent.CustSyncDate := lProperty.Value;
lProperty := AAppointmentItem.UserProperties.Find(PROPERTY_TIMETELLSYNCID);
if IDispatch(lProperty) <> nil then
lEvent.CustSyncEntryID := lProperty.Value;
[Edited to add 2013-6-10]
And here is the code modified to handle all three properties at once using GetProperties (as MS recommends):
lPropertyAccessor := AAppointmentItem.PropertyAccessor;
lSchemas := VarArrayOf([cPublicStringNameSpace + PROPERTY_TIMETELLID,
cPublicStringNameSpace + PROPERTY_TIMETELLSYNCTIME,
cPublicStringNameSpace + PROPERTY_TIMETELLSYNCID]);
try
lValues := lPropertyAccessor.GetProperties(lSchemas);
if VarType(lValues[0]) <> varError then
lEvent.CustSyncTTID := lValues[0];
if VarType(lValues[1]) <> varError then
begin
lDT := lValues[1];
lDT := TTimeZone.Local.ToLocalTime(lDT);
lEvent.CustSyncDate := lDT;
end;
if VarType(lValues[2]) <> varError then
lEvent.CustSyncEntryID := lValues[2];
except
end;

Related

how can i add hyperlink into open word document in OleContainer

i want to use MsWord as editor for my HTML document.
i open anther form with some list of files.
i want the user to select one of the files
and add this as alink in the open document (at the place the user select)
i open HTML document in word created in olecontainer.
with :
with OleContainerFrame do
begin
OleContainer1.CreateObjectFromFile(FileToEditName{myfile.html}, False);
OleContainer1.AutoActivate := aaGetFocus;
OleContainer1.DoVerb(ovOpen);
OleContainer1.Run;
end;
how can i add this link, as :
AddHperyLink(SomeText,TheHyperLink)....
at the place the user select
Suppose there is a TEdit on your form which contains a URI (I used the BBC's site). Then the following code will add a hyperlink to it in the active Word document in your OLEContainer:
procedure TForm1.Button1Click(Sender: TObject);
begin
OleContainer1.OleObject.ActiveDocument.Hyperlinks.Add(
Anchor := OleContainer1.OleObject.Selection.Range,
Address := Edit1.Text, // contains e.g. http://www.bbc.co.uk
TextToDisplay := 'Link'
);
end;
The way this works is that OleContainer1.OleObject is a variant reference to Word.Application (see e.g. the Word2000.Pas unit that comes with Delphi) and once you have this reference you can call Word's automation methods using late (or early) binding.
Btw the unusual syntax of the arguments to OleContainer1.OleObject.ActiveDocument.Hyperlinks.Add is a special syntax that Delphi supports to enable named parameters to be used in latebound calls.
Update: You say in a comment that you have tried the code above but get the error "Method 'Selection' not supported by automation object". When I put together my test project, I didn't have an association set up between HTML and MS Word, so I write the code necessary to activate Word and load an HTML file into it. I do this in the FormCreate event:
procedure TForm1.FormCreate(Sender: TObject);
var
V : OleVariant;
AFileName : String;
begin
OleContainer1.CreateObject('Word.Application', False);
OleContainer1.Run;
V := OleContainer1.OleObject;
Caption := V.Name;
V.Visible := True;
AFileName := ExtractFilePath(Application.ExeName) + 'Hello.Html';
V.Documents.Add(AFileName);
end;
Note that this and Button1Click are the entire code of my project and it inserts the link as you asked. If you get a different result, I think it must be because of some detail of your set-up that we readers can't see.
yes that work.
i didnot now we can use
(Anchor := ....
);
but now
word remove the execet PATH and change it to 'href="../../../../MzIAI/Images/2019-06/12/45545_5679.Pdf">'
and remove full path

Update existing Google calendar entry using TMS Cloud component

I use the following code to add a new entry to my Google calendar using TMS Cloud component
I have the new entries in a table in a database and they are added OK
But I would like to be able to update an entry also, but that I cant find any help about.
The demo that is supplied with the controls has the option, but I don't want to load all entries from the calendar and select the one to edit there. I want to save some sort of entryID that it is given and use that to update.
DevExpress has an option that lets you sync a table to and from Outlook calendar (works very nice) so I think it should be possible to do with TMS
The question is: how do I get an ID of a calendar entry when creating it so I can store it with the record in table and use it afterwards for updating?
var
ci: TGCalendarItem;
begin
AdvGCalendar.App.Key := Settings.Google.Key;
AdvGCalendar.App.Secret := Settings.Google.Secret;
AdvGCalendar.Logging := true;
if not AdvGCalendar.TestTokens then
AdvGCalendar.RefreshAccess;
if not AdvGCalendar.TestTokens then
AdvGCalendar.DoAuth
else
Connected := True;
ci := AdvGCalendar.Items.Add;
AdvGCalendar.GetCalendars();
ci.CalendarID := Settings.Google.Calendar;
if ci.CalendarID <> '' then
begin
ci.Location := CiLocation;
ci.Description := CiDescription;
ci.Summary := CiSummary;
ci.StartTime := EncodeDateTime(YearOf(StartDate), MonthOf(StartDate), DayOf(StartDate), HourOf(StartTime), MinuteOf(StartTime), 0, 0);
ci.EndTime := EncodeDateTime(YearOf(StopDate), MonthOf(StopDate), DayOf(StopDate), HourOf(StopTime), MinuteOf(StopTime), 0, 0);
ci.IsAllDay := False;
ci.Visibility := viPrivate;
AdvGCalendar.Add(ci);
end;
end;
The problem was related to the fact that there was an error in the version I was using of the component so that the ID was not returned OK.
Upgrading to a newer version fixed this so now it is working.

Sending email with Indy 9 with an embedded picture

I am trying to add to a program of mine the capability of sending html email via SMTP with Indy 9. If the program only contains text (the text will be in Hebrew so I need to display it right to left, which means that I am using HTML statements), then the email is sent correctly. My problem lays with embedding pictures into the HTML stream.
The HTML stream will use a command like
<IMG SRC="cid:foo4atfoo1atbar.net" ALT="IETF logo">
Whilst the Indy 10 component TIdAttachmentFile has a ComponentID property whose value has to be set to the value that 'cid' references, I can't find where to set the ComponentID property in Indy 9.
At the moment, the code which deals with adding the picture (whose name is in laPicture.text) looks like this
if laPicture.text <> '' then
with TIdAttachment.Create (email.MessageParts, laPicture.text) do
begin
ContentDisposition:= 'inline';
ContentType:= 'image/jpeg';
DisplayName:= ExtractFileName (laPicture.text);
filename:= ExtractFileName (laPicture.text);
end;
Where do I define the ContentID?
And, although this is a stupid question, how do I know which version of Indy I have?
TIdAttachment derives from TIdMessagePart, which has a public ContentID property. If your installed version of Indy 9 does not have that property, then you are using an outdated version, so use the ExtraHeaders property instead to add a Content-ID header manually.
Have a look at the following blog article on Indy's website for more information about working with HTML emails:
HTML Messages
Update: so, if the HTML says cid:foo4atfoo1atbar.net then you need to do this in your code to match it:
with TIdAttachment.Create (email.MessageParts, laPicture.text) do
begin
...
ContentID := '<foo4atfoo1atbar.net>';
// or this, if you do not have the ContentID property available:
// ExtraHeaders.Values['Content-ID'] := '<foo4atfoo1atbar.net>';
end;
Note that in Indy 9, you have to provide the brackets manually. Indy 10 inserts them for you if they are omitted, eg:
ContentID := 'foo4atfoo1atbar.net';
I found a solution - I didn't need Indy10 not the Content-ID field.
The code which I showed in my question was fine, the problem was probably in the HTML code which displayed the picture. I thought that the "cid" variable had to 'point' to the value of Content-ID; it transpires that it can be set to the name of the file (TIDAttachment.filename), as follows
<img src="cid:' + ExtractFileName (laPicture.text) + '"><br>
The above line gets inserted into the html stream at the appropriate place.
This works for me:
function SendEmail(SMTP: TIdSMTP; CONST AdrTo, AdrFrom, Subject, Body, HtmlImage, DownloadableAttachment: string; SendAsHtml: Boolean= FALSE): Boolean;
VAR MailMessage: TIdMessage;
begin
Result:= FALSE;
Assert(SMTP <> NIL, 'SMTP in NIL!');
MailMessage:= TIdMessage.Create(NIL);
TRY
MailMessage.ConvertPreamble:= TRUE;
MailMessage.Encoding := meDefault;
MailMessage.Subject := Subject;
MailMessage.From.Address := AdrFrom;
MailMessage.Priority := mpNormal;
MailMessage.Recipients.EMailAddresses := AdrTo;
{How to send multi-part/attachment emails with Indy:
www.indyproject.org/2005/08/17/html-messages
www.indyproject.org/2008/01/16/new-html-message-builder-class }
WITH IdMessageBuilder.TIdMessageBuilderHtml.Create DO
TRY
if SendAsHtml
then Html.Text := Body
else PlainText.Text := Body;
{ This will be visible ONLY if the email contains HTML! }
if SendAsHtml AND FileExists(HtmlImage)
then HtmlFiles.Add(HtmlImage);
if FileExists(DownloadableAttachment)
then Attachments.Add(DownloadableAttachment);
FillMessage(MailMessage);
FINALLY
Free;
END;
{ Connect }
TRY
if NOT SMTP.Connected
then SMTP.Connect;
EXCEPT
on E: Exception DO
begin
AppLog.AddError('Cannot connect to the email server.');
AppLog.AddError(E.Message);
end;
END;
{ Send mail }
if SMTP.Connected then
TRY
SMTP.Send(MailMessage);
Result:= TRUE;
EXCEPT
on E:Exception DO
begin
AppLog.AddError('Connected to server but could not send email!');
AppLog.AddError(E.Message);
end;
END;
if SMTP.Connected
then SMTP.Disconnect;
FINALLY
FreeAndNil(MailMessage);
END;
end;
Note: Replace AppLog with your personal logging system or with ShowMessage.
You need of course the libeay32.dll + ssleay32.dll. I would have posted a link to them, but could not find them anymore.

Outlook automation with Delphi - Queue

I currently have the following code:
while not (sqlMailMergeData.Eof) do
begin
if sqlMailMergeData.FieldByName('Email').AsString <> '' then
begin
Inc(Count);
{Connect to Outlook}
MailItem := OpOutlook1.CreateMailItem;
MailItem.MsgTo := sqlMailMergeData.FieldByName('Email').AsString;
MailItem.Body := Form48.Memo1.Text;
MailItem.Subject := Form48.Edit3.Text;
MailItem.Send;
end;
Form34.sqlMailMergeData.next;
end;
However Outlook prompts you to allow ever email with a delay of 5 seconds. Sending after the loop overwrite the same MailItem.
MailItem.Save;
Saves all the items to draft without prompting. This is not a bad solution and could be an extra feature but requires more user input to move the items to outbox.
Is there a function to send each mail item to the outbox? or should I consider creating a string of all the email address e.g.
MailItem.MsgTo := "example#email.com; example2#email.com"
Thanks
When working with outlook, you might consider using outlook redemption1, this way you can bypass the security prompt and send the mail directly from your code.
Then your only option is to do what Redemption (I am its author) is doing under the hood - use Extended MAPI.
This is the code which works fine for me:
Outlook := CreateOleObject ('Outlook.Application');
// Repet the code below for each mail:
OutlookMail := Outlook.CreateItem (olMailItem); // olMailItem = 0;
OutlookMail.Recipients.Add ('example#email.com').Resolve;
OutlookMail.Recipients.Add ('example2#email.com').Resolve;
OutlookMail.Subject := Form48.Edit3.Text;
OutlookMail.Body := Form48.Memo1.Text;
OutlookMail.BodyFormat := olFormatHTML;
// OutlookMail.SendUsingAccount := OutlookAccount; // If you need to select the acount
OutlookMail.Send;

how to show Only relevant information in dbgrid delphi

information:
I have an order form.
With "keuze" and "aantal" it wright a new line. The Orderline gets an OrderID.
But the user may only see the orderline from his OrderID.
How can i make it work that it only shows, for example the OrderID "47" ?
procedure TfmOrder.btInvoerenClick(Sender: TObject);
begin
dm.atOrder.open;
dm.atOrder.Append;
dm.atOrder ['OrderStatus'] := ('Aangemeld');
dm.atOrder ['klantID'] := fminloggen.userid;
dm.atOrder ['OrderDatum'] := Kalender.date;
dm.atOrder ['Opmerkingen'] := leOpmerkingen.text;
dm.atOrder.post;
cbkeuze.Visible := true;
dbRegel.Visible := true;
leAantal.visible := true;
btOpslaan.Visible:= true;
end;
This is the code for making a new Order
procedure TfmOrder.btOpslaanClick(Sender: TObject);
var orderid:string;
begin
dm.atOrderregel.Open;
dm.atDier.open;
dm.atorderregel.Append;
dm.atOrderregel ['AantalDieren'] := leAantal.text;
dm.atOrderregel ['OrderID'] := dm.atOrder ['OrderID'];
dm.atOrderregel ['Diernaam'] := cbKeuze.Text;
dm.atOrderregel.Post;
leaantal.clear;
cbkeuze.ClearSelection;
end;
And this for a new orderline
thanks in advance
I know got a different error using this code:
begin
dm.atorder.Open;
dm.atorder.filter := 'KlantID = ' + (fminloggen.userid);
dm.atorder.filtered := true;
while not dm.atorder.Eof do
begin
cbOrder.Items.Add (dm.atorder['OrderID']);
dm.atOrder.Next;
end;
dm.atOrder.Close;
end;
It gives an error: The arguments are from the wrong type, or doesn't have right reach or are in conflict with each other.
here is userid declared.
var Gevonden: boolean;
userid : string;
begin
dm.atInlog.open;
Gevonden := false;
while (not Gevonden) and (not dm.atInlog.eof) do
begin
if dm.atInlog['email'] = leUser.Text
then
begin
Gevonden := true ;
inlognaam := dm.atInlog['email'];
userid := dm.atInlog['KlantID'];
end
else
dm.atInlog.Next
end;
this is obviously in another form
You can use the Filter property of the data set:
atOrderregel.Filter := 'OrderID = 47';
atOrderregel.Filtered := True;
You can add the grid's columns property statically in the object inspector, showing only the fields you need. If the columns list is empty (default) it is filled with all available fields.
Just add as many columns as you need and link each column to the corresponding field. You can reorder the columns and set the widths and titles individually. There are still some more properties available which are worth to explore.
Im assuming your grid is bound to a datasource component. This datasource is then linked with a TDataset descendant. There are a couple of ways you could acheive the desired filtering of the dataset to display only orderid 47.
Firstly, you could set the Datasets SQL property to contain a (server side) SQL query such as:
SELECT * from table WHERE OrderID = #OrderID
You would also need to create a parameter in the dataset to pass the (changing) value for the required OrderID. So add a new Parameter to the dataset (#OrderID), and then at runtime you can set this parameter value in code, something like:
DataSet.Parameters['#OrderID'].Value := ParameterValue;
Alternatively, you could also FILTER the dataset (client side) to just show the correct data:
Set your SQL property of the dataset to retrive the entire table, something like:
SELECT * FROM table
And then at runtime you could set the Filter property of the dataset to only get OrderID 47:
Dataset.Filter := 'OrderID = '+InttoStr(ParameterValue);
Depending on your needs one method may suit better (performance/memory) wise.
As Najem has commented, there is also a third method - using a Master-Detail dataset relationship. This method works using two datasets, one is the master of the other. When the master table record is changed, the detail dataset is then filtered using the value defined in the Key or MasterFields property of the M-D relatioship.
If you are connected to some datasource you could always create a SQL Query. Something like:
SELECT * FROM YourDBTable WHERE OrderID=47

Resources