Java - 使用JNA的Windows任务栏 - 如何将窗口图标(HICON)转换为Java图像?

时间:2013-10-09 17:47:43

标签: java windows image winapi jna

我正在尝试将应用程序切换器添加到我正在处理的更大的项目中。它需要在Windows XP / Vista / 7/8上运行。我正在使用Java 1.7。下面是我创建的示例应用程序,用于演示我遇到的一些问题。我对JNA很新。

非常感谢this answer(以及其他许多人!)的'Hovercraft Full of Eels',它构成了测试应用的基础。

以下是我的问题:

  1. 图像绘制 - 我从窗口图标中获取的图像以黑白方式绘制。我在McDowell的this answer修改了getImageForWindow中的代码(谢谢!)。有没有更好的方法将HICON对象转换为java.awt.Image?我注意到com.sun.jna.platform.win32.W32API.HICON中有一个名为'fromNative'的方法,但我无法弄清楚如何使用它。

  2. 获取图标 - 我用来获取图标句柄的调用GetClassLongW(hWnd,GCL_HICON)不会从64位窗口返回图标。我想我需要GetClassLongPtr,但我似乎无法通过JNA访问它。

  3. 根据Alt-tab弹出窗口获取正确的窗口列表 - 我尝试复制this C++ answer中的操作,但我无法获得第二个(GetAncestor等)和用Java实现的第三(STATE_SYSTEM_INVISIBLE)检查。我正在使用一个糟糕的替代品,即排除标题为空白的窗口(忽略了一些合法的窗口)。

  4. 注意:运行此示例需要JNA和Platform jars:

        package test;
    
    import static test.WindowSwitcher.User32DLL.*;
    import static test.WindowSwitcher.Gdi32DLL.*;
    import com.sun.jna.Native;
    import com.sun.jna.Pointer;
    import com.sun.jna.platform.win32.WinDef.HWND;
    import java.awt.GridLayout;
    import java.awt.Image;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.image.BufferedImage;
    import java.util.Vector;
    import javax.swing.ImageIcon;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.WindowConstants;
    
    public class WindowSwitcher
    {
        public static void main(String args[])
        {
            JFrame JF = new JFrame();
            JPanel JP = new JPanel(new GridLayout(0, 1));
    
            JF.getContentPane().add(JP);
    
            Vector<WindowInfo> V = getActiveWindows();
            for (int i = 0; i < V.size(); i++)
            {
                final WindowInfo WI = V.elementAt(i);
                JButton JB = new JButton(WI.title);
    
                if(WI.image != null)
                {
                    JB.setIcon(new ImageIcon(WI.image));
                }
    
                JB.addActionListener(new ActionListener()
                {
                    @Override
                    public void actionPerformed(ActionEvent e)
                    {
                        SetForegroundWindow(WI.hWnd);
                    }
                });
    
                JP.add(JB);
            }
    
            JF.setSize(600,50+V.size()*64);
            JF.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            JF.setAlwaysOnTop(true);
            JF.setFocusableWindowState(false);
            JF.setVisible(true);
        }
    
        public static Vector<WindowInfo> getActiveWindows()
        {
            final Vector<WindowInfo> V = new Vector();
    
            EnumWindows(new WNDENUMPROC()
            {
                public boolean callback(Pointer hWndPointer, Pointer userData)
                {
                    HWND hWnd = new HWND(hWndPointer);
    
                    // Make sure the window is visible
                    if(IsWindowVisible(hWndPointer))
                    {
                        int GWL_EXSTYLE = -20;
                        long WS_EX_TOOLWINDOW = 0x00000080L;
    
                        // Make sure this is not a tool window
                        if((GetWindowLongW(hWndPointer, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) == 0)
                        {
                            // Get the title bar text for the window
                            char[] windowText = new char[512];
                            GetWindowTextW(hWnd, windowText, windowText.length);
                            String wText = Native.toString(windowText);
    
                            // Make sure the text is not null or blank
                            if(!(wText == null || wText.trim().equals("")))
                            {
                                // Get the icon image for the window (if available)
                                Image image = getImageForWindow(hWnd, wText);
    
                                // This window is a valid taskbar button, add a WindowInfo object to the return vector
                                V.add(new WindowInfo(wText, hWnd, image));
                            }
                        }
                    }
    
                    return true;
                }
            }, null);
    
            return V;
        }
    
        public static Image getImageForWindow(HWND hWnd, String wText)
        {
            // Get an image from the icon for this window
            int hicon = GetClassLongW(hWnd, GCL_HICON);
    
            if(hicon == 0)
                return null;
    
            Pointer hIcon = new Pointer(hicon);
    
            int width = 64;
            int height = 64;
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            draw(image, hIcon, DI_NORMAL);
            BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            draw(mask, hIcon, DI_MASK);
            applyMask(image, mask);
    
            return image;
        }
        public static void draw(BufferedImage image, Pointer hIcon, int diFlags)
        {
            int width = image.getWidth();
            int height = image.getHeight();
    
            Pointer hdc = CreateCompatibleDC(Pointer.NULL);
            Pointer bitmap = CreateCompatibleBitmap(hdc, width, height);
    
            SelectObject(hdc, bitmap);
            DrawIconEx(hdc, 0, 0, hIcon, width, height, 0, Pointer.NULL, diFlags);
    
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < width; y++)
                {
                    int rgb = GetPixel(hdc, x, y);
                    image.setRGB(x, y, rgb);
                }
            }
    
            DeleteObject(bitmap);
            DeleteDC(hdc);
        }
        private static void applyMask(BufferedImage image,
                BufferedImage mask)
        {
            int width = image.getWidth();
            int height = image.getHeight();
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    int masked = mask.getRGB(x, y);
                    if ((masked & 0x00FFFFFF) == 0)
                    {
                        int rgb = image.getRGB(x, y);
                        rgb = 0xFF000000 | rgb;
                        image.setRGB(x, y, rgb);
                    }
                }
            }
        }
    
        static class User32DLL
        {
            static
            {
                Native.register("user32");
            }
    
            public static native int GetWindowTextW(HWND hWnd, char[] lpString, int nMaxCount);
    
            public static native boolean EnumWindows(WNDENUMPROC lpEnumFunc, Pointer arg);
    
            public static interface WNDENUMPROC extends com.sun.jna.win32.StdCallLibrary.StdCallCallback
            {
                boolean callback(Pointer hWnd, Pointer arg);
            }
    
            public static native boolean IsWindowVisible(Pointer hWnd);
    
            public static native boolean SetForegroundWindow(HWND hWnd);
    
            public static native int GetWindowLongW(Pointer hWnd, int nIndex);
    
            public static int GCL_HICON = -14;
            public static int GCL_HICONSM = -34;
            public static native int GetClassLongW(HWND hWnd, int nIndex);
    
            /** @see #DrawIconEx(Pointer, int, int, Pointer, int, int, int, Pointer, int) */
            public static final int DI_COMPAT = 4;
            public static final int DI_DEFAULTSIZE = 8;
            public static final int DI_IMAGE = 2;
            public static final int DI_MASK = 1;
            public static final int DI_NORMAL = 3;
            public static final int DI_APPBANDING = 1;
    
            /** http://msdn.microsoft.com/en-us/library/ms648065(VS.85).aspx */
            public static native boolean DrawIconEx(Pointer hdc, int xLeft,
                    int yTop, Pointer hIcon, int cxWidth, int cyWidth,
                    int istepIfAniCur, Pointer hbrFlickerFreeDraw,
                    int diFlags);
        }
    
        static class Gdi32DLL
        {
            static
            {
                Native.register("gdi32");
            }
    
            /** http://msdn.microsoft.com/en-us/library/dd183489(VS.85).aspx */
            public static native Pointer CreateCompatibleDC(Pointer hdc);
    
            /** http://msdn.microsoft.com/en-us/library/dd183488(VS.85).aspx */
            public static native Pointer CreateCompatibleBitmap(Pointer hdc, int nWidth, int nHeight);
    
            /** http://msdn.microsoft.com/en-us/library/dd162957(VS.85).aspx */
            public static native Pointer SelectObject(Pointer hdc, Pointer hgdiobj);
    
            /** http://msdn.microsoft.com/en-us/library/dd145078(VS.85).aspx */
            public static native int SetPixel(Pointer hdc, int X, int Y, int crColor);
    
            /** http://msdn.microsoft.com/en-us/library/dd144909(VS.85).aspx */
            public static native int GetPixel(Pointer hdc, int nXPos, int nYPos);
    
            /** http://msdn.microsoft.com/en-us/library/dd183539(VS.85).aspx */
            public static native boolean DeleteObject(Pointer hObject);
    
            /** http://msdn.microsoft.com/en-us/library/dd183533(VS.85).aspx */
            public static native boolean DeleteDC(Pointer hdc);
        }
    }
    class WindowInfo
    {
        String title;
        HWND hWnd;
        Image image;
    
        public WindowInfo(String title, HWND hWnd, Image image)
        {
            this.title = title;
            this.hWnd = hWnd;
            this.image = image;
        }
    }
    

