Manipular ventana en segundo plano

Iniciado por rigorvzla, 6 Enero 2019, 01:27 AM

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

rigorvzla

hola amigos en este codigo, puedo enviar una orden a un proceso, el detalle esta en que me trae al frente ese proceso, y necesito que mande la orden pero deje la ventana en segundo plano o no ponerla como ventana activa

[DllImport("User32.dll")]
        static extern int SetForegroundWindow(IntPtr point);

Process p = (Process)listBox.SelectedItem;
                    if (p != null)
                    {
                        IntPtr h = p.MainWindowHandle;
                        SetForegroundWindow(h);
                        simuladorTeclas.Keyboard.KeyPress(VirtualKeyCode.VK_A);
                    }


con ese codigo, envio una pulsacion de tecla a la ventana selecionada en el listbox, y en efecto funciona, el detalle es que trae al frente la ventana de ese proceso, mi idea es que se mantenga en segundo plano y no se convierta en principal o ventana activa.


rigorvzla

Hola Elektro en efecto uso este nugget
https://github.com/michaelnoonan/inputsimulator
muy practico y bueno, lo muestro en el codigo que deje anteriormente, mi no saber es como enviar por medio de ese nugget una pulsacion de tecla a una ventana NO activa, en el codigo anterior selecciono el proceso desde un listbox y envia la pulsacion a esa ventana (ejemplo notepad) todo va bien , lo malo es que la trae al frente o la vuelve activa y no quiero eso.

eh revisado este post que me dejaste
https://foro.elhacker.net/net/autologin_en_otra_aplicacion_c-t489446.0.html;msg2178786#msg2178786

CitarDe lo contrario, lo recomendable, y dado que el propósito sería evitar depender de que la ventana tenga activo el foco de entrada, sería utilizar la función nativa SendMessage (o PostMessage, según cómo lo quieras hacer) junto al mensaje de ventana WM_SETTEXT, logicamente antes de poder enviar el mensaje primero debes identificar la ventana (el control) al que le quieres enviar el mensaje (el texto a insertar). De nuevo no hace falta mencionar que tienes ejemplos (miles de ellos) en la WWW. Y si necesita enviar cualquier otra cosa que no sea texto, es decir, si lo que quieres es simular una tecla o una combinación de teclas, entonces debes usar el mensaje de ventana WM_KEYDOWN + WM_KEYUP (quizás también WM_SYSKEYDOWN + WM_SYSKEYDOWN, dependiendo de las teclas específicas que necesites simular). Repito, esta metodología no requiere tener activo el foco de entrada de la ventana objetivo.

esta parte no lo entendi muy bien, ya que no se usar mucho el Sendkey por eso me decidi con el inputsimulator.

En resumen, es posible enviar la accion a una ventana no activa una pulsacion de tecla con el inputSimulator? de ser afirmativo, como?

de lo contrario, como puedo lograr mi objetivo con el sendkey.

Eleкtro

#3
Cita de: rigorvzla en  7 Enero 2019, 13:43 PMmi no saber es como enviar por medio de ese nugget una pulsacion de tecla a una ventana NO activa

No puedes.

Cita de: rigorvzla en  7 Enero 2019, 13:43 PMEn resumen, es posible enviar la accion a una ventana no activa una pulsacion de tecla con el inputSimulator?

No. La librería Windows Input Simulator es un wrapper de la función nativa SendInput, y el propósito de dicha función es simular las pulsaciones de teclado, movimientos del mouse o pulsaciones de los botones a la ventana que tenga el foco de entrada activo.

Así pues, ni con SendInput ni con System.Windows.Forms.SendKeys.Send puedes hacer tal cosa, ya que ambos requieren que primero actives el foco de la ventana a la que pretendes enviarle las pulsaciones (función nativa AttachThreadInput + SetFocus, o SetForegroundWindow), lo cual activará dicha ventana.

Creo que todo lo que acabo de explicar ya lo expliqué en el comentario que te sugerí leer. El propósito era evitar la necesidad de volver a explicar lo mismo...

Cita de: rigorvzla en  7 Enero 2019, 13:43 PMde lo contrario, como puedo lograr mi objetivo con el sendkey.

Como ya recomendé en el párrafo que has citado, deberías utilizar las funciones de mensajería de ventana. La más simple para este caso es la función PostMessage, ya que si deseas simular una pulsación del teclado usando la función SendMessage / SendMessageTimeout entonces deberás construir un "parámetro de tecla" más complejo (el cómo construirlo está especificado en la documentación del mensaje de ventana WM_KEYDOWN y WM_KEYUP en docs.microsoft.com / MSDN). Un ejemplo básico:

Código (vbnet) [Seleccionar]

