How might one invoke a callback whenever the current active window changes. I've seen how it might be done using CBTProc. However, global events aren't easy to hook into with managed code. I'm interested in finding a way that doesn't require polling. I'd prefer an event driven approach.
Regards
Create a new windows forms project, add a textbox, make it multiline, and set the textbox Dock property to fill, name it Log and paste in the following code (you'll need to add System.Runtime.InteropServices to your usings)...
WinEventDelegate dele = null;
public Form1()
{
InitializeComponent();
dele = new WinEventDelegate(WinEventProc);
IntPtr m_hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
}
delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
private const uint WINEVENT_OUTOFCONTEXT = 0;
private const uint EVENT_SYSTEM_FOREGROUND = 3;
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
private string GetActiveWindowTitle()
{
const int nChars = 256;
IntPtr handle = IntPtr.Zero;
StringBuilder Buff = new StringBuilder(nChars);
handle = GetForegroundWindow();
if (GetWindowText(handle, Buff, nChars) > 0)
{
return Buff.ToString();
}
return null;
}
public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
Log.Text += GetActiveWindowTitle() + "\r\n";
}
I know this thread is old, but for sake of future use:
when running the code you'll notice a crash after a while. This is caused from the line in the Form constructor:
public Form1()
{
InitializeComponent();
WinEventDelegate dele = new WinEventDelegate(WinEventProc);//<-causing ERROR
IntPtr m_hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
}
Instead of the above make the following modification:
public Form1()
{
InitializeComponent();
dele = new WinEventDelegate(WinEventProc);
IntPtr m_hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
}
WinEventDelegate dele = null;
..works now as expected!
You can use SetWinEventHook and listen for the EVENT_SYSTEM_FOREGROUND event. Use the WINEVENT_OUTOFCONTEXT flag to avoid the global-hook problem.
Related
More recently, simulating a click using PostMessage has begun to activate the window. Previously, this was not and the click was performed in an inactive window without focus.
int coords = (y << 16) + x;
IntPtr lParam = new IntPtr(coords);
PostMessage(hwnd, WM_LBUTTONDOWN, IntPtr.Zero, lParam);
PostMessage(hwnd, WM_LBUTTONUP, IntPtr.Zero, lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
const Int32 WM_LBUTTONDOWN = 0x201;
const Int32 WM_LBUTTONUP = 0x202;
i try to send Messages from one Application to another Application..
I think the Code works perfectly and is not the problem, but the text is sending to the wrong textbox? I looked with Spy++, and i have two textboxes in Application 2, but for both textboxes i return the same Class from Spy++ ---> "WindowsForms10.EDIT.app.0.141b42a_r13_ad1"...
My Main Question is now, why have both textboxes the same "class" and how can i fix this? thx
private const int WM_SETTEXT = 0x000C;
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName,string lpWindowName);
[DllImport("User32.dll")]
private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindows);
[DllImport("User32.dll")]
private static extern Int32 SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, StringBuilder lParam);
private void button4_Click(object sender, EventArgs e)
{
IntPtr hWnd = FindWindow(null, "Form1");
if (!hWnd.Equals(IntPtr.Zero))
{
IntPtr edithWnd = FindWindowEx(hWnd, IntPtr.Zero, "WindowsForms10.EDIT.app.0.141b42a_r13_ad1", null);
if (!edithWnd.Equals(IntPtr.Zero))
SendMessage(edithWnd, WM_SETTEXT, IntPtr.Zero, new StringBuilder("Hello World!"));
}
}
//Working Stuff...
IntPtr edithWnd = FindWindowEx(hWnd, IntPtr.Zero, "WindowsForms10.EDIT.app.0.141b42a_r12_ad1", null);
IntPtr nextHnd = edithWnd;
IntPtr editWnd = FindWindowEx(hWnd, nextHnd, "WindowsForms10.EDIT.app.0.141b42a_r12_ad1", null);
IntPtr nextHnd1 = editWnd;
IntPtr editWnd1 = FindWindowEx(hWnd, nextHnd1, "WindowsForms10.EDIT.app.0.141b42a_r12_ad1", null);
IntPtr nextHnd2 = editWnd1;
IntPtr editWnd2 = FindWindowEx(hWnd, nextHnd2, "WindowsForms10.EDIT.app.0.141b42a_r12_ad1", null);
I wrote this code to test the Inject mouse method but it is not working for me. The test is supposed to click in the google text box search area, but the box never gets highlighted. Any idea why?
Google's page does load. The code runs (confirmed through break points), but nothing happens.
public partial class Form1 : Form
{
private IWebView webView;
public Form1()
{
InitializeComponent();
initiate();
}
private void button1_Click(object sender, EventArgs e)
{
click(650, 405);
}
private async void initiate()
{
WebSession session = WebCore.CreateWebSession(
#"C:\SessionDataPath", WebPreferences.Default);
webView = WebCore.CreateWebView(
this.ClientSize.Width,
this.ClientSize.Height, session, WebViewType.Window
);
webView.ParentWindow = this.Handle;
webView.Source = new Uri("http://www.google.com");
await Task.Delay(30000);
click(650, 405);
}
public void click(int x, int y)
{
webView.InjectMouseMove(x, y);
webView.InjectMouseDown(MouseButton.Left);
webView.InjectMouseUp(MouseButton.Left);
}
}
I tried to get this code to work with chromium handle by looking at the proper chromium class but it didn't work
private async Task<bool> clickCoorindate(Point point)
{
webView.FocusView();
int x = point.X; // X coordinate of the click
int y = point.Y; // Y coordinate of the click
IntPtr handle = webView.ProcessHandle;
StringBuilder className = new StringBuilder(100);
while (className.ToString() != "Chrome_RenderWidgetHostHWND") // The class control for the browser
{
handle = GetWindow(handle, 5); // Get a handle to the child window
GetClassName(handle, className, className.Capacity);
if (className.ToString() == "Chrome_RenderWidgetHostHWND")
handle = Handle;
}
IntPtr lParam = (IntPtr)((y << 16) | x); // The coordinates
IntPtr wParam = IntPtr.Zero; // Additional parameters for the click (e.g. Ctrl)
const uint downCode = 0x201; // Left click down code
const uint upCode = 0x202; // Left click up code
const uint moveCode = 0x200;
SendMessage(handle, downCode, wParam, lParam); // Mouse button down
SendMessage(handle, upCode, wParam, lParam); // Mouse button up
Thread.Sleep(20);
SendMessage(handle, downCode, wParam, lParam); // Mouse button down
SendMessage(handle, upCode, wParam, lParam); // Mouse button up
return true;
}
As mentioned in the documentation (see: WebViewType), a windowed view captures all input itself and you cannot inject input programmatically using Awesomium API (you could do this as you tried, by sending native Windows messages to the appropriate HWND but it's not suggested and straightforward procedure).
To be able to inject input programmatically using the InjectXXX methods, make sure your view is of type offscreen.
in my XNA game i use a lot Awesomium and this is my InputSystem i've implemented in my awesomium component, it works very well.
note that this is just a part of my class, so some methods aren't here but they are not needed to understand the process
basically in my thread. basically i hook to the messages in my form and relay them to the WebView. Hope this helps
public partial class BasicAwesomiumComponent : DrawableGameComponent {
private delegate Int32 ProcessMessagesDelegate(Int32 code, Int32 wParam, ref Message lParam);
private static class User32 {
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr SetWindowsHookEx(Int32 windowsHookId, ProcessMessagesDelegate function, IntPtr mod, Int32 threadId);
[DllImport("user32.dll", SetLastError = true)]
internal static extern Int32 UnhookWindowsHookEx(IntPtr hook);
[DllImport("user32.dll", SetLastError = true)]
internal static extern Int32 CallNextHookEx(IntPtr hook, Int32 code, Int32 wParam, ref Message lParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern Boolean TranslateMessage(ref Message message);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr FindWindow(String className, String windowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern int RegisterWindowMessage(String msg);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SendMessage(HandleRef hWnd, Int32 msg, Int32 wParam, Int32 lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern bool SystemParametersInfo(Int32 nAction, Int32 nParam, ref Int32 value, Int32 ignore);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern int GetSystemMetrics(Int32 nIndex);
}
private static class Kernel32 {
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern Int32 GetCurrentThreadId();
}
private static class SystemMetrics {
internal static Int32 MouseWheelScrollDelta {
get {
return 120;
}
}
internal static Int32 MouseWheelScrollLines {
get {
var scrollLines = 0;
if (User32.GetSystemMetrics(75) == 0) {
var hwnd = User32.FindWindow("MouseZ", "Magellan MSWHEEL");
if (hwnd != IntPtr.Zero) {
var windowMessage = User32.RegisterWindowMessage("MSH_SCROLL_LINES_MSG");
scrollLines = (Int32)User32.SendMessage(new HandleRef(null, hwnd), windowMessage, 0, 0);
if (scrollLines != 0) {
return scrollLines;
}
}
return 3;
}
User32.SystemParametersInfo(104, 0, ref scrollLines, 0);
return scrollLines;
}
}
}
private enum WindowsMessage {
KeyDown = 0x0100,
KeyUp = 0x0101,
Char = 0x0102,
MouseMove = 0x0200,
LeftButtonDown = 0x0201,
LeftButtonUp = 0x0202,
LeftButtonDoubleClick = 0x0203,
RightButtonDown = 0x0204,
RightButtonUp = 0x0205,
RightButtonDoubleClick = 0x0206,
MiddleButtonDown = 0x0207,
MiddleButtonUp = 0x0208,
MiddleButtonDoubleClick = 0x0209,
MouseWheel = 0x020A,
}
private struct Message {
internal IntPtr HWnd;
internal Int32 Msg;
internal IntPtr WParam;
internal IntPtr LParam;
internal IntPtr Result;
}
private IntPtr hookHandle;
private ProcessMessagesDelegate processMessages;
private Int32 ProcessMessages(Int32 code, Int32 wParam, ref Message lParam) {
if (this.Enabled && code == 0 && wParam == 1) {
bool processed = false;
switch ((WindowsMessage)lParam.Msg) {
case WindowsMessage.KeyDown:
case WindowsMessage.KeyUp:
case WindowsMessage.Char:
WebKeyboardEvent keyboardEvent = new WebKeyboardEvent((uint)lParam.Msg, lParam.WParam, lParam.LParam, 0);
awesomiumContext.Post(state => {
if (!WebView.IsLive) return;
WebView.InjectKeyboardEvent(keyboardEvent);
}, null);
processed = true;
break;
case WindowsMessage.MouseWheel:
var delta = (((Int32)lParam.WParam) >> 16);
awesomiumContext.Post(state => {
if (!WebView.IsLive) return;
WebView.InjectMouseWheel(delta / SystemMetrics.MouseWheelScrollDelta * 16 * SystemMetrics.MouseWheelScrollLines, 0);
}, null);
processed = true;
break;
}
if (!processed) {
WindowsMessage message = (WindowsMessage)lParam.Msg;
awesomiumContext.Post(state => {
if (!WebView.IsLive) return;
switch (message) {
case WindowsMessage.MouseMove:
var mouse = Mouse.GetState();
WebView.InjectMouseMove(mouse.X - area.X, mouse.Y - area.Y);
break;
case WindowsMessage.LeftButtonDown:
WebView.InjectMouseDown(MouseButton.Left);
break;
case WindowsMessage.LeftButtonUp:
WebView.InjectMouseUp(MouseButton.Left);
break;
case WindowsMessage.LeftButtonDoubleClick:
WebView.InjectMouseDown(MouseButton.Left);
break;
case WindowsMessage.RightButtonDown:
WebView.InjectMouseDown(MouseButton.Right);
break;
case WindowsMessage.RightButtonUp:
WebView.InjectMouseUp(MouseButton.Right);
break;
case WindowsMessage.RightButtonDoubleClick:
WebView.InjectMouseDown(MouseButton.Right);
break;
case WindowsMessage.MiddleButtonDown:
WebView.InjectMouseDown(MouseButton.Middle);
break;
case WindowsMessage.MiddleButtonUp:
WebView.InjectMouseUp(MouseButton.Middle);
break;
case WindowsMessage.MiddleButtonDoubleClick:
WebView.InjectMouseDown(MouseButton.Middle);
break;
}
}, null);
}
User32.TranslateMessage(ref lParam);
}
return User32.CallNextHookEx(IntPtr.Zero, code, wParam, ref lParam);
}
}
update:
note that in my component, to hook the message pump, i use
int currentThread = Kernel32.GetCurrentThreadId();
// Create the message hook.
hookHandle = User32.SetWindowsHookEx(3, ProcessMessages, IntPtr.Zero, currentThread);
my surface in an Offscreen webview so the more complex, this should work for you too
i posted a separated answer to give another direction:
take a look at this gist: https://gist.github.com/robertkhrona/918109
it seems to suggest to do
webView.InjectMouseMove(x,y);
webView.InjectMouseDown(MouseButton.Left);
webView.InjectMouseMove(x,y);
webView.InjectMouseUp(MouseButton.Left);
so moving (to the same position) between the two mousedown/up event
btw i think this shouldn't be needed tho
which version of awesomium are you running?
update:
Remember to set the focus on your WebView before injecting inputs
webView.Focus();
I set the viewtype to offscreen and the injectclick worked fine, when set to window it doesn't work. I don't know why, but I can work with that.
UPD. I have the same situation in C++ win32 code.
C#:
WinAPI part:
const uint EVENT_CONSOLE_CARET = 0x4001;
const uint EVENT_CONSOLE_END_APPLICATION = 0x4007;
delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll", SetLastError = true))]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax,
IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc,
uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll")]
static extern bool UnhookWinEvent(IntPtr hWinEventHook);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
Code:
Process process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.Start();
_winEventProc = new WinEventDelegate(WinEventProc);
m_hhook = SetWinEventHook(EVENT_CONSOLE_CARET,
EVENT_CONSOLE_END_APPLICATION,
IntPtr.Zero,
_winEventProc,
(uint) process.Id,
0,
WINEVENT_OUTOFCONTEXT);
lastError = Marshal.GetLastWin32Error();
My program cannot catch events from process that I've created. If I change "(uint) process.Id" to "(uint) 0" it working good. I changed "(uint) process.Id" to specific process Id (watched it in task manager) and result was the same(bad). I even tried:
1) start cmd.exe (*)
2) start my program without creating new cmd.exe process
3) press any key at cmd.exe(*)
4) use GetWindowThreadProcessId in _winEventProc
5) re-run my program with pID that I get on 4 step
And it doesn't work. I don't know why but it working well only with processID 0.
p.s. sorry for my bad english
I am trying to automate a dynamically appearing dialogue box.
I need to pass text to it,s text field and then press a button over this.
What I have tried so far.
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
IntPtr handle= FindWindowByCaption(System.IntPtr.Zero, "Caption Of File");
I am getting the handle of dialogue box correctly.
List<IntPtr> childWindows= GetChildWindows(handle);//To get the child controls in this dialogue box
Source
But when I try to cast it to control I get null.
foreach (IntPtr i in childWindows)
{
Control c = Control.FromHandle(i);
}
So can any body tell what is wrong.I am supposing that I shall cast handle to control and then interact with control properties(e.g: text).
I've been using code like this sucessfully for years to perform single sign on to an application that prompts the user for their username/pwd/domain. The only caution is you need to know the control structure of the dialog you're targeting, but this is easily accomplished with Spy++ and rarely changes. Of course you will need to modify this code for the control structure of your window.
[DllImport("user32.dll")]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,
string lpszClass, string lpszWindow);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, string lParam);
[DllImport("User32.Dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
private const uint WM_GETTEXTLENGTH = 0x000E;
private const uint WM_SETTEXT = 0x000C;
private const uint WM_GETTEXT = 0x000D;
private const uint BM_CLICK = 0x00F5;
private const uint WM_CLOSE = 0x0010;
enum GetWindow_Cmd : uint
{
GW_HWNDFIRST = 0,
GW_HWNDLAST = 1,
GW_HWNDNEXT = 2,
GW_HWNDPREV = 3,
GW_OWNER = 4,
GW_CHILD = 5,
GW_ENABLEDPOPUP = 6
}
var dialog FindWindow("optionalClassNameHere", "Log On"); //Get the handle of the window
var w3 = GetWindow(dialog , (uint)GetWindow_Cmd.GW_CHILD); //I use GetWindow to walk the window controls
var wUid = FindWindowEx(w3, IntPtr.Zero, "Edit", "");
var w4 = GetWindow(wUid, (uint)GetWindow_Cmd.GW_HWNDNEXT);
var wPwd = FindWindowEx(w4 , IntPtr.Zero, "Edit", "");
var wOK = FindWindowEx(w3, IntPtr.Zero, "Button", "OK");
SendMessage(wUid, WM_SETTEXT, 0, _WinDomain + "\\" + Username); //Send username to username edit control
SendMessage(wPwd, WM_SETTEXT, 0, Password); //Send password to password edit control
PostMessage(wOK, BM_CLICK, 0, 0); //Send left click(0x00f5) to OK button
Control.FromHandle can only work for controls in your process that are implemented by Control descendents. I'd guess this window is outside your process.
You need to use Win32 API methods to modify it.