Open word document in readonly mode - delphi

I'm using automation to open documents in Word. Sometimes I need to open document in Read mode ON:
var
WordDocument: _Document;
WA: TWordApplication;
begin
WA := TWordApplication.Create( nil );
WA.OnQuit := DocumentClose;
WA.Connect;
WordDocument := Wa.Documents.Open( FileName, EmptyParam, true {ReadOnly}, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam );
But user can off the Read mode in opened document:
How can I handle this in OnQuit event in procedure DocumentClose ?
In DocumentClose I want to know if document is in read mode or not.
I do not have any solution because I did not have enough experience with it.
So, I need your suggestions, advices about it. Sorry for my English and if I have to add more informations, please let me know. Thanks
UPDATE
I've tried to read protection type, but it's always return the first case. So, when document opening as ReadOnly isn't protected as wdAllowOnlyReading. Some documents can be protected with password, but there is not problem with it.
const
wdAllowOnlyReading: Longword = $00000003;
wdNoProtection: Longword = $ffffffff;
var
ProtectionType: TOleEnum;
begin
ProtectionType := WordDocument.ProtectionType;
case ProtectionType of
wdNoProtection : Showmessage('NoProtection');
wdAllowOnlyReading: Showmessage('ReadOnly');
end;
end;

I'm not sure exactly what you mean by "ReadOnly".
The WordDocument has a ReadOnly boolean property which is read-only in the sense that you can read its value but not set it. This property returns true if when the document was opened, it was already open e.g. on a different workstation, so that the user would get the prompt "This document is locked for editing ..." and asked whether to open the document in read-only mode or whether Word should open a copy instead.
The other sense in which a Word document might be "read only" is if the used has marked it "Final" by clicking the Word button (that leads to the File menu, etc) and gone to Prepare | Mark as Final (in the "Ribbon" versions of MS Word).
To read these properties in code you can do e.g.
if WordDoc.Final then
Caption := 'Final'
else
Caption := 'not Final';
if WordDoc.ReadOnly then
Caption := Caption + ' Read only'
else
Caption := Caption + ' Read/write'
Note: The Final property is not surfaced in Delphi's Word2000.Pas, so to use it you need to go from early binding to late binding, like this:
var vWordDoc : OleVariant;
[...]
vWordDoc := WordDoc;
if vWordDoc.Final then
[...]
Unlike the ReadOnly property, you can toggle the Final property simply by
WordDoc.Final := not WordDoc.Final
But whether you can do this successfully when WordDoc.ReadOnly is True depends on why WordDoc.ReadOnly is True.
If WordDoc.ReadOnly is True because the document was edit-locked when it was opened because it was already open at another workstation, WordDoc.Final is read-only. Otoh, if it's True because you specified ReadOnly in the call to .Open(), then you need to watch out: You can then set Final to False and the user will then be able to edit the document despite its having been opened ReadOnly!
Another complication is that ProtectionType is not directly related to "ReadOnly", as I imagine you've gathered: it can, but doesn't necessarily, prevent editing except to certain regions of a document.

Related

ExportAsFixedFormat's IgnorePrintAreas parameter seems not to have effect

