Obtener texto de label en aplicación externa WPF

Iniciado por TomaSs, 12 Mayo 2016, 01:38 AM

0 Miembros y 1 Visitante están viendo este tema.

TomaSs

Hola chicos, quería plantearos la siguiente pregunta, ya que no ha habido manera de encontrar nada por todo internet :S

Mi duda es como podría obtener el texto de un label wpf (textblock) de una aplicación externa.
Lo que si que he podido ver usando spy++ es que aunque mire diferentes labels, todas las identifica con la misma ventana (class HwndWrapper), por lo que entiendo que no está identificando la label en si sino la ventana general que las alberga.
También decir que he encontrado una herramienta (que hace las veces de spy) para identificar elementos wpf, y si que me saca el texto, etc. La herramienta es snoop, pero desde mi propio código c# no se si existiría alguna forma o api o algo para sacarlo, sin que tenga que ser recurriendo a inyección dll, api hooking, ni nada similar.

Decir que para controles estándar no tengo ningún problema para obtenerlo, mediante las llamadas a las diferentes funciones de user32 para obtener el handle, clase, texto, etc etc, pero esto se me escapa...

Un saludo y a ver si me podéis echar un cable! ;)

Eleкtro

#1
Hola.

1:
La herramienta Spy++, aparte de poder considerarla obsoleta por su descontinuación de desarrollo o desactualización, está diseñada y enfocada para la inspección de ventanas Win32, y aquí surge el problema, por que una aplicación WPF no trabaja con el modelo de la API Win32/Win64, es una tecnología completamente diferente, sin embargo, el elemento de ventana principal de una aplicación WPF sigue exponiendo un handle de ventana (HWND) accesible mediante la class HwndWrapper, que sirve como contenedor de la WinAPI y WPF.

Como herramientas más modernas, completas y en constante actualización para inspeccionar ventanas Win32 y WPF, estas son mis recomendaciones:

  • XAML Spy
    Solamente para aplicaciones WPF.
    Tiene una interfáz de usuario muy amigable y bonita.
  • Microsoft UI Inspect
    Para aplicaciones Win32 y WPF.
    Lo puedes encontrar en el SDK de Windows para tu versión de Windows actual.




2:
Para identificar un elemento de una UI en WPF, generálmente no es necesario recurrir a la metodología que estás aplicando con la WinAPI, puedes evitar toda la tediosidad que supone la implementación y el uso inseguro de código no administrado con las típicas definiciones de las funciones que has mencionado de la lib. user32.dll para identificar elementos en la UI y etc...

En su lugar, puedes usar código administrado aprovechando el potencial del framework de Microsoft UI Automation, con los ensamblados de WPF (puedes utilizarlos desde windowsForms también, e incluso puedes utilizarlos para ventanas Win32 también, aunque todo esto dependerá siempre de ciertas circunstancias).

El uso de UI Automation resulta bastante sencillo, se basa en crear condiciones de búsqueda para hallar el elemento deseado, deberiamos saber lo que andamos buscando tras haber inspeccionado la aplicación con UI Inspect o nuestra herramienta favorita, pero si desconocemos lo que tenemos que buscar entonces siempre podemos iterar las ventanas y los árboles de controles para ir descubriendo la información como hariamos con el callback de la función EnumChildwindows y etc. para ventanas Win32.

Si creamos un nuevo proyecto WPF y le añadimos un LabelControl a la ventana principal, quedando así el código XAML:
Código (xml) [Seleccionar]
<Window x:Class="WpfApplication1.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
       xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
       xmlns:local="clr-namespace:WpfApplication1"
       mc:Ignorable="d"
       Title="Mi Ventana" Height="350" Width="525">
   <Grid>
       <Label Content="This is a Label control." />
   </Grid>
</Window>


Esto sería un ejemplo básico para identificar el label y obtener su texto en la aplicación compilada:
Código (vbnet) [Seleccionar]

Imports Windows
Imports Windows.Automation

...

Dim pid As Integer = Process.GetProcessesByName("WpfApplication1").Single().Id

Dim parentConditions As New OrCondition({
   New PropertyCondition(AutomationElement.ProcessIdProperty, pid, PropertyConditionFlags.None),
   New PropertyCondition(AutomationElement.NameProperty, "Mi Ventana", PropertyConditionFlags.IgnoreCase)
})

Dim labelConditions As New AndCondition({
   New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Text, PropertyConditionFlags.None),
   New PropertyCondition(AutomationElement.RuntimeIdProperty, {7, 5924, 9119245}, PropertyConditionFlags.None)
})

Dim parentElement As AutomationElement = AutomationElement.RootElement.FindFirst(TreeScope.Children, parentConditions)
Dim labelElement As AutomationElement = parentElement.FindFirst(TreeScope.Children, labelConditions)

If (labelElement IsNot Nothing) Then
   Dim labelText As String = labelElement.Current.Name
   Console.WriteLine(labelText)
End If


Nótese que en el código de arriba la condición de RuntimeIdProperty está hardcodeada, el label de tu aplicación tendrá otra Id asignada (lo puedes averiguar facilmente con UI Inspect, o en tiempo de ejecución con la función AutomationElement.GetRuntimeId) ...igual que el nombre del executable y el título de la ventana,
y dependiendo de la estructura de la aplicación seguramente debas añadir condiciones adicionales para identificar el espacio de trabajo y/o ventana donde se encuentre ese label.

Saludos.








TomaSs

#2
Cita de: Eleкtro en 12 Mayo 2016, 12:36 PM
Hola.

1:
La herramienta Spy++, aparte de poder considerarla obsoleta por su descontinuación de desarrollo o desactualización, está diseñada y enfocada para la inspección de ventanas Win32, y aquí surge el problema, por que una aplicación WPF no trabaja con el modelo de la API Win32/Win64, es una tecnología completamente diferente, sin embargo, el elemento de ventana principal de una aplicación WPF sigue exponiendo un handle de ventana (HWND) accesible mediante la class HwndWrapper, que sirve como contenedor de la WinAPI y WPF.

Como herramientas más modernas, completas y en constante actualización para inspeccionar ventanas Win32 y WPF, estas son mis recomendaciones:

  • XAML Spy
    Solamente para aplicaciones WPF.
    Tiene una interfáz de usuario muy amigable y bonita.
  • Microsoft UI Inspect
    Para aplicaciones Win32 y WPF.
    Lo puedes encontrar en el SDK de Windows para tu versión de Windows actual.




2:
Para identificar un elemento de una UI en WPF, generálmente no es necesario recurrir a la metodología que estás aplicando con la WinAPI, puedes evitar toda la tediosidad que supone la implementación y el uso inseguro de código no administrado con las típicas definiciones de las funciones que has mencionado de la lib. user32.dll para identificar elementos en la UI y etc...

En su lugar, puedes usar código administrado aprovechando el potencial del framework de Microsoft UI Automation, con los ensamblados de WPF (puedes utilizarlos desde windowsForms también, e incluso puedes utilizarlos para ventanas Win32 también, aunque todo esto dependerá siempre de ciertas circunstancias).

El uso de UI Automation resulta bastante sencillo, se basa en crear condiciones de búsqueda para hallar el elemento deseado, deberiamos saber lo que andamos buscando tras haber inspeccionado la aplicación con UI Inspect o nuestra herramienta favorita, pero si desconocemos lo que tenemos que buscar entonces siempre podemos iterar las ventanas y los árboles de controles para ir descubriendo la información como hariamos con el callback de la función EnumChildwindows y etc. para ventanas Win32.

Si creamos un nuevo proyecto WPF y le añadimos un LabelControl a la ventana principal, quedando así el código XAML:
Código (xml) [Seleccionar]
<Window x:Class="WpfApplication1.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
       xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
       xmlns:local="clr-namespace:WpfApplication1"
       mc:Ignorable="d"
       Title="Mi Ventana" Height="350" Width="525">
   <Grid>
       <Label Content="This is a Label control." />
   </Grid>
</Window>


Esto sería un ejemplo básico para identificar el label y obtener su texto en la aplicación compilada:
Código (vbnet) [Seleccionar]

Imports Windows
Imports Windows.Automation

...

Dim pid As Integer = Process.GetProcessesByName("WpfApplication1").Single().Id

Dim parentConditions As New OrCondition({
   New PropertyCondition(AutomationElement.ProcessIdProperty, pid, PropertyConditionFlags.None),
   New PropertyCondition(AutomationElement.NameProperty, "Mi Ventana", PropertyConditionFlags.IgnoreCase)
})

Dim labelConditions As New AndCondition({
   New PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Text, PropertyConditionFlags.None),
   New PropertyCondition(AutomationElement.RuntimeIdProperty, {7, 5924, 9119245}, PropertyConditionFlags.None)
})

Dim parentElement As AutomationElement = AutomationElement.RootElement.FindFirst(TreeScope.Children, parentConditions)
Dim labelElement As AutomationElement = parentElement.FindFirst(TreeScope.Children, labelConditions)

If (labelElement IsNot Nothing) Then
   Dim labelText As String = labelElement.Current.Name
   Console.WriteLine(labelText)
