Let's say I have multiple chrome windows open (not tabs),
how can I check the browser title?
I tried the following:
Process[] p = Process.GetProcessesByName("chrome");
foreach (Process item in p)
{
Console.WriteLine(item.MainWindowTitle);
}
but it return me only the last open window name and all other are blanks..
I had to do something like this, but it was amazingly fiddly involving calling Windows API functions. The problem was that Chrome seems to use a single process for multiple windows or some other weirdness that meant the simple approach didn't work for me.
Anyway, try this and see if it works. Basically it uses the Chrome window class name (which might be Chrome_WidgetWin_0 or Chrome_WidgetWin_1) to enumerate all windows with that class name, and returns the window titles for those which are not blank.
Note that this also always returns a windows title called "Chrome App Launcher" for some reason, so you might need to filter that out.
Note: you can also do this for Firefox by using "MozillaWindowClass" and for IE by using "IEFrame" (although any of those are likely to change with different versions).
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
namespace Demo
{
class WindowsByClassFinder
{
public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lparam);
[SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public extern static bool EnumWindows(EnumWindowsDelegate lpEnumFunc, IntPtr lparam);
[SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
[DllImport("User32", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int GetWindowText(IntPtr windowHandle, StringBuilder stringBuilder, int nMaxCount);
[DllImport("user32.dll", EntryPoint = "GetWindowTextLength", SetLastError = true)]
internal static extern int GetWindowTextLength(IntPtr hwnd);
/// <summary>Find the windows matching the specified class name.</summary>
public static IEnumerable<IntPtr> WindowsMatching(string className)
{
return new WindowsByClassFinder(className)._result;
}
private WindowsByClassFinder(string className)
{
_className = className;
EnumWindows(callback, IntPtr.Zero);
}
private bool callback(IntPtr hWnd, IntPtr lparam)
{
if (GetClassName(hWnd, _apiResult, _apiResult.Capacity) != 0)
{
if (string.CompareOrdinal(_apiResult.ToString(), _className) == 0)
{
_result.Add(hWnd);
}
}
return true; // Keep enumerating.
}
public static IEnumerable<string> WindowTitlesForClass(string className)
{
foreach (var windowHandle in WindowsMatchingClassName(className))
{
int length = GetWindowTextLength(windowHandle);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(windowHandle, sb, sb.Capacity);
yield return sb.ToString();
}
}
public static IEnumerable<IntPtr> WindowsMatchingClassName(string className)
{
if (string.IsNullOrWhiteSpace(className))
throw new ArgumentOutOfRangeException("className", className, "className can't be null or blank.");
return WindowsMatching(className);
}
private readonly string _className;
private readonly List<IntPtr> _result = new List<IntPtr>();
private readonly StringBuilder _apiResult = new StringBuilder(1024);
}
class Program
{
void run()
{
ChromeWindowTitles().Print();
}
public IEnumerable<string> ChromeWindowTitles()
{
foreach (var title in WindowsByClassFinder.WindowTitlesForClass("Chrome_WidgetWin_0"))
if (!string.IsNullOrWhiteSpace(title))
yield return title;
foreach (var title in WindowsByClassFinder.WindowTitlesForClass("Chrome_WidgetWin_1"))
if (!string.IsNullOrWhiteSpace(title))
yield return title;
}
static void Main()
{
new Program().run();
}
}
static class DemoUtil
{
public static void Print(this object self)
{
Console.WriteLine(self);
}
public static void Print(this string self)
{
Console.WriteLine(self);
}
public static void Print<T>(this IEnumerable<T> self)
{
foreach (var item in self)
Console.WriteLine(item);
}
}
}
I know this is already answered, but I also have made a solution, which enumerates all Windows within a thread.
It was built from Matthew Watson's solution, hence some similarities.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Chrome_Windows
{
class Program
{
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(uint dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("User32", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr windowHandle, StringBuilder stringBuilder, int nMaxCount);
[DllImport("user32.dll", EntryPoint = "GetWindowTextLength", SetLastError = true)]
internal static extern int GetWindowTextLength(IntPtr hwnd);
private static List<IntPtr> windowList;
private static string _className;
private static StringBuilder apiResult = new StringBuilder(256); //256 Is max class name length.
private delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
static void Main(string[] args)
{
List<IntPtr> ChromeWindows = WindowsFinder("Chrome_WidgetWin_1", "chrome");
foreach (IntPtr windowHandle in ChromeWindows)
{
int length = GetWindowTextLength(windowHandle);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(windowHandle, sb, sb.Capacity);
Console.WriteLine(sb.ToString());
}
}
private static List<IntPtr> WindowsFinder(string className, string process)
{
_className = className;
windowList = new List<IntPtr>();
Process[] chromeList = Process.GetProcessesByName(process);
if (chromeList.Length > 0)
{
foreach (Process chrome in chromeList)
{
if (chrome.MainWindowHandle != IntPtr.Zero)
{
foreach (ProcessThread thread in chrome.Threads)
{
EnumThreadWindows((uint)thread.Id, new EnumThreadDelegate(EnumThreadCallback), IntPtr.Zero);
}
}
}
}
return windowList;
}
static bool EnumThreadCallback(IntPtr hWnd, IntPtr lParam)
{
if (GetClassName(hWnd, apiResult, apiResult.Capacity) != 0)
{
if (string.CompareOrdinal(apiResult.ToString(), _className) == 0)
{
windowList.Add(hWnd);
}
}
return true;
}
}
}
I know this is an old thread, but I have found the answer to this, at least for my use case anyway. I wanted to find all the open chrome windows/tabs by title as well, but in my case I wanted to close the ones I found containing x Title. After reading icbytes and dor-cohen's post above I realized I could achieve what I needed by calling Process.GetProcessesByName() more than once. When making this call you do get an array of all the running chrome processes, but only one instance will contain a value for MainWindowTitle. This is a bit annoying for several reasons. You can have multiple chrome sessions open with and "active" "displayed tab", but still the call only ever returns an array of chrome proc's with just one instance in that array having an value for MainWindowTitle. Again, my solution is not necessarily the OP's intention as he states just wanting to list the titles. My solution wants to close each found title.
What I have done is as follows:
Once I find the first chrome process with the title I am looking for I call CloseMainWindow() on that process. Do not call Kill() as it will crash the browser altogether. I am just closing the active or top level window here. I am posting my code below. I hope this will help someone else! Thanks!
bool foundAll = false;
do
{
bool foundOne = false;
procs = Process.GetProcessesByName("chrome");
foreach (Process p in procs)
{
if (p.MainWindowTitle.Length > 0)
{
string t = p.MainWindowTitle.Replace(" - Google Chrome", "");
if (t.ToLower().Contains(this.BrowserTabText.ToLower()))
{
foundOne = true;
this.WriteEventLogEntry($"Found Tab Title: {this.BrowserTabText} with PID: {p.Id}. \r\nWe will close it.", EventLogEntryType.Information);
p.CloseMainWindow();
break;
}
}
}
if (!foundOne)
{
foundAll = true;
}
} while (!foundAll);
You must get a list of processes.
Iterate through the list and only where name is "chrome".
This will allow You to get all titles.
Because if You have more then one chrome process , Your call will give You only one, because You call it only once.
Which it returns is perhaps another question. In Your case it is the last.
Related
Having inched slightly closer to this question-
Fixing a prompt window over the main window in a windows application using C#
I am asking this question in a hope that I will be able to fix the above. Here's it. How do get hold of a pop-up window that is thrown from an in-built function in C#.
The method is Microsoft.Office.Interop.Word.Document.CheckSpelling() and the pop-up window is the Spell-Check dialog box.
This example finds the spell checker window and closes it.
1) Find process of Word
2) Enumerate all windows belonging to this process ID
3) Get the window with the appropriate class name
4) Close that window
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Word = NetOffice.WordApi;
namespace WordSpellCheckerTest
{
class Program
{
static void Main(string[] args)
{
using (var app = new Word.Application())
{
SpellCheckerDemo(app);
}
}
private static async void SpellCheckerDemo(Word.Application app)
{
app.Visible = true;
var doc = app.Documents.Add();
doc.Words.First.InsertBefore("Here's some text that requiires speell cheking");
var process = GetProcess(app);
var task = new Task(() => CheckSpellingTask(doc));
task.Start();
//Wait for the window to open
Thread.Sleep(4000);
//I found the classname by comparing the list of windows for the process before and after opening the spell checker
//Then I checked the title to confirm
var spellCheckerWindow = WindowStuff.GetWindowsWithPID(process.Id).FirstOrDefault(w => w.ClassName == "bosa_sdm_msword");
//Do something with the window
spellCheckerWindow.Close();
//Wait for the task to finish before closing
task.Wait();
doc.Close(false);
app.Quit();
}
private static async Task CheckSpellingTask(Word.Document doc)
{
try
{
doc.CheckSpelling();
}
catch { }
}
//From my answer here: http://stackoverflow.com/questions/8673726/get-specific-window-handle-using-office-interop/41462638#41462638
private static Process GetProcess(Word.Application app)
{
var tempDocument = app.Documents.Add();
var project = tempDocument.VBProject;
var component = project.VBComponents.Add(NetOffice.VBIDEApi.Enums.vbext_ComponentType.vbext_ct_StdModule);
var codeModule = component.CodeModule;
codeModule.AddFromString("#If Win64 Then\r\n Declare PtrSafe Function GetCurrentProcessId Lib \"kernel32\" () As Long\r\n#Else\r\n Declare Function GetCurrentProcessId Lib \"kernel32\" () As Long\r\n#End If");
var result = app.Run("GetCurrentProcessId");
var process = Process.GetProcessById((int)result);
tempDocument.Close(false);
return process;
}
//Hacked together from the pInvoke pages for these WinAPI calls
public class WindowStuff
{
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, [Out] StringBuilder lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumWindows(WindowEnumerator lpEnumFunc, ArrayList lParam);
private delegate bool WindowEnumerator(IntPtr handleWindow, ArrayList handles);
const uint WM_CLOSE = 0x0010;
const uint WM_GETTEXTLENGTH = 0x000E;
const uint WM_GETTEXT = 0x000D;
public struct Info
{
public uint Hwnd;
public uint PID;
public string ClassName;
public string Title;
public Info(IntPtr hwnd )
{
uint processID;
GetWindowThreadProcessId(hwnd, out processID);
Hwnd = (uint)hwnd;
PID = processID;
ClassName = GetClassName(hwnd);
Title = GetWindowTitle(hwnd);
}
public void Close()
{
SendMessage(new IntPtr(Hwnd), WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
}
public static List<Info> GetWindowsWithPID(int pID)
{
return GetWindowsInner().Cast<IntPtr>().Select(hwnd => new Info(hwnd)).Where(i => i.PID == (uint)pID).ToList();
}
private static ArrayList GetWindowsInner()
{
var windowHandles = new ArrayList();
WindowEnumerator callBackPtr = GetWindowHandle;
EnumWindows(callBackPtr, windowHandles);
return windowHandles;
}
static string GetWindowTitle(IntPtr hwnd)
{
int length = (int)SendMessage(hwnd, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
StringBuilder sb = new StringBuilder(length + 1);
SendMessage(hwnd, WM_GETTEXT, (IntPtr)sb.Capacity, sb);
return sb.ToString();
}
private static bool GetWindowHandle(IntPtr windowHandle, ArrayList windowHandles)
{
windowHandles.Add(windowHandle);
return true;
}
private static string GetClassName(IntPtr hWnd)
{
var className = new StringBuilder(256);
return GetClassName(hWnd, className, className.Capacity) != 0 ? className.ToString() : string.Empty;
}
}
}
}
I am working on an WPF application to monitor my activities on my computer. I use Process.GetProcesses() and some filtering to get the processes I am interested in (example:Calculator) then I record their StartTime. I am also using WIN32/USER32 API method GetForegroundWindow() to get the window the user is using.
The problem is that when the windows are Windows/UWP applications they are always hosted by the process ApplicationFrameHost. So the GetForegroundWindow() method returns that window with a title (example:Calculator), but not the real process being hosted.
What I need is either another way to get the foreground window that includes the real process being hosted, or some way to connect the window to process.
Anyone that knows how to accomplish this? All help would be really appreciated.
I eventually found a way to do this, so I am going answer my own question so maybe someone in the future with the same problem could find it useful.
This is the class with the WinApiFunctions:
public class WinAPIFunctions
{
//Used to get Handle for Foreground Window
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr GetForegroundWindow();
//Used to get ID of any Window
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
public delegate bool WindowEnumProc(IntPtr hwnd, IntPtr lparam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc callback, IntPtr lParam);
public static int GetWindowProcessId(IntPtr hwnd)
{
int pid;
GetWindowThreadProcessId(hwnd, out pid);
return pid;
}
public static IntPtr GetforegroundWindow()
{
return GetForegroundWindow();
}
}
And this is the class I used to test if it would work. I used it in a simple console program that just writes out the name of the process that has current focus:
class FindHostedProcess
{
public Timer MyTimer { get; set; }
private Process _realProcess;
public FindHostedProcess()
{
MyTimer = new Timer(TimerCallback, null, 0, 1000);
Console.ReadKey();
}
private void TimerCallback(object state)
{
var foregroundProcess = Process.GetProcessById(WinAPIFunctions.GetWindowProcessId(WinAPIFunctions.GetforegroundWindow()));
if (foregroundProcess.ProcessName == "ApplicationFrameHost")
{
foregroundProcess = GetRealProcess(foregroundProcess);
}
Console.WriteLine(foregroundProcess.ProcessName);
}
private Process GetRealProcess(Process foregroundProcess)
{
WinAPIFunctions.EnumChildWindows(foregroundProcess.MainWindowHandle, ChildWindowCallback, IntPtr.Zero);
return _realProcess;
}
private bool ChildWindowCallback(IntPtr hwnd, IntPtr lparam)
{
var process = Process.GetProcessById(WinAPIFunctions.GetWindowProcessId(hwnd));
if (process.ProcessName != "ApplicationFrameHost")
{
_realProcess = process;
}
return true;
}
}
Chris, there is the alternative way which I have discovered trying to apply your solution to a related problem. While trying to analyse how the ApplicationFrameHost.exe-related stuff worked, I have stumbled upon the documented way of getting the true foreground window / thread and its process by passing 0 instead of the actual thread ID to GetGUIThreadInfo.
It might not fully work for the edge-case scenarios of your problem, but I felt that this might be a useful contribution for people of the future who might face the same problems ;-)
Here is the example of how this can be applied (pseudo C++'ish code):
GUITHREADINFO gti = { sizeof(GUITHREADINFO) };
GetGUIThreadInfo(0, >i); // <- note the `0`
DWORD processId = 0;
GetWindowThreadProcessId(gti.hwndFocus, &processId);
const auto procName = Util::GetProcessName(processId);
It solved my problem (obtaining the actual keyboard layout + finding the real foreground window) for all more-or-less common apps I have tested it against.
Basically, the answer you provided will also fail if the active window is in full screen mode,
for example if you have Skype app opened along with Microsoft Remote Desktop, which is the
active one and on full screen mode, EnumChildWindows would return SkypeApp not RDPClient.
and to get this fixed, you should follow the workaround suggested by Ivanmoskalev,
check this out:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetGUIThreadInfo(uint idThread, ref GUITHREADINFO lpgui);
public static IntPtr getThreadWindowHandle(uint dwThreadId)
{
IntPtr hWnd;
// Get Window Handle and title from Thread
var guiThreadInfo = new GUITHREADINFO();
guiThreadInfo.cbSize = Marshal.SizeOf(guiThreadInfo);
GetGUIThreadInfo(dwThreadId, ref guiThreadInfo);
hWnd = guiThreadInfo.hwndFocus;
//some times while changing the focus between different windows, it returns Zero so we would return the Active window in that case
if (hWnd == IntPtr.Zero)
{
hWnd = guiThreadInfo.hwndActive;
}
return hWnd;
}
[DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr windowHandle, out int processId);
static void Main(string[] args)
{
var current = getThreadWindowHandle(0);
int processId = 0;
GetWindowThreadProcessId(current, out processId);
var foregroundProcess = GetActiveProcess(processId);
}
private static Process GetActiveProcess(int activeWindowProcessId)
{
Process foregroundProcess = null;
try
{
foregroundProcess = Process.GetProcessById(activeWindowProcessId);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
if (string.IsNullOrWhiteSpace(GetProcessNameSafe(foregroundProcess)))
{
var msg = "Process name is empty.";
Console.WriteLine(msg);
}
return foregroundProcess;
}
About the same code from Chris Johnsson except that I'm not using Process because it doesn't work very well on the UWP app.
Code for start:
new Timer(TimerCallback, null, 0, 1000);
void TimerCallback(object state)
{
var process = new ProcessUtils.FindHostedProcess().Process;
string name = string.Empty;
if (process.IsPackaged)
{
var apps = process.GetAppDiagnosticInfos();
if (apps.Count > 0)
name = apps.First().AppInfo.DisplayInfo.DisplayName;
else
name = System.IO.Path.GetFileNameWithoutExtension(process.ExecutableFileName);
}
else
name = System.IO.Path.GetFileNameWithoutExtension(process.ExecutableFileName);
Debug.WriteLine(name);
}
And the FindHostedProcess class where I use ProcessDiagnosticInfo instead of Process
public class FindHostedProcess
{
public ProcessDiagnosticInfo Process { get; private set; }
public FindHostedProcess()
{
var foregroundProcessID = WinAPIFunctions.GetforegroundWindow();
Process = ProcessDiagnosticInfo.TryGetForProcessId((uint)WinAPIFunctions.GetWindowProcessId(foregroundProcessID));
// Get real process
if (Process.ExecutableFileName == "ApplicationFrameHost.exe")
WinAPIFunctions.EnumChildWindows(foregroundProcessID, ChildWindowCallback, IntPtr.Zero);
}
private bool ChildWindowCallback(IntPtr hwnd, IntPtr lparam)
{
var process = ProcessDiagnosticInfo.TryGetForProcessId((uint)WinAPIFunctions.GetWindowProcessId(hwnd));
if (process.ExecutableFileName != "ApplicationFrameHost.exe")
Process = process;
return true;
}
}
Finally reuse Chris Johnson's WinAPIFunctions class.
Basically, how do I tell if my program is layered above all the other ones?
A fairly simple way is to P/Invoke GetForegroundWindow() and compare the HWND returned to the application's form.Handle property.
using System;
using System.Runtime.InteropServices;
namespace MyNamespace
{
class GFW
{
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
public bool IsActive(IntPtr handle)
{
IntPtr activeHandle = GetForegroundWindow();
return (activeHandle == handle);
}
}
}
Then, from your form:
if (MyNamespace.GFW.IsActive(this.Handle))
{
// Do whatever.
}
You can use:
if (GetForegroundWindow() == Process.GetCurrentProcess().MainWindowHandle)
{
//do stuff
}
WINAPI imports (at class level):
[System.Runtime.InteropServices.DllImport("user32.dll")] public static extern bool GetForegroundWindow();
Assign a property to hold the value, and add the check to the form's GotFocus event through IDE, or after InitializeComponent();
e.g.:
//.....
InitalizeComponent();
this.GotFocus += (myFocusCheck);
//...
private bool onTop = false;
private void myFocusCheck(object s, EventArgs e)
{
if(GetFore......){ onTop = true; }
}
If your window inherits form, you can check Form.Topmost property
A good solution is given by this answer to an identical question:
https://stackoverflow.com/a/7162873/386091
/// <summary>Returns true if the current application has focus, false otherwise</summary>
public static bool ApplicationIsActivated()
{
var activatedHandle = GetForegroundWindow();
if (activatedHandle == IntPtr.Zero) {
return false; // No window is currently activated
}
var procId = Process.GetCurrentProcess().Id;
int activeProcId;
GetWindowThreadProcessId(activatedHandle, out activeProcId);
return activeProcId == procId;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
The currently accepted solution by Euric doesn't work if your program shows a dialog box or has detachable windows (like if you use a windows docking framework).
I need to be able to list all active applications on a windows machine.
I had been using this code...
Process[] procs = Process.GetProcesses(".");
foreach (Process proc in procs)
{
if (proc.MainWindowTitle.Length > 0)
{
toolStripComboBox_StartSharingProcessWindow.Items.Add(proc.MainWindowTitle);
}
}
until I realized that this doesn't list cases like WORD or ACROREAD when multiple files are opened each in their own window. In that situation, only the topmost window is listed using the above technique. I assume that's because there's only one process even though two (or more) files are opened. So, I guess my question is: How do I list all windows rather than their underlying process?
pinvoke using EnumWindows in user32.dll. something like this would do what you want.
public delegate bool WindowEnumCallback(int hwnd, int lparam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool EnumWindows(WindowEnumCallback lpEnumFunc, int lParam);
[DllImport("user32.dll")]
public static extern void GetWindowText(int h, StringBuilder s, int nMaxCount);
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(int h);
private List<string> Windows = new List<string>();
private bool AddWnd(int hwnd, int lparam)
{
if (IsWindowVisible(hwnd))
{
StringBuilder sb = new StringBuilder(255);
GetWindowText(hwnd, sb, sb.Capacity);
Windows.Add(sb.ToString());
}
return true
}
private void Form1_Load(object sender, EventArgs e)
{
EnumWindows(new WindowEnumCallback(this.AddWnd), 0);
}
I have made a similar method, but it also filter on window style ToolWindow and hidden windows store applications that circument the hidden flag by being cloaked.
public static class WindowFilter
{
public static bool NormalWindow(IWindow window)
{
if (IsHiddenWindowStoreApp(window, window.ClassName)) return false;
return !window.Styles.IsToolWindow && window.IsVisible;
}
private static bool IsHiddenWindowStoreApp(IWindow window, string className)
=> (className == "ApplicationFrameWindow" || className == "Windows.UI.Core.CoreWindow") && window.IsCloaked;
}
The above example is part of a project of github, were you can see the rest of the code.
https://github.com/mortenbrudvik/WindowExplorer
Looking for hints, tips and search terms for changing the text on a win32 window from C#.
More specifically, I'm trying to change the text on the print dialog from "Print" to "OK", as I am using the dialog to create a print ticket and not do any printing.
How can I find the dialog's window handle? Once I've got it, how would I go about finding the button in the child windows of the form? Once I've found that, how would I change the text on the button? And how can I do all this before the dialog is shown?
There's a similar question here, but it points to a CodeProject article that is waaay more complex than needed and is taking me a bit longer to parse through than I'd like to spend on this. TIA.
You should use Spy++ to take a look at the dialog. The class name is important and the control ID of the button. If it is a native Windows dialog then the class name should be "#32770". In which case you'll have a lot of use for my post in this thread. Here is another in C#. You change the button text by P/Invoking SetWindowText() on the button handle.
using System;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class SetDialogButton : IDisposable {
private Timer mTimer = new Timer();
private int mCtlId;
private string mText;
public SetDialogButton(int ctlId, string txt) {
mCtlId = ctlId;
mText = txt;
mTimer.Interval = 50;
mTimer.Enabled = true;
mTimer.Tick += (o, e) => findDialog();
}
private void findDialog() {
// Enumerate windows to find the message box
EnumThreadWndProc callback = new EnumThreadWndProc(checkWindow);
if (!EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero)) mTimer.Enabled = false;
}
private bool checkWindow(IntPtr hWnd, IntPtr lp) {
// Checks if <hWnd> is a dialog
StringBuilder sb = new StringBuilder(260);
GetClassName(hWnd, sb, sb.Capacity);
if (sb.ToString() != "#32770") return true;
// Got it, get the STATIC control that displays the text
IntPtr hCtl = GetDlgItem(hWnd, mCtlId);
SetWindowText(hCtl, mText);
// Done
return true;
}
public void Dispose() {
mTimer.Enabled = false;
}
// P/Invoke declarations
private const int WM_SETFONT = 0x30;
private const int WM_GETFONT = 0x31;
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
[DllImport("user32.dll")]
private static extern IntPtr GetDlgItem(IntPtr hWnd, int item);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool SetWindowText(IntPtr hWnd, string txt);
}
Usage:
using (new SetDialogButton(1, "Okay")) {
printDialog1.ShowDialog();
}