PDA

Ver la Versión Completa : Leer strings de otra aplicacion


cesarsoftware
10-12-2012, 16:29:03
Hola foreros, me gustaria poder leer las lineas escritas (y poder escribir) de un campo, creo que es richtext de otra aplicacion.
El motivo es que dicha aplicacion guarda los datos en formato binario y me parece que va a ser dificil interpretar esa informacion.:confused:
Y parece ser que tienen una aplicacion (que solo funciona en win95) que ya hace eso que ahora me piden a mi (para modernizarlo, jejeje)

Hasta ahora lo que he conseguido es:
1) Localizo las aplicaciones en ejecucion.

function EnumWinProc(Wnd: HWND; Param: pointer): boolean; stdcall; export;
var
WinText: array[0..255] of char;
pID: longword;
begin
Result := True;
// Obtenemos el Texto de la Ventana
GetWindowText(Wnd, WinText, 255);
if (WinText <> '')
and (IsWindowVisible(Wnd))
and (GetWindow(Wnd, gw_Owner) = 0)
then
begin
GetWindowThreadProcessId(Wnd, pID);
FormMain.Memo1.Lines.Add(IntToStr(pID) + ' ' + WinText);
FormMain.Memo1.Lines.Add(StringOfChar('-', 80));
ListWinInfoFromPId(pID, FormMain.Memo1.Lines); // este es el paso 2
end;
end;


2) Localizo las ventanas de cada aplicacion.

procedure ListWinInfoFromPId(PId: DWORD; strings: TStrings);
type
TWinParam = record
PId: DWORD;
S: TStrings;
end;
PWinParam = ^TWinParam;
var
WinParam: TWinParam;

function EnumWindowsProc(Handle: Thandle; lParam: LPARAM): boolean; stdcall;
var
PId: DWORD;
buffer: array [1..255] of char;
begin
Result := true;
PId := 0;
GetWindowThreadProcessId(Handle, PId);
if PWinParam(lParam).PId = PId then
begin
PWinParam(lParam).S.Add('hWnd: ' + IntToHex(Handle, 8)+'h');
GetWindowText(Handle, @buffer, 255);
PWinParam(lParam).S.Add('Caption: ' + buffer);
GetClassName(Handle, @buffer, 255);
PWinParam(lParam).S.Add('Clase: ' + buffer);
PWinParam(lParam).S.Add('--------------------');
Result := true;
end;
end;

begin
WinParam.PId:= PId;
WinParam.S:= strings;
EnumWindows(@EnumWindowsProc, LPARAM(@WinParam));
end;


3) Localizar los controles de cada ventana, aqui es donde necesito ayuda:o
Para obtener los controles de nuestra aplicacion uso

for i := 0 to (ComponentCount - 1) do
begin
if Components[i] is TWinControl then
begin
if (Components[i] is TEdit)
or (Components[i] is TMaskEdit)
or (Components[i] is TCheckBox) then
begin
Memo1.Lines.Add(Components[i].Name);
TFormMain(Components[i]).OnEnter := nil;
TFormMain(Components[i]).OnExit := nil;
end;
end;
end;


¿Como podria obtener los controles de cada ventana? he buscado por todas parte y no encuentro la funcion, y se que deberia existir.

El cuarto paso seria crear una thread que vigile dicho control y cuando entren nuevas lineas, las capturo e interpreto.
El quinto paso serie escribir en ese mismo control ciertas lineas que son necesarias.

Alguna ayudita, please.:D

nlsgarcia
11-12-2012, 03:42:27
cesarsoftware,

Revisa este link:

Llamar una aplicación y ejecutar acciones: http://www.clubdelphi.com/foros/showthread.php?t=81589


Espero sea útil :)

Nelson.

ecfisa
11-12-2012, 06:45:57
Hola sesarsoftware.

Otra opción, agrega un Edit, un ComboBox, tres Buttons dentro de un Panel, debajo un RichEdit y proba este ejemplo:

