How to make Word invisible during OLE automation from Delphi - delphi

From our application we use OLE automation to build a fairly complex Word-document. I would like to make Word invisible while the document is being made, since there is a lot of pasting and insertions that takes quite a long time.
I use the following code to establish a Word connection:
function ConnectToWord : TWordAutomationResult;
begin
WordApp := TWordApplication.Create(nil);
try
WordApp.Connect;
WordApp.Visible := false;
except on E: Exception do
begin
Result := waeErrorConnectingToWord;
exit;
end;
end;
end;
And I use the following code to open an existing document, which is then edited by my application.
function TWordAUtomation.OpenDocument(aFileName: string) : WordDocument;
var vFileName,
vConfirmConversions,
vReadOnly,
vAddToRecentFiles,
vPasswordDocument,
vPasswordTemplate,
vRevert,
vWritePasswordDocument,
vWritePasswordTemplate,
vFormat,
vEncoding,
vVisible,
vOpenConflictDocument,
vOpenAndRepair,
vWdDocumentDirection,
vNoEncodingDialog : OleVariant;
begin
Result := nil;
if not FileExists(aFileName) then exit;
vFileName := aFileName;
vConfirmConversions := True;
vReadOnly := False;
vAddToRecentFiles := False;
vPasswordDocument := EmptyParam;
vPasswordTemplate := EmptyParam;
vRevert := True;
vWritePasswordDocument := EmptyParam;
vWritePasswordTemplate := EmptyParam;
vFormat := wdOpenFormatAuto;
vEncoding := EmptyParam;
vVisible := False; //Document should be invisible
vOpenConflictDocument := EmptyParam;
vOpenAndRepair := EmptyParam;
vWdDocumentDirection := EmptyParam;
vNoEncodingDialog := EmptyParam;
Result := WordApp.Documents.Open(vFileName, vConfirmConversions, vReadOnly, vAddToRecentFiles, vPasswordDocument, vPasswordTemplate, vRevert, vWritePasswordDocument, vWritePasswordTemplate, vFormat, vEncoding, vVisible, vOpenAndRepair, vWdDocumentDirection, vNoEncodingDialog);
end;
It works on my computer! (TM)
For some of our customers Word remains visible during the editing process. What reasons can there be for this? As far as I can tell the problem arises for customers that use some sort of remote computing, like managed clients etc. Are there some additional properties that deals with application visibility that only have effect during remote desktop connections etc? I'm not very knowledgeable about such things :-(

I'm maintaining the Word automation for our software and also had reports of Word windows popping up in Citrix clients. I don't know what causes this and how to get rid of it.
There is only one way I can simulate Word becoming visible again and that is opening a Word-document while your application is processing. But I don't think that is the cause of your problems.
PS: You call TWordApplication.Connect and then you set Visible to False. Know that when you call Connect and you haven't changed ConnectKind, it will connect to a running instance of Word. When your client is editing a document this document will suddenly dissappear. Perhaps it is better to set ConnectKind to NewInstance so you always work in a new winword.exe process. The existing winword.exe will remain available for your client and he can continue working at his document while your application is processing the other.
Ofcourse this approach has some drawbacks too:
When your client opens a new Word-document, it is opened in your instance of Word
You can get errors on Normal.dot being modified by another application

Instead of using TWordApplication, use CreateOLEObject:
var WordApp: Variant;
procedure OpenWordFIle( const Filename: String );
begin
WordApp := CreateOLEObject('Word.Application');
WordApp.Visible := False;
WordApp.Documents.Open( Filename );
Application.ProcessMessages;
end;
To close it gracefully:
procedure CloseWordFile;
begin
WordApp.ActiveDocument.Close( $00000000 );
WordApp.Quit;
WordApp := unassigned;
end;
If you don't close it, Word application will be open even after your close your Delphi application.
Some useful resources where you can find more options to open/close Word Files:
http://msdn.microsoft.com/en-us/library/office/ff835182.aspx
How can I call documents.open and avoid the 'file in use' dialog?

in my case it happend similar as you described. I looks the application is still running even if you disconnect. The first time it will not be shown, but as soon as you have a second open then the application will be visible. in My case it helped to explicitly quite the application. It quit's only the instance that is doing the work in background. Another open document edited by the local user will not be touched.
WordDocument.Disconnect;
**WordApplication.Quit;**
WordApplication.Disconnect;

Related

How to pass parameters to existing instance of application

