You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取不含后台应用的任务管理器应用列表?

问题描述

我尝试获取当前正在使用的应用列表,用以下C#代码从Windows设备中检索应用,但返回结果包含Media Player、ApplicationFrameHost、Settings这些在任务管理器「后台应用」标签里的程序,没法把它们和正常应用区分开。请问怎么像任务管理器那样分离应用列表和后台应用?

原C#代码

public enum WindowState
{
    Normal,
    Minimized,
    Maximized
}

public class WindowWrapper : IWin32Window
{
    private IntPtr _handle;
    private string _title;
    private WindowState _state;

    public IntPtr Handle => _handle;
    public string Title => _title;
    public WindowState State => _state;

    public WindowWrapper(IntPtr handle, string title, WindowState state)
    {
        _handle = handle;
        _title = title;
        _state = state;
    }
}

public class NativeMethods
{
    public static readonly IntPtr GWL_STYLE = new IntPtr(-16);
    public static readonly UInt64 WS_VISIBLE = 0x10000000L;
    public static readonly UInt64 WS_BORDER = 0x00800000L;
    public static readonly UInt64 DESIRED_WS = WS_BORDER | WS_VISIBLE;

    public delegate bool EnumWindowsCallback(IntPtr hwnd, IntPtr lParam);

    [StructLayout(LayoutKind.Sequential)]
    private struct WINDOWPLACEMENT
    {
        public int length;
        public int flags;
        public int showCmd;
        public POINT ptMinPosition;
        public POINT ptMaxPosition;
        public RECT rcNormalPosition;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT
    {
        public int x;
        public int y;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    public static List<WindowWrapper> GetAllWindows()
    {
        List<WindowWrapper> windows = new List<WindowWrapper>();

        EnumDesktopWindows(IntPtr.Zero, (hwnd, lParam) =>
        {
            if ((GetWindowLongA(hwnd, GWL_STYLE) & DESIRED_WS) == DESIRED_WS && !IsIconic(hwnd)
                && IsWindowVisible(hwnd) && IsWindow(hwnd) && hwnd != IntPtr.Zero)
            {
                StringBuilder buffer = new StringBuilder(255);
                GetWindowText(hwnd, buffer, buffer.Capacity);
                string title = buffer.ToString();

                WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
                placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
                GetWindowPlacement(hwnd, ref placement);

                WindowState state = WindowState.Normal;

                int processId;
                GetWindowThreadProcessId(hwnd, out processId);

                var process = Process.GetProcessById(processId);

                if (IsProcessInForeground(processId))
                {
                    state = WindowState.Normal;  // Consider it as normal if it's in the foreground
                }
                else
                {
                    switch (placement.showCmd)
                    {
                        case 1:
                            state = WindowState.Normal;
                            break;
                        case 2:
                            state = WindowState.Minimized;
                            break;
                        case 3:
                            state = WindowState.Maximized;
                            break;
                    }
                }

                WindowWrapper wnd = new WindowWrapper(hwnd, title, state);
                if (!string.IsNullOrEmpty(wnd.Title) /*&& !string.IsNullOrEmpty(process.MainWindowTitle) */&& !process.HasExited && process.MainWindowHandle == hwnd)
                {
                    windows.Add(wnd);
                }
            }
            return true;
        }, IntPtr.Zero);

        return windows;
    }

    private static bool IsProcessInForeground(int processId)
    {
        IntPtr foregroundWindow = GetForegroundWindow();
        int foregroundProcessId;
        GetWindowThreadProcessId(foregroundWindow, out foregroundProcessId);

        return (foregroundProcessId == processId);
    }

    [DllImport("user32.dll", SetLastError = true)]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", SetLastError = true)]
    private static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsCallback lpfn, IntPtr lParam);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool IsWindow(IntPtr hWnd);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool IsIconic(IntPtr hWnd);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

    private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

    [DllImport("user32.dll")]
    private static extern void GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