unit Unit1;

interface

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

type
TfrBusca = class(TForm)
RichEdit1: TRichEdit;
Panel1: TPanel;
Label1: TLabel;
Edit1: TEdit;
btnSearchApp: TButton;
Label2: TLabel;
ComboBox1: TComboBox;
btnAppToRE: TButton;
btnREToApp: TButton;
procedure btnSearchAppClick(Sender: TObject);
procedure btnAppToREClick(Sender: TObject);
procedure btnREToAppClick(Sender: TObject);
private

public
{ Public declarations }
end;

var
frBusca: TfrBusca;

implementation {$R *.dfm} {$WARNINGS OFF}

type
TClassNfo = class(TObject)
Name: string;
Handle: HWND;
end;

(* Agregar nombre de clases al ComboBox *)
function CallbackFn(aHandle: HWND; LB: TListBox): LongBool; stdcall;
var
Buffer: array[0..255] of Char;
CN: TClassNfo;
begin
Result := True;
if GetClassName(aHandle, Buffer, 256) <> 0 then
begin
CN := TClassNfo.Create;
CN.Name := Buffer;
CN.Handle := aHandle;
LB.AddItem(CN.Name, TClassNfo(CN));
end;
end;

(* Buscar controles en la aplicacion *)
procedure TfrBusca.btnSearchAppClick(Sender: TObject);
var
H: HWND;
begin
ComboBox1.Clear;
H := FindWindow(nil, PChar(Edit1.Text));
if H <> 0 then
EnumChildWindows(H, @CallbackFn, LPARAM(ComboBox1));
end;

(* Texto del control de la aplicación al RichEdit *)
procedure TfrBusca.btnAppToREClick(Sender: TObject);
var
Buffer: array[0..8192] of Char;
R : Integer;
begin
with ComboBox1 do
begin
repeat
R:= GetClassName(TClassNfo(Items.Objects[ItemIndex]).Handle,
Buffer, SizeOf(Buffer));
SendMessage(TClassNfo(Items.Objects[ItemIndex]).Handle,
WM_GETTEXT, SizeOf(Buffer), Integer(@Buffer));
RichEdit1.Text:= Buffer;
until (Buffer = Items[ItemIndex])or(R <> 0);
end;
if R > 0 then
RichEdit1.Text:= Buffer;
end;

(* Texto del RichEdit a control de la aplicación *)
procedure TfrBusca.btnREToAppClick(Sender: TObject);
var
Buffer: array[0..8192] of Char;
R : Integer;
begin
with ComboBox1 do
begin
repeat
R:= GetClassName(TClassNfo(Items.Objects[ItemIndex]).Handle,
Buffer, SizeOf(Buffer));
if Buffer = Items[ItemIndex] then
begin
StrPCopy(Buffer,RichEdit1.Text);
SendMessage(TClassNfo(Items.Objects[ItemIndex]).Handle,
WM_SETTEXT, SizeOf(Buffer), Integer(@Buffer));
end;
until (Buffer = Items[ItemIndex])or(R <> 0);
end;
end;
end.


Si no sabes que clases puede contener la aplicación, quizá te interese comprobar en la función CallbackFn si el control es un Edit Control (http://msdn.microsoft.com/en-us/library/windows/desktop/bb775458%28v=vs.85%29.aspx)

...
if GetClassName(aHandle, Buffer, 256) <> 0 then
begin
if Buffer = 'Edit' then // <= Aqui
begin
CN := TClassNfo.Create;
CN.Name := Buffer;
CN.Handle := aHandle;
LB.AddItem(CN.Name, TClassNfo(CN))
end
...

Logicamente es fundamental escribir en el edit textualmente el título de la ventana cuyo handle queremos obtener. También habrá controles de los cuales no podamos recibir o enviar texto alguno.

Saludos.:)