Friend NotInheritable Class NativeMethods

   Private Sub New()
   End Sub

   <DllImport("User32.dll", SetLastError:=True)>
   Friend Shared Function PostMessage(
       <MarshalAs(UnmanagedType.SysInt)> hWnd As IntPtr,
       <MarshalAs(UnmanagedType.U4)> msg As Integer,
       <MarshalAs(UnmanagedType.SysInt)> wParam As IntPtr,
       <MarshalAs(UnmanagedType.SysInt)> lParam As IntPtr
       ) As <MarshalAs(UnmanagedType.Bool)> Boolean
   End Function

End Class

...

Código (vbnet) [Seleccionar]
Public Shared Function SendKeyDown(ByVal hWnd As IntPtr, ByVal key As Integer) As Boolean
   Const WM_KEYDOWN As Integer = 256
   Return NativeMethods.PostMessage(hWnd, WM_KEYDOWN, New IntPtr(key), IntPtr.Zero)
End Function

Public Shared Function SendKeyUp(ByVal hWnd As IntPtr, ByVal key As Integer) As Boolean
   Const WM_KEYUP As Integer = 257
   Return NativeMethods.PostMessage(hWnd, WM_KEYUP, New IntPtr(key), IntPtr.Zero)
End Function

Public Shared Function SendKeyPress(ByVal hWnd As IntPtr, ByVal key As Integer) As Boolean
   Return ((SendKeyDown(hWnd, key) And SendKeyUp(hWnd, key)) = True)
End Function

...

Código (vbnet) [Seleccionar]
Dim hWnd As IntPtr = Process.GetProcessesByName("notepad").Single().MainWindowHandle
Dim key As Integer = System.Windows.Forms.Keys.F5 ' VK_F5 (0x74)

Dim success As Boolean = SendKeyPress(hWnd, key)
If Not (success) Then
   Dim win32ErrCode As Integer = Marshal.GetLastWin32Error()
   Throw New Win32Exception(win32ErrCode)
End If


Saludos.








rigorvzla

#4
gracis hombre!! es lo maximo y ademas busque informacion extra por que lei uno de tus post donde mucha MUCHISIMA infomracion existe en ingles solo que hay que saber poner las palabras correctas jeje ahi te eh citado . agradezdo de tu ayuda. como un extra , como podria obtener el nombre de un proceso seleccionado desde un listbox?


string = text "System.Diagnostics.Process (conhost)"
no me funciona sale el nombre completo de toda la using y entre parentecis el proceso jeje

Eleкtro

#5
Cita de: rigorvzla en  8 Enero 2019, 17:41 PMcomo podria obtener el nombre de un proceso seleccionado desde un listbox?

Si solo conoces el nombre del proceso entonces debes usar la función Process.GetProcessesByName() (como en el ejemplo que te mostré.). Dicha función devolverá un Array, puesto que puede haber más de un proceso en ejecución con el mismo nombre, por ende, debes utilizar algún criterio adicional para elegir un único proceso del Array. Lo ideal es que te asegures de conocer el identificador de proceso (PID), ya que dicho identificador es único, y de esa manera puedes usar la función Process.GetProcessById() para devolver un único proceso / elemento...

un ejemplo:

VB.NET:
Código (vbnet) [Seleccionar]
Dim prDict As New SortedDictionary(Of String, Integer)(
   Process.GetProcesses().ToDictionary(Function(pr) String.Format("{0} [{1}]", pr.ProcessName, pr.Id),
                                       Function(pr) pr.Id), StringComparer.Ordinal)

Me.ListBox1.DataSource = prDict.Keys.ToList()

...
Código (vbnet) [Seleccionar]
Private Sub ListBox1_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs)
Dim lb As ListBox = DirectCast(sender, ListBox)
Dim pid As Integer = prDict(lb.SelectedItem.ToString())
Dim pr As Process = Process.GetProcessById(pid)
' ...
End Sub


C#:
Código (csharp) [Seleccionar]
SortedDictionary<string, int> prDict =
   new SortedDictionary<string, int>(Process.GetProcesses().ToDictionary(
       (pr) => string.Format("{0} [{1}]", pr.ProcessName, pr.Id), (pr) => pr.Id),
       StringComparer.Ordinal);

this.listBox1.DataSource = prDict.Keys.ToList();

...
Código (csharp) [Seleccionar]
private void listBox1_SelectedIndexChanged(object sender, EventArgs e) {
   ListBox lb = (ListBox)sender;
   int pid = prDict[lb.SelectedItem.ToString()];
   Process pr = Process.GetProcessById(pid);
   // ...
}







