c# - 从桌面应用程序获取 "modern"Windows 应用程序的图标?

标签 c# wpf windows-store-apps icons pinvoke

我开发了一个函数,它返回给定窗口句柄的窗口图标。看起来像这样。

private static BitmapSource GetWindowIcon(IntPtr windowHandle)
{
    var hIcon = default(IntPtr);
    hIcon = SendMessage(windowHandle, WM_GETICON, ICON_BIG, IntPtr.Zero);

    if (hIcon == IntPtr.Zero)
        hIcon = GetClassLongPtr(windowHandle, GCL_HICON);

    if (hIcon == IntPtr.Zero)
    {
        hIcon = LoadIcon(IntPtr.Zero, (IntPtr)0x7F00/*IDI_APPLICATION*/);
    }

    if (hIcon != IntPtr.Zero)
    {
        return Imaging.CreateBitmapSourceFromHIcon(hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
    } else {
        throw new InvalidOperationException("Could not load window icon.");
    }
}

我将此函数与 GetForegroundWindow 结合使用以获取事件窗口的图标。

但是,它似乎为通用应用程序生成了同样沉闷的图标。

是否有可能以某种方式从正在运行的通用应用程序中获取图 block 图像或图标?

最佳答案

下面是一些示例代码,演示了如何完成此操作。请注意:

  1. 你应该运行这个升级版,否则你将无法访问包含应用程序资源的文件夹(我想,我并没有真正检查过自己,因为为了调查我授予自己访问该文件夹的权限)。
  2. 现代应用程序在 ApplicationFrameHost 主机进程下运行。您将需要一些技巧才能获得实际的可执行文件(如 Calculator.exe),这些技巧在代码中有注释。
  3. 现代应用程序 list 包含 Logo 路径,但可能有多个 Logo (例如黑色、白色、白色)。您将需要一些逻辑来选择一个。我自己没有对此进行详细调查。
  4. 我在 Windows 10 的计算器应用程序上对此进行了测试,它运行良好。但是,当然需要对更多应用进行更多测试以确保一切正常。

代码如下:

public static class IconHelper {
    public static BitmapSource GetForegroundWindowIcon() {
        var hwnd = GetForegroundWindow();
        uint pid;
        GetWindowThreadProcessId(hwnd, out pid);
        Process proc = Process.GetProcessById((int) pid);
        // modern apps run under ApplicationFrameHost host process in windows 10
        // don't forget to check if that is true for windows 8 - maybe they use another host there
        if (proc.MainModule.ModuleName == "ApplicationFrameHost.exe") {
            // this should be modern app
            return GetModernAppLogo(hwnd);
        }
        return GetWindowIcon(hwnd);
    }

    public static BitmapSource GetModernAppLogo(IntPtr hwnd) {
        // get folder where actual app resides
        var exePath = GetModernAppProcessPath(hwnd); 
        var dir = System.IO.Path.GetDirectoryName(exePath);
        var manifestPath = System.IO.Path.Combine(dir, "AppxManifest.xml");            
        if (File.Exists(manifestPath)) {
            // this is manifest file
            string pathToLogo;
            using (var fs = File.OpenRead(manifestPath)) {
                var manifest = XDocument.Load(fs);
                const string ns = "http://schemas.microsoft.com/appx/manifest/foundation/windows10";
                // rude parsing - take more care here
                pathToLogo = manifest.Root.Element(XName.Get("Properties", ns)).Element(XName.Get("Logo", ns)).Value;
            }
            // now here it is tricky again - there are several files that match logo, for example
            // black, white, contrast white. Here we choose first, but you might do differently
            string finalLogo = null;
            // serach for all files that match file name in Logo element but with any suffix (like "Logo.black.png, Logo.white.png etc)
            foreach (var logoFile in Directory.GetFiles(System.IO.Path.Combine(dir, System.IO.Path.GetDirectoryName(pathToLogo)),
                System.IO.Path.GetFileNameWithoutExtension(pathToLogo) + "*" + System.IO.Path.GetExtension(pathToLogo))) {
                finalLogo = logoFile;
                break;
            }

            if (System.IO.File.Exists(finalLogo)) {
                using (var fs = File.OpenRead(finalLogo)) {
                    var img = new BitmapImage() {
                    };
                    img.BeginInit();
                    img.StreamSource = fs;
                    img.CacheOption = BitmapCacheOption.OnLoad;
                    img.EndInit();
                    return img;
                }
            }
        }
        return null;
    }

    private static string GetModernAppProcessPath(IntPtr hwnd) {
        uint pid = 0;
        GetWindowThreadProcessId(hwnd, out pid);            
        // now this is a bit tricky. Modern apps are hosted inside ApplicationFrameHost process, so we need to find
        // child window which does NOT belong to this process. This should be the process we need
        var children = GetChildWindows(hwnd);
        foreach (var childHwnd in children) {
            uint childPid = 0;
            GetWindowThreadProcessId(childHwnd, out childPid);
            if (childPid != pid) {
                // here we are
                Process childProc = Process.GetProcessById((int) childPid);
                return childProc.MainModule.FileName;
            }
        }

        throw new Exception("Cannot find a path to Modern App executable file");
    }

    public static BitmapSource GetWindowIcon(IntPtr windowHandle) {
        var hIcon = default(IntPtr);
        hIcon = SendMessage(windowHandle, WM_GETICON, (IntPtr) ICON_BIG, IntPtr.Zero);

        if (hIcon == IntPtr.Zero)
            hIcon = GetClassLongPtr(windowHandle, GCL_HICON);

        if (hIcon == IntPtr.Zero) {
            hIcon = LoadIcon(IntPtr.Zero, (IntPtr) 0x7F00 /*IDI_APPLICATION*/);
        }

        if (hIcon != IntPtr.Zero) {
            return Imaging.CreateBitmapSourceFromHIcon(hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
        }
        else {
            throw new InvalidOperationException("Could not load window icon.");
        }
    }

    #region Helper methods
    const UInt32 WM_GETICON = 0x007F;
    const int ICON_BIG = 1;
    const int GCL_HICON = -14;

    private static List<IntPtr> GetChildWindows(IntPtr parent)
    {
        List<IntPtr> result = new List<IntPtr>();
        GCHandle listHandle = GCHandle.Alloc(result);
        try
        {
            EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
            EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
        }
        finally
        {
            if (listHandle.IsAllocated)
                listHandle.Free();
        }
        return result;
    }

    private static bool EnumWindow(IntPtr handle, IntPtr pointer)
    {
        GCHandle gch = GCHandle.FromIntPtr(pointer);
        List<IntPtr> list = gch.Target as List<IntPtr>;
        if (list == null)
        {
            throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
        }
        list.Add(handle);
        //  You can modify this to check to see if you want to cancel the operation, then return a null here
        return true;
    }

    public delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam);
    [DllImport("user32.Dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool EnumChildWindows(IntPtr parentHandle, EnumWindowProc callback, IntPtr lParam);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int GetWindowThreadProcessId(IntPtr handle, out uint processId);

    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    static extern IntPtr LoadIcon(IntPtr hInstance, IntPtr lpIconName);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

    private static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex)
    {
        if (IntPtr.Size > 4)
            return GetClassLongPtr64(hWnd, nIndex);
        else
            return new IntPtr(GetClassLongPtr32(hWnd, nIndex));
    }

    [DllImport("user32.dll", EntryPoint = "GetClassLong")]
    public static extern uint GetClassLongPtr32(IntPtr hWnd, int nIndex);

    [DllImport("user32.dll", EntryPoint = "GetClassLongPtr")]
    public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, int nIndex);
    #endregion

}

用法是:

var icon = IconHelper.GetForegroundWindowIcon();

关于c# - 从桌面应用程序获取 "modern"Windows 应用程序的图标?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32122679/

相关文章:

c# - CoreWindow.GetForCurrentThread().SizeChanged 和 Window.Current.SizeChanged 事件有什么区别

c# - 使用 sqlite-net 删除行的 SQLite 异常

c# - ASP.NET 网络 API : Optional Guid parameters

C# - Thread.Join(Milliseconds) 和 finally block

wpf - 具有自动高度的网格行中的列表框。滚动条不工作

c# - 阅读使用内联填充的 C# Textblock 文本属性

c# - VS for Mac - 使用自动布局实现横向和纵向 View

c# - mvc脚手架从数据库中删除原始数据

c# - 在 WPF 中添加动态按钮

c# - 为什么 ReSharper 提示这种 ResourceLoader.GetString 的使用?