I am using Delphi 10.4. I want my FMX app to open only one instance, and so I am using a Mutex. I open a URL from my site in order to open my app with specific parameters, and with these my app opens other apps as needed based on the parameters.
So, if ParamStr(1) has a value then I skip the Mutex, make the execution that I want, and then end the app with Application.Terminate in order not to have another window open.
Now, instead of that, I want that when the app is opened with parameters, and I have already one instance running, to pass the parameters to the active instance instead of opening a new one.
I am quite new at coding, so I searched for it, but I didn't find anything that can make this work.
Update 1
try
if (MutexErr = ERROR_SUCCESS) and (ParamStr(1) = '') then
begin
Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end
else if (MutexErr = ERROR_SUCCESS) and (ParamStr(1) <> '') then
begin
Inc(CharCount, Length(ParamStr(1)) +1);
Inc(CharCount);
Data := StrAlloc(CharCount);
try
PData := Data;
for i := 1 to ParamCount do
begin
StrPCopy(PData, ParamStr(i));
inc(PData, Length(ParamStr(i)) +1);
end;
PData^ := #0;
CopyDataStruct.cbData := CharCount * SizeOf(Char);
CopyDataStruct.lpData := Data;
CopyDataStruct.dwData := cCopyDataSecurityToken;
SendMessage(wHandle, WM_COPYDATA, 0, LPARAM(#CopyDataStruct));
finally
StrDispose(Data);
end;
end;
finally
if Assigned(AppMutex) then
AppMutex.Free;
end;
Main Form
Latest approach
Sorry but I have changed so many times the code in order to try solutions that now it's a mess

Memory leaks on Gnostice PDF Toolkit

I'm using Gnostice PDF Toolkit v5.0.0.457, and it works fine to operate with PDF files (merge files, split files, insert blank pages, insert text watermarks, insert image watermarks, print files, ...).
But I always have big memory leaks that prevent me to use it on the server side.
Am I doing something wrong in this code (it simply adds a text watermark to a PDF file) ? :
implementation
{$R *.dfm}
uses gtCstPDFDoc, gtPDFDoc;
procedure TTestForm.btnTestWatermarkClick(Sender: TObject);
var PDFDoc: TgtPDFDocument;
txtWatermark: TgtTextWatermarkTemplate;
begin
PDFDoc := TgtPDFDocument.Create(nil);
txtWatermark := TgtTextWatermarkTemplate.Create;
try
PDFDoc.LoadFromFile('C:\Temp\Test.pdf');
txtWatermark.FontEncoding := feWinAnsiEncoding ;
txtWatermark.Text := 'Hello World';
txtWatermark.Font.Name := 'Arial';
txtWatermark.Font.Size := 12;
txtWatermark.Font.Color := clBlack;
txtWatermark.X := 150;
txtWatermark.Y := 150;
txtWatermark.HorizPos := hpCustom;
txtWatermark.VertPos := vpCustom;
txtWatermark.Overlay := True;
PDFDoc.InsertWatermark(txtWatermark, '1');
PDFDoc.SaveToFile('C:\Temp\TestOutput.pdf');
finally
PDFDoc.Reset;
PDFDoc.Free;
txtWatermark.Free;
end;
end;
This simple example already generates important memory leaks.
PS: I know that the best place to get support is the manufacturer itself, but my subscription ended a year ago.
Thank you
Update: We have re-subscribed with Gnostice, and the current version has the memory leaks solved. Thanks to all.

FindComponent Not Finding Components Created at Runtime

I use Delphi 7 with a number of third party components. My main stub application loads a number of DLLs, which are various modules like creditors, debtors, purchase orders, and so on.
I have an issue with FindComponent(). 99% of the time, it works how it should. But not for the code below.
I was trying to create a form reports, where I keep all the details of the reports selection criteria in a table, and then create the criteria on the fly. In theory, it should work perfectly, but for some reason after creating the components, FindComponent() cannot find them.
try
for i := gbSelectionCriteria.ComponentCount - 1 downto 0 do begin
ShowMessage(gbSelectionCriteria.Components[i].Name);
gbSelectionCriteria.Components[i].Free;
end;
// The above loop to remove the components from the groupbox works fine
// Creating the components works
fSysData.tbSelectionCriteria.First;
while not fSysData.tbSelectionCriteria.EOF do begin
case fSysData.tbSelectionCriteriaComponentType.AsInteger of
1 : begin // TMyAdvEdit
with TMyAdvEdit.Create(gbSelectionCriteria) do begin
Visible := False;
Parent := gbSelectionCriteria;
Name := fSysData.tbSelectionCriteriaName.AsString;
Left := fSysData.tbSelectionCriteriaLeft.AsInteger;
Top := fSysData.tbSelectionCriteriaTop.AsInteger;
Width := fSysData.tbSelectionCriteriaWidth.AsInteger;
LabelCaption := fSysData.tbSelectionCriteriaCaption.AsString;
LabelPosition := AdvEdit.lpLeftCenter;
LabelAlwaysEnabled := True;
LabelTransparent := True;
EditType := MyEditType[fSysData.tbSelectionCriteriaDataType.AsInteger];
Text := '';
OnClick := GetClickEvent(fSysData.tbSelectionCriteriaOnClickEvent.AsString);
OnDblClick := GetClickEvent(fSysData.tbSelectionCriteriaOnDblClickEvent.AsString);
OnKeyPress := GetKeyPressEvent(fSysData.tbSelectionCriteriaOnKeyPressEvent.AsString);
Visible := True;
// at this point findComponent finds nothing
if FindComponent(Name) <> nil then
ShowMessage(Name+' Created');
end;
edEdit.OnClick := GetClickEvent(fSysData.tbSelectionCriteriaOnClickEvent.AsString);
edEdit.OnDblClick := GetClickEvent(fSysData.tbSelectionCriteriaOnDblClickEvent.AsString);
edEdit.OnKeyPress := GetKeyPressEvent(fSysData.tbSelectionCriteriaOnKeyPressEvent.AsString);
edEdit.Visible := True;
if FindComponent(edEdit.Name) <> nil then
ShowMessage(edEdit.Name+' Created');
end;
2 : begin
end;
3 : begin
end;
4 : begin
end;
5 : begin
end;
6 : begin
end;
7 : begin
end;
8 : begin
end;
end;
fSysData.tbSelectionCriteria.Next;
end;
if fSysData.tbSysReports.Locate('ReportID', TAdvOfficeRadioButton(Sender).Tag, []) then begin
ReportData.ReportID := TAdvOfficeRadioButton(Sender).Tag;
ReportData.RepName := fSysData.tbSysReportsReportName.AsString;
ReportData.RepTitle := fSysData.tbSysReportsReportTitle.AsString;
ReportData.RepModule := fSysData.tbSysReportsModule.AsString;
ReportData.RepOrientation := fSysData.tbSysReportsReportOrientaton.AsString;
ReportData.RepPageIndex := fSysData.tbSysReportsCriteriaPageIndex.AsInteger;
end;
finally
end;
The Process of the reports is:
User clicks a button
Radio buttons are created from the button click
User clicks a radio button
Report criteria is created from the radio button click
User enters data or DblClicks to select data from a list.
User Clicks Preview button to view Report - this is where FindComponent fails and returns nil..
All the code worked before when I had created all the criteria at design time, then added the code above.
The code below is part of what needs to be added to the query to retrieve the data for the report:
if Length(TMyAdvEdit(FindComponent('edQuoteReference')).Text) > 0 then
qryTempTable.SQL.Add(' and q.UserReference = "' + TMyAdvEdit(FindComponent('edQuoteReference')).Text + '"');
This is the first time FindComponent() fails and goes no further.
I have tried various ways to create the components, but each of them results in an Access Violation because the component is nil.
I have looked everywhere, and tried everything I can think of, for a solution to this problem.
FindComponent searches for components owned by the subject of the method call. You call FindComponent on the form, and so look for the component amongst those components owned by the form. But the control you search for is not owned by the form, it is owned by gbSelectionCriteria, which is what you passed to the control's constructor as the Owner argument.
If you wish to use FindComponent in the way you do you therefore need to make the form be the owner of the controls that you create. Then when you call FindComponent on the form, it can find the control because it is the owner. Pass Self to the control's constructor to make this come to pass:
TMyAdvEdit.Create(Self)
I'm having to make some reasonably large guesses here. Perhaps this code actually resides in a data module rather than a form. But the essential principle will be as I say.
Firstly I do apologize if this is in the wrong spot..
Thanks for the response and the answer, I have been doing this for a lot of years and I can't believe I missed something so small.
this,
if FindComponent(Name) <> nil then
should have been this,
if gbSelectionCriteria.FindComponent(Name) <> nil then
I don't normally use with, it was just one way to test create the component.
I set the components visibility to false before and then to true after it is created to stop flicker as it creates.
Thanks again..

TDirectoryWatch not firing first time

I have a small application that is used to process some files made in another program.
I use an older component by Angus Johnson called TDirectoryWatch
On my FormCreate I have the following code
DirectoryWatch := TDirectoryWatch.Create(self);
DirectoryWatch.OnChange := FileAction;
DirectoryWatch.Directory := Folders.Path(dirInput);
DirectoryWatch.Active := True;
If the program is started and there is put a new file in the directory everything fires and runs OK.
But if there is a file in the directory when the program is started nothing happens even if I make a call to FileAction(nil);
FileAction is the name of the procedure that handles the files
I have a call to FileAction from a popupmenu and that handles the files in the directory
So my question is: how to make sure that existing files are handled at program start?
Or is there a better way to handle this problem.
Added code for FileAction
procedure TfrmMain.FileAction(Sender: TObject);
var
MailFile: string;
MailInfo: TMailInfo;
ListAttachments: TstringList;
i: integer;
MailBody: string;
begin
for MailFile in TDirectory.GetFiles(Folders.Path(dirInput), CheckType) do
begin
if FileExists(MailFile) then
begin
MailInfo := TMailInfo.Create(MailFile);
try
if FileProcessing = False then
begin
Logfile.Event('Behandler fil: ' + MailFile);
FileProcessing := True;
MailBody := '';
Settings.Load;
MailInfo.Load;
Settings.Mail.Signature := '';
Settings.Mail.Subject := MailInfo.Subject;
ListAttachments := TStringList.Create;
ListAttachments.Clear;
for i := 1 to MaxEntries do
begin
if (MailInfo.Attachment[i] <> '') and (FileExists(MailInfo.Attachment[i])) then
ListAttachments.Add(MailInfo.Attachment[i]);
end;
for i := 1 to MaxEntries do
begin
MailBody := MailBody + MailInfo.MailBody[i];
end;
try
if MailBody <> '' then
begin
if MailInfo.SenderBcc then
Mailing.Send(MailInfo.SenderMail, MailInfo.Recipient, MailInfo.SenderMail, MailInfo.Subject, MailBody, ListAttachments, True)
else
Mailing.Send(MailInfo.SenderMail, MailInfo.Recipient, MailInfo.Subject, MailBody, ListAttachments, True);
end;
finally
ListAttachments.Free;
end;
FileProcessing := False;
DeleteFile(MailFile);
end;
finally
MailInfo.Free;
end;
end;
end;
end;
The component doesn't notify about changes when your program starts up because at the time your program starts, there haven't been any changes yet.
Your policy appears to be that at the time your program starts up, all existing files are to be considered "new" or "newly changed," so your approach of manually calling the change-notification handler is correct.
The only thing the component does when it detects a change is to call the change-notification handler. If you explicitly call that function, and yet you still observe that "nothing happens," then there are more deep-seated problems in your program that you need to debug; it's not an issue with the component or with the basic approach described here.

Cannot set printer settings via ResetDc when doing the second time call to a dll

I have a dll in which I have printing unit.
In my application I have exported methods to begin,end printing process as well as print documents.
The calls to a dll can be as follows:
1-> begin printing and print document
2-> print document
3-> end printing.
I am changing settings before each printed page (tray, orientation, etc)
Step 1 is done successfully
when I make another call (2) and try to change printer settings, method: ResetDc
for a dev mode structure returns false ...
Is it the problem that when I am back in app after 1 call, app printers unit changes something which prevents subsequent calls in a dll?
How to workaround the problem?
EDIT
Here is the function which set page settings.
When 1 call is made (from the above list) ResetDC is executed successfully, but in the next call ResetDC returns false. Why ... ?
function PRPageSetup(paperSize: Integer = DMPAPER_A4): Boolean;
var
pDevice: pChar;
pDriver: pChar;
pPort: pChar;
hDMode: THandle;
PDMode: PDEVMODE;
PrnHandle: THandle;
begin
result := false;
GetMem(pDevice, cchDeviceName);
GetMem(pDriver, MAX_PATH);
GetMem(pPort, MAX_PATH);
Printer.GetPrinter(pDevice, pDriver, pPort, hDMode);
if hDMode <> 0 then
begin
pDMode := GlobalLock(hDMode);
if pDMode <> nil then
begin
pDMode^.dmFields := pDMode^.dmFields or DM_PAPERSIZE or DM_ORIENTATION;
pDMode^.dmPaperSize := paperSize;
if Printer.Printing then
PrnHandle := printer.Canvas.Handle
else
PrnHandle := Printer.Handle;
if ResetDc(PrnHandle, pDMode^) <> 0
then PRCanReset := true
else PRCanReset := false;
Result := true;
GlobalUnlock(hDMode);
end;
end;
FreeMem(pDevice, cchDeviceName);
FreeMem(pDriver, MAX_PATH);
FreeMem(pPort, MAX_PATH);
end;
I have finally managed to solve the problem.
I have discovered that I cannot setup thepage while TMetaFileCanvases where still in the memory. I needed to free them first and then reconfigure the page.
Thanks all for your effort and time.

Resources