cesarsoftware
11-12-2012, 10:08:18
cesarsoftware,

Revisa este link:


Espero sea útil :)

Nelson.

Primero Gracias por ayudar.
Ya lo revise, pero ese ejemplo es para cuando se programan las dos aplicaciones, en mi caso una de ellas ya existe (es un CAD) y vaya usted a saber en que lenguaje esta hecha.

cesarsoftware
11-12-2012, 10:10:02
Gracias ecfisa, tiene buena pinta, ahora me pongo a probar esta tecnica.

Un saludo.

cesarsoftware
11-12-2012, 12:19:39
Hola ecfisa.
Muy bien para localizar los controles, estaria bien obtener sus nombres ademas del THandle.

Pero...no me va el leer texto con TfrBusca.btnAppToREClick(Sender: TObject);

repeat
R:= GetClassName(TClassNfo(Items.Objects[ItemIndex]).Handle,
Buffer, SizeOf(Buffer));
SendMessage(TClassNfo(Items.Objects[ItemIndex]).Handle,
WM_GETTEXT, SizeOf(Buffer), Integer(@Buffer));
RichEdit1.Text:= Buffer;
until (Buffer = Items[ItemIndex])or(R <> 0);


R siempre devuelve 0 (cero) y se queda en el repeat; tambien pasa en la funcion TfrBusca.btnREToAppClick(Sender: TObject);

Lo he hecho de otra forma (mas simple) y me mete en un listbox, la aplicacion(proceso), sus formularios y sus respectivos controles
Añadir un listbox y un richedit

// Obtener todos los formularios y sus controles de un proceso
procedure ListWinInfoFromPId(PId: longword; strings: TStrings);
type
TWinParam = record
PId: longword;
S: TStrings;
end;
PWinParam = ^TWinParam;
var
WinParam: TWinParam;

// Obtener los controles
function CallbackFn(handle: Thandle; strings: TStrings): longbool; stdcall;
var
buffer: array[0..255] of char;
begin
Result := True;
if GetClassName(handle, buffer, 256) <> 0 then
// if buffer = 'TComboBox' then
strings.Add(IntToStr(handle) + ' ' + buffer);
end;

// Obtener los formularios
function EnumWindowsProc(handle: Thandle; lPar: LPARAM): boolean; stdcall;
var
PId: longword;
buffer: array [0..255] of char;
begin
Result := True;
PId := 0;
// Comprobamos que sean de esa PId, sino se obtiene la lista desordenada
GetWindowThreadProcessId(handle, PId);
if PWinParam(lPar).PId = PId then
begin
PWinParam(lPar).S.Add('hWnd: ' + IntToHex(handle, 8) + 'h');
GetWindowText(handle, @buffer, 255);
PWinParam(lPar).S.Add('Caption: ' + buffer);
GetClassName(handle, @buffer, 255);
PWinParam(lPar).S.Add('Clase: ' + buffer);
// Obtenemos todos los controles del formulario ¡Solo si los ha mostrado!
EnumChildWindows(handle, @CallbackFn, LPARAM(PWinParam(lPar).S));
PWinParam(lPar).S.Add(StringOfChar('-', 40));
end;
end;

begin
WinParam.PId := PId;
WinParam.S := strings;
// Obtener todos los formularios de un proceso
EnumWindows(@EnumWindowsProc, LPARAM(@WinParam));
end;

// Obtener todos los procesos activos
function EnumWinProc(handle: Thandle; param: pointer): boolean; stdcall; export;
var
nombre: array[0..255] of char;
PId: longword;
begin
Result := True;
// Obtenemos el nombre del proceso
GetWindowText(handle, nombre, 255);
if (nombre <> '')
and (IsWindowVisible(handle))
and (GetWindow(handle, gw_Owner) = 0)
then
begin
GetWindowThreadProcessId(handle, PId);
FormMain.ListBox1.Items.Add(IntToStr(PId) + ' ' + nombre);
FormMain.ListBox1.Items.Add(StringOfChar('-', 80));
// Obtenemos los formularios y los campos del proceso
ListWinInfoFromPId(PId, FormMain.ListBox1.Items);
end;
end;


