Windows CE 環境における输入法の列挙と切り替え実装

Windows CE 環境において、独自のユーザーインターフェースを採用しているアプリケーションでは、システム標準の输入法切り替え機能を利用できないケースが多々あります。システムトレイや標準の入力バーを使用しない場合、開発者が自ら输入法のリストを取得し、UI 上で選択可能にする必要があります。

本稿では、レジストリ情報を参照して利用可能な输入法を列挙し、プログラムから活性化する方法について解説します。Windows CE 5.0 環境を想定していますが、基本的なレジストリ構造は共通しています。

実装の仕組み

输入法の情報は Windows レジストリに保存されています。主に以下の 2 つのキーを参照します。

  • HKCU\Keyboard Layout\Preload: ユーザーごとに定義された输入法のロード順序と ID が保存されています。
  • HKLM\System\CurrentControlSet\Control\Layouts: 各输入法 ID に対応する表示名称(Layout Text)が保存されています。

これらの情報を組み合わせることで、ID と表示名のペアリストを構築できます。選択時には LoadKeyboardLayout および ActivateKeyboardLayout API を使用します。

クラス設計

以下のヘッダーファイルでは、输入法の情報を管理するクラス CImeManager を定義しています。メモリ管理を容易にするために std::vector を採用し、レジストリパスの区切り文字も修正しています。

/**************************************************************************** 
 * File: ImeManager.h
 * Description: Windows CE Input Method Enumeration and Activation
 * Version: 1.0
 *****************************************************************************/  
#ifndef __IME_MANAGER_H__
#define __IME_MANAGER_H__

#include <vector>
#include <string>
#include <windows.h>
#include <winreg.h>

struct ImeEntry {
    std::wstring layoutId;
    std::wstring displayName;
};

class CImeManager {
private:
    std::vector<ImeEntry> m_imeList;

    bool FetchRegistryData() {
        HKEY hKeyPreload = NULL;
        HKEY hKeyLayouts = NULL;
        const wchar_t* szPreloadPath = L"Keyboard Layout\\Preload";
        const wchar_t* szLayoutsBase = L"System\\CurrentControlSet\\Control\\Layouts\\";
        
        // Open Preload key to get IDs
        if (RegOpenKeyEx(HKEY_CURRENT_USER, szPreloadPath, 0, 0, &hKeyPreload) != ERROR_SUCCESS) {
            return false;
        }

        DWORD index = 1;
        wchar_t szValueName[16];
        wchar_t szLayoutId[16];
        DWORD dwType = REG_SZ;
        DWORD dwDataSize = sizeof(szLayoutId);

        while (true) {
            _itow(index, szValueName, 10);
            dwDataSize = sizeof(szLayoutId);
            
            if (RegQueryValueEx(hKeyPreload, szValueName, NULL, &dwType, (LPBYTE)szLayoutId, &dwDataSize) != ERROR_SUCCESS) {
                break;
            }

            // Open Layouts key to get Name
            std::wstring wsLayoutPath = szLayoutsBase;
            wsLayoutPath += szLayoutId;

            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, wsLayoutPath.c_str(), 0, 0, &hKeyLayouts) == ERROR_SUCCESS) {
                wchar_t szName[128];
                dwDataSize = sizeof(szName);
                if (RegQueryValueEx(hKeyLayouts, L"Layout Text", NULL, &dwType, (LPBYTE)szName, &dwDataSize) == ERROR_SUCCESS) {
                    ImeEntry entry;
                    entry.layoutId = szLayoutId;
                    entry.displayName = szName;
                    m_imeList.push_back(entry);
                }
                RegCloseKey(hKeyLayouts);
            }
            index++;
        }
        RegCloseKey(hKeyPreload);
        return !m_imeList.empty();
    }

public:
    CImeManager() {
        FetchRegistryData();
    }

    ~CImeManager() {
        m_imeList.clear();
    }

    bool ActivateIme(size_t index) {
        if (index >= m_imeList.size()) {
            return false;
        }

        HKL hKl = LoadKeyboardLayout(m_imeList[index].layoutId.c_str(), KLF_ACTIVATE);
        if (hKl) {
            ActivateKeyboardLayout(hKl, KLF_SETFORPROCESS);
            return true;
        }
        return false;
    }

    const std::vector<ImeEntry>& GetImeList() const {
        return m_imeList;
    }
};
#endif

利用例

アプリケーションのエントリーポイントにおいて、上記クラスをインスタンス化し、取得したリストを処理するサンプル代码如下です。リストを巡回し、デバッグ出力を行い、特定の输入法を活性化します。

// MainApp.cpp
#include "stdafx.h"
#include "ImeManager.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
    CImeManager imeMgr;
    const std::vector<ImeEntry>& list = imeMgr.GetImeList();

    for (size_t i = 0; i < list.size(); ++i) {
        // UI 描画処理などはここに記述
        // Example: DrawText(hdc, list[i].displayName.c_str(), ...);
        
        // デバッグ出力
        RETAILMSG(1, (TEXT("IME [%d]: %s\r\n"), i, list[i].displayName.c_str()));
    }

    // 例:リストの最初の输入法を活性化
    if (!list.empty()) {
        imeMgr.ActivateIme(0);
    }

    return 0;
}

レジストリキーのパス指定にはバックスラッシュを使用し、メモリ管理はコンテナクラスに委ねることで、リークのない実装となっています。

タグ: WindowsCE InputMethod Registry WinAPI C++

6月19日 22:31 投稿