質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.77%

自作Edit Controlを作成する上での問題点

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 1,818

Weapon

score 89

前提・実現したいこと

Edit Controlで垂直中央寄せをするためteratail:WinAPI エディットコントロールのプロシージャにおいてサブクラス化やReact/Wineなど聞きましたがReactのWM_PAINTを参考にサブクラス化する方法が思いつかなかったので一からつくることにしました.
(以下のコードはサブクラス化をいじってみたのですが元のウィンドウがでてきてしまいます.)

自作する上での疑問点なのですが

  • アクティブ時の入力カーソル
  • 文字列の変換
  • 文字列の選択

です.

一つ目のアクティブ時の入力カーソルですが
文字入力時に横に出るカーソルはどのような技術・関数で作られているのでしょうか?GDIで自分で描画する必要があるのでしょうか?

二つ目の文字列の変換は
Reactのコードで見たようにIMMのAPIを用いればいいのでしょうか?

三つ目ですが
DrawTextやTextOutされた文字は選択することができません.
とはいえブラウザなど様々なUIにおいて選択できるものが(選択できないほうがいいものも含めて)たくさんあります.これはほかの関数やAPIの形で提供されているものなのでしょうか?それとも標準のEdit Controlでない限りマウスキャプチャーと背景色変更で自己実装する必要があるのでしょうか?

サブクラス化したコード

#define UNICODE

#include <Windows.h>
#include <Windowsx.h>

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK EditProc(HWND, UINT, WPARAM, LPARAM);

#define ID_BUTTON 101

const wchar_t CLASS_NAME[] = L"CLASS NAME";

HINSTANCE hGlobalInstance;
WNDPROC    getEditControl;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR nCmdLine, int nCmdShow) {
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(nCmdLine);

    hGlobalInstance = hInstance;

    WNDCLASSEX wc = {
        sizeof(WNDCLASSEX), CS_VREDRAW | CS_HREDRAW, WindowProc,
        0, 0, hInstance,
        NULL, (HCURSOR)LoadCursor(NULL,IDC_ARROW), (HBRUSH)GetStockObject(WHITE_BRUSH),
        NULL, CLASS_NAME, NULL
    };
    RegisterClassEx(&wc);

    HWND hwnd = CreateWindowEx(
        0, CLASS_NAME, L"Title", WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        CW_USEDEFAULT, CW_USEDEFAULT, 960, 540,
        NULL, NULL, hInstance, NULL
    );

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_CREATE:
    {
        HWND hedit = CreateWindowEx(
            0, L"Edit", L"Hello World", ES_CENTER | WS_CHILD | WS_VISIBLE | WS_BORDER,
            160, 160, 190, 60,
            hwnd, (HMENU)ID_BUTTON, hGlobalInstance, 0
        );

        /*
        TEXTMETRIC tm = {};
        ZeroMemory(&tm, sizeof(TEXTMETRIC));
        GetTextMetrics(GetDC(hedit), &tm);

        wchar_t mes[1024] = {};
        wsprintf(mes, L"tmHeight : %d\ntmAscent : %d\ntmDescent : %d\n", tm.tmHeight, tm.tmAscent, tm.tmDescent);
        MessageBox(hwnd, mes, L"RESULT", MB_OK);
        */

        getEditControl = (WNDPROC)GetWindowLong(hedit, GWL_WNDPROC);

        SetWindowLong(hedit, GWLP_WNDPROC, (LONG)EditProc);
    }
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK EditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_KEYDOWN:
        SendMessage(hwnd, WM_PAINT, 0, 0);
        break;

    case WM_PAINT:

        wchar_t inputstring[1024];
        ZeroMemory(inputstring, sizeof(wchar_t) * 1024);
        GetWindowText(hwnd, inputstring, 1024);

        PAINTSTRUCT eps = {};
        HDC ehdc = BeginPaint(hwnd, &eps);
        TextOut(ehdc, 30, 30, inputstring, lstrlen(inputstring));
        EndPaint(hwnd, &eps);

        break;
    }

    return CallWindowProc(getEditControl, hwnd, uMsg, wParam, lParam);
}

補足情報

Windows10 Pro
VisualStudio2017 Community

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