y apara usarlo


procedure TFormMain.Create(Sender: TObject);
// Obtener todos los controles de todos los formularios de todos los procesos
EnumWindows(@EnumWinProc, LongInt(Self));
end;

procedure TFormMain.ListBox1Click(Sender: TObject);
var
buffer: array[0..8192] of Char;
p: integer;
handle: HWND;
s: string;
begin
s := ListBox1.Items[ListBox1.ItemIndex];
p := Pos(' ' , s);
s := Copy(s, 1, p - 1);
handle := StrToIntDef(s, 0);
if handle = 0 then
Exit;
// r := GetClassName(handle, Buffer, SizeOf(Buffer)); // siempre devuelve 0
SendMessage(handle, WM_GETTEXT, SizeOf(Buffer), Integer(@Buffer));
RichEdit1.Text := Buffer; // buffer nunca tiene el texto de ningun control
end;


Teniendo el handle del control ¿Como podemos obtener su clase, Tedit, Tcombox, etc, si no me funciona GetClassName? Uso Delphi 2010 y windows 7 64.

¿Se te ocurre algo?

Gracias

Edito: Lo mejor seria obtener el objeto ¿Se puede? asi seria mas facil, se podria usar richedit1.text := Trichedit(TObject).Text

ecfisa
11-12-2012, 17:45:11
Hola cesarsoftware.

Pero...no me va el leer texto con TfrBusca.btnAppToREClick(Sender: TObject);
...
R siempre devuelve 0 (cero) y se queda en el repeat; tambien pasa en la funcion TfrBusca.btnREToAppClick(Sender: TObject);

No me sucede lo mismo, en mi caso funciona bién, pero no estoy usando un S.O. de 64 bits, tal vez sea ese el motivo. Por las dudas te adjunto mi prueba.


Teniendo el handle del control ¿Como podemos obtener su clase, Tedit, Tcombox, etc, si no me funciona GetClassName? Uso Delphi 2010 y windows 7 64.
...
¿Se te ocurre algo?
Edito: Lo mejor seria obtener el objeto ¿Se puede? asi seria mas facil, se podria usar richedit1.text := Trichedit(TObject).Text

Luego voy a revisarlo, te aviso cualquier novedad.

Saludos.:)

cesarsoftware
11-12-2012, 18:27:22
Ooohhhh:confused:
He descargado y ejecutado tu aplicacion y ocurre lo mismo, R devuelve 0,eniff!!

¿Como va lo de obtener el objeto:D?

ecfisa
11-12-2012, 19:03:36
Ooohhhh:confused:
He descargado y ejecutado tu aplicacion y ocurre lo mismo, R devuelve 0,eniff!!

¿Como va lo de obtener el objeto:D?
En eso estoy, en eso estoy ... :)

Pero hay un pequeño problema, no precisamente los nombres obtenidos se enmarcarán dentro de las definiciones de Delphi (Tedit, Tcombox, TMemo, etc) es por eso que te comenté lo de verificar si era un Edit Control (http://msdn.microsoft.com/en-us/library/windows/desktop/bb775458%28v=vs.85%29.aspx).

Por ejemplo al obtener desde Mozilla Firefox (situado en este hilo), se tienen las clases MozillaWindowClass, GeckoPluginWindows y GeckoFPSandBoxChildWindow sobre las cuales yo :confused: .
http://img707.imageshack.us/img707/7761/cesarsoftware.jpg

Con el Bloc de notas
http://img202.imageshack.us/img202/9500/cesarsoftware2.jpg


Saludos. :)

cesarsoftware
11-12-2012, 19:23:17
Gracias por seguir el tema.

