发送候选文字到光标所在位置

输入法发送候选文字到光标所在处是怎么做到的?

这个问题折磨我将近半个月,今天终于搞定,分享下

用spy++可以捕获当我们当输入完文字按空格时,输入法调用哪些windows消息机制来完成文字从输入法候选列表被放到光标所在处。因输入法只涉及到IME消息机制,所以我们监控只监控IME消息即可,如下图所示:
本人截图
本人亲自试了下,一共捕捉到这几个消息:

WM_IME_STARTCOMPOSITION
WM_IME_COMPOSITION
WM_IME_CHAR
WM_IME_NOTIFY
WM_IME_REQUEST
WM_IME_SETCONTEXT

本人发现了WM_IME_CHAR 这个关键信息,以前用过WM_CHAR发送过英文字母,于是自然而然的会好奇这个跟WM_CHAR有什么区别呢?继续百度一下

WM_CHAR:未经输入法而直接送人程序中的字符会响应WM_CHAR消息
WM_IME_CHAR:所有经由输入法产生的字符都会产生WM_IME_CHAR消息

此时恍然大悟,原来如此,如果我们输入汉字的话,就必须要用WM_IME_CHAR,如果仅发送由键盘操作的英文字母、阿拉伯数字等只需使用WM_CHAR即可。当然也可使用WM_IME_CHAR,系统底层会帮你转为WM_CHAR

好了,调试到这里我们大概知道是怎么实现的了,不就是通过postMessage发送的,然后调用了windows的消息机制,从而把输入法候选列表中的文字送到当前光标所在位置,话不多说直接上代码

c++实现:

#include <windows.h>
using namespace std;
int main()
{
	Sleep(3000);
	// 窗口句柄
	HWND wnd;
	// 获得当前激活的窗口句柄
	wnd = GetForegroundWindow();
	// 获取本身的线程ID
	DWORD SelfThreadId = GetCurrentThreadId();
	// 根据窗口句柄获取线程ID
	DWORD ForeThreadId = GetWindowThreadProcessId(wnd, NULL);
	// 附加线程
	AttachThreadInput(ForeThreadId, SelfThreadId, true);
	// 获取具有输入焦点的窗口句柄
	wnd = GetFocus();
	// 取消附加的线程
	AttachThreadInput(ForeThreadId, SelfThreadId, false);
	if (wnd) {
	   // 发送消息
		PostMessageA(wnd, WM_CHAR, (WPARAM)'a', 0);
		PostMessageA(wnd, WM_IME_CHAR, (WPARAM)'测', 0);
		PostMessageA(wnd, WM_IME_CHAR, (WPARAM)'试', 0);
	}
	return 0;
}

java实现:

<dependency>
	<groupId>net.java.dev.jna</groupId>
	<artifactId>jna</artifactId>
	<version>5.7.0</version>
</dependency>

<dependency>
	<groupId>net.java.dev.jna</groupId>
	<artifactId>jna-platform</artifactId>
	<version>5.7.0</version>
</dependency>
package com;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef;
import static com.sun.jna.platform.win32.WinUser.WM_CHAR;
import com.sun.jna.win32.W32APIOptions;

/**
 * Created by administor on 2021/3/17.
 */
public class CursorDemo {

    public interface MyUser32 extends Library {
        MyUser32 INSTANCE =  Native.load("user32.dll", MyUser32.class, W32APIOptions.DEFAULT_OPTIONS);
        WinDef.HWND GetFocus();
    }

    public interface MyThread32 extends Library {
        MyThread32 INSTANCE =  Native.load("api-ms-win-core-processthreads-l1-1-0.dll", MyThread32.class);
        int GetCurrentThreadId();
    }
    
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(3000);
        // 窗口句柄
        WinDef.HWND wnd;
        // 获得当前激活的窗口句柄
        wnd = User32.INSTANCE.GetForegroundWindow();
        if (wnd != null) {
            //获取本身的线程ID
            int SelfThreadId = MyThread32.INSTANCE.GetCurrentThreadId();
            //根据窗口句柄获取线程ID
            int ForeThreadId = User32.INSTANCE.GetWindowThreadProcessId(wnd, null);
            //附加线程
            User32.INSTANCE.AttachThreadInput(new WinDef.DWORD(ForeThreadId), new WinDef.DWORD(SelfThreadId), true);
            //获取具有输入焦点的窗口句柄
            wnd = MyUser32.INSTANCE.GetFocus();
            //取消附加的线程
            User32.INSTANCE.AttachThreadInput(new WinDef.DWORD(ForeThreadId), new WinDef.DWORD(SelfThreadId), false);
           // 发送消息
            User32.INSTANCE.PostMessage(wnd, WM_CHAR, new WinDef.WPARAM('a'), new WinDef.LPARAM(0));
            User32.INSTANCE.PostMessage(wnd, WM_CHAR, new WinDef.WPARAM('测'), new WinDef.LPARAM(0));
            User32.INSTANCE.PostMessage(wnd, WM_CHAR, new WinDef.WPARAM('试'), new WinDef.LPARAM(0));
        }
    }
}

java中还有一种方法实现就是把上图用c++写好的函数封装成dll文件,java直接调用即可,原理类似

本人用java编写时遇到了很多问题,总结如下
1、jna中不存在GetFocus方法
解决方法:定义接口MyUser32见代码
2、jna中不存在GetCurrentThreadId方法
解决方法:定义接口MyThread32见代码
3、Dependency Walker用来查看user32.dll缺失什么文件
本例中,因为在jna中找不到GetCurrentThreadId,所以就反编译了下user32.dll,被我发现这个函数在api-ms-win-core-processthreads-l1-1-0.dll这个包里,windows系统自带,直接调用即可
本人截图

备注:
1、如果需要往javaFx窗口上光标处发送文字,需要如下实现,最后一个参数改成1,代表数字个数,不然没有效果,java同c++

	PostMessageA(wnd, WM_CHAR, (WPARAM)'a', 1);
	PostMessageA(wnd, WM_IME_CHAR, (WPARAM)'测', 1);
	PostMessageA(wnd, WM_IME_CHAR, (WPARAM)'试', 1);
	User32.INSTANCE.PostMessage(wnd, WM_CHAR, new WinDef.WPARAM('a'), new WinDef.LPARAM(1));
    User32.INSTANCE.PostMessage(wnd, WM_CHAR, new WinDef.WPARAM('测'), new WinDef.LPARAM(1));
    User32.INSTANCE.PostMessage(wnd, WM_CHAR, new WinDef.WPARAM('试'), new WinDef.LPARAM(1));

2、Spy++的使用(区分32位跟64位,取决你的系统):
https://www.cnblogs.com/chenyangchun/p/7262457.html

3、Dependency Walker的使用(区分32位跟64位,取决你的系统):
https://blog.csdn.net/lsq2902101015/article/details/46911457

4、windows需要的dll如果你的电脑没有,可以到这里下载https://www.dll-files.com/

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值