End If


Nótese que en el código de arriba la condición de RuntimeIdProperty está hardcodeada, el label de tu aplicación tendrá otra Id asignada (lo puedes averiguar facilmente con UI Inspect, o en tiempo de ejecución con la función AutomationElement.GetRuntimeId) ...igual que el nombre del executable y el título de la ventana,
y dependiendo de la estructura de la aplicación seguramente debas añadir condiciones adicionales para identificar el espacio de trabajo y/o ventana donde se encuentre ese label.

Saludos.

En primer lugar muchas gracias por tu respuesta Elektro, la verdad que siempre eres de gran ayuda, gracias.

Y bueno, ahora tengo que decir que he estado probando lo que tu muy bien me explicaste y no ha habido manera de conseguir sacar el texto de los labels que busco, ya que ni soy capaz de identificarlos por código.
Y lo más gracioso es que en el inspect si que aparecen perfectamente identificados, y como hijos de la ventana que yo examino pero a mi nada, incluso metiendo la condicion de búsqueda "Condition.TrueCondition" para que me saque todos, no hay manera.
Me pasa como les pasa a este y este, que por lo que he podido comprobar es que se puede acceder a ellos de forma managed o unmanaged, y esta segunda es la forma en la que accede Inspector a los elementos, pero ni idea de como tendría que proceder en mi código para hacer eso, no he encontrado nada, salvo esta librería que por algún lado comentaban que servía para algo sobre el tema, pero no se yo... xdd
No se si te habrás topado tu con este problema alguna vez, o sabrías decirme que puedo hacer.

Muchas gracias de nuevo!

Eleкtro

#3
La persona que escribió la respuesta que has citado de StackOverflow, se refiere a que al final ha acabado trasladando el código de C# a un nuevo proyecto de VC++ (escrito en modo no administrado).

¿Qué cuales cambios necesitarías hacer?, pues exactamente lo mismo que hizo esa persona, si quieres usar la API de UI Automation cambia de lenguaje para desarrollarlo en C++ importando los headers que se especifican en la referencia de la API de UI Automation (aquí), por que la otra opción sería perder unas semanas, un mes, o varios meses de tu vida en definir una infinidad de P/Invokes y wrappers en C#/VB.NET (e investigar y testearlos), lo que aparte de acabar logrando ser un código con probable inestabilidad (lo insuficientemente controlado), no es muy viable perder el tiempo de esa manera que digamos.

Como esa persona dijo, la herramienta de Microsoft UI Inspect está desarrollada en VC++, sin embargo tu estás bajo VB.NET o C#, y por ese motivo, cuando puedes usar lo mismo en un lenguaje que ya conoces, me parece una tontería pensar en hacer lo contrario, pero de todas formas si quieres probar a hacerlo mediante código no administrado entonces ya tienes un enlace a la API de UI Automation indicando, como ya he dicho, los headers de C++ y toda la información necesaria respecto a las definiciones.

No puedo ofrecerte una solución por que ni tan siquiera conozco la estructura de la UI que estás analizando, ni tengo el programa para testearlo, ni tampoco he pasado por el mismo problema que mencionas, lo siento, pero ten por seguro que debe existir una solución mediante código administrado, dudo mucho que las classes de .Net con los wrappers de UI Automation omitan elementos de la UI que al usar su equivalente desde la implementación "nativa" éstos no se omitan, eso suena demasiado extraño, así que desde la ignorancia yo simplemente creo que falta algo que añadirle a la ecuación (ej. una condición de búsqueda más especializada), pero que por el momento desconoces que factor hay que añadirle.

Saludos!.








TomaSs

Es que el tema es probable que tenga que ver con que el texto del label que estoy intentando sacar pertenece a un proceso diferente al de la ventana en la que esta incrustado y aparece, pero realmente inspector los muestra como descendientes de la ventana que menciono.

Me explico mejor:
- Proceso A: ventana normal win32
- Proceso B: al que pertenecen las etiquetas que se muestran sobre la ventana de proceso A.

Ese es el contexto, y tambien tengo que decir que de primeras me sorprendio que inspector mostrara las etiauetas que menciono dentro de la ventana, cuando realmente pertenecen a procesos diferentes.
De hecho, algo curioso que tambien ocurre es que cuando muevo la ventana, las etiquetas se ve como se mueven mas tarde, por lo que esta claro que no pertenecen a la misma.
Por eso tal vez tenga que buscar por las ramas del proceso B, en vez de las de la ventana del proceso A, a pesar de lo que muestre Inspector.

Hare alguna prueba mas tras el finde y ya comentare como va...

Muchas graciass!!!