In a Delphi application I am using since years the following code to export xlxs to pdf:
function TExportTool.ExportExcelToPDF(aFileName, aNewFileName: String): Boolean;
// reference : http://embarcadero.newsgroups.archived.at/public.delphi.oleautomation/200811/081103142.html
// unluckily the link above is dead
{- Sheet is counted from 1 and upwards !! }
Var
App,oWB,oSheet : OleVariant;
begin
Result := False;
App:= CreateOleObject('Excel.Application');
Try
App.Visible:= 0;
oWb := App.WorkBooks.Open(ExpandUNCFileName(afilename),1); // Open read only
Try
oSheet := oWB.ActiveSheet;
oSheet.ExportAsFixedFormat(0, //xlTypePDF is constant 0
aNewFileName,
EmptyParam,
EmptyParam,
EmptyParam, // this should be IgnorePrintAreas
EmptyParam,
EmptyParam,
EmptyParam,
EmptyParam
);
Finally
End;
Result := True;
Finally
App.Quit;
App:= UnAssigned;
End;
end;
// IMPROVED WORKING CODE FOLLOWS
function TExportTool.ExportExcelToPDF(aFileName, aNewFileName: String): Boolean;
// reference : http://embarcadero.newsgroups.archived.at/public.delphi.oleautomation/200811/081103142.html
{- Sheet is counted from 1 and upwards !! }
procedure RestoreOriginalPrintArea (oSheet: OleVariant);
// Excel loses print area settings in non-English version of application when file is opened using automation:
// https://stackoverflow.com/questions/71379893/exportasfixedformats-ignoreprintareas-parameter-seems-not-to-have-effect
var
i:Integer;
begin
for i:= 1 to oSheet.Names.Count do
begin
if VarToStr(oSheet.Names.Item(i).Name).EndsWith('!Print_Area') then
begin
oSheet.PageSetup.PrintArea:='Print_area';
Break;
end;
end;
end;
Var
App,oWB,oSheet : OleVariant;
i:Integer;
begin
Result := False;
App:= CreateOleObject('Excel.Application');
Try
App.Visible:= 0;
oWb := App.WorkBooks.Open(ExpandUNCFileName(afilename),1); // Open read only
Try
oSheet := oWB.ActiveSheet;
RestoreOriginalPrintArea(oSheet); // workaround
oSheet.ExportAsFixedFormat(0, //xlTypePDF is constant 0
aNewFileName,
0, // standard quality = 0, Max quality = 1
false, //include doc properties
false, //ignore print area
EmptyParam,
EmptyParam,
EmptyParam,
EmptyParam
);
Finally
End;
Result := True;
Finally
oWB.Close(false); // better to close the WorkBook too
App.Quit;
App:= UnAssigned;
End;
end;
Now i realized that the pdf created with this code behave like when saving to pdf from Excel using the option "Ignore Print areas" (it is one of the options of the export to pdf from Excel feature).
So I decided to "uncheck" that checkbox also from code and I studied the parameters of ExportAsFixedFormat (reference here).
The fifth parameter is IgnorePrintAreas, so I was assuming that passing False to it, the print areas would have been ignored.
I tried several common sense solution, including:
passing only that parameter (passing either True or False )
passing all the first 5 parameters (just in case they are mandatory at runtime)
but no result: the pdf created by my application still "ignores the print areas".
Does anyone has a suggestion or has experience on this specific subject to give me a pointer to fix this issue?
Thanks.
UPDATE
Thanks to the useful accepted answer I appended to the code above the solution for reference, notice two things:
the RestoreOriginalPrintArea procedure that contains the workaround
the call to oWB.Close(false) at the end
Root cause of error:
Excel loses print area settings in non-English version of application when file is opened using automation.
Why this is happening:
When you define print area in a sheet, Excel internally creates a named range. It has two properties defining its name:
Name this property is always of the form WorksheetsName!Print_Area (if the sheet's name contains some special characters it is also enclosed in single quotes).
NameLocal has similar structure, but the second part is translated into the language of the application.
This is what it looks like when you open the file in Excel and inspect these properties in VBA, but when you open the same file using automation (for example using the code in question), then NameLocal is no longer translated. This bug causes the named range to not be recognized correctly as print area. oSheet.PageSetup.PrintArea returns an empty string.
Workaround:
Restore original print area after opening the file using:
oSheet.PageSetup.PrintArea:='Print_Area';
This line of code will throw an exception when there was no print area defined in sheet, so there are two options:
Place the line inside try..except block.
Iterate the Names collection and look for a Name ending with !Print_Area, for example:
var i:Integer;
for i:= 1 to oSheet.Names.Count do
begin
if VarToStr(oSheet.Names.Item(i).Name).EndsWith('!Print_Area') then
begin
oSheet.PageSetup.PrintArea:='Print_area';
Break;
end;
end;
Other important change:
Because the file could have been modified you also need to add:
oWB.Close(false); //do not save changes
before closing the application, otherwise each call to this function would result in another Excel process still running invisible.

File Open Dialog with Preview in Delphi 10.3

I changed for Delphi 10.3 and its default TOpenDialog contains a preview pane. I made some searches and found the IFileDialogCustomize interface provided by Microsoft to customize standard WinAPI dialogs. I know I have to use the OnSelectionChange event handler to modify the picture of the pane. The big question for me is : how can I access the preview pane image by IFileDialogCustomize? What is the ItemID for this? I couldn't find any answer to this question on the net. Somebody know the answer? Then please share with me and the community! :)
I replaced some code fragments by ... for the sake of brevity, because these are trivial or app dependent sections.
procedure TMainWindow.OnSelectionChange( Sender : TObject );
var
dc : HDC;
aBMP : TBitmap;
function isSelectedFilePreviewAble : boolean;
begin
result := ...;
end;
functon getPreviewPictureDC : HDC;
var
iCustomize : IFileDialogCustomize;
h : THandle;
begin
if OpenDialog1.QueryInterface( IFileDialogCustomize, iCustomize ) = S_OK then
begin
h := iCustomize.??? this is the missing code fragment
result := GetDC( h );
end else
result := 0;
end;
procedure generatePreviewPicture;
begin
...
end;
begin
dc := getPreviewPictureDC;
if ( dc <> 0 ) then
begin
aBMP := TBitmap.Create;
try
if ( isSelectedFilePreviewAble ) then
generatePreviewPicture;
StretchBlt( aBMP.Handle, ...);
finally
aBMP.Free;
ReleaseDC( dc );
end;
end;
end;
I made some searches and found the IFileDialogCustomize interface provided by Microsoft to customize standard WinAPI dialogs.
First, IFileDialogCustomize does not "customize standard WinAPI dialogs". It customizes only IFileOpenDialog and IFileSaveDialog dialogs, no others.
Second, TOpenDialog primarily uses the legacy Win32 API GetOpenFileName() function. On Windows Vista+, GetOpenFileName() uses IFileOpenDialog internally with basic options enabled, so that legacy apps can still have a modern look.
Although, under the following conditions, TOpenDialog will instead use IFileOpenDialog directly rather than using GetOpenFileName():
Win32MajorVersion is >= 6 (Vista+)
UseLatestCommonDialogs is True
StyleServices.Enabled is True
TOpenDialog.Template is nil
TOpenDialog.OnIncludeItem, TOpenDialog.OnClose, and TOpenDialog.OnShow are unassigned.
But even so, TOpenDialog still does not give you access to its internal IFileOpenDialog interface, when it is used.
If you really want to access the dialog's IFileOpenDialog and thus its IFileDialogCustomize, you need to use TFileOpenDialog instead of TOpenDialog (just know that dialog won't work on XP and earlier systems, if you still need to support them).
The big question for me is : how can I access the preview pane image by IFileDialogCustomize?
You don't. The preview pane is not a dialog customization, so it can't be accessed via IFileDialogCustomize. Even if you could get a control ID for the preview pane (which you can't), there is no function of IFileDialogCustomize that would allow you to access the preview pane's HWND or HDC, or otherwise alter the content of the preview pane in any way. The preview pane is an integral and private component of IFileDialog for any file type that supports previews. It is not something that you can access and draw on directly. IFileOpenDialog itself will update the preview pane as needed when the user selects a file that has (or lacks) a preview to display.
My boss want to show previews for our own file formats.
The correct way to handle that on Vista+ is to create a Preview Handler for your custom file types. Then, any Shell component that wants to display previews of your files, including IFileOpenDialog, can use your handler.

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

How to remove, or change an attached template via OleAutomation

My company has a large selection of templates which are used to generate customer correspondence. I need to modify the existing processes so that copies of generated files (template + data) are saved for later editing.
My problem is that when I open one of these saved MSWord documents, edit, then close, MSWord is insisting that changes have been made to the template (the one selected in the generation process).
I am not really sure why this is happening, but it may be that the generated document contains a reference to the template upon which it was based, but that because the template is in a remote location, MSWord is attempting to generate a new local file.
If that diagnosis is correct, then I need a method to remove the template reference from the document.
If the diagnosis is incorrect then what is the likely explanation/solution?
I have found that BOTH resultant files contain a reference to the template.
Note: Manual editing in Word has no issue. If I let the letter generate and save to disk from Winword, I can open it and manipulate it quite happily. Somewhere in the automation steps the problem is being created.
Interestingly - I have changed the save format to '.rtf' and the problem remains.
Further - it doesn't matter if I say 'Yes' to saving changes to the template, it continues to prompt me each time I open and close the document (whether I edit or not)
I have discovered that by saving the document as wdFormatXML I can see the reference to the letter template and edit it. If I do that the problem goes away.
I am now attempting to achieve the same result via automation, but with no success;
WordApp.ActiveDocument.Set_AttachedTemplate(tmplt);
Does not work for values of tmplt 'Normal.dot', varNull, 'c:\progra~1\etc\Simple.dotx' and so on. The function call tells me it cannot find the template for the first 2 of those values, or merely hangs.
I am back to my original question - how does one clear the attached template ?
I eventually figured it. My problem was down to late-binding in some way. I found that the following code worked
var
docpath : OleVariant;
fmt : OleVariant;
tmplt : OleVariant;
WordApp : WordApplication;
WordDoc : WordDocument;
begin
docpath := SaveLoggedDocToDisk(GetCurrentFileName());
WordApp := CoWordApplication.Create;
try
fmt := EDITABLE_FORMAT;
tmplt := '';
WordDoc := WordApp.Documents.Open(docpath, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, fmt, EmptyParam, EmptyParam );
WordDoc.Set_AttachedTemplate(tmplt);
The problem I had with earlier versions of this code was that
WordApp.ActiveDocument.SetAttachedTemplate(tmplt);
although it appears equivalent, was not behaving. By creating a variable of type WordDocument the routine sprang into life.

Displaying Hints in a Delphi XE2 TActionMainMenubar

I am struggling greatly trying to get hints displayed in a TActionMainMenuBar.
Delphi XE2.
I am creating the menus at runtime. I add categories, subitems, clear the menu out, that all works great. Clicking on the menu item works properly (for now it just does a ShowMessage with the action item tag but that's fine).
This is the code that adds a new menu item :
function TActionF.NewAction(AParent: TActionClientItem; Caption: String; aTag : integer; ExecuteAction: TNotifyEvent):TActionClientItem;
var
newActionClient : TActionClientItem;
AnewAction : TAction;
begin
newActionClient := TActionClientItem(AParent.Items.insert(AParent.Items.Count));
newActionClient.Caption := Caption; //??
newActionClient.UsageCount := -1; // turn of menu priority stuff for now
AnewAction := TAction.Create(Self);
AnewAction.Tag := aTag;
AnewAction.ImageIndex := -1;
AnewAction.Caption := Caption;
AnewAction.Hint := Caption + 'Action Tag = ' + IntToStr(aTag);
AnewAction.OnHint := acnDoHint; // fixed, could be parameter, but onHint is never called !!??
AnewAction.OnExecute := ExecuteAction; // passed as parameter
newActionClient.Action := AnewAction;
Result := newActionClient;
end;
I am setting the Hint" of the action. I have also experimented with assigning the OnHint, but the OnHint is never called. I simply cannot get at that hint when browsing the menu.
I have ShowHint set True everywhere I can see a place to do it.
The problem is that I cannot get any menu hints displayed no matter what I try. If i could just get at it I could display it myself (if the program won't). The OnHint is never called.
I have posted the full source of my menu program (Delphi XE2) , a small example narrowed down as best I could, in my public DropBox if anyone wants to see the program.
https://dl.dropbox.com/u/58421925/Actions.zip
This does exactly what you want: www.delphi.about.com/od/vclusing/a/menuitemhints.htm
It handles the WM_MENUSELECT message and shows the hint in its own window ( TMenuItemHint = class(THintWindow) ).

Resources