La funcion
SendMessage(handle, WM_GETTEXT, SizeOf(Buffer), Integer(@Buffer));
No me rellena el buffer con el "texto" que tenga el control. (visto que si es un Tedit como el de notepad, entonces devuelve su texto, genial.)

pero si uso
SendMessage(handle, WM_SETFOCUS, SizeOf(Buffer), Integer(@Buffer));
(ya, ya se que aqui el buffer pinta poco, no se donde buscar los parametros para WM_SETFOCUS)
Da el foco perfectamente a cada control:D

Creo que sera un tema de delphi 2010 mas que de S.O., tengo que probar tu aplicacion en un xp

cesarsoftware
11-12-2012, 19:41:03
Un momento.....

Esto si funciona, con TBitBtn, con Edit, con ComboBox, con StatusBar...

buffer := 'hola';
SendMessage(handle, WM_SETTEXT, SizeOf(Buffer), Integer(@Buffer));


Pregunto, ¿Seguro que estan bien los parametros WParam y LParam para la funcion SendMessage con GETTEXT?
Porque entiendo que guindos coge un area de memoria sabiendo en LParam Integer(@Buffer) y su tamaño en WParam SizeOf(buffer)
SendMessage(handle, WM_GETTEXT, SizeOf(Buffer), Integer(@Buffer));

Voy a probar con PostMessage

cesarsoftware
11-12-2012, 19:50:33
Nada, con postmessage, tampoco.

Que poquito falta....:D

nlsgarcia
11-12-2012, 20:04:24
cesarsoftware,


Creo que sera un tema de delphi 2010 mas que de S.O., tengo que probar tu aplicacion en un xp


Probe la aplicación de ecfisa en Windows 7 Professional x32, Windows 7 Professional x64 y Windows XP Professional x32 (Los últimos dos en Máquinas Virtuales) con las aplicaciones de Notepad y Calculator y funciono correctamente.

Nota: La aplicación la compile con Delphi 7 antes de probarla.

Espero sea útil :)

Nelson.

cesarsoftware
11-12-2012, 20:25:31
SOLUCIONADO.:D

Como no el problema lo tenia Delphi 2010, vamos digo yo, porque a mi no me funciona el codigo de eficsa, recordar que GetClassName me devuelve 0. ¿Usais Delphi 2010?

Es necesario y obligatorio preguntar cuanto va a ocupar el texto a leer y pedir que lea justo ese tamaño


procedure TFormMain.ListBox1Click(Sender: TObject);
var
buffer: array[0..8192] of char;
p: word;
t: longint;
handle: THandle;
s: string;
begin
s := ListBox1.Items[ListBox1.ItemIndex];
p := Pos(' ' , s);
s := Copy(s, 1, p - 1);
handle := StrToIntDef(s, 0);
if handle = 0 then
Exit;
// Obtener el tamaño del texto del control
t := SendMessage(handle, WM_GETTEXTLENGTH, 0, 0);
// Leer el texto del control
SendMessage(handle, WM_GETTEXT, t + 1, Integer(@buffer));
{
si ponemos SizeOf(Buffer) o una constante distinta de t + 1 no funciona
SendMessage(handle, WM_GETTEXT, SizeOf(Buffer), Integer(@Buffer));
}
end;


Espero que os sirva.
Gracias a todos
PD: podemos seguir hasta obtener el objeto y asi hacer con el lo que nos plazca ¿no?;)

ecfisa
11-12-2012, 23:08:45
SOLUCIONADO.:D

Como no el problema lo tenia Delphi 2010, vamos digo yo, porque a mi no me funciona el codigo de eficsa, recordar que GetClassName me devuelve 0. ¿Usais Delphi 2010?

Es necesario y obligatorio preguntar cuanto va a ocupar el texto a leer y pedir que lea justo ese tamaño


procedure TFormMain.ListBox1Click(Sender: TObject);
var
buffer: array[0..8192] of char;
p: word;
t: longint;
handle: THandle;
s: string;
begin
s := ListBox1.Items[ListBox1.ItemIndex];
p := Pos(' ' , s);
s := Copy(s, 1, p - 1);
handle := StrToIntDef(s, 0);
if handle = 0 then
Exit;
// Obtener el tamaño del texto del control
t := SendMessage(handle, WM_GETTEXTLENGTH, 0, 0);
// Leer el texto del control
SendMessage(handle, WM_GETTEXT, t + 1, Integer(@buffer));
{
si ponemos SizeOf(Buffer) o una constante distinta de t + 1 no funciona
SendMessage(handle, WM_GETTEXT, SizeOf(Buffer), Integer(@Buffer));
}
end;


Hola cesarsoftware.

Primeramente me alegro de que hayas encontrado la solución al tamaño :) (lo tendré en cuenta cuando pueda usar otra versión de delphi).

Pero no entiendo por que estas obteniendo el handle del objeto a partir del texto del item del ComboBox/ListBox. El handle de cada objeto fué previamente guardado en el componente; entonces basta con:

procedure TfrBusca.ComboBox1Click(Sender: TObject);
var
t: Longint;
Buffer: array[0..8192] of char;
begin
with ComboBox1 do
begin
t := SendMessage(TClassNfo(Items.Objects[ItemIndex]).Handle,
WM_GETTEXTLENGTH, 0, 0); // Obtener tamaño (para Delphi 2010)
SendMessage(TClassNfo(Items.Objects[ItemIndex]).Handle,
WM_GETTEXT,t+1, Integer(@buffer)); // obtener texto del objeto
RichEdit1.Text:= Buffer; // almacenar texto en RichEdit
end;
end;


Saludos. :)

cesarsoftware
12-12-2012, 10:12:51
Hola ecfisa,

Lo hago porque como pinto el handle dentro del listbox (para ver el handle y la clase), asi me ahorro (de momento) tener el objeto dentro del listbox, sin mas.

// Obtener los controles
function CallbackFn(handle: Thandle; strings: TStrings): longbool; stdcall;
var
buffer: array[0..255] of char;
begin
Result := True;
if GetClassName(handle, buffer, 256) <> 0 then
// if buffer = 'TComboBox' then
strings.Add(IntToStr(handle) + ' ' + buffer);
end;


Lo que necesito ahora es distinguir cada objeto (ademas de por el handle), quiero decir que si tenemos dos TEdit ¿Cual de ellos es el que queremos usar?
Tendiendo el handle del objeto deberiamos porder obtener esa informacion ¿no? Pero no encuentro la funcion "GetObjectName".
¿Sabes cual es?

Un saludo.

ecfisa
12-12-2012, 13:05:48
Hola.

Es que eso he tratado de decirte cuando sugería comprobar si era un "Edit Control" (About Edit Control (http://msdn.microsoft.com/en-us/library/windows/desktop/bb775456%28v=vs.85%29.aspx)), por que según creo no es posible a partir de un identificador de ventana (HWND) externo a tu aplicación, obtener el nombre de una instancia VCL. Es decir, operando con el HWND podes obtener o cambiar, ventanas, texto, colores, tamaño, ubicación , etc, pero mediante llamas API.

Saludos.

cesarsoftware
12-12-2012, 13:17:43
Claro, es logico que no se compilen los nombres VCL, ¡es que lo queremos todo, jejeje:o!

En la aplicación que quiero capturar solo "se ve" un richedit (espero que sea eso o un memo, pero se le ven colores y formato, todavia no he pasado por el cliente para comprobarlo, ya que al ser un CAD y llevar mochila no puedo instalarlo en un pc mio), por lo que entiendo que no tendre problemas, pero para hacer un software "automático" deberiamos poder distinguirlo, supongo que se puede hacer por posicion, tamaño o algo, asi.

Si tengo novedades lo publico.

Thanks.