    [DllImport("user32.dll")]
    private static extern UInt64 GetWindowLongA(IntPtr hWnd, IntPtr nIndex);
}

解决方案

任务管理器区分「应用」和「后台应用」的核心是判断窗口是否为用户可见的主应用窗口,而非系统后台服务或辅助窗口。可以通过以下几个关键调整实现:

1. 添加扩展样式相关常量与API

在NativeMethods中新增用于判断主窗口的扩展样式常量,以及获取窗口拥有者的API:

public static readonly IntPtr GWL_EXSTYLE = new IntPtr(-20);
public static readonly UInt64 WS_EX_APPWINDOW = 0x00040000L;
public static readonly UInt32 GW_OWNER = 4;

[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetWindow(IntPtr hWnd, UInt32 uCmd);

2. 修改窗口筛选条件

  • 移除!IsIconic(hwnd):最小化的应用仍属于正常应用,不应被排除
  • 新增窗口拥有者判断:排除有父窗口的子窗口/对话框
  • 新增WS_EX_APPWINDOW样式检查:仅保留会显示在任务栏的主应用窗口

修改后的核心判断逻辑:

UInt64 windowStyle = GetWindowLongA(hwnd, GWL_STYLE);
bool hasRequiredStyle = (windowStyle & DESIRED_WS) == DESIRED_WS;
bool hasNoOwner = GetWindow(hwnd, GW_OWNER) == IntPtr.Zero;
UInt64 exStyle = GetWindowLongA(hwnd, GWL_EXSTYLE);
bool isMainAppWindow = (exStyle & WS_EX_APPWINDOW) == WS_EX_APPWINDOW;

if (hasRequiredStyle && IsWindowVisible(hwnd) && IsWindow(hwnd) && hwnd != IntPtr.Zero
    && hasNoOwner && isMainAppWindow)

3. 优化进程与窗口关联逻辑

调整GetAllWindows方法,避免进程已退出的异常,同时简化窗口状态判断:

public static List<WindowWrapper> GetAllWindows()
{
    List<WindowWrapper> windows = new List<WindowWrapper>();

    EnumDesktopWindows(IntPtr.Zero, (hwnd, lParam) =>
    {
        UInt64 windowStyle = GetWindowLongA(hwnd, GWL_STYLE);
        bool hasRequiredStyle = (windowStyle & DESIRED_WS) == DESIRED_WS;
        bool hasNoOwner = GetWindow(hwnd, GW_OWNER) == IntPtr.Zero;
        UInt64 exStyle = GetWindowLongA(hwnd, GWL_EXSTYLE);
        bool isMainAppWindow = (exStyle & WS_EX_APPWINDOW) == WS_EX_APPWINDOW;

        if (hasRequiredStyle && IsWindowVisible(hwnd) && IsWindow(hwnd) && hwnd != IntPtr.Zero
            && hasNoOwner && isMainAppWindow)
        {
            StringBuilder buffer = new StringBuilder(255);
            GetWindowText(hwnd, buffer, buffer.Capacity);
            string title = buffer.ToString();

            if (string.IsNullOrEmpty(title))
                return true;

            WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
            placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
            GetWindowPlacement(hwnd, ref placement);

            WindowState state = WindowState.Normal;
            switch (placement.showCmd)
            {
                case 1:
                    state = WindowState.Normal;
                    break;
                case 2:
                    state = WindowState.Minimized;
                    break;
                case 3:
                    state = WindowState.Maximized;
                    break;
            }

            int processId;
            GetWindowThreadProcessId(hwnd, out processId);
            try
            {
                var process = Process.GetProcessById(processId);
                if (!process.HasExited && process.MainWindowHandle == hwnd)
                {
                    WindowWrapper wnd = new WindowWrapper(hwnd, title, state);
                    windows.Add(wnd);
                }
            }
            catch (ArgumentException)
            {
                // 进程已退出,直接忽略
            }
        }
        return true;
    }, IntPtr.Zero);

    return windows;
}

4. 可选:UWP应用处理

如果需要包含Settings、Media Player这类UWP应用,可通过检查进程的包信息(需引用Windows.ApplicationModel)来区分宿主进程与实际应用:

using Windows.ApplicationModel;

// 在获取process后添加
bool isUwpApp = false;
try
{
    var packageId = PackageId.FromProcessId(processId);
    isUwpApp = true;
}
catch
{
    // 非UWP应用会抛出异常,直接跳过
}

关键说明

  • WS_EX_APPWINDOW是任务管理器区分主应用窗口的核心标志,只有带该样式的窗口才会显示在任务栏
  • 排除有窗口拥有者的子窗口,避免把弹窗、对话框误判为独立应用
  • 保留最小化窗口的判断逻辑,符合任务管理器的展示规则

内容的提问来源于stack exchange,提问作者Elanchezhiyan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 11:45:00