4

I'm trying to add an application-switcher to a bigger project I'm working on. It needs to work on Windows XP/Vista/7/8. I'm using Java 1.7. Below is a sample application I created to demonstrate some of the problems I'm having. I'm very new to JNA.

Thanks very much to 'Hovercraft Full of Eels' for this answer (and many others!) which forms the basis for the test application.

Here are my questions:

  1. Image drawing - The images that I get from the window icons are being drawn in black and white. I modified the code in getImageForWindow from this answer by McDowell (Thanks!). Is there a better way to convert HICON objects to java.awt.Image? I notice there's a method called 'fromNative' in com.sun.jna.platform.win32.W32API.HICON but I can't figure out how to use it.

  2. Getting the icons - The call I use to get an icon handle, GetClassLongW(hWnd, GCL_HICON), doesn't return icons from 64bit windows. I think I need GetClassLongPtr for that but I can't seem to access it via JNA.

  3. Getting the correct window list, as per the Alt-tab popup - I tried to replicate what is done in this C++ answer but I couldn't manage to get the 2nd (GetAncestor, etc) and 3rd (STATE_SYSTEM_INVISIBLE) checks implemented in Java. I'm using a poor substitute which is to exclude windows where the title is blank (which ignores some legitimate windows).

Note: JNA and Platform jars are required to run this example:

    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;
    }
}
4

2 回答 2

3

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

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

变通方法解决了上述问题 1 和 2,但如果有人有更好的解决方案,请告诉我。问题 3 我没有得到任何结果,但我现在必须做的窗口列表。

这是我更新的代码... 注意:运行此示例需要 JNA 和平台 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;
    }
}
于 2013-10-18T11:13:51.183 回答
1

丑陋的方法

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);
于 2014-03-03T09:46:56.137 回答