Nótese que este ejemplo tan solo tiene el objetivo de demostrarte como solucionar el problema que has descrito. No se tiene en cuenta si un proceso sigue en ejecución o se terminó despues de haber seleccionado el nombre de dicho proceso en el ListBox; para ello siempre puedes utilizar una colección que implemente la interfaz INotifyCollectionChanged (ej. ObservableCollection) y refrescar la lista de procesos en ejecución del ListBox mediante una tasa de refresco a tu elección...

Saludos








rigorvzla

#6
muchisimas gracias, has respondido 2 preguntas mas de un  golpe como lo que era, que me de la lista de manera ordenada y la obtencion del id del proceso, grande pues!!! jeje.

Yo lo que hice para poder obtener lo que queria fue, extraer el proceso de los parentesis con un split jeje y asunto resuelto.

string = text "System.Diagnostics.Process (Proceso)"

de todos modos, gracias nuevamente ya que por estetica habia quedado cutre, y con tu explicacion podre hacerlo de una manera elegante y con mas detalle, ordenada y con el Id en caso de ser necesario mas adelante nuevamente gracias!!

Graciasa tambien por la ayuda de el metodo e segundo plano tambien lo resolvi de la siguiente manera asi como estan en tus codigos de ejemplo, solo que lo aplique a C#

[DllImport("user32.dll")]
                static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
                const UInt32 WM_KEYDOWN = 0x0100;
                const UInt32 WM_KEYUP = 0x0101;

[STAThread]
                public static void accionTecla(int tecla, string proceso)
                {
                    Process[] processes = Process.GetProcessesByName(proceso);
                    foreach (Process proc in processes)
                    {
                        PostMessage(proc.MainWindowHandle, WM_KEYDOWN, tecla, 0);
                        PostMessage(proc.MainWindowHandle, WM_KEYUP, tecla, 0);
                    }
                }

Sirvio de lo lindo, mas no se, por que en un textbox si digo que precione "a" me la preciona 2 veces... asumo por ambos WM_KEY  pero probe con solo el down y hay aplicaciones que deja la tecla marcada y no la suelta, cuando puse el up , walla!! asunto resuelto, y apesar que no me ha dado problemas , me gustaria saber si puedo solo mostrar unasola pulsasion de letra "a" .

Aparte pero del mismo tema, busque hacer combinaciones como "ctrl + 1" y se volvia loco apretando unos muuuuchas veces use el sentido comun pero.... como q falta algo mas jeje

 
PostMessage(proc.MainWindowHandle, WM_KEYDOWN, tecla1, 0);
PostMessage(proc.MainWindowHandle, WM_KEYDOWN, tecla2, 0);
PostMessage(proc.MainWindowHandle, WM_KEYUP, tecla2, 0);
PostMessage(proc.MainWindowHandle, WM_KEYUP, tecla1, 0);


Este ultimo codigo es referencial pero asi lo aplique bien hecho y pasa lo que mensione.

Eleкtro

#7
Cita de: rigorvzla en  9 Enero 2019, 13:16 PMstatic extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);

La firma de esa función debe ser portable, como en el ejemplo que te mostré. Me pregunto por que en lugar de convertir (usando cualquier convertidor de código online) y copiar el código que te mostré, copiaste un código aleatorio de Internet...

Simplemente reemplaza el valor int de los parámetros lParam y wParam, por IntPtr, tal que así:

