Entry

プログラミングメモ - 補完機能付きのエディットボックス

2009年04月23日

久々の MFC ネタ。

この頃のウェブサービスを見ていると,入力するときに内容を補完してくれるエディットボックスがあったりします。ウェブサービスを見なくても,ブラウザの URL を入力するトコなんかを見ると,補完してくれる。こんな感じで。

自動補完テストの図

一方,VC++ の MFC では,デフォルトでこういう部品はそろっていません。つことで,ちと作ってみました。最近の補完機能は,下に小さな窓を出して候補を示すのが主流なんでしょうけど,いきなりそれは難しいので,まずは入力した部分より先の文字列を選択状態にして補完するタイプのモノを作ってみた。

ソースと実行ファイルはこちら。

VC6 で作ってます。興味のある方は,適当に触ってみてください。

少し解説しておきます。まずヘッダ。

#if !defined(AFX_SUGGESTEDIT_H__916FD3A6_6CAA_4B3F_AAA1_6DA3982D5ABC__INCLUDED_)
#define AFX_SUGGESTEDIT_H__916FD3A6_6CAA_4B3F_AAA1_6DA3982D5ABC__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif

class CString;
class CStringArray;

class CSuggestEdit : public CEdit {
public:
    CSuggestEdit();
public:
    //{{AFX_VIRTUAL(CSuggestEdit)
    //}}AFX_VIRTUAL
public:
    virtual ~CSuggestEdit();
protected:
    //{{AFX_MSG(CSuggestEdit)
    afx_msg void OnUpdate();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
protected:
    CString m_strText;
    CStringArray m_aKey;
};

//{{AFX_INSERT_LOCATION}}

#endif // !defined(AFX_SUGGESTEDIT_H__916FD3A6_6CAA_4B3F_AAA1_6DA3982D5ABC__INCLUDED_)

こゆ機能は自前で作らなくちゃいけないので,普通のエディットボックスクラスである CEdit クラスを継承したクラスを作って,ここに機能を実装していくことになります。まず,メンバに CString 型の m_strText と,CStringArray 型の m_aKey を持たせておく。前者は,ユーザが入力した文字列を取っておくためのメンバ変数で,後者は,候補となる文字列を収めておくための配列です。

protected な m_aKey にはアクセサを付けとかないと,外のクラスから候補を登録できないんだけれども,ここではテストなので内部のコンストラクタで候補を直接登録しています。アクセサは別途設けてください。

また,今回はエディットボックスが変更されるたびに補完候補を検索して,入力文字列の内容を変化させています。これは,CEdit クラスに投げられるメッセージを受け取って OnUpdate() を呼び出すことにしました。

つことで,続きまして実装ファイル。

#include "stdafx.h"
#include "suggest.h"

#include "SuggestEdit.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CSuggestEdit::CSuggestEdit() {
    m_strText = _T("");
    // sample
    m_aKey.Add(_T("foo"));
    m_aKey.Add(_T("bar"));
    m_aKey.Add(_T("baz"));
    m_aKey.Add(_T("bazbazbaz"));
}

CSuggestEdit::~CSuggestEdit() {
}

BEGIN_MESSAGE_MAP(CSuggestEdit, CEdit)
    //{{AFX_MSG_MAP(CSuggestEdit)
    ON_CONTROL_REFLECT(EN_UPDATE, OnUpdate)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void
CSuggestEdit::OnUpdate() {
    CString strText;
    GetWindowText(strText);

    if (m_strText.GetLength() < strText.GetLength()) {
        for (int i = 0; i < m_aKey.GetSize(); i++) {
            CString& strKey = m_aKey.GetAt(i);
            int nKey = strKey.GetLength();
            int nText = strText.GetLength();
            if (nText < nKey) {
                CString& strKeyLeft = strKey.Left(nText);
                if (strKeyLeft == strText) {
                    SetWindowText(strKey);
                    SetSel(nText, nKey, TRUE);
                    break;
                }
            }
        }
    }
    m_strText = strText;
}

コンストラクタで候補を登録している箇所は,本来,アクセサを通じて登録するところです。例えば,DB から読み出した文字列を登録する,みたいな具合。

あとは,OnUpdate() の実装です。

これは中身を見てもらえば分かると思うんですけれど,エディットボックスから文字列をもらって,長さが長くなってたら,候補の配列メンバとちまちまとつき合わせてます。長くなった(追記した)ときだけ補完するのは,長さが短くなっても(削除しても)補完してたら,補完と削除の間でぐるぐる回っちゃうからです。つことで,削除したときは補完しない。見つかったら,エディットボックスの中身を整えて文字列を出力します。線形探索ってとこがアレなんですけど,まぁね,こゆのはね。最後に,入力した文字列をメンバ変数に保存して次の入力に備えます。

おそらく,こゆ UI を欲しいと思っている VC ユーザは,割といるんじゃないかと思います。んなもんで,多分,同じようなもんを作っている人はたくさんいるはず。ここは,あたしのそりゅーしょんってことで。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN