I am working on building an application similar to Windows Steps Recorder (WSR) but with more features that are required by my company. I have pretty much done everything but one thing I am struggling on is retrieving the Text/ Names of controls clicked in other applications.
E.g. When using WSR if you click a button labelled OK on another application it can pick this up and automatically makes a comment on that step such as "User left click on OK"
Anyone know how I can retrieve this information using the window handle/ mouse pointer location (x, y)?
Another example I can give is from using the AutoIT Window Info tool
As you can see in the figure provided above AutoIt can retrieve the element information within applications using the Finder Tool.
If someone can give me a pointer towards how this is achieved using C# that would be great as currently I don't know where to start. All other examples I have found online are for returning control values from within the application opposed to background applications.
I am building my application in WinForms due to the fact that some of the namespaces I am using aren't compatible with WPF.
Would this be a case of using EnumWindows() and EnumChildWindows()??
Note: I do not need help with the mouse hooks etc. simply just the retrival of infromation from a control that has been clicked on.
Thanks,
Maisy
Hey guys I've managed to retrive the text information. It may not be the prettiest way of doing it but it works.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
namespace EnumChildWinTest
{
public partial class Form1 : Form
{
public IntPtr windowHandle = IntPtr.Zero; // Default value
public Form1()
{
InitializeComponent();
}
[DllImport("user32.dll")] private static extern int GetWindowText(int hWnd, StringBuilder title, int size);
StringBuilder title = new StringBuilder(256);
// Go Button
private void button1_Click(object sender, EventArgs e)
{
textBox1.Clear();
textBox1.AppendText($"Window Handle: {GetWindowHandle(textBox2.Text)} {Environment.NewLine}");
foreach (var child in GetChildWindows(windowHandle))
{
textBox1.AppendText($"Child window: {child.ToString()} {Environment.NewLine}");
GetWindowText(child.ToInt32(), title, 256);
textBox1.AppendText($"Title:{title}{Environment.NewLine}");
}
}
public IntPtr GetWindowHandle(string processName)
{
Process[] processes = Process.GetProcessesByName(processName);
foreach (Process p in processes)
{
windowHandle = p.MainWindowHandle;
}
return windowHandle;
}
public List<IntPtr> GetChildWindows(IntPtr handle)
{
var allChildWindows = new WindowHandleInfo(windowHandle).GetAllChildHandles();
return allChildWindows;
}
}
}
public class WindowHandleInfo
{
private delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam);
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr lParam);
private IntPtr _MainHandle;
public WindowHandleInfo(IntPtr handle)
{
this._MainHandle = handle;
}
public List<IntPtr> GetAllChildHandles()
{
List<IntPtr> childHandles = new List<IntPtr>();
GCHandle gcChildhandlesList = GCHandle.Alloc(childHandles);
IntPtr pointerChildHandlesList = GCHandle.ToIntPtr(gcChildhandlesList);
try
{
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(this._MainHandle, childProc, pointerChildHandlesList);
}
finally
{
gcChildhandlesList.Free();
}
return childHandles;
}
private bool EnumWindow(IntPtr hWnd, IntPtr lParam)
{
GCHandle gcChildhandlesList = GCHandle.FromIntPtr(lParam);
if (gcChildhandlesList == null || gcChildhandlesList.Target == null)
{
return false;
}
List<IntPtr> childHandles = gcChildhandlesList.Target as List<IntPtr>;
childHandles.Add(hWnd);
return true;
}
}
Related
Sorry if this is a vague question, it's a very specific case and difficult to explain. Here's what I'm trying to do (this is for a 64 bit Windows application by the way)
Look to see if a particular save file dialog window is open inside an application (image of it below, it's a dialog box that pops up inside of the application when exporting something)
Once I have a pointer to that window, somehow access and use its elements in such a way that I'm able to name the file I'm saving, navigate to a desired file path, then save it, all through code
Here's a photo of the window that I'm trying to control through code (for reference)
Image
So far I've been able to find code that gives me all the active windows, including the one I'm targeting. Here is that code:
using System;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using HWND = System.IntPtr;
using System.Text;
/// <summary>Contains functionality to get all the open windows.</summary>
public static class OpenWindowGetter
{
/// <summary>Returns a dictionary that contains the handle and title of all the open windows.</summary>
/// <returns>A dictionary that contains the handle and title of all the open windows.</returns>
public static IDictionary<HWND, string> GetOpenWindows()
{
HWND shellWindow = GetShellWindow();
Dictionary<HWND, string> windows = new Dictionary<HWND, string>();
EnumWindows(delegate (HWND hWnd, int lParam)
{
if (hWnd == shellWindow) return true;
if (!IsWindowVisible(hWnd)) return true;
int length = GetWindowTextLength(hWnd);
if (length == 0) return true;
StringBuilder builder = new StringBuilder(length);
GetWindowText(hWnd, builder, length + 1);
windows[hWnd] = builder.ToString();
return true;
}, 0);
return windows;
}
private delegate bool EnumWindowsProc(HWND hWnd, int lParam);
[DllImport("USER32.DLL")]
private static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);
[DllImport("USER32.DLL")]
private static extern int GetWindowText(HWND hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("USER32.DLL")]
private static extern int GetWindowTextLength(HWND hWnd);
[DllImport("USER32.DLL")]
private static extern bool IsWindowVisible(HWND hWnd);
[DllImport("USER32.DLL")]
private static extern IntPtr GetShellWindow();
}
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
foreach (KeyValuePair<IntPtr, string> window in OpenWindowGetter.GetOpenWindows())
{
IntPtr handle = window.Key;
string title = window.Value;
Console.WriteLine("{0}: {1}", handle, title);
}
}
}
}
I'm not sure what to do from here. What I need now help with now is checking for the particular window I'm targeting (named Export Selection as seen in the photo).. then somehow get references to its components and control them.
I've looked into using Spy++ to get info about the components, and then using FindWindowEx and SendMessage to control them. This is not something I completely understand, as I'm limited in my C# knowledge. Is this the correct approach, and if so how would I go about doing it?
Yea, using Spy++ is a good approach to understand that application and its controls. As you seem already to be able to detect the correct window, the next step is now to use the SendMessage function to send data to the individual controls of the window. Look up in the Win32 API documentation to find the correct messages to send. I.e. you would use the WM_SETTEXT message to put text into the "File name" input box. See here: https://learn.microsoft.com/en-US/windows/win32/winmsg/wm-settext.
Or you use the SetWindowText function, which basically does the same, but is easier to use. You already have the prototype for the GetWindowText method declared in your code. The C# declaration for SetWindowText should be
[DllImport("USER32.DLL")]
private static extern int SetWindowText(HWND hWnd, String lpString);
I am building a Winform application that requires Clipboard fonctions hence I need to make calls to user32.dll . Now, whenevr I launched the application, I got the following error System.UnauthorizedAccessException: 'Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))'.
After further investigation, it turns out that when I attempt to register the clipBoardViewer using SetClipboardViewer(), I get an invalid Handle error code from user32.dll .
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Threading;
namespace NiceClip
{
public partial class MainForm : Form
{
[DllImport("User32.dll")]
protected static extern int SetClipboardViewer(int hWndNewViewer);
[DllImport("User32.dll")]
protected static extern int GetLastError();
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
IntPtr nextClipboardViewer;
NotifyIcon niceClipIcon;
Icon niceClipIconImage;
ContextMenu contextMenu = new ContextMenu();
bool reallyQuit = false;
bool isCopying = false;
public MainForm()
{
InitializeComponent();
nextClipboardViewer = (IntPtr)SetClipboardViewer((int)this.Handle);
if (this.clipboardHistoryList.Items.Count > 0)
this.clipboardHistoryList.SetSelected(0, true);
clipboardHistoryList.Select();
this.TopMost = true;
niceClipIconImage = Properties.Resources.clipboard;
niceClipIcon = new NotifyIcon
{
Icon = niceClipIconImage,
Visible = true
};
MenuItem quitMenuItem = new MenuItem("Quit");
MenuItem showFormItem = new MenuItem("NiceClip");
this.contextMenu.MenuItems.Add(showFormItem);
this.contextMenu.MenuItems.Add("-");
this.contextMenu.MenuItems.Add(quitMenuItem);
niceClipIcon.ContextMenu = contextMenu;
quitMenuItem.Click += QuitMenuItem_Click;
showFormItem.Click += ShowForm;
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
}
/// <summary>
/// Takes care of the external DLL calls to user32 to receive notification when
/// the clipboard is modified. Passes along notifications to any other process that
/// is subscribed to the event notification chain.
/// </summary>
protected override void WndProc(ref System.Windows.Forms.Message m)
{
const int WM_DRAWCLIPBOARD = 0x308;
const int WM_CHANGECBCHAIN = 0x030D;
int error = Marshal.GetLastWin32Error(); // Error is code 1400 (invalid window handle)
switch (m.Msg)
{
case WM_DRAWCLIPBOARD:
if (!isCopying)
AddClipBoardEntry();
break;
case WM_CHANGECBCHAIN:
if (m.WParam == nextClipboardViewer)
nextClipboardViewer = m.LParam;
else
SendMessage(nextClipboardViewer, m.Msg, m.WParam,
m.LParam);
break;
default:
base.WndProc(ref m);
break;
}
}
/// <summary>
/// Adds a clipboard history to the clipboard history list.
/// </summary>
private void AddClipBoardEntry()
{
if (Clipboard.ContainsText()) // FAILS HERE
{
string clipboardText = Clipboard.GetText();
if (!String.IsNullOrEmpty(clipboardText))
{
clipboardHistoryList.Items.Insert(0, clipboardText);
toolStripStatusLabel.Text = "Entry added in the clipboard history.";
deleteButton.Enabled = true;
}
}
else
{
toolStripStatusLabel.Text = "History entry was not added because it was null or empty";
}
}
THe line int error = Marshal.GetLastWin32Error(); returns error code 1400 which is Invalid window handle, see this.
Possible Solutions
I have verified that the thread apartment is correctly set
namespace NiceClip
{
static class Program
{
[STAThread] // Here
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
I also tried to use GC.KeepAlive() so it wouldn't collect the form or it's handle but I really don't understand why it would.
When I debug the application, I observe that, RIGHT BEFORE calling SetClipBoardViewer(), the form handle has a value (that I think) is valid, at least it's not null or 0x0000so I don't understand why the handle would be deemed as invalid.
Note
The application has compiled before on the same computer, I just
haven't worked on it for a while and now it doesn't work.
The full application is available on GitHub at this commit in it's current state (minus a few lines I added for debugging purposes and some comments to help you guys make sense out of this).
I found the error (source code from GIT). Change the application setting.
Go to Properties --> Security and change to 'This is a Full trust application'.
After this change the application starts without a problem and the clipboard functions work correctly.
I have looked at this question and answer
How to send text to Notepad in C#/Win32?
A slight variation that I think shouldn't matter.. Is that I have a bunch of notepad windows.. So to test this I copied notepad.exe to be notepadd.exe and opened notepadd.exe, so only one of my notepad windows is the notepadd.exe process.
I have this code
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace testsendmessage
{
public partial class Form1 : Form
{
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
Process[] notepads = Process.GetProcessesByName("notepadd");
if (notepads.Length == 0) return;
if (notepads[0] != null)
{
IntPtr child = FindWindowEx(notepads[0].MainWindowHandle, new IntPtr(0), "Edit", null);
SendMessage(child, 0x000C, 0, "abcd");
}
}
}
}
It's not touching the notepad window though.
I tried debugging and I see that the notepads array has one item, which is certainly correct.
And it gets within the 'if' and it runs SendMessage(child, 0x000C, 0, "abcd");
But I see nothing appearing in the notepad window
I'm not getting an error from the code it's just nothing appearing in the notepad window.. And I don't really understand winapi stuff much, so i'm not sure how to proceed in trying to solve it?
As you can see it reaches that line, and I can use the watch window to look at the notepads Process array, and at 'child' but I don't know what I should be looking at to determine why it's not sending to the window
Added
New code based on Remy's suggestion
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace testsendmessage
{
public partial class Form1 : Form
{
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
public static extern IntPtr SendMessageWStr(IntPtr hWnd, uint uMsg, IntPtr wParam, string lParam);
const uint WM_SETTEXT = 0x000C;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
Process[] notepads = Process.GetProcessesByName("notepadd");
if (notepads.Length == 0) return;
if (notepads[0] != null)
{
IntPtr child = FindWindowEx(notepads[0].MainWindowHandle, new IntPtr(0), "Edit", null);
SendMessageWStr(child, WM_SETTEXT, IntPtr.Zero, "abcd");
}
}
}
}
But I still get the same issue that the notepad window was blank before clicking button and is blank after too. It's not sending the text to the notepadd window. Despite the fact that it is reaching that line of code that is meant to send the text to it.
Further addition.
Current code,
I've changed FindWindowEx to FindWindowExW and i've changed new IntPtr(0) to IntPtr.Zero and it still is unresponsive.
I've opened up notepadd.exe from cmd, I see the window there. And of course notepadd.exe in task manger, But clicking the button in my application is not writing any text into that window.
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace testsendmessage
{
public partial class Form1 : Form
{
[DllImport("user32.dll", EntryPoint = "FindWindowExW")]
public static extern IntPtr FindWindowExW(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
public static extern IntPtr SendMessageWStr(IntPtr hWnd, uint uMsg, IntPtr wParam, string lParam);
const uint WM_SETTEXT = 0x000C;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
Process[] notepads = Process.GetProcessesByName("notepadd");
if (notepads.Length == 0) return;
if (notepads[0] != null)
{
IntPtr child = FindWindowExW(notepads[0].MainWindowHandle, IntPtr.Zero, "Edit", null);
SendMessageWStr(child, WM_SETTEXT, IntPtr.Zero, "abcd");
}
}
}
}
Much credit to Remy. After some troubleshooting I ended up finding the code worked so it's a mystery why I was finding it didn't.
A good troubleshooting step is to try moving the window around with nircmd, so you know you have the handle.
To get the handle of the window, you can use nirsoft winlister, or winspector
You can use nircmd commands like nircmd win close handle 0x00230632 change that 0x00230632 to whatever you find the handle to be. Or better don't close it(otherwise you'll have to reopen it and the new window will have a new handle), so a command like nircmd win move handle 0x00B917AE 80 10 100 100 So you know the handle is right, regardless of any issue with the code.
Winspector also shows the child of the notepad window
Going back to the C# code, you can
skip the child and try writing into the parent, it should write into the title of the window
Try using SendMessage to specify a window directly. You write the handle in decimal rather than hex. e.g. if the handle is 3479948 then
e.g. SendMessage(new IntPtr(3479948), WM_SETTEXT, IntPtr.Zero, "abcdee");
You can also check that notepads[0].MainWindowHandle is picking up the correct value.. the handle shown in winspector.
You can look at the IntPtr child = FindWindowEx(...) line, make sure the 'child' is picking up the child handle.
You can try writing to that one directly with SendMessage. e.g. SendMessage(new IntPtr(1234567), WM_SETTEXT, IntPtr.Zero, "abcdee"); // (if winspector shows the child window to be that handle).
In Visual C#, I'm trying to take text from multiple textboxes (one at a time) and paste them into Notepad. I do this by copying to the clipboard, alt-tabbing, and then pasting into notepad...then again for the other textboxes. This code represents this idea:
subBox1.SelectAll();
subBox1.Copy();
SendKeys.Send("%{TAB}"); // alt+tab
SendKeys.Send("^v"); // paste
SendKeys.Send("{TAB}"); // tab
subBox2.SelectAll();
subBox2.Copy();
SendKeys.Send("^v");
SendKeys.Send("{TAB}");
subBox3.SelectAll();
subBox3.Copy();
SendKeys.Send("^v");
SendKeys.Send("{TAB}");
As you can see, this copies and pastes from three textboxes (named subBox1, 2, and 3). But, for some reason, only the last textbox's contents are getting copied over. This also happens if I comment out the third box...in that case, only the second textbox's content gets copied over. I've tried using the SelectAll() and Copy() as you see here, as well as the Clipboard class. Both have the same issue.
For example, if the textbox contents are "asdf", "qwer", and "zxcv" respectively, all I see is "zxcv" three times.
Any idea why this is happening? I've been stuck on this for about an hour now and have no idea what's going on.
Thanks a ton!
SendKeys doesn't wait for the other application to process the keys you send, so by the time notepad gets around to processing your keypresses, your program has already copied subBox3's text over the top of the other texts.
You need to use SendWait instead.
As well, instead of sending Alt+Tab, you could use something like this:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);
// ...
SetForegroundWindow(FindWindow(null, "Untitled - Notepad"));
I'd use SendMessage for more accurate results. To use SendMessage, you first need a valid window handle to the text area of Notepad. This can be done in a variety of ways, but I prefer just using my simple child lookup function.
You will need the following namespace imports and PInvoke declarations:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text;
//pinvoke
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
[return:MarshalAs(UnmanagedType.Bool)]
private static extern bool GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
private static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
[return:MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr hParent, delChildWndProc callback, IntPtr lpParam);
//delegate callback for EnumChildWindows:
[return:MarshalAs(UnmanagedType.Bool)]
private delegate bool delChildWndProc(IntPtr hWnd, IntPtr lParam);
Now, onto the child window lookup. Basically works similar to FindWindowEx, but I wanted to write my own, and it retrieves multiple windows which can be nice. It uses the following wrapper class to describe information between calls:
private class WindowLookup
{
public string LookupName { get; private set; }
public List<IntPtr> MatchedChildren { get; private set; }
public int Depth { get; set; }
public int MaxDepth { get; set; }
public WindowLookup(string lookup, int maxdepth)
{
this.MatchedChildren = new List<IntPtr>();
this.LookupName = lookup;
this.MaxDepth = maxdepth;
if (this.MaxDepth > 0)
this.MaxDepth++; //account for the depth past the parent control.
this.Depth = 0;
}
}
And then the following functions do all the work:
private static List<IntPtr> FindAllWindows(IntPtr hParent, string className, int maxdepth = 0)
{
var lookup = new WindowLookup(className, maxdepth);
var gcAlloc = GCHandle.Alloc(lookup);
try
{
LookupChildProc(hParent, GCHandle.ToIntPtr(gcAlloc));
}
finally
{
if (gcAlloc.IsAllocated)
gcAlloc.Free();
}
return lookup.MatchedChildren;
}
private static bool LookupChildProc(IntPtr hChild, IntPtr lParam)
{
var handle = GCHandle.FromIntPtr(lParam);
WindowLookup lookup = null;
if (handle.IsAllocated && (lookup = handle.Target as WindowLookup) != null)
{
if (lookup.Depth < lookup.MaxDepth || lookup.MaxDepth == 0)
{
lookup.Depth++;
var builder = new StringBuilder(256);
if (GetClassName(hChild, builder, builder.Capacity) && builder.ToString().ToLower() == lookup.LookupName.ToLower())
lookup.MatchedChildren.Add(hChild);
EnumChildWindows(hChild, LookupChildProc, lParam);
}
}
return true;
}
You don't need to worry about the implementation of these functions too much, they'll work as-is. The key thing is that using these functions, you can find the handle to notepad's Edit window (the text area you type in) very easily.
var notepads = Process.GetProcessesByName("notepad");
if (notepads.Length > 0)
{
foreach(var notepad in notepads) //iterate through all the running notepad processes. Of course, you can filter this by processId or whatever.
{
foreach(var edit in FindAllWindows(notepad.MainWindowHandle, "Edit"))
{
//next part of the code will go here, read on.
}
}
}
Now, where I left the code was in the middle of a loop through the "Edit" windows of each notepad process running at the time. Now that we have a valid window handle, we can use SendMessage to send stuff to it. In particular, appending text. I wrote the following function to handle appending text to a remote control:
private static void AppendWindowText(IntPtr hWnd, string text)
{
if (hWnd != IntPtr.Zero)
{
//for your reference, 0x0E (WM_GETTEXTLENGTH), 0xB1 (EM_SETSEL), 0xC2 (EM_REPLACESEL)
int len = SendMessage(hWnd, 0x000E, IntPtr.Zero, IntPtr.Zero).ToInt32();
var unmanaged = Marshal.StringToHGlobalAuto(text);
SendMessage(hWnd, 0x00B1, new IntPtr(len), new IntPtr(len));
SendMessage(hWnd, 0x00C2, IntPtr.Zero, unmanaged);
Marshal.FreeHGlobal(unmanaged);
}
}
Now that we have our AppendWindowText function, you can add a function call to it in the nested loop above (where I put the comment):
AppendWindowText(edit, "Some text here");
And there you have it. It's a bit of a wordy response, but in the end this method is far more reliable than using SendKeys and focusing the window etc. You never need to lose focus of your own application.
If you have any questions, feel free to comment and I'll answer as best I can.
Cheers,
J
EDIT: Some references:
SendMessage function (MSDN)
EnumChildWindows function (MSDN)
Appending text using SendMessage
I am working on an application which needs to get the last active window handle. Suppose my application is running then I want to get last active window handle that was just previously open just before my application.
#EDIT1: This is not the duplicate question. I need to get the handle of last active window not the current window.
This is similar to
alternate SO question, I would assume you would just track the active window and upon change you would then know the previously active
Edit, this is basically code copied from the question I linked that was looking for current active window but with logic to persist the lastHandle and identify when you have a new lastHandle. It's not a proven, compilable implementation:
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
static IntPtr lastHandle = IntPtr.Zero;
//This will be called by your logic on when to check, I'm assuming you are using a Timer or similar technique.
IntPtr GetLastActive()
{
IntPtr curHandle = GetForeGroundWindow();
IntPtr retHandle = IntPtr.Zero;
if(curHandle != lastHandle)
{
//Keep previous for our check
retHandle = lastHandle;
//Always set last
lastHandle = curHandle;
if(retHandle != IntPtr.Zero)
return retHandle;
}
}
I needed the same thing of the last handle from the previous window I had open. The answer from Jamie Altizer was close, but I modified it to keep from overwriting the previous window when my application gets focus again. Here is the full class I made with the timer and everything.
static class ProcessWatcher
{
public static void StartWatch()
{
_timer = new Timer(100);
_timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
_timer.Start();
}
static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
setLastActive();
}
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
public static IntPtr LastHandle
{
get
{
return _previousToLastHandle;
}
}
private static void setLastActive()
{
IntPtr currentHandle = GetForegroundWindow();
if (currentHandle != _previousHandle)
{
_previousToLastHandle = _previousHandle;
_previousHandle = currentHandle;
}
}
private static Timer _timer;
private static IntPtr _previousHandle = IntPtr.Zero;
private static IntPtr _previousToLastHandle = IntPtr.Zero;
}