Código (csharp) [Seleccionar]
internal sealed class NativeMethods {

private NativeMethods() {}

[DllImport("User32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal extern static bool PostMessage([MarshalAs(UnmanagedType.SysInt)] IntPtr hWnd,
                                       [MarshalAs(UnmanagedType.U4)] uint msg,
                                       [MarshalAs(UnmanagedType.SysInt)] IntPtr wParam,
                                       [MarshalAs(UnmanagedType.SysInt)] IntPtr lParam);

}





Cita de: rigorvzla en  9 Enero 2019, 13:16 PMprobe con solo el down y hay aplicaciones que deja la tecla marcada y no la suelta, cuando puse el up , walla!! asunto resuelto

Los nombres de los mensajes de ventana son auto-descriptivos, WM_KEYDOWN = presionar tecla, WM_KEYUP = soltar tecla. Logicamente hasta que no envies el mensaje WM_KEYUP, la tecla seguirá virtuálmente presionada.




Cita de: rigorvzla en  9 Enero 2019, 13:16 PMme gustaria saber si puedo solo mostrar unasola pulsasion de letra "a" .

Primero debes enviar el mensaje WM_KEYDOWN para presionar la tecla, y luego WM_KEYUP para soltarla. Así es como funciona, no le busques atajos.




Cita de: rigorvzla en  9 Enero 2019, 13:16 PMbusque hacer combinaciones como "ctrl + 1" y se volvia loco

En teoría no se puede simular la pulsación de modificadores de tecla o teclas extendidas mediante SendMessgae / PostMessage. Me apoyo en la opinión de gurús de la programación y de .NET como el señor Hans Passant: https://stackoverflow.com/a/5145435/1248295.

Aunque en realidad si que hay formas de hacerlo con PostMessage, pero para casos específicos en los que sabes exactamente que parámetros (lParam y wParam) debes enviar despues de haber analizado a fondo como se envían los mensajes de ventana en la aplicación.

En fin. Para simular la pulsación de teclas "especiales" como ALT, CONTROL o SHIFT izquierdo y derecho, siempre puedes utilizar la función keybd_event para modificar el estado de una tecla (estado presionado, o no presionado) de forma global, pues no se puede modificar el estado de dichas teclas solamente para una ventana en específico...

Al código que ya tienes, le añadirías esto:

Código (csharp) [Seleccionar]
[DllImport("user32.dll", EntryPoint="keybd_event", SetLastError=true)]
internal extern static void KeybdEvent([MarshalAs(UnmanagedType.U1)] byte vkey,
                                      [MarshalAs(UnmanagedType.U1)] byte scanCode,
                                      [MarshalAs(UnmanagedType.U4)] KeybdEventFlags flags,
                                      [MarshalAs(UnmanagedType.SysUInt)] UIntPtr extraInfo);

[DllImport("user32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.U4)]
internal extern static uint MapVirtualKey([MarshalAs(UnmanagedType.U4)] uint code,
                                         [MarshalAs(UnmanagedType.U4)] MapVirtualKeyMapTypes mapType);

...

Código (csharp) [Seleccionar]
[Flags]
internal enum KeybdEventFlags: uint {
NonExtendedKey = 0x0,
ExtendedKey = 0x1,
KeyUp = 0x2
}

internal enum MapVirtualKeyMapTypes: uint {
VKeyToScanCode = 0x0,
ScanCodeToVkey = 0x1,
VKeyToChar = 0x2,
ScanCodeToVKeyExtended = 0x3,
VKeyToScanCodeExtended = 0x4
}

...

Entonces, así puedes hacer para presionar por ejemplo la combinación de teclas CONTROL+S a una ventana no activa, en este ejemplo Notepad:

Código (csharp) [Seleccionar]
Dim hWnd As IntPtr = Process.GetProcessesByName("notepad").Single().MainWindowHandle

Dim modKey As Byte = CByte(Keys.LControlKey)
Dim scanCode As Byte = CByte(NativeMethods.MapVirtualKey(modKey, MapVirtualKeyMapTypes.VKeyToScanCode))

' Presionar modificador de tecla
NativeMethods.KeybdEvent(modKey, scanCode, KeybdEventFlags.NonExtendedKey, UIntPtr.Zero)

' Presionar la tecla "S"
SendKeyPress(hWnd, Keys.S)
Thread.Sleep(100) ' Intervalo necesario ya que PostMessage es asincrónico.

' Soltar modificador de tecla
NativeMethods.KeybdEvent(modKey, scanCode, KeybdEventFlags.NonExtendedKey Or KeybdEventFlags.KeyUp, UIntPtr.Zero)





Citar
Código (csharp) [Seleccionar]
PostMessage(proc.MainWindowHandle, WM_KEYDOWN, tecla1, 0);
PostMessage(proc.MainWindowHandle, WM_KEYDOWN, tecla2, 0);
PostMessage(proc.MainWindowHandle, WM_KEYUP, tecla2, 0);
PostMessage(proc.MainWindowHandle, WM_KEYUP, tecla1, 0);

Eso estaría bien siempre y cuando 'tecla1' o 'tecla2' no sean modificadores de tecla (CTRL, ALT, SHIFT), y además siempre que entre llamada y llamada añadas un intervalo de espera de digamos 100 ms para evitar una posible desincronización de los eventos del teclado, pues como ya dije más arriba PostMessage es una función asincrónica.

Saludos.








rigorvzla

Genial, me has aclarado grandes dudas, muchas  gracias, y disculpo lo del codigo solo que hay cosas que aun se me dificultan de leer (codigo) para pasar a C# , de todos modos esto seguramente lo consultare mas adelante cuando mis conocimientos sean un poco mayores, entendi lo de las teclas especiales y es algo que dejare para mas adelante!! jeje , pocierto gracias nuevamente por el ListBox quedo todo , nene jejeje

Eleкtro

Cita de: rigorvzla en  9 Enero 2019, 23:50 PMdisculpo lo del codigo solo que hay cosas que aun se me dificultan de leer (codigo) para pasar a C#

No hay nada que disculpar, supongo que es comprensible, yo te lo puse en VB.NET esperando que pusieras de tu parte y lo convirtieses a C# por que no me gusta darlo todo hecho y regalado, pero claro, tampoco estás obligado a convertir nada. xD

Saludos.