You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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提供的系统级语言变更监听接口,无论你的窗口是否激活,只要系统输入语言变更,就会触发通知。

修改步骤:

  1. 替换接口实现:将ITfLanguageProfileNotifySink替换为ITfSystemLanguageProfileNotifySink,实现它的OnSystemLanguageChange方法。
  2. 注册系统级通知:在Init方法中,注册IID_ITfSystemLanguageProfileNotifySink而非原来的IID_ITfLanguageProfileNotifySink。
  3. 更新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()

注意事项

  1. 使用COM接口时,确保正确调用CoInitialize/CoUninitialize,并且处理好COM对象的引用计数。
  2. 全局钩子或WinEventHook需要在程序退出时正确卸载,避免资源泄漏。
  3. ITfSystemLanguageProfileNotifySink是Windows Vista及以上系统支持的接口,兼容性良好。

内容的提问来源于stack exchange,提问作者chadb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 20:20:34