Sending selected text to Delphi current program unit

HI experts,

Can you explain me how I can send to the current Delphi Unit I'm working on a portion of selected text in my own application at current caret position (a little like I can send selected text to Word using automation) ?

I need to check that Delphi (5)
- is installed // if not delete option in the menu prog or toolbar
- is running // if not open it giving it the name of the project to edit and the unit to use
- a project is open // which project, name, path
- the name of the current Unit // the unit where to insert portion of text
                                           // portion of text will be or header, or tested functions and or procedures, ...
- the position (line) of the caret // to insert the portion of text
- eventually keepinp the formatting (= indentation)

My prog contains actually more than 10.000 snippets (I could use the librarian of GExpert or dig into the code source, but it's too technical for me) and I want my prog use it's own solution.

Points begin at 100 but they will reach the followings levels:
- 100 : for fully explanation of the code
- 250 : working code solution using the clipboard (if it's possible)
or
- 400 : working code solution without using the clipboard but a solution like Word automation

Thanks all for your help,

LVL 9
bernaniAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

ZhaawZSoftware DeveloperCommented:
>> - is installed
You could check existence of registry key HKEY_CURRENT_USER\Software\Borland\Delphi\5.0\ (remvoe '5.0\' if you don't care about version)

>> - is running
The simpliest way - use FindWindow('TApplication', 'Delphi 5') - if it returns 0, Delphi probably is not running. You could also check some other Delphi IDE windows.
If you don't care about version, use EnumWindows() function to enumerate all top windows and check if there is some window with classname 'TApplication' (GetClassName()) and title that starts with 'Delphi ' (GetWindowText()).
Safer way - read value of HKEY_CURRENT_USER\Software\Borland\Delphi\5.0\App (this is main file name of Delphi) and check if it is running.
About 2nd way - I have not done this before so I don't know how exactly it should be done. I believe you'll have to enumerate all running processes and get filenames of them.

>> - a project is open
Project or some 'Edit Window'? Sometimes just some *.pas file can be opened (without opening whole project). I think it would be much easier to check if Edit Window is opened.

>> - the name of the current Unit
This can be done by using GetWindowText (use FindWindow() to get handle of Edit Window - it's caption contains name of 'current unit' (it can be also project file, not an unit))

>> - the position (line) of the caret
I believe this can be done by sending some messages to edit control (control that holds all source text) in Edit Window but I didn't find which messages you have to send. It's possible that you'll have to make small .dll file to do this.

>> - eventually keepinp the formatting
Didn't understand this.

>> - 250 : working code solution using the clipboard (if it's possible)
>> or
>> - 400 : working code solution without using the clipboard but a solution like Word automation
I think it must be possible to be done without using clipboard. Even if it's not, it is possible to restore data in clipboard after operation.



I don't have much time now. I could try to write some sample codes for you next week (if noone will help you before me) ;)
Anyway, you'll have to use Windows API to do all this stuff. Use MSDN (http://msdn.microsoft.com/) - it has much info about WinAPI.
bernaniAuthor Commented:
Hi ZhaawZ,

Thanks for you answer which gives me some interested infos and clues how to do the job or at least in which direction to start.

 >> - eventually keepinp the formatting
Didn't understand this.
=  keeping indenting of lines in this way:

if .....
   begin
   ...
   ...
   end else
  ...

>> I could try to write some sample codes for you next week (if noone will help you before me) ;)
     Even in this case, I'd appreciate if you could write some sample codes, I'm not really an expert in Apis.
     And I'll give you the necessary points for your work.

Bernani

ZhaawZSoftware DeveloperCommented:
About formatting - if you add text with indents, they will be there. I don't know yet about making bold or coloring some reserved words - it could be a problem (but I'm not sure).

Does it have to be Delphi 5 or it can be any other version of Delphi?
OWASP Proactive Controls

Learn the most important control and control categories that every architect and developer should include in their projects.

bernaniAuthor Commented:
HI ZhaawZ,

Don't worry too much about formatting, it's not too important; it's just to keep the clarity of the inserted text in the Delphi Unit from my program (and in Delphi Units I only need Ascii text, as you know).

I'd like the code to be in Delphi 5  because it's the version wich I currently use to develop my prog.
It will be  easier to upgrade the code from D5 to newer version of Delphi.

What I'd like is a code wich can be used with any version of Delphi, independent of the Delphi version installed but I shoud concede that I don't know if it's possible (I'm not yet an expert in Delphi).

I use my prog to store functions, procedure in sorted categories from different sources (web, scans, mails newsgroups ....).
Each category contains  a number of files in Rtf format.
Each file generally contains one function or procedure with explanation for the parameters, use clause, sample of calling or related stuff.

In this way:

Categories
    Apis
    Automation
    Clipboard
    Databases
    Dates
    Functions
    ...
    Word Office

The category Functions contains for example:
   UseSetLength().rtf
   FreeingMemory.rtf
   AccessRecent docs.rtf
   GetUserInput.rtf
   CenterForm.rtf
   ConvertDFMtoTXT file.rtf
   CopyInternetExplorerAddressBar.rtf
   CreatingTemporaryStack.rtf
   CutFilename.rtf
   CutPathname.rtf
   DetectWindowsShutdown
   ProcessorSpeedMHz.rtf
   ChangeFileExt.rtf
   ...

Until now, I used a ChecListbox to be able to select which files I want to send to MSWord (using automation) to compose a complete new document with a bunch of procedures and functions I need for a particular program, by checking the correspondent filenames in each category;  I also used it to send part of the selected content of the filename to MSWord. I can also compose this new document in my own program to create a complete help topic.  

What I want now to do, is to export directly the same content not to MSWord but directly to an open Delphi Unit (or the complete filename's content or simply the selection - at the caret's position in the Delphi Unit) by clicking on a button or a key combination in my own program.

Each click or keypress on a selection send or the whole content or the selection to the Delphi Unit.
When I close my program, the Unit  in Delphi is filled with all the selections done in the onclick or keypress event

I use this technique to save web page's content, using only the F12 key and without quitting the explorer. With my program minimized in the systray, I need only to select what I want to save in the web page, hit the F12 key and the selection is auomatically saved in a particular folder, with a unique name, in Rtf format, ready to be renamed automatically and used by the program. I use it to save the content of the answered questions here on ExpertExchange renaming the file in a correct way with the content of the "Solution title:".

The program will also used to created a complete catalog of the category and filenames: it's already done for each category in this way:
[Catégorie]
160=Named
[Items]
0=000 - Listing Contenu.ini
1=Check valid email address ...-1.rtf
2=Minimize app if idle for a period of time.rtf
3=Ms Access.rtf
4=Outgoing email Delphi 4 through web host.rtf
...

Hope this gives you an idea of the program and what I want to do with the selection to send to Delphi.




 
carcotasuCommented:
unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons, ComCtrls;

type
  TForm1 = class(TForm)
    RichEdit1: TRichEdit;
    BitBtn1: TBitBtn;
    procedure BitBtn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    function GetOtherWindowMemoText(const sCaption : String) : WideString;
    function SetOtherWindowMemoText(const sCaption : String; const sText : String) : WideString;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
SetOtherWindowMemoText('Untitled - Notepad',RichEdit1.Lines.Text);
end;

Function TForm1.GetOtherWindowMemoText(const sCaption : String) : WideString;
var
  hWindow : THandle;
  hChild : THandle;
  aTemp: array[0..5000] of Char;
  sClassName : String;
begin
  Result := '';
  hWindow := FindWindow(Nil,PChar(sCaption));
  if hWindow = 0 then begin
    ShowMessage('Could NOT find the other program');
  exit;
end;
  hChild := GetWindow(hWindow, GW_CHILD);
  while hChild <> 0 do Begin
    if GetClassName(hChild, aTemp, SizeOf(aTemp)) > 0 then begin
      sClassName := StrPAS(aTemp);
      if sClassName = 'Edit' then begin
        SendMessage(hChild,WM_GETTEXT,SizeOf(aTemp),Integer(@aTemp));
        Result := StrPAS(aTemp);
      end;
    end;
    hChild := GetWindow(hChild, GW_HWNDNEXT);
  end;
end;

Function TForm1.SetOtherWindowMemoText(const sCaption : String; const sText : String) : WideString;
var
  hWindow : THandle;
  hChild: THandle;
  aTemp: array[0..5000] of Char;
  sClassName : String;
begin
  Result := '';
  hWindow := FindWindow(Nil,PChar(sCaption));
  if hWindow = 0 then begin
    ShowMessage('Could NOT find the other program');
    exit;
  end;
  hChild := GetWindow(hWindow, GW_CHILD);
    while hChild <> 0 do Begin
      if GetClassName(hChild, aTemp, SizeOf(aTemp)) > 0 then begin
        sClassName := StrPAS(aTemp);
        if sClassName = 'Edit' then begin
          StrPCopy(aTemp,sText);
          SendMessage(hChild,WM_SETTEXT,SizeOf(aTemp),Integer(@aTemp));
        end;
      end;
      hChild := GetWindow(hChild, GW_HWNDNEXT);
    end;
end;

//Let say you want to get the text from Notepad, then you should pass the title of notepad to the previous function and assign the return value to Rich edit.    An example:

//...............................................
//Get text from noteppad.
//RichEdit1.Lines.Text := GetOtherWindowMemoText('Untitled - Notepad');
//...............................................
///Set notepad memo text.
//SetOtherWindowMemoText('Untitled - Notepad',RichEdit1.Lines.Text);



end.

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
bernaniAuthor Commented:
Hi carcotasu,

Thanks very much for your code above.
It pass all my test and works great (after changing the name of Notepad - Untitled to the french version I'm using).

Have you got and idea how to pass the result to the Delphi Editor rather than Notepad : your prog above do exactly what I wanna do with my prog and Delphi Editor.

If I pass the name of my Unit1.pas to the previous function, it's recognized but the variable sClassName remains empty and it's doesn't work (I presume it's due to the fact than sClassName = 'Edit' in the code).
Have you and idea which is the parameter to change in sClassName = 'Edit'  (the name of the Delphi Editor - What's it's name ?).

Or do I have to do sth else ?

Thanks for your precious help  .

carcotasuCommented:
I think this unit help you, it's a lot of functions for get a name, handle, caption, class, kill proceess tree, etc...

create a file in Notepad and save with ProcessViewer.pas name;
create a new project with a 2 component 1 BitBtn and 1 ListBox
include a ProcessViewer:
{
.....
 Form1: TForm1;

implementation
uses processviewer ;
{$R *.dfm}
}

then at BitBtn.Click event put this code:

GetProcessList ;
listbox1.Items.Text:=processviewer.my.Text;

good luck

**********************************************************************************
unit ProcessViewer;

interface

uses
Windows, Dialogs, SysUtils, Classes, ShellAPI, TLHelp32, Forms;

const
SleepForReCheck=5000;

type TProcessInfo=record
FileName: string;
Caption: string;
Visible: boolean;
Handle: DWord;
PClass: string;
ThreadID: DWord;
PID: DWord;
end;


var
DateiList,CaptionList,VisibleList,HandleList,ClassList,ThreadIdList,PIDList, my: TStringList;
ProcessInfo: array of TProcessInfo;

function EnumWindowsProc(hWnd: HWND; lParam: LPARAM): Bool; stdcall;
function KillProcessByPID(PID: DWord): boolean;
function KillProcessByFileName(FileName: string; KillAll: boolean): boolean;
procedure GetProcessList;
function GetFileNameFromHandle(Handle: hwnd):string;
function IsFileActive(FileName: String): boolean;

implementation

procedure GetProcessList;
var
i,Laenge: integer;
begin
DateiList.Clear;
my.Clear ;
HandleList.Clear;
ClassList.Clear;
CaptionList.Clear;
VisibleList.Clear;
ThreadIdList.Clear;
PIDList.Clear;
EnumWindows(@EnumWindowsProc, 0);
Laenge:=DateiList.Count;
my:=DateiList ;
SetLength(ProcessInfo,Laenge);
for i:=0 to Laenge-1 do begin
  DateiList[i]:=UpperCase(DateiList[i]);
  my[i]:=DateiList[i]+' --> ' +CaptionList[i];
  with ProcessInfo[i] do begin
    FileName:=DateiList[i];
    Caption:=CaptionList[i];
    Visible:=VisibleList[i]='1';
    Handle:=StrToInt64(HandleList[i]);
    PClass:=ClassList[i];
    ThreadID:=StrToInt64(ThreadIdList[i]);
    PID:=StrToInt64(PIDList[i]);
  end;
end;
end;

function IsFileActive(FileName: String): boolean;
var
i: integer;
begin
result:=false;
if FileName='' then exit;
GetProcessList;
FileName:=UpperCase(ExtractFileName(FileName));
for i:=0 to Length(ProcessInfo)-1 do
begin
if Pos(FileName,ProcessInfo[i].FileName)>0 then
begin
result:=true;
break;
end;
end;
end;

function GetFileNameFromHandle(Handle: hwnd):string;
var
PID: DWord;
aSnapShotHandle: THandle;
ContinueLoop: Boolean;
aProcessEntry32: TProcessEntry32;
begin
GetWindowThreadProcessID(Handle, @PID);
aSnapShotHandle := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
aProcessEntry32.dwSize := SizeOf(aProcessEntry32);
ContinueLoop := Process32First(aSnapShotHandle, aProcessEntry32);
while Integer(ContinueLoop) <> 0 do
begin
if aProcessEntry32.th32ProcessID = PID then
begin
result:=aProcessEntry32.szExeFile;
break;
end;
ContinueLoop := Process32Next(aSnapShotHandle, aProcessEntry32);
end;
CloseHandle(aSnapShotHandle);
end;

function EnumWindowsProc(hWnd: HWND; lParam: LPARAM): Bool;
var
Capt,Cla: array[0..255] of char;
Datei: string;
ident: dword;
begin
GetWindowText(hWnd, Capt, 255);
GetClassName(hwnd,Cla,255);
ThreadIdList.Add(IntToStr(GetWindowThreadProcessId(hwnd,nil)));
Datei:=GetFileNameFromhandle(hwnd);
DateiList.Add(Datei);
HandleList.Add(IntToStr(HWnd));
if IsWindowVisible(HWnd) then VisibleList.Add('1') else VisibleList.Add('::0');
ClassList.Add(Cla);
CaptionList.Add(Capt);
GetWindowThreadProcessId(StrToInt(HandleList[HandleList.Count-1]),@ident);
PIDList.Add(IntToStr(ident));
Result:=true;
end;

function KillProcessByPID(PID : DWord): boolean;
var
myhandle : THandle;
i: integer;
begin
myhandle := OpenProcess(PROCESS_TERMINATE, False, PID);
TerminateProcess(myhandle, 0);
for i:=0 to SleepForReCheck do Application.ProcessMessages; //Genug Zeit geben
GetProcessList;
Result:=PIDList.IndexOf(IntToStr(PID))=-1;
end;

function KillProcessByFileName(FileName: string; KillAll: boolean): boolean;
var
i: integer;
FileFound: boolean;
begin
result:=false;
if FileName='' then exit;
FileName:=UpperCase(ExtractFileName(FileName));
result:=true;
GetProcessList;
if KillAll then
begin
//Kill all
FileFound:=false;
repeat
GetProcessList;
FileFound:=false;
for i:=0 to DateiList.Count-1 do
begin
if Pos(Filename,DateiList[i])>0 then
begin
FileFound:=true;
break;
end;
end;
if i<DateiList.Count then
begin
if not KillProcessByPID(StrToInt64(PIDList[i])) then
begin
result:=false;
exit;
end;
end;
until not FileFound;
end else
begin
//Kill one
for i:=0 to DateiList.Count-1 do
begin
if Pos(Filename,DateiList[i])>0 then break;
end;
if i<DateiList.Count then
begin
if not KillProcessByPID(StrToInt64(PIDList[i])) then
begin
result:=false;
exit;
end;
end;
end;
end;

initialization
my:=TStringList.Create;
DateiList:=TStringList.Create;
HandleList:=TStringList.Create;
ClassList:=TStringList.Create;
CaptionList:=TStringList.Create;
VisibleList:=TStringList.Create;
ThreadIdList:=TStringList.Create;
PIDList:=TStringList.Create;

finalization
my.Free ;
DateiList.Free;
HandleList.Free;
ClassList.Free;
CaptionList.Free;
VisibleList.Free;
ThreadIdList.Free;
PIDList.Free;

end.

carcotasuCommented:
this unit help you to find Handle ID from the Delphi Editor for send a text to it.
bernaniAuthor Commented:
Hi carcotasu,

Thanks for your code. I'll try it but It's not sufficient to achiee my goal.

If I analyze the results found in winsight I see that the name I must supply to you function must be "Unit1.pas". Yhe one I use for the test.

You function contains the code if sClassName = 'Edit' then begin
I think it shoud be : if sClassName = 'TEditControl' then begin

The levels in winsight for the window of Unit1.pas seems to be the following:

.... {TEditWindow} ......{Unit1.pas}
Child: TStatusBar
Child: TPanel
         Child: TPanel
                  Child: TEDitControl  // I believe that's the component editor of Delphi hte one I need to reach
         Child: TPanel
                   Child: TToolBar
                   Child: TXTTabControl
Child: TEditorDockPanel

If I modify your function with: if sClassName = 'TEditControl' then begin

and put a showmessage to display the sClassName, it gives: TStatusBar, TPanel, TEditorDockPanel ... in other way, the first level of children. And the control I want to reach is 2 levels deeper (TPanel and finally TEditControl).

Could you see if your functions can be modified in this sense ? I'm nearly sure the solution is not so far ...

Thanks,



bernaniAuthor Commented:
Hi,

if I use
if FindWindow('TAppBuilder', nil)> 0 then showmessage('Delphi is Running');
let me know if Delphi is running or not.

if FindWindow('TEditWindow', pchar('Unit1.pas')) > 0 then showmessage('Found Unit1.pas');
gives me a correct answer but isn't able to find other open units.

It seems a very difficult matter, so I increase the points to the max = 500.

Hope someone can help.

btw  carcotasu the process viewer works well but gives me an exception when I close the application.

Bernani
ZhaawZSoftware DeveloperCommented:
> if FindWindow('TEditWindow', pchar('Unit1.pas')) > 0 then showmessage('Found Unit1.pas');
> gives me a correct answer but isn't able to find other open units.
There's no need to write pchar('unit1.pas') - it will be enough with just 'unit1.pas'

Here's a small script that finds all opened Delphi windows (checked by classname and window title, so if some other opened window has same classname and similar title, then this can be buggy) and all opened edit windows for these Delphi apps (edit windows are grouped by app - used process id for this).



procedure TForm1.FormCreate(Sender: TObject);
type
  TDelphiWnd = record
    pid    : cardinal;                // process id
    handle : cardinal;                // handle of main window
    title  : array [0..63] of char;   // title of main window
    edits  : array of record          // info about all edit windows in current process
      handle : cardinal;              // handle of window
      title  : array [0..63] of char; // title of window (same as unit filename?)
    end;
  end;
  TInfo = record
    step : integer;
    wnds : array of TDelphiWnd;
  end;
  PInfo = ^TInfo;

  function EnumWndsProc(wnd : cardinal; info : PInfo) : boolean; stdcall;
  var
    n : integer;
    tmp : cardinal;
    txt : array [0..63] of char;
  begin
  result := true;
  case info^.step of
    1 : begin // get Delphi windows
          GetClassName(wnd, @txt, 64);
          if lstrcmp(@txt, 'TAppBuilder') <> 0 then exit;
          GetWindowText(wnd, @txt, 8);
          if lstrcmp(@txt, 'Delphi ') <> 0 then exit;
          with info^ do begin
            SetLength(wnds, length(wnds) + 1);
            with wnds[high(wnds)] do begin
              handle := wnd;
              GetWindowText(wnd, @title, 64);
              GetWindowThreadProcessId(wnd, pid);
            end;
          end;
        end;
    2 : begin // get Edit windows
          GetWindowThreadProcessId(wnd, tmp);
          with info^ do begin
            for n := 0 to high(wnds) do if wnds[n].pid = tmp then break;
            if n > high(info^.wnds) then exit;
            GetClassName(wnd, @txt, 64);
            if lstrcmp(@txt, 'TEditWindow') <> 0 then exit;
            with wnds[n] do begin
              SetLength(edits, length(edits) + 1);
              with edits[high(edits)] do begin
                handle := wnd;
                GetWindowText(wnd, @title, 64);
              end;
            end;
          end;
        end;
  end;
  end;

var
  info : TInfo;
  n1, n2 : integer;

begin
info.step := 1; // get all main windows
EnumWindows(@EnumWndsProc, integer(@info));
info.step := 2; // get all edit windows
EnumWindows(@EnumWndsProc, integer(@info));

for n1 := 0 to high(info.wnds) do begin
  ListBox1.Items.Append(info.wnds[n1].title);
  for n2 := 0 to high(info.wnds[n1].edits) do begin
    ListBox1.Items.Append('-- ' + info.wnds[n1].edits[n2].title);
  end;
end;
end;
bernaniAuthor Commented:
Hi ZhaawZ ,

Thanks for your answer. I'll test your code.

I ask myself, if I can retrevieve the current open unit in D5 (as I can get the name and choose this one or another unit in the ListBox.ItemIndex), how can I insert the selected text from my application to the current or choosen unit ?

This is what I want to achieve.

Thanks a lot for your precious help.

Bernani
bernaniAuthor Commented:
Hi,

I've found a way to achieve my goal in the book Delphi 7 Studio written by Olivier Dahan and Paul Toth (Eyrolles - ISBN 2-212-11143-6) chapter 24 (French version: Les editeurs de propriétés et de composants - Properties and components editors).

As the snippets given by Zhaawh and carcotasu have helped me to solve other tasks, I split the points between them.
bernaniAuthor Commented:
To Zaawh and carcatasu,

Thanks for your help.

I give more a little more points to carcotasu for his 2 excellent functions (graded A):
    function GetOtherWindowMemoText(const sCaption : String) : WideString;
    function SetOtherWindowMemoText(const sCaption : String; const sText : String) : WideString;

Bernani.
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.