Edit Control の自作は 10 年以上前にゲームを作成していた時は頻繁にやってましたね。
ゲーム画面に表示されるチャットの入力欄なんてのは完全にレンダリングで作成した Edit Control になり、ゲームの操作と両立させるために標準のコトンロールは使いづらかったです。と言うか標準コントロールのグラフィックスがゲーム画面とマッチしないため、そのまま使用するという選択の余地はありませんでしたが。
当時は DirectX のサンプルにそのまんま Edit Control があった気がします。IME 制御のサンプルとしても優秀でした。

一つ目のアクティブ時の入力カーソルですが

通常のウィンドウの場合、Caret 操作系の API を使用します。
CreateCaret でキャレットを作成し、DestroyCaret で作成したキャレットを破棄します。

WM_SETFOCUS 時に ShowCaret でキャレットを表示し、WM_KILLFOCUS 時に HideCaret でキャレットを非表示にします。

文字の入力や削除時に適宜 SetCaretPos  でキャレット位置を更新します。

なお、DirectX や OpenGL で描画しているサーフィスで入力欄を実現しようとすると上記の API では実現できず、その場合はキャレット相当の画像を自身で描画することになります。OpenGL はわかりませんが、DirectX の方はサンプルと言うか UI 部品としてソース提供されていたと思います。

二つ目の文字列の変換は

必要に応じて IMM のメソッドを呼び出すことになりますが、通例 IME の API を直接呼び出す必要はありません。UNICODE でウィンドウを作成している場合、WM_CHAR で IME 確定後の文字(列)が通知されるので、WM_CHAR を処理していれば問題ありません。WM_SETFOCUS でフォーカスを受け取るように実装していれば、自動的に IME が有効になるかと思います。 ReactOS のソースは通常のテキストボックスと同様に変換中の文字列を入力位置に表示するためのものです。IME 自身が持つ変換ウィンドウを使用する場合、特にこれらの処理を実装する必要はありません。

三つ目ですが

これは API 等で提供されるものではありません。ReactOS がそうなっているように選択中の文字列を選択中であることを示すために描画する必要があります。ゲームの場合、手抜きしてマウス選択は受け付けないとかはありかと思いますが、通常のコントロールではマウスでの選択とキーボード操作の両方を処理するため、けっこうめんどくさい処理になります。なお、標準の Edit Control ですらダブルクリックで単語選択等を実装しているので、動作を近づけようとすると ReactOS の内容のままになると思います。


(2019/03/30 追記)

軽く書いてみたのですがWM_SETFOCUSはウィンドウをクリックしたら飛ぶものではないのですか?WM_LBUTTONDOWNでSetFocusしたら上手くいきましたが
アクティブとフォーカスは別ということですか?

Windows においてはアクティブ(ウィンドウ)とフォーカス(が設定されたウィンドウ)は意味が異なります。アクティブウィンドウはトップレベルウィンドウのうち選択されているものを指します。フォーカスはすべてのウィンドウのうちキーボードメッセージを受け取る対象のウィンドウのことを指します。

結論から言うと ReactOS のソースがそうなっているように子ウィンドウでは WM_LBUTTONDOWN 等のマウスメッセージで SetFocus を呼び出す必要があります。WM_LBUTTONDOWN 以外では WM_MOUSEACTIVATE で SetFocus することも可能ですが、これはあまりお勧めしません。

以下余談ですが、トップレベルウィンドウでは WM_MOUSEACTIVATE を DefWindowProc に渡すと自動的にフォーカスが設定されるため、子ウィンドウの処理とは異なるアプローチが可能です。逆に子ウィンドウ側で WM_MOUSEACTIVATE を処理するようにするとトップレベルウィンドウがアクティブ化する時の挙動がおかしくなります。ReactOS のソースでは Edit Control をトップレベルウィンドウにした場合、WM_MOUSEACTIVATE でフォーカスが設定されるため、フォーカスが当たっていない場合のみ SetFocus を呼び出すようになっているものと推測されます。

なお、以下のマイクロソフトのページの下の方に Edit Control で処理するメッセージについて記載がありますが、どのタイミングで SetFocus を呼び出しているかについては言及されていません。が、WM_MOUSEACTIVATE を処理していない以上、WM_LBUTTONDOWN で処理していると推測するしかない感じですね。
https://docs.microsoft.com/en-us/windows/desktop/controls/about-edit-controls

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/29 12:46

    軽く書いてみたのですがWM_SETFOCUSはウィンドウをクリックしたら飛ぶものではないのですか?WM_LBUTTONDOWNでSetFocusしたら上手くいきましたが
    アクティブとフォーカスは別ということですか?

    キャンセル

  • 2019/03/30 16:24

    追記しました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.77%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る