Java:使用中文(繁体)时的错位候选列表 - 新的语音键盘

时间:2015-02-13 16:34:21

标签: java windows swing ime

我试图在英语(美国)Windows 7上使用中文(繁体,台湾),中文(繁体) - 新的语音键盘。当我输入基于Java Swing的文本区域时,候选人列表显示在屏幕的右下角,无论文本区域在屏幕上的位置如何。当我没有使用Java程序时,候选列表显示在正确的位置,直接在我打字的文本下面。

是否有其他人遇到此行为并找到了解决方法?我还没有在网上找到有关此行为的其他报告。

提前感谢您的帮助!

系统详细信息:

  • Microsoft New Phonetic IME 10.1(10.1.7601.0)
    • 中文输入模式
    • 半个或全部形状(无关紧要)
    • 标准键盘布局
  • Windows 7,64位(32位同样发生)
  • 影响Java 6,7和8
  • 影响Swing和JavaFX

1 个答案:

答案 0 :(得分:0)

我最终发现报告了类似的问题,但大多数都与日本的IME有关,并且已经在JDK中得到修复。我没有找到任何特定于此中文IME的报告,但我找到了一个解决方法,以防它对其他人有用。

简要总结是我在收听WM_IME_STARTCOMPOSITION Windows消息。当我看到它时,我找到了IME候选窗口,将其移动到我想要的位置,并覆盖其WindowProc以防止进一步移动。在合成期间,我还会监听WM_KEYDOWN事件,因为我在用户编写时不再收到任何WM_IME消息,即使候选窗口关闭并在整个合成过程中多次重新创建。当我收到WM_IME_ENDCOMPOSITON消息时,我停止侦听WM_KEYDOWN消息。

作为替代方法,我尝试使用IMC_SETCANDIDATEPOS命令发送WM_IME_CONTROL消息来移动候选窗口,但是这个特定的IME似乎忽略了它。

我使用JNA(https://github.com/twall/jna)覆盖包含我的文本区域和IME候选窗口的窗口上的WindowProc。

下面的代码段是变通方法的一个示例。

hwndMain = WIN_INSTANCE.FindWindow(null, "Main Window");

// Note the existing WindowProc so we can restore it later.
prevWndProc = new BaseTSD.LONG_PTR((long) WIN_INSTANCE.GetWindowLong(hwndMain, WinUser.GWL_WNDPROC));

// Register a new WindowProc that we will use to intercept IME messages.
mainListener = new WindowsCallbackListener() {
    @Override
    public int callback(int hWnd, int uMsg, int uParam, int lParam) {
        if (uMsg == WM_IME_STARTCOMPOSITION || (imeComposing && uMsg == WM_KEYDOWN)) {
            imeComposing = true;

            final WinDef.HWND hwndIme = WIN_INSTANCE.FindWindow("SYSIME7_READING_UI", null);

            if (hwndIme != null && !hwndIme.equals(imeWindow)) {
                // We found an IME window that is not the same as the last one. We assume the last one was
                // closed. We need to register our callback with the new window.
                imeWindow = hwndIme;

                final Point imeWindowLocation = getImeWindowLocation();
                WIN_INSTANCE.MoveWindow(hwndIme, imeWindowLocation.x, imeWindowLocation.y, 0, 0, true);

                final BaseTSD.LONG_PTR prevWndProcIme =
                        new BaseTSD.LONG_PTR((long) WIN_INSTANCE.GetWindowLong(hwndIme, WinUser.GWL_WNDPROC));

                imeListener = new WindowsCallbackListener() {
                    @Override
                    public int callback(int hWnd, int uMsg, int uParam, int lParam) {
                        if (uMsg == WM_WINDOWPOSCHANGING) {
                            final WindowPosition pos = new WindowPosition(new Pointer((long)lParam));
                            pos.read();
                            pos.flags |= SWP_NOMOVE;
                            pos.write();
                        }

                        // Call the window's actual WndProc so the events get processed.
                        return WIN_INSTANCE.CallWindowProc(prevWndProcIme, hWnd, uMsg, uParam, lParam);
                    }
                };

                // Set the WndProc function to use our callback listener instead of the window's one.
                WIN_INSTANCE.SetWindowLong(hwndIme, WinUser.GWL_WNDPROC, imeListener);
            }
        }
        else if (uMsg == WM_IME_ENDCOMPOSITION) {
            // We can discard the IME listener since its window is closed. If another one gets opened, we'll
            // create a new listener.
            imeListener = null;
            imeComposing = false;
        }

        // Call the window's previous WindowProc so the event continues to get processed.
        return WIN_INSTANCE.CallWindowProc(prevWndProc, hWnd, uMsg, uParam, lParam);
    }
};

// Set the WindowProc function to use our WindowProc so the event continues to get processed.
WIN_INSTANCE.SetWindowLong(hwndMain, WinUser.GWL_WNDPROC, mainListener);

上面的代码假定以下定义:

private static final MyUser32 WIN_INSTANCE = MyUser32.INSTANCE;
private static final int SWP_NOMOVE = 2;
private static final int WM_KEYDOWN = 256;
private static final int WM_WINDOWPOSCHANGING = 70;
private static final int WM_IME_ENDCOMPOSITION = 270;
private static final int WM_IME_STARTCOMPOSITION = 269;
private WinDef.HWND hwndMain;
private BaseTSD.LONG_PTR prevWndProc;

// Keep references to these listeners so they don't get garbage-collected.
private WindowsCallbackListener mainListener;
private WindowsCallbackListener imeListener;

private boolean imeComposing;
private WinDef.HWND imeWindow;

public static class WindowPosition extends Structure {
    public WinDef.HWND hwnd;
    public WinDef.HWND hwndInsertAfter;
    public int x;
    public int y;
    public int cx;
    public int cy;
    public int flags;

    public WindowPosition(Pointer p) {
        super(p);
    }

    @Override
    protected List getFieldOrder() {
        return Arrays.asList("hwnd", "hwndInsertAfter", "x", "y", "cx", "cy", "flags");
    }
}

private interface MyUser32 extends User32 {
    MyUser32 INSTANCE = (MyUser32) Native.loadLibrary("user32", MyUser32.class, W32APIOptions.DEFAULT_OPTIONS);
    int CallWindowProc(BaseTSD.LONG_PTR prevWndProc, int hWnd, int uMsg, int uParam, int lParam);
    int SetWindowLong(HWND hwnd, int nIndex, BaseTSD.LONG_PTR listener) throws LastErrorException;
    int SetWindowLong(HWND hwnd, int nIndex, WindowsCallbackListener listener) throws LastErrorException;
}

private interface WindowsCallbackListener extends Callback, StdCall {
    int callback(int hWnd, int Msg, int wParam, int lParam);
}