如何让ITfLanguageProfileNotifySink在窗口非激活时响应语言变更?
问题
我希望在应用程序未处于激活状态时,也能获知系统语言的变更情况。因此我基于ITfLanguageProfileNotifySink实现了监听逻辑,但发现ITfLanguageProfileNotifySink::OnLanguageChange仅在窗口激活时才会执行。请问如何让该方法在窗口非顶层激活状态下也能触发?
示例代码
#include <windows.h> #include <msctf.h> LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); class NotifyMe : protected ITfLanguageProfileNotifySink { public: ITfSource *m_tfSource; DWORD m_dwCookie; void Init(); virtual HRESULT STDMETHODCALLTYPE OnLanguageChange(LANGID langid, __RPC__out BOOL *pfAccept); virtual HRESULT STDMETHODCALLTYPE OnLanguageChanged(); // IUnknown implementation virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject); virtual ULONG STDMETHODCALLTYPE AddRef(); virtual ULONG STDMETHODCALLTYPE Release(); ULONG m_ulRefCount; ///< COM object reference count }; int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { // Register the window class. const wchar_t CLASS_NAME[] = L"Sample Window Class"; WNDCLASS wc = {}; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.lpszClassName = CLASS_NAME; RegisterClass(&wc); // Create the window. HWND hwnd = CreateWindowEx( 0, // Optional window styles. CLASS_NAME, // Window class L"Learn to Program Windows", // Window text WS_OVERLAPPEDWINDOW, // Window style // Size and position CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, // Parent window NULL, // Menu hInstance, // Instance handle NULL // Additional application data ); if(hwnd == NULL) { return 0; } CoInitialize(nullptr); NotifyMe notify; notify.Init(); ShowWindow(hwnd, nCmdShow); MSG msg = {}; while(GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } void NotifyMe::Init() { m_tfSource = NULL; ITfInputProcessorProfiles *pProfiles; HRESULT hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER, IID_ITfInputProcessorProfiles, (LPVOID*)&pProfiles); if(SUCCEEDED(hr)) { hr = pProfiles->QueryInterface(IID_ITfSource, (LPVOID*)&m_tfSource); if(SUCCEEDED(hr)) { hr = m_tfSource->AdviseSink(IID_ITfLanguageProfileNotifySink, (ITfLanguageProfileNotifySink*)this, &m_dwCookie); if(FAILED(hr) || m_dwCookie == -1) { m_tfSource->Release(); m_tfSource = NULL; } } pProfiles->Release(); } } HRESULT STDMETHODCALLTYPE NotifyMe::OnLanguageChange(LANGID langid, __RPC__out BOOL *pfAccept) { if(pfAccept) *pfAccept = TRUE; return S_OK; } HRESULT STDMETHODCALLTYPE NotifyMe::OnLanguageChanged() { OutputDebugStringA("Language Changed"); return S_OK; } HRESULT STDMETHODCALLTYPE NotifyMe::QueryInterface(REFIID riid, __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject) { if(!ppvObject) return E_INVALIDARG; if(riid == IID_IUnknown) *ppvObject = static_cast<IUnknown*>(this); else if(riid == IID_ITfLanguageProfileNotifySink) *ppvObject = static_cast<ITfLanguageProfileNotifySink*>(this); else { *ppvObject = NULL; return E_NOINTERFACE; } AddRef(); return NOERROR; } ULONG STDMETHODCALLTYPE NotifyMe::AddRef() { InterlockedIncrement(&m_ulRefCount); return m_ulRefCount; } ULONG STDMETHODCALLTYPE NotifyMe::Release() { // Decrement the object's internal counter. ULONG ulRefCount = InterlockedDecrement(&m_ulRefCount); if(m_ulRefCount == 0) delete this; return ulRefCount; } LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_DESTROY: PostQuitMessage(0); return 0; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1)); EndPaint(hwnd, &ps); return 0; } // i want to listen to these events when the window is not in focus case WM_SETFONT: OutputDebugStringA("Font Changed"); break; case WM_INPUTLANGCHANGE: OutputDebugStringA("Language Changed - WndProc"); break; // -- along with paint //case WM_PAINT: } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
解决方案
核心原因
ITfLanguageProfileNotifySink的设计是绑定当前激活的文本服务上下文,只有当你的窗口处于前台激活状态时,才会收到该接口的通知。要实现全局监听,必须改用系统级的监听方案。
方案1:使用ITfSystemLanguageProfileNotifySink(推荐)
这是Text Services Framework提供的系统级语言变更监听接口,无论你的窗口是否激活,只要系统输入语言变更,就会触发通知。
修改步骤:
- 替换接口实现:将
ITfLanguageProfileNotifySink替换为ITfSystemLanguageProfileNotifySink,实现它的OnSystemLanguageChange方法。 - 注册系统级通知:在
Init方法中,注册IID_ITfSystemLanguageProfileNotifySink而非原来的IID_ITfLanguageProfileNotifySink。 - 更新
QueryInterface,添加对IID_ITfSystemLanguageProfileNotifySink的支持。
修改后的关键代码:
#include <windows.h> #include <msctf.h> LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); class NotifyMe : protected ITfSystemLanguageProfileNotifySink { public: ITfSource *m_tfSource; DWORD m_dwCookie; void Init(); // ITfSystemLanguageProfileNotifySink 核心方法 virtual HRESULT STDMETHODCALLTYPE OnSystemLanguageChange(LANGID langid); // IUnknown 实现 virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject); virtual ULONG STDMETHODCALLTYPE AddRef(); virtual ULONG STDMETHODCALLTYPE Release(); ULONG m_ulRefCount = 1; // 默认初始引用计数为1 }; // 系统语言变更时触发 HRESULT STDMETHODCALLTYPE NotifyMe::OnSystemLanguageChange(LANGID langid) { char buf[64]; sprintf(buf, "System Language Changed: 0x%04X", langid); OutputDebugStringA(buf); return S_OK; } void NotifyMe::Init() { m_tfSource = NULL; ITfInputProcessorProfiles *pProfiles; HRESULT hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER, IID_ITfInputProcessorProfiles, (LPVOID*)&pProfiles); if(SUCCEEDED(hr)) { hr = pProfiles->QueryInterface(IID_ITfSource, (LPVOID*)&m_tfSource); if(SUCCEEDED(hr)) { // 注册系统级语言变更通知 hr = m_tfSource->AdviseSink(IID_ITfSystemLanguageProfileNotifySink, (ITfSystemLanguageProfileNotifySink*)this, &m_dwCookie); if(FAILED(hr) || m_dwCookie == -1) { m_tfSource->Release(); m_tfSource = NULL; } } pProfiles->Release(); } } HRESULT STDMETHODCALLTYPE NotifyMe::QueryInterface(REFIID riid, void **ppvObject) { if(!ppvObject) return E_INVALIDARG; if(riid == IID_IUnknown) *ppvObject = static_cast<IUnknown*>(this); else if(riid == IID_ITfSystemLanguageProfileNotifySink) *ppvObject = static_cast<ITfSystemLanguageProfileNotifySink*>(this); else { *ppvObject = NULL; return E_NOINTERFACE; } AddRef(); return NOERROR; } ULONG STDMETHODCALLTYPE NotifyMe::AddRef() { return InterlockedIncrement(&m_ulRefCount); } ULONG STDMETHODCALLTYPE NotifyMe::Release() { ULONG ulRefCount = InterlockedDecrement(&m_ulRefCount); if(m_ulRefCount == 0) delete this; return ulRefCount; } // 其余wWinMain、WindowProc代码保持不变
方案2:使用SetWinEventHook监听系统语言变更
如果不想依赖Text Services Framework,可以使用Windows的SetWinEventHook监听EVENT_SYSTEM_LANGUAGECHANGE事件,这是一种更通用的全局事件监听方式,不需要DLL支持。
示例代码:
#include <windows.h> HHOOK g_hLangEventHook = NULL; // 语言变更回调函数 void CALLBACK LanguageChangeCallback(HWINEVENTHOOK hook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) { OutputDebugStringA("System Language Changed (WinEventHook)"); } // 初始化钩子 void InitGlobalLangListener() { g_hLangEventHook = SetWinEventHook(EVENT_SYSTEM_LANGUAGECHANGE, EVENT_SYSTEM_LANGUAGECHANGE, NULL, LanguageChangeCallback, 0, 0, WINEVENT_OUTOFCONTEXT); } // 卸载钩子 void UninitGlobalLangListener() { if(g_hLangEventHook != NULL) { UnhookWinEvent(g_hLangEventHook); g_hLangEventHook = NULL; } } // 在wWinMain中调用InitGlobalLangListener(),退出时调用UninitGlobalLangListener()
注意事项
- 使用COM接口时,确保正确调用
CoInitialize/CoUninitialize,并且处理好COM对象的引用计数。 - 全局钩子或WinEventHook需要在程序退出时正确卸载,避免资源泄漏。
ITfSystemLanguageProfileNotifySink是Windows Vista及以上系统支持的接口,兼容性良好。
内容的提问来源于stack exchange,提问作者chadb
相关产品推荐
相关产品推荐

