我正在开发一个WPF应用程序来监控我在计算机上的活动。我使用Process.GetProcesses()
和一些过滤来获取我感兴趣的过程(例如:Calculator)然后我记录他们的StartTime。我还使用WIN32 / USER32 API方法GetForegroundWindow()
来获取用户正在使用的窗口。
问题是,当Windows是Windows / UWP应用程序时,它们总是由进程ApplicationFrameHost托管。因此GetForegroundWindow()
方法返回带有标题的窗口(例如:Calculator),但不会返回托管的实际进程。
我需要的是另一种获取前台窗口的方法,包括托管的实际进程,或者某种方式将窗口连接到进程。
任何知道如何实现这一目标的人?所有的帮助将非常感激。
答案 0 :(得分:11)
我最终找到了一种方法来做到这一点,所以我要回答我自己的问题,所以未来有同样问题的人可能会觉得它很有用。
这是具有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();
}
}
这是我用来测试它是否可行的类。我在一个简单的控制台程序中使用它,它只写出当前焦点的进程名称:
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;
}
}
答案 1 :(得分:2)
克里斯,我发现有另一种方法尝试应用您的解决方案to a related problem。在尝试分析与ApplicationFrameHost.exe
相关的东西如何工作时,我偶然发现了通过将0
而不是实际的线程ID传递给{{ 3}}。
对于您遇到的问题,这种方法可能无法完全解决问题,但是我认为这可能对将来可能会遇到相同问题的人们有所帮助;-)
以下是如何应用此示例(伪C ++代码):
GUITHREADINFO gti = { sizeof(GUITHREADINFO) };
GetGUIThreadInfo(0, >i); // <- note the `0`
DWORD processId = 0;
GetWindowThreadProcessId(gti.hwndFocus, &processId);
const auto procName = Util::GetProcessName(processId);
它为我测试过的所有或多或少的常见应用程序解决了我的问题(获取实际的键盘布局+找到真正的前景窗口)。
答案 2 :(得分:0)
基本上,如果活动窗口处于全屏模式,则您提供的答案也将失败,
例如,如果您已将Skype应用程序与Microsoft远程桌面一起打开,
活动模式并在全屏模式下,EnumChildWindows
将返回SkypeApp而不是RDPClient。
要解决此问题,您应该遵循Ivanmoskalev建议的解决方法,
检查一下:
[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;
}
答案 3 :(得分:0)
与 Chris Johnsson 相同的代码,除了我没有使用 Process ,因为它在UWP应用中无法很好地工作。
开始代码:
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);
}
和 FindHostedProcess 类,其中我使用 ProcessDiagnosticInfo 而不是 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;
}
}
最终重用 Chris Johnson的 WinAPIFunctions 类。