いつもお世話になっています。
現在エディットボックスに制限文字数を指定し制限を超える文字を入力した場合に
制限文字数以上の文字を次のエディットボックスに移動させるという処理を
作成しているのですが良い解決策が見つかりません。
制限が5文字と指定した場合
Edit1「123」 ←「4567」を入力
Edit2「890」
↓
Edit1「12345」
Edit2「67890」
と、なって欲しいのです。
エディットボックスの新しいクラスを作り制限文字数と移動場所を指定し
class CMoveEdit : public CEdit
int m_iMaxLen; // 制限文字数
CWnd *m_pNext; // 移動場所
OnChangeで制限文字数以上の切取とフォーカス移動までは作ってみたのですが
void CMoveEdit::OnChange()
{
CString w;
GetWindowText(w);
if(w.GetLength() >= m_iMaxLen && m_pNext != NULL)
{
m_pNext->SetFocus();
}
if(w.GetLength() > m_iMaxLen)
{
// 半角対応は後回し
SetWindowText(w.Left(m_iMaxLen));
SendMessage(WM_KEYDOWN, VK_END);
}
}
変換確定時に1文字ずつOnChangeが呼ばれているのでどうしようかと思っている
状態です。
開発環境は WinXP(SP2) VC++.NET MFC です。
なにか良い解決策があればよろしくお願いします。
WM_CHARをSendMessageする。
if (this->m_pNext)
{
if (CWnd::GetFocus()->GetSafeHwnd() == this->GetSafeHwnd())
{
this->m_pNext->SetFocus();
this->m_pNext->SendMessage(EM_SETSEL, 0, 0);
}
this->m_pNext->SendMessage(WM_CHAR, (WPARAM)nChar,
MAKELPARAM(nRepCnt, nFlags));
}
OnPasteのほうは別途違う処理にしないといけないでしょう。
それと全角文字が絡むと結構大変かも。((サロゲードペアなしの)UNICODEなら楽でしょ
うけど)
>WM_CHARをSendMessageする。
だと途中に挿入するケースがうまくいかないですね。。。
(はみ出る文字を特定するのが面倒そう。UNICODEビルドならいいのだけど。)
そもそもBlueさんが書かれているように
Edit1の途中に文字を入力しているケースを
考えると無条件にフォーカスを移動したら駄目だと思いますけれど。
文字を入力した結果、カーソルに位置が制限文字数の後ろに
行ってしまった時だけ移動する必要があるように思います。
極端な話、Edit1の先頭にカーソルを持ってきた時に
どんな動きになるか考えないといけないような気がしますね。
一応いろいろ試してみた。
void CMoveEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出し
ます。
// 文字数制限がありコントロールコード以外のとき対象
if (m_nLimit && !_istcntrl(nChar))
{
// 文字数
int nLength = ::GetWindowTextLengthW(m_hWnd);
// 選択中文字数
int nStart, nEnd, nSelectLength;
this->GetSel(nStart, nEnd);
nSelectLength = nEnd - nStart;
// 入力可能な場合
if (m_nLimit - nLength + nSelectLength <= 0)
{
// 挿入位置でない
if (nStart != m_nLimit)
{
// はみ出る文字を取得
LPWSTR lpszText = new WCHAR[m_nLimit + 1];
::GetWindowTextW(m_hWnd, lpszText, m_nLimit + 1);
UINT nOverChar = lpszText[m_nLimit - 1];
// はみ出る文字を削除
lpszText[m_nLimit - 1] = L'\0';
::SetWindowTextW(m_hWnd, lpszText);
delete lpszText;
// 文字位置を戻す
this->SetSel(nStart, nStart);
#ifdef UNICODE
// はみ出る文字をNextに
this->NextOnChar(nOverChar, 1, 0);
#else
// ASCIIコード変換
WCHAR szTemp[2] = {nOverChar};
CHAR szMBChar[3];
::WideCharToMultiByte(CP_ACP, 0, szTemp, -1, szMBChar, 3,
NULL, NULL);
// はみ出る文字をNextに
this->NextOnChar((UCHAR)szMBChar[0], 1, 0);
if (szMBChar[1])
this->NextOnChar((UCHAR)szMBChar[1], 1, 0);
#endif
}
else
{
// 現在入力文字をNextに
this->NextOnChar(nChar, 0, 0);
return;
}
}
}
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
void CMoveEdit::NextOnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (this->m_pNext)
{
if (CWnd::GetFocus()->GetSafeHwnd() == this->GetSafeHwnd())
{
this->m_pNext->SetFocus();
this->m_pNext->SendMessage(EM_SETSEL, 0, 0);
}
this->m_pNext->SendMessage(WM_CHAR, (WPARAM)nChar,
MAKELPARAM(nRepCnt, nFlags));
}
}
ただし、ロードしているcomctl32.dllがバージョン6以上でないと
SetSel(GetSel、EM_SETSELもかな?)が文字数(の位置)を返さないようです。
(manifestファイル等で、ビジュアルスタイルを有効にしないと使えない)
本来ならcomctl32.dllのバージョンで切り分ける処理が必要そうですが面倒なので
手を抜いてあります。
参考
エディットボックスの入力バイト数を指定するには?
http://hpcgi1.nifty.com/MADIA/Vcbbs/wwwlng.cgi?print+200602/06020029.txt
# は私のコードですけどねw
エディットの文字数制限
http://www.vcppclub.com/bbs01/wforum.cgi?mode=allread&no=3737&page=0
訂正
> // 入力可能な場合
// 入力不可能な場合
でした。
Blueさん、PATIOさん、ありがとうございます。
>UNICODEビルドならいいのだけど
残念ながら違います・・・。
自分の方では入力時では何もせずOnTimerで、はみ出した文字を移動させるという
ちょっとムリヤリにやってみたのですがPATIOさんのフォーカス位置の指摘を見て
またもや暗礁に。
(言われるまで気が付かなかったです・・・。)
Blueさんのソースも試してみたのですがやはりフォーカス位置が・・・。
(正攻法でやるにはここまで必要な事に少しビックリしました。)
やはり簡単そうに見えて難しいですね・・・。
>フォーカス位置が
とは?
(キャレット位置ではないですよ?)
具体的にどうなってほしいのでしょうか?
(最初の動きはできていると思っているけど)
いっそ複数に分けないで一つのエディットボックスにして、描画時に複数個に見せ
かけるというのはアウトでしょうか。
>comctl32.dllのバージョンで切り分ける処理
を加えようとしたところ、マルチバイト文字の場合、先行コードしか
WM_CHARに飛んでこないのか挿入パターンのときに先にNextにWM_CHARを投げている
せいかうまいこといきませんでした。
↓
先にCEdit::OnCharを呼べば問題ないようです。
また
>UINT nOverChar = lpszText[m_nLimit - 1];
はWCHARでキャストしないとだめそう。
>#else
(snip)
>#endif
はいちいち文字コード変換しなくても、SendMessageWでUnicodeを送ることができるよう
です。
で、いろいろやっているんですが、comctl32.dllのバージョンが6未満の対応がちゃん
とできていないのか、3つ以上テキストボックスがあるときに、1つめも2つめも文字
がオーバーするときに複数文字入力すると、2つが、常に先頭に挿入してしまうような
動きになってしまっています。
[12345]
[abced]
[ ]
のとき1番目に[ABC]といれると
[12345]
[CBAab]
[dec ]
となる。
(3番目はABCが挿入後に処理しているわけでないのでどうしても逆になってしまう。)
一応そのときのコードです。
# と載せようとしたら文字数オーバー
(前半)
◎MoveEdit.h
public:
int m_nLimit;
CWnd* m_pNext;
protected:
virtual void PreSubclassWindow();
private:
DWORD m_dwMajor, m_dwMinor;
void GetSelEx(int& nStartChar, int& nEndChar) const;
void SetSelEx(int nStartChar, int nEndChar, BOOL bNoScroll = FALSE);
UINT GetOverChar();
void NextOnChar(UINT nChar, UINT nRepCnt, UINT nFlags, BOOL
bWideCharCode = FALSE);
◎MoveEdit.cpp
// comctl32.dllのバージョンを取得する
void WINAPI GetComCtlVersion(DWORD* pdwMajor, DWORD* pdwMinor)
{
HMODULE hDLL;
// デフォルトは4.0
*pdwMajor = 4;
*pdwMinor = 0;
hDLL = ::LoadLibrary(TEXT(comctl32.dll));
if (hDLL)
{
DLLGETVERSIONPROC pDllGetVersion =
static_cast<DLLGETVERSIONPROC>(static_cast<void*>(::GetProcAddress
(hDLL, DllGetVersion)));
if (pDllGetVersion)
{
DLLVERSIONINFO dvi = {0};
dvi.cbSize = sizeof(dvi);
HRESULT hr = (*pDllGetVersion)(&dvi);
if (SUCCEEDED(hr))
{
*pdwMajor = dvi.dwMajorVersion;
*pdwMinor = dvi.dwMinorVersion;
}
}
::FreeLibrary(hDLL);
}
}
void CMoveEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// 文字数制限がありコントロールコード以外のとき対象
if (this->m_nLimit && !_istcntrl(nChar))
{
// 文字数
int nLength = ::GetWindowTextLengthW(this->GetSafeHwnd());
// 選択中文字数
int nStart, nEnd, nSelectLength;
this->GetSelEx(nStart, nEnd);
nSelectLength = nEnd - nStart;
// 入力不可能の場合
if (this->m_nLimit - nLength + nSelectLength <= 0)
{
// 挿入位置でない
if (nStart != this->m_nLimit)
{
// はみ出る文字(Unicode)を取得
UINT nOverChar = this->GetOverChar();
// 文字位置を戻す
this->SetSelEx(nStart, nEnd);
// 現在入力文字の処理
CEdit::OnChar(nChar, nRepCnt, nFlags);
// はみ出た文字(Unicode)をNextに
this->NextOnChar(nOverChar, 1, 0, TRUE);
}
else
// 現在入力文字をNextに
this->NextOnChar(nChar, 0, 0);
return;
}
}
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
void CMoveEdit::NextOnChar(UINT nChar, UINT nRepCnt, UINT nFlags, BOOL
bWideCharCode)
{
if (this->m_pNext)
{
if (CWnd::GetFocus()->GetSafeHwnd() == this->GetSafeHwnd())
{
this->m_pNext->SetFocus();
this->m_pNext->SendMessage(EM_SETSEL, 0, 0);
}
if (bWideCharCode)
::SendMessageW(this->m_pNext->GetSafeHwnd(), WM_CHAR,
static_cast<WPARAM>(nChar),MAKELPARAM(nRepCnt, nFlags));
else
this->m_pNext->SendMessage(WM_CHAR, static_cast<WPARAM>(nChar),
MAKELPARAM(nRepCnt, nFlags));
}
}
(後半)
void CMoveEdit::GetSelEx(int& nStartChar, int& nEndChar) const
{
int nSelectLength;
this->GetSel(nStartChar, nEndChar);
nSelectLength = nEndChar - nStartChar;
if (this->m_dwMajor < 6)
{
// バイト位置から文字位置に変換
UCHAR* lpszText = new UCHAR[this->m_nLimit * 2 + 1];
::GetWindowTextA(this->GetSafeHwnd(),
static_cast<LPSTR>(static_cast<void*>(lpszText)),
this->m_nLimit * 2 + 1);
if (nSelectLength)
{
lpszText[nEndChar] = '\0';
nSelectLength = _mbslen(&lpszText[nStartChar]);
}
lpszText[nStartChar] = '\0';
nStartChar = _mbslen(lpszText);
nEndChar = nStartChar + nSelectLength;
delete lpszText;
}
}
void CMoveEdit::PreSubclassWindow()
{
GetComCtlVersion(&this->m_dwMajor, &this->m_dwMinor);
CEdit::PreSubclassWindow();
}
void CMoveEdit::SetSelEx(int nStartChar, int nEndChar, BOOL bNoScroll)
{
if (this->m_dwMajor < 6)
{
// 文字位置からバイト位置に変換
UCHAR* lpszText = new UCHAR[this->m_nLimit * 2 + 1];
::GetWindowTextA(this->GetSafeHwnd(),
static_cast<LPSTR>(static_cast<void*>(lpszText)),
this->m_nLimit * 2 + 1);
UCHAR* p = lpszText;
nStartChar = lpszText - _mbsninc(p, nStartChar);
p = lpszText;
nEndChar = lpszText - _mbsninc(p, nEndChar);
delete lpszText;
}
this->SetSel(nStartChar, nEndChar, bNoScroll);
}
UINT CMoveEdit::GetOverChar()
{
// はみ出る文字を取得
LPWSTR lpszText = new WCHAR[this->m_nLimit + 1];
::GetWindowTextW(this->GetSafeHwnd(), lpszText, this->m_nLimit + 1);
UINT nOverChar = static_cast<WCHAR>(lpszText[this->m_nLimit - 1]);
// はみ出る文字を削除
lpszText[m_nLimit - 1] = L'\0';
::SetWindowTextW(this->GetSafeHwnd(), lpszText);
delete lpszText;
return nOverChar;
}