2 个答案:

答案 0 :(得分:1)

我找到了一个适用于我的目的的解决方法。它比我的第一次尝试简单得多!我现在使用sun.awt.shell.ShellFolder获取图标,遗憾的是,这是一个未记录/不受支持的类,可能会在将来的Java版本中删除。还有另一种使用FileSystemView获取图标的方法,但返回的图标对于我的目的而言太小了(在我的下面的示例中已经注释掉了 - getImageForWindowIcon方法)。

此变通方法基于this SO answer by aleroot。我得到进程文件路径(用于打开窗口的exe文件,我存储在WindowInfo对象中,其他窗口详细信息),然后我使用ShellFolder获取与该文件关联的图标。 注意:这并不总是给出正确的图标(例如,用于运行Netbeans进程的文件是java.exe,因此您获得了Java图标,而不是Netbeans图标!)。但在大多数情况下,它运作良好!

解决方案解决了问题1&amp;以上2,但如果有人有任何更好的解决方案,请告诉我。我没有得到问题3的任何地方,但我现在必须做的窗口清单。

这是我更新的代码...... 注意:运行此示例需要JNA和Platform jar:

    package test;

import static test.WindowSwitcher.User32DLL.*;
import static test.WindowSwitcher.Kernel32.*;
import static test.WindowSwitcher.Psapi.*;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.ptr.PointerByReference;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
import sun.awt.shell.ShellFolder;

public class WindowSwitcher
{
    public static void main(String args[])
    {
        new WindowSwitcher();
    }
    public WindowSwitcher()
    {
        JFrame JF = new JFrame("Window Switcher");
        JPanel JP = new JPanel(new GridLayout(0, 1));

        JF.getContentPane().add(JP);

        Vector<WindowInfo> V = getActiveWindows();
        for (int i = 0; i < V.size(); i++)
        {
            final WindowInfo WI = V.elementAt(i);
            JButton JB = new JButton(WI.title);

            if(WI.image != null)
            {
                JB.setIcon(new ImageIcon(WI.image));
            }

            JB.addActionListener(new ActionListener()
            {
                @Override
                public void actionPerformed(ActionEvent e)
                {
                    SetForegroundWindow(WI.hWnd);
                }
            });

            JP.add(JB);
        }

        JF.setSize(600,50+V.size()*64);
        JF.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        JF.setAlwaysOnTop(true);
        JF.setFocusableWindowState(false);
        JF.setVisible(true);
    }

    private Vector<WindowInfo> getActiveWindows()
    {
        final Vector<WindowInfo> V = new Vector();

        EnumWindows(new WNDENUMPROC()
        {
            public boolean callback(Pointer hWndPointer, Pointer userData)
            {
                HWND hWnd = new HWND(hWndPointer);

                // Make sure the window is visible
                if(IsWindowVisible(hWndPointer))
                {
                    int GWL_EXSTYLE = -20;
                    long WS_EX_TOOLWINDOW = 0x00000080L;

                    // Make sure this is not a tool window
                    if((GetWindowLongW(hWndPointer, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) == 0)
                    {
                        // Get the title bar text for the window (and other info)
                        WindowInfo info = getWindowTitleAndProcessDetails(hWnd);

                        // Make sure the text is not null or blank
                        if(!(info.title == null || info.title.trim().equals("")))
                        {
                            // Get the icon image for the window (if available)
                            info.image = getImageForWindow(info);

                            // This window is a valid taskbar button, add a WindowInfo object to the return vector
                            V.add(info);
                        }
                    }
                }

                return true;
            }
        }, null);

        return V;
    }

    private static final int MAX_TITLE_LENGTH = 1024;
    private WindowInfo getWindowTitleAndProcessDetails(HWND hWnd)
    {
        if(hWnd == null)
            return null;

        char[] buffer = new char[MAX_TITLE_LENGTH * 2];
        GetWindowTextW(hWnd, buffer, MAX_TITLE_LENGTH);
        String title = Native.toString(buffer);

        PointerByReference pointer = new PointerByReference();
        GetWindowThreadProcessId(hWnd, pointer);    //GetForegroundWindow()
        Pointer process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pointer.getValue());
        GetModuleBaseNameW(process, null, buffer, MAX_TITLE_LENGTH);
        String Sprocess = Native.toString(buffer);
        GetModuleFileNameExW(process, null, buffer, MAX_TITLE_LENGTH);
        String SprocessFilePath = Native.toString(buffer);

        return new WindowInfo(title, Sprocess, SprocessFilePath, hWnd, null);
    }

    private Image getImageForWindow(WindowInfo info)
    {
        if(info.processFilePath == null || info.processFilePath.trim().equals(""))
            return null;

        try
        {
            File f = new File(info.processFilePath);

            if(f.exists())
            {
                // https://stackoverflow.com/questions/10693171/how-to-get-the-icon-of-another-application
                // http://www.rgagnon.com/javadetails/java-0439.html
                ShellFolder sf = ShellFolder.getShellFolder(f);
                if(sf != null)
                    return sf.getIcon(true);

                // Image returned using this method is too small!
                //Icon icon = FileSystemView.getFileSystemView().getSystemIcon(f);
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    static class Psapi
    {
        static
        {
            Native.register("psapi");
        }

        public static native int GetModuleBaseNameW(Pointer hProcess, Pointer hmodule, char[] lpBaseName, int size);

        public static native int GetModuleFileNameExW(Pointer hProcess, Pointer hmodule, char[] lpBaseName, int size);
    }

    static class Kernel32
    {

        static
        {
            Native.register("kernel32");
        }
        public static int PROCESS_QUERY_INFORMATION = 0x0400;
        public static int PROCESS_VM_READ = 0x0010;

        public static native Pointer OpenProcess(int dwDesiredAccess, boolean bInheritHandle, Pointer pointer);
    }

    static class User32DLL
    {
        static
        {
            Native.register("user32");
        }

        public static native int GetWindowThreadProcessId(HWND hWnd, PointerByReference pref);

        public static native int GetWindowTextW(HWND hWnd, char[] lpString, int nMaxCount);

        public static native boolean EnumWindows(WNDENUMPROC lpEnumFunc, Pointer arg);

        public static interface WNDENUMPROC extends com.sun.jna.win32.StdCallLibrary.StdCallCallback
        {
            boolean callback(Pointer hWnd, Pointer arg);
        }

        public static native boolean IsWindowVisible(Pointer hWnd);

        public static native boolean SetForegroundWindow(HWND hWnd);

        public static native int GetWindowLongW(Pointer hWnd, int nIndex);
    }
}
class WindowInfo
{
    String title, process, processFilePath;
    HWND hWnd;
    Image image;

    public WindowInfo(String title, String process, String processFilePath, HWND hWnd, Image image)
    {
        this.title = title;
        this.process = process;
        this.processFilePath = processFilePath;
        this.hWnd = hWnd;
        this.image = image;
    }
}

答案 1 :(得分:0)

丑陋的方法

ShellFolder sf = ShellFolder.getShellFolder(new File("."));
Method m = sf.getClass().getDeclaredMethod("makeIcon", Long.TYPE, Boolean.TYPE);
m.setAccessible(true);
Image image = (Image) m.invoke(null, Long.valueOf(iconHandle), Boolean.TRUE);