Since at least D2007 a project file can have a main source file with differing base name. The DevExpress demos make use of this: E.g. there is a single dpr file UnboundListDemo.dpr which serves as the main source for both UnboundListDemoD11.dproj and UnboundListDemoD12.dproj.
Now if I have a Project: IOTAProject then Project.FileName returns the dproj file name. I couldn't find an "official" way to get the dpr's file name. Is there any? One can get it from parsing the dproj file (see here) but I'd prefer a ToolsAPI method.
Edit: I came up with this code based on Jon's answer:
function IsProjectSource(const FileName: string): Boolean;
begin
Result := IsDpr(FileName) or IsBpr(FileName) or IsPackage(FileName);
end;
function GxOtaGetProjectFileName2(Project: IOTAProject; NormalizeBdsProj: Boolean = False): string;
var
i: Integer;
Module: IOTAModule;
Editor: IOTAEditor;
begin
Result := '';
if Assigned(Project) then begin
Result := Project.FileName;
if NormalizeBdsProj and IsBdsprojOrDproj(Result) then begin
Module := Project as IOTAModule;
for i := 0 to Module.ModuleFileCount - 1 do
begin
Editor := Module.ModuleFileEditors[i];
if IsProjectSource(Editor.FileName) then begin
Result := Editor.FileName;
Exit;
end;
end;
end;
end;
end;
where the Is... routines are from GX_GenericUtils.
Edit 2: How to create one of these situations:
Create new VCL application.
Save as MyProject.dproj.
Close project in IDE.
In Windows explorer, rename MyProject.dproj to MyProjectD11.dproj.
From now on be sure to open MyProjectD11.dproj, not MyProject.dpr!
If you iterate the editors on the IOTAProject instance, you'll probably find the dpr.
var
Module: IOTAModule;
Project: IOTAProject;
Editor: IOTAEditor;
begin
// Set Project Here....
Module := Project as IOTAModule;
for I := 0 to Module.ModuleFileCount - 1 do
begin
Editor := Module.ModuleFileEditors[I];
end;
end;
Related
I am looking to find out which services/interface I need to use to add an item to the right-click menu of a source file in the Delphi IDE.
For example, if I right-click on a unit's tab, it has items to "Close page", "Close all other pages", "Properties", etc. I want to add custom items to that menu, if possible.
I looked over the ToolsAPI unit but I have no clue where to begin. I assume there's an interface I can use to enumerate items and add items, but I dont know where to start looking.
If that's not possible, I'd settle for the code editor's context menu.
Maybe there's some samples online for this, but I'm still looking and have found none.
Any help appreciated.
Remy Lebeau has pointed you in exactly the right directions with his link to
the GExperts guide.
If you've not done this sort of stuff before, it can still
be a bit of a performance to get started on writing your own IDE add-in, so
I've set out below a minimal example of how to add an item to the code editor's
pop-up menu.
What you do, obviously, is to create a new package, add the unit below to it,
and then install the package in the IDE. The call to Register in the unit
does what's necessary to install the new item in the editor pop-up menu.
Make sure that the code editor is open at the time you install the package. The
reason is that, as you'll see, the code checks whether there is an active editor
at the time. I've left how to ensure that the pop-up item gets added even if there
is no code editor active at the time. Hint: if you look at the ToolsAPI.Pas unit for whichever
version of Delphi you're using, you'll find that it includes various kinds of notifier,
and you can use a notification from at least one of them to defer checking if there
is an editor active until one is likely to be.
Btw, the code adds the menu item to the context menu which pops up over the editor window itself rather than the active tab. Part of the fun with IDE add-ins is the fun of experimenting to see if you can get exactly what you want. I haven't tried it myself, but I doubt that adding the menu item to the context menu of one of the editor tabs would be that difficult - seeing as the Delphi IDE is a Delphi app, as you can see from the code below, you can use FindComponent (or just iterate over a Components collection) to find what you want. However, it is better, if you can, to locate things via the ToolsAPI interfaces. See Update below.
interface
uses
Classes, Windows, Menus, Dialogs, ToolsAPI;
type
TIDEMenuItem = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
function GetName: string;
function GetIDString: string;
function GetMenuText: string;
function GetState: TWizardState;
procedure Execute;
end;
TIDEMenuHandler = class(TObject)
procedure HandleClick(Sender: TObject);
end;
procedure Register;
implementation
var
MenuItem: TMenuItem;
IDEMenuHandler: TIDEMenuHandler;
EditorPopUpMenu : TPopUpMenu;
procedure TIDEMenuItem.Execute;
begin
ShowMessage('Execute');
end;
function TIDEMenuItem.GetIDString: string;
begin
Result := 'IDEMenuItemID';
end;
function TIDEMenuItem.GetMenuText: string;
begin
Result := 'IDEMenuItemText';
end;
function TIDEMenuItem.GetName: string;
begin
Result := 'IDEMenuItemName';
end;
function TIDEMenuItem.GetState: TWizardState;
begin
Result := [wsEnabled];
end;
procedure TIDEMenuHandler.HandleClick(Sender: TObject);
begin
ShowMessage(TIDEMenuItem(Sender).GetName + ' Clicked');
end;
procedure AddIDEMenu;
var
NTAServices: INTAServices40;
EditorServices: IOTAEditorServices;
EditView: IOTAEditView;
begin
NTAServices := BorlandIDEServices as INTAServices40;
EditorServices := BorlandIDEServices as IOTAEditorServices;
EditView := EditorServices.TopView;
if Assigned(EditView) then begin
EditorPopUpMenu := TPopUpMenu(EditView.GetEditWindow.Form.FindComponent('EditorLocalMenu'));
Assert(EditorPopUpMenu <>Nil);
IDEMenuHandler := TIDEMenuHandler.Create;
MenuItem := TMenuItem.Create(Nil);
MenuItem.Caption := 'Added IDE editor menu item';
MenuItem.OnClick := IDEMenuHandler.HandleClick;
EditorPopUpMenu.Items.Add(MenuItem)
end
else
ShowMessage('Code editor not active');
end;
procedure RemoveIDEMenu;
begin
if MenuItem <> Nil then begin
EditorPopUpMenu.Items.Remove(MenuItem);
FreeAndNil(MenuItem);
IDEMenuHandler.Free;
end;
end;
procedure Register;
begin
RegisterPackageWizard(TIDEMenuItem.Create);
AddIDEMenu;
end;
initialization
finalization
RemoveIDEMenu;
end.
Update: The following code finds the TabControl of the editor window and adds the menu item to its context menu. However, note that it does not account for there being a second editor window open.
procedure AddIDEMenu;
var
NTAServices: INTAServices40;
EditorServices: IOTAEditorServices;
EditView: IOTAEditView;
TabControl : TTabControl;
function FindTabControl(AComponent : TComponent) : TTabControl;
var
i : Integer;
begin
Result := Nil;
if CompareText(AComponent.ClassName, 'TXTabControl') = 0 then begin
Result := TTabControl(AComponent);
exit;
end
else begin
for i := 0 to AComponent.ComponentCount - 1 do begin
if CompareText(AComponent.Components[i].ClassName, 'TXTabControl') = 0 then begin
Result := TTabControl(AComponent.Components[i]);
exit;
end
else begin
Result := FindTabControl(AComponent.Components[i]);
if Result <> Nil then
exit;
end;
end;
end;
end;
begin
NTAServices := BorlandIDEServices as INTAServices40;
EditorServices := BorlandIDEServices as IOTAEditorServices;
EditView := EditorServices.TopView;
if Assigned(EditView) then begin
TabControl := FindTabControl(EditView.GetEditWindow.Form);
Assert(TabControl <> Nil, 'TabControl not found');
EditorPopUpMenu := TabControl.PopupMenu;
Assert(EditorPopUpMenu <> Nil, 'PopUP menu not found');
//EditorPopUpMenu := TPopUpMenu(EditView.GetEditWindow.Form.FindComponent('EditorLocalMenu'));
Assert(EditorPopUpMenu <>Nil);
IDEMenuHandler := TIDEMenuHandler.Create;
MenuItem := TMenuItem.Create(Nil);
MenuItem.Caption := 'Added IDE editor menu item';
MenuItem.OnClick := IDEMenuHandler.HandleClick;
EditorPopUpMenu.Items.Add(MenuItem)
end
else
ShowMessage('No editor active');
end;
Assuming I have the Delphi IDE open, how can I open a .pas file selected in another app and open it in the Delphi IDE, as well as positioning it to a specific line number?
I've seen some editing tools do this.
I'm not sure if it's just an option to a normal file open (eg., using default file association), or a command-line option, or you need DDE or COM or something entirely different.
Note that I don't want to close the project and reopen a new or fake project.
Also, I don't want the file added to the project. I just want to open it.
For example, When you <ctrl>-click on a varible or type, the IDE will open the file containing that symbol and go to the line where that symbol is declared. That's all I want to do -- but from an external app. (I'm not looking for a symbol, just a line.)
I'm using Delphi XE5 at the moment, so I'm interested in newer Delphi versions, not pre-XE2 or so.
(Part of the question is, how do I ensure that if the IDE is already open, the the file is opened in anew tab inside of the current IDE rather than in another instance of the IDE?)
The code below (for D7) shows how this can be done by way of an IDE add-in .Dpk compiled
into a Bpl. It started as just a "proof of concept", but it does actually work.
It comprises a "sender" application which uses WM_COPYDATA to send the FileName, LineNo & Column to a receiver hosted in the .Bpl file.
The sender sends the receiver a string like
Filename=d:\aaad7\ota\dskfilesu.pas
Line=8
Col=12
Comment=(* some comment or other*)
The Comment line is optional.
In the .Bpl, the receiver uses OTA services to open the requested file and positions the editor caret, then inserts the comment, if any.
The trickiest thing was to find out how to handle one particular complication, the case where the named file to be opened is one with an associated form. If so, in D7 (and, I assume, other IDE versions with the floating designer option enabled) when the IDE
opens the .Pas file, it also opens the .Dfm, and left to its own devices, that would leave the form editor in front of the code editor. Calling the IOTASourceEditor.Show for the .Pas file at least puts the IDE code editor in front of the .Dfm form, but that didn't satisfy me, because by now my curiosity was piqued - how do you get a form the IDE is displaying off the screen?
I spent a lot of time exploring various blind alleys, because the OTA + NTA services don't seem to provide any way to explicitly close an IOTAEditor or any of its descendants. In the end it turned out that the thing to do is simply get a reference to the form and just send it a WM_CLOSE(!) - see comments in the code.
Fwiw, being a novice at OTA, at first (before I found out how IOTAModules work) I found that far and away the most difficult part of this was discovering how to get hold of the IEditView interface needed to set the editor caret position, but as usual with these interfacey things, once you get the "magic spell" exactly right, it all works.
Good luck! And thanks for the fascinating challenge!
unit Receiveru;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, ToolsAPI;
type
TOTAEditPosnForm = class(TForm)
Memo1: TMemo;
private
FEdLine: Integer;
FEdCol: Integer;
FEditorFileName: String;
FEditorInsert: String;
procedure WMCopyData(var Msg : TWMCopyData); message WM_COPYDATA;
procedure HandleCopyDataString(CopyDataStruct : PCopyDataStruct);
procedure OpenInIDEEditor;
property EditorFileName : String read FEditorFileName write FEditorFileName;
property EdLine : Integer read FEdLine write FEdLine;
property EdCol : Integer read FEdCol write FEdCol;
property EditorInsert : String read FEditorInsert write FEditorInsert;
end;
var
OTAEditPosnForm: TOTAEditPosnForm;
procedure Register;
implementation
{$R *.dfm}
procedure MonitorFiles;
begin
OTAEditPosnForm := TOTAEditPosnForm.Create(Nil);
OTAEditPosnForm.Show;
end;
procedure Register;
begin
MonitorFiles;
end;
procedure TOTAEditPosnForm.OpenInIDEEditor;
var
IServices : IOTAServices;
IActionServices : IOTAActionServices;
IModuleServices : IOTAModuleServices;
IEditorServices : IOTAEditorServices60;
IModule : IOTAModule;
i : Integer;
IEditor : IOTAEditor;
ISourceEditor : IOTASourceEditor;
IFormEditor : IOTAFormEditor;
IComponent : IOTAComponent;
INTAComp : INTAComponent;
AForm : TForm;
IEditView : IOTAEditView;
CursorPos : TOTAEditPos;
IEditWriter : IOTAEditWriter;
CharPos : TOTACharPos;
InsertPos : Longint;
FileName : String;
begin
IServices := BorlandIDEServices as IOTAServices;
Assert(Assigned(IServices), 'IOTAServices not available');
IServices.QueryInterface(IOTAACtionServices, IActionServices);
if IActionServices <> Nil then begin
IServices.QueryInterface(IOTAModuleServices, IModuleServices);
Assert(IModuleServices <> Nil);
// Close all files open in the IDE
IModuleServices.CloseAll;
if IActionServices.OpenFile(EditorFileName) then begin
// At this point, if the named file has an associated .DFM and
// we stopped here, the form designer would be in front of the
// code editor.
IModule := IModuleServices.Modules[0];
// IModule is the one holding our .Pas file and its .Dfm, if any
// So, iterate the IModule's editors until we find the one
// for the .Pas file and then call .Show on it. This will
// bring the code editor in front of the form editor.
ISourceEditor := Nil;
for i := 0 to IModule.ModuleFileCount - 1 do begin
IEditor := IModule.ModuleFileEditors[i];
FileName := IEditor.FileName;
Memo1.Lines.Add(Format('%d %s', [i, FileName]));
if CompareText(ExtractFileExt(IEditor.FileName), '.Pas') = 0 then begin
if ISourceEditor = Nil then begin
IEditor.QueryInterface(IOTASourceEditor, ISourceEditor);
IEditor.Show;
end
end
else begin
// Maybe the editor is a Form Editor. If it is
// close the form (the counterpart to the .Pas, that is}
IEditor.QueryInterface(IOTAFormEditor, IFormEditor);
if IFormEditor <> Nil then begin
IComponent := IFormEditor.GetRootComponent;
IComponent.QueryInterface(INTAComponent, INTAComp);
AForm := TForm(INTAComp.GetComponent);
//AForm.Close; < this does NOT close the on-screen form
// IActionServices.CloseFile(IEditor.FileName); <- neither does this
SendMessage(AForm.Handle, WM_Close, 0, 0); // But this does !
end;
end;
end;
// Next, place the editor caret where we want it ...
IServices.QueryInterface(IOTAEditorServices, IEditorServices);
Assert(IEditorServices <> Nil);
IEditView := IEditorServices.TopView;
Assert(IEditView <> Nil);
CursorPos.Line := edLine;
CursorPos.Col := edCol;
IEditView.SetCursorPos(CursorPos);
// and scroll the IEditView to the caret
IEditView.MoveViewToCursor;
// Finally, insert the comment, if any
if EditorInsert <> '' then begin
Assert(ISourceEditor <> Nil);
IEditView.ConvertPos(True, CursorPos, CharPos);
InsertPos := IEditView.CharPosToPos(CharPos);
IEditWriter := ISourceEditor.CreateUndoableWriter;
Assert(IEditWriter <> Nil, 'IEditWriter');
IEditWriter.CopyTo(InsertPos);
IEditWriter.Insert(PChar(EditorInsert));
IEditWriter := Nil;
end;
end;
end;
end;
procedure TOTAEditPosnForm.HandleCopyDataString(
CopyDataStruct: PCopyDataStruct);
begin
Memo1.Lines.Text := PChar(CopyDataStruct.lpData);
EditorFileName := Memo1.Lines.Values['FileName'];
edLine := StrToInt(Memo1.Lines.Values['Line']);
edCol := StrToInt(Memo1.Lines.Values['Col']);
EditorInsert := Trim(Memo1.Lines.Values['Comment']);
if EditorFileName <> '' then
OpenInIDEEditor;
end;
procedure TOTAEditPosnForm.WMCopyData(var Msg: TWMCopyData);
begin
HandleCopyDataString(Msg.CopyDataStruct);
msg.Result := Length(Memo1.Lines.Text);
end;
initialization
finalization
if Assigned(OTAEditPosnForm) then begin
OTAEditPosnForm.Close;
FreeAndNil(OTAEditPosnForm);
end;
end.
Code for sender:
procedure TSenderMainForm.btnSendClick(Sender: TObject);
begin
SendMemo;
end;
procedure TSenderMainForm.SendData(
CopyDataStruct: TCopyDataStruct);
var
HReceiver : THandle;
Res : integer;
begin
HReceiver := FindWindow(PChar('TOTAEditPosnForm'),PChar('OTAEditPosnForm'));
if HReceiver = 0 then begin
Caption := 'CopyData Receiver NOT found!';
end
else begin
Res := SendMessage(HReceiver, WM_COPYDATA, Integer(Handle), Integer(#CopyDataStruct));
if Res > 0 then
Caption := Format('Received %d characters', [Res]);
end;
end;
procedure TSenderMainForm.SendMemo;
var
MS : TMemoryStream;
CopyDataStruct : TCopyDataStruct;
S : String;
begin
MS := TMemoryStream.Create;
try
S := Memo1.Lines.Text + #0;
MS.Write(S[1], Length(S));
CopyDataStruct.dwData := 1;
CopyDataStruct.cbData := MS.Size;
CopyDataStruct.lpData := MS.Memory;
SendData(CopyDataStruct);
finally
MS.Free;
end;
end;
Is it possible to make readonly source code (eg., .pas & .dfm) writable from within the Delphi IDE? The right click option to make files Readonly/Writable within the IDE doesn't change the properties on the file system. Is there an IDE extension or similar that can achieve this?
A way to do this without having to integrate a source control system would be preferable. I am using Delphi XE and Delphi 6.
Thanks!
sse
This is how I would do it.
Create a new package which will be installed into the IDE at designtime. If you have an existing package handy then you could carry on using it. Make sure the package requires the designide package. You can do this in the project manager, or just by viewing the project source and adding designide to the requires clause.
Now add the following unit to your package.
unit MakeEditable;
interface
procedure Register;
implementation
uses
Windows, SysUtils, Menus, ToolsAPI;
type
TMakeEditable = class(TObject)
private
FEditorServices: IOTAEditorServices;
FFileMenu: TMenuItem;
FMakeEditable: TMenuItem;
function MenuItemWithCaptionLike(const Menu: TMenuItem; const DesiredCaption: string): TMenuItem;
procedure MakeEditableClick(Sender: TObject);
public
constructor Create;
destructor Destroy; override;
end;
constructor TMakeEditable.Create;
var
Index: Integer;
PreviousMenuItem: TMenuItem;
begin
inherited;
FEditorServices := (BorlandIDEServices as IOTAEditorServices);
FFileMenu := MenuItemWithCaptionLike((BorlandIDEServices as INTAServices40).MainMenu.Items, 'File');
if Assigned(FFileMenu) then begin
PreviousMenuItem := MenuItemWithCaptionLike(FFileMenu, 'Reopen');
if Assigned(PreviousMenuItem) then begin
Index := PreviousMenuItem.MenuIndex;
if Index>=0 then begin
FMakeEditable := TMenuItem.Create(FFileMenu);
FMakeEditable.Caption := 'Ma&ke Editable';
FMakeEditable.OnClick := MakeEditableClick;
FFileMenu.Insert(Index, FMakeEditable);
end;
end;
end;
end;
destructor TMakeEditable.Destroy;
begin
FMakeEditable.Free;
inherited;
end;
function TMakeEditable.MenuItemWithCaptionLike(const Menu: TMenuItem; const DesiredCaption: string): TMenuItem;
var
i: Integer;
Target, Found: string;
begin
Target := StringReplace(LowerCase(Trim(DesiredCaption)), '&', '', [rfReplaceAll, rfIgnoreCase]);
for i := 0 to Menu.Count-1 do begin
Result := Menu.Items[i];
Found := StringReplace(LowerCase(Trim(Result.Caption)), '&', '', [rfReplaceAll, rfIgnoreCase]);
if Pos(Target, Found)>0 then begin
exit;
end;
end;
Result := nil;
end;
procedure TMakeEditable.MakeEditableClick(Sender: TObject);
procedure MakeFileEditable(const FileName: string);
var
Attributes: DWORD;
begin
Attributes := GetFileAttributes(PChar(FileName));
SetFileAttributes(PChar(FileName), Attributes and not FILE_ATTRIBUTE_READONLY);
end;
var
FileName: string;
FileExt: string;
LinkedFileName: string;
EditBuffer: IOTAEditBuffer;
begin
EditBuffer := FEditorServices.TopBuffer;
FileName := EditBuffer.FileName;
if FileExists(FileName) then begin
MakeFileEditable(FileName);
EditBuffer.IsReadOnly := False;
FileExt := ExtractFileExt(FileName);
if SameText(FileExt,'.dfm') then begin
LinkedFileName := ChangeFileExt(FileName, '.pas');
end else if SameText(FileExt,'.pas') then begin
LinkedFileName := ChangeFileExt(FileName, '.dfm');
end else begin
LinkedFileName := '';
end;
if (LinkedFileName<>'') and FileExists(LinkedFileName) then begin
MakeFileEditable(LinkedFileName);
end;
end;
end;
var
MakeEditableInstance: TMakeEditable;
procedure Register;
begin
MakeEditableInstance := TMakeEditable.Create;
end;
initialization
finalization
MakeEditableInstance.Free;
end.
When you compile and install this package you will now have a new menu item on the File menu which both clears the read-only flag in the input buffer and makes the file writeable.
You can call a .bat file from the tools menu. So you could write a .bat file to do the work for you, and call it with $EDNAME as the parameter.
Your .bat file should see the filename as %1. Then you'd need a little bit of logic to change the read-only flag (attrib command?) and then see if there is a .dfm and do that one as well.
You could also (obviously) make a Delphi command-line app to do this, if you're not comfortable with .bat files.
This idea inspired by this article that talks about how to use a .bat file to integrate SVN commands with the Delphi tools menu:
http://delphi.wikia.com/wiki/Adding_TortoiseSVN_to_the_Tools_menu
Another idea: Just add an option to the tools menu: "open command prompt here". It lets you do all sorts of things from the command line, such as running the attrib command.
Add a new entry to the Tools menu, and use these settings:
Title: Open Command Prompt Here
Program: cmd.exe
Working Dir (leave blank)
Parameters: cd $PATH($EDNAME)
Also, make yourself an "Open Folder Here" entry:
Title: Open Folder Here
Program: explorer.exe
Working Dir (leave blank)
Parameters: $PATH($EDNAME)
What is a new way to get current file that is being worked on in Experts for Delphi XE
Previously in Delphi 5-7 we used ToolServices.getCurrentFile
Perhaps the deprecated units ToolIntf, ExptIntf etc. are no longer working. You can use IOTAModuleServices.CurrentModule instead. Here's a quick example:
function GetCurrentEditorFileName: string;
var
Module: IOTAModule;
Editor: IOTAEditor;
begin
Result := '';
Module := (BorlandIDEServices as IOTAModuleServices).CurrentModule;
if Assigned(Module) then
begin
Editor := Module.CurrentEditor;
if Assigned(Editor) then
Result := Editor.FileName;
end;
end;
An alternate method is to pass the "name of file in the editor" to your tool as a parameter. $EDNAME
I'm trying embed an Excel file into my Delphi 5 application, so I can avoid my users just deleting the file accidentally.
Using the embedded file, I create it on disk with a Save dialog, and then open it with the Excel := CreateOleObject('Excel.Application'); method. I've seen examples on how to load a resource, using THandles, but I don't seem to get it working with Excel.WorkBooks.Open(EmbeddedExcelFile);.
Have you had to do something like this before? How would you do it?
Thanks!
You have to include the file as a resource. Say you have a blah.xls
Create a blah.rc file with the following content
blah RCDATA blah.xls
compile it with the resource compiler into blah.res
embed the RES file within the executable
{$R 'blah.res'}
in your application code, extract the file and run it with this code
procedure ExtractAndRun(resourceID:string; resourceFn:string);
var
resourceStream: TResourceStream;
fileStream: TFileStream;
tempPath: string;
fullFileName: string;
begin
tempPath:=GetTempDir;
FullFilename:=TempPath+'\'+resourceFN;
if not FileExists(FullFilename) then
begin
resourceStream := TResourceStream.Create(hInstance, resourceID, RT_RCDATA);
try
fileStream := TFileStream.Create(FullFilename, fmCreate);
try
fileStream.CopyFrom(resourceStream, 0);
finally
fileStream.Free;
end;
finally
resourceStream.Free;
end;
end;
ShellExecute(0,'open', pchar(FullFilename), nil, nil, SW_SHOWNORMAL);
end;
you'll have to add ShellApi in your uses clause
maybe you'll need this GetTempDir function
function GetTempDir: string;
var
Buffer: array[0..MAX_PATH] of char;
begin
GetTempPath(SizeOf(Buffer) - 1, Buffer);
result := StrPas(Buffer);
end;
invoke the function like this
extractAndRun('blah','blah.xls');
I am pretty sure it will not work. You have to save the file in a temp folder, alter it and and then do whatever you want.