エディットボックスの表示がうまくいかない

[戻る]
まに 2008/12/06(土) 19:16:41
お世話になります。
VC++2005で、ダイアログ形式のソフトを作成しています。
ダイアログ全体に画像が表示され、その画像と重なるようにして、
エディットボックスが表示されたり、消えたりする。
そんなソフトを作成しています。
そこで、現在困っていることが、2つあります。


●その1
エディットボックスに全角文字を入力すると、
入力した文字以外、エディットボックスが消えてしまう。

「消えてしまう」というより、描画した画像で隠されてしまうのだと思います。
入力中の文字だけが、画面に表示されるような状態で、
変換確定した文字や、エディットボックスの周囲も、隠されてしまいます。

OnPaintでエディットボックスの再描画を行えば解決するかと思いますが、
再描画の方法が分かりません。
以下、OnPaintの抜粋です。

void CTest1Dlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // 描画のデバイス コンテキスト

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>
                                        (dc.GetSafeHdc()), 0);

        // クライアントの四角形領域内の中央
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // アイコンの描画
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialog::OnPaint();
        CClientDC myDC(this);
        ViewPaint(&myDC);        // ダイアログ全体へ画像を表示

        // ここでエディットボックスを再描画すればいいはず?
    }
}



●その2
エディットボックスをいったん非表示にすると、キャレットが表示されなくなる。

エディットボックスの表示/非表示を、
「ShowWindow(SW_SHOW)」、
「ShowWindow(SW_HIDE)」
を使って切り替えているのですが、
一度非表示にして、再度表示させると、以降、
エディットボックスのキャレットが表示されなくなってしまいます。



上記2点の解決方法をご存知でしたら、
ご教授いただけましたら幸いです。

ISLe 2008/12/06(土) 22:46:10
●その1
OnEraseBkgnd
で画像を描画してやればいいかもしれない。

金魚ちゃん 2008/12/07(日) 16:38:35
>一度非表示にして、再度表示させると、以降、
>エディットボックスのキャレットが表示されなくなってしまいます。
キーボードのフォーカスが外れるからじゃないの。
だから SW_SHOW したときに、エディットボックスにフォーカスを設定。

まに 2008/12/07(日) 21:05:45
追加情報です。
その1、その2ともに、いったん別のソフトのウィンドウで隠してから、
再度表示させると、ちゃんとした表示になります。


> OnEraseBkgndで画像を描画してやればいいかもしれない。
以下のような使い方で会ってますでしょうか…?
やってみましたが、うまくいきませんでした。
OnPaintやViewPaintとの順番も、いろいろと変えてみたのですが…

void CTest1Dlg::OnPaint()
{
    if (IsIconic())
    {
        …中略…
    }
    else
    {
        CDialog::OnPaint();
        CClientDC myDC(this);
        ViewPaint(&myDC);        // ダイアログ全体へ画像を表示
        OnEraseBkgnd(&myDC);

        // ここでエディットボックスを再描画すればいいはず?
    }
}


> キーボードのフォーカスが外れるからじゃないの。
フォーカスの設定は、もちろんしています。
それでもキャレットが表示されませんでした。



「その2」ですが、一応、解決しました。

ダイアログには、エディットボックス以外のコントロールを
一つも配置してなかったのですが、
ダミーのボタンコントロールを追加したら、
「その2」は再現されなくなりました。

推測ですが、エディットボックスしかコントロールがないため、
そのエディットボックスを非表示にしたことで、
フォーカスのやり場が失われてしまい、
そのために再現していたのでは…と考えています。

昨日、書き込みした時点では気付かなかったのですが、
エディットボックスを再表示させた後、
「エディットボックス内の文字列が勝手に選択状態になる」
という現象も起こってたようです。

また、ダミーのボタンコントロールも、TabStopプロパティやVisibleプロパティを
FALSEにすると、再現するようになります。
その場合は、コード上でフォーカスをセットすることで、再現されなくなりました。
(以下のソースの@の部分)

BOOL  CTestDlg::PreTranslateMessage(MSG* pMsg)
{
    if (pMsg->message == WM_KEYDOWN) {

        CEdit*        myEdit1    = (CEdit*)GetDlgItem(IDC_EDIT1);
        CButton*      myButton1  = (CButton*)GetDlgItem(IDC_BUTTON3);
        static  bool  VisibleFlg = TRUE;
        short         c          = (short)pMsg->wParam;

        // F2キーでエディットボックスの表示/非表示を切り替え
        if (c == VK_F2) {
            VisibleFlg = !VisibleFlg;
            if (!VisibleFlg) {
                myButton1->SetFocus();         // ←@
                myEdit1->ShowWindow(SW_HIDE);
            }
            else {
                myEdit1->ShowWindow(SW_SHOW);
                myEdit1->SetFocus();
            }
        }

    }

    return CDialog::PreTranslateMessage(pMsg);
}

ただ、「一応、解決した」と書いたのは、
できればダミーのボタンコントロールを追加しないで解決させたいからです。
(そんなふうに考えるのは自分だけでしょうか…?)

まに 2008/12/07(日) 23:25:54
「その2」に関する質問です。
ダイアログ上に、コントロールが一つもない状態の時って、
フォーカスはどこに設定されているのでしょうか?

「a) どこにもフォーカスされてない」のか、
「b) ダイアログ自体が、フォーカスを持っている」かの、
どちらかだと思いますが………

ちなみに、PreTranslateMessage関数内で、
this->GetFocus()で取得したアドレスを調べたところ、
NULLではありませんでした。

フォーカスを、その状態(aまたはbの状態)にすれば、
ダミーのボタンコントロールを配置しなくて済むと思っているのですが、
そのようなことって、出来ないものでしょうか?

ISLe 2008/12/08(月) 00:56:32
OnEraseBkgndを呼び出すんじゃなくて、void CTest1Dlg::OnEraseBkgnd()をオーバーラ
イドしてその中で画像を描画したらどうかということです。
OnEraseBkgndというのはウィンドウメッセージWM_ERACEBKGNDのメッセージハンドラのこ
とです。
#ダイアログだと呼ばれなかったっけ…?

エディットボックスにフォーカスが移動したときテキストが全選択状態になるのはデフ
ォルトの動作なのでこれもコントロールをオーバーライドしてフォーカスを取得したと
きSetSelメソッドを使うとかEM_SETSELメッセージを投げるとかして選択状態を解除すれ
ばキャレットが表示されるのではないでしょうか。

ISLe 2008/12/08(月) 00:58:03
間違えた
WM_ERASEBKGND
です。

まに 2008/12/08(月) 12:25:48
お世話になります。

> OnEraseBkgndを呼び出すんじゃなくて、void CTest1Dlg::OnEraseBkgnd()を
> オーバーライドしてその中で画像を描画したらどうかということです。

すみません、あまり詳しくないもので…
↓こんな感じってことですね?

BOOL CTest1Dlg::OnEraseBkgnd(CDC* pDC)
{
    CClientDC myDC(this);
    myDC.BitBlt(0,0,500,500,&DcView,0,0,SRCCOPY);
    return CDialog::OnEraseBkgnd(pDC);
}

OnPaintの中で描画した時と、微妙に
挙動(入力済みの文字が消えるタイミング)が変化しましたが、
現象は改善されませんでした。
また、プログラム起動時に画像が描画されなくなりました。

なお、エディットボックスの周囲だけでなく、
「他のコントロールも、描画した画像に隠されてしまう」
ということが新たに判明しました。

VC++2005に、VBのRefreshメソッドみたいなのがあれば
いいんだけどな〜って、思っているのですが………
ありませんか………?



> コントロールをオーバーライドしてフォーカスを取得したと
> きSetSelメソッドを使うとかEM_SETSELメッセージを投げるとかして
> 選択状態を解除すればキャレットが表示されるのではないでしょうか。

以下のように、いろんなタイミングでSetSelを実行してみました。
「エディットボックス内の文字列が勝手に選択状態になる」
という現象は回避できるようになりましたが、
「キャレットが表示されなくなる」という現象は、改善できませんでした。
「EM_SETSELメッセージを投げる」については、詳しくないのでまだ試していません。
必要でしたら、調査して試してみるつもりでいます。


BOOL CTest1Dlg::PreTranslateMessage(MSG* pMsg)
{
    if (pMsg->message == WM_KEYDOWN) {
        static  bool    VisibleFlg = TRUE;
        short           c = (short)pMsg->wParam;

        if (c == VK_F2) {
            VisibleFlg = !VisibleFlg;
            if (!VisibleFlg) {
                m_Edit1.SetSel(0,0);
                m_Edit1.ShowWindow(SW_HIDE);
            }
            else {
                m_Edit1.ShowWindow(SW_SHOW);
                m_Edit1.SetSel(0,0);
                m_Edit1.SetFocus();
            }
        }

    }
    return CDialog::PreTranslateMessage(pMsg);
}

void CTest1Dlg::OnEnSetfocusEdit1()
{
    m_Edit1.SetSel(0,0);
}


エディットボックスコントロールのDisableを、
プログラム上で切り替えできれば改善されるかも…?
と考えているのですが、そんなことって、できないものでしょうか…?

以上、報告でした。

ISLe 2008/12/09(火) 16:05:41
> VC++2005に、VBのRefreshメソッドみたいなのがあれば

UpdateWindowというのがそのまんまですが、メッセージハンドラの実装ができていない
現状で期待する結果は得られないでしょう。


根本的にウィンドウメッセージやデバイスコンテキストについての理解が足りていない
と思います。
ClientDC myDC(this);
ではなくハンドラの引数として与えられたデバイスコンテキストを使ってください。
最初の投稿を見直したらOnPaintでBeginPaint/EndPaintを使ってなかったですね。


エディットコントロールはオリジナルの挙動を変えたいならCEditを継承して独自のエデ
ィットコントロールにカスタマイズしたほうが良いと思います。
外からメソッドを呼び出して戻ったとき状態が変化しているとは限らないです。
特にフォーカスに関してはややこしいことが多いですから。

まに 2008/12/09(火) 23:48:28
ISLeさん、ありがとうございます。

「その1」ですが、解決しました。
解決前と解決後のコードを、以下に記載します。

void CTest1Dlg::OnPaint()
{
    if (IsIconic())
    {
        …中略…
    }
    else
    {
        // 解決前
        // CDialog::OnPaint();
        // CClientDC myDC(this);
        // ViewPaint(&myDC);        // ダイアログ全体へ画像を表示

        // 解決後
        CPaintDC myDC(this);
        main.ViewPaint(&myDC);        // ダイアログ全体へ画像を表示
        CDialog::OnPaint();
    }
}

「CClientDC」を、「CPaintDC」へ変更しました。
また、「CDialog::OnPaint();」を呼ぶ順番も、変更しました。
そうしないと、エディットボックスの横幅を超えて全角文字を入力したために
入力文字がエディットボックスの左下にはみ出て表示された時、
はみ出た部分に、画像が描画されなくなる現象が発生するようです。

> 最初の投稿を見直したらOnPaintでBeginPaint/EndPaintを使ってなかったですね。
BeginPaint/EndPaintは、手持ちの参考書にも載ってなかったのですが、
使わないとマズイのでしょうか…?
すみませんが、そこだけでもご教授ください。<(_ _)>

他のスレで、勉強方法についてデカい口を叩いてはおりますが、
「自分自身、きちんと理解しているのか?」と問われると、
ご指摘のように、自信のない部分があります。
現状、それでもそれなりのものは作れてる自負はあるので、
「まっいっか」で通しちゃってますが…。

なので、一応解決したとはいえ、「CPaintDC」と「CClientDC」の違いさえ、
分かってなかったりします。(「CDC」クラスの派生クラスなんだ〜」ぐらいしか…)
というか、ヘルプの活用方法自体、分かってない状態なんです。

そんなわけで、「本に書いてあることはできるけど、
それ以上のことはなかなか出来ない」というのが、現状です。
本当は、きっちり勉強した方がいいのかも…と、不安に思っているのですが、
「とにかく作るのが先決!マズい部分は後で直そう!」という方針で、
勉強を後回しにしちゃっている状態です。(もしかして、危険でしょうか…?)

とりあえず、「その1」が年内に解決できて本当によかったです。
ISLeさん、本当にありがとうございました。

「その2」は、できればダミーのボタンコントロールを配置しないで
解決させたいので、引き続きアドバイスをお待ちします。
「勉強しろ!」と言われれば、それまでですが…

ISLe 2008/12/10(水) 02:37:56
MSDNライブラリでCPaintDCの解説を見ると

> 構築時に CWnd::BeginPaint 関数を実行し、破棄時に CWnd::EndPaint 関数を実行し
ます。 

とあります。
http://msdn.microsoft.com/ja-jp/library/a48eab8d.aspx

わたしはMFCを使わないのでCPaintDCまでは行き着かなかったです。
が、効用を見る限りOnPaintの中ではCPaintDCを使うのが必須でしょう。

> また、「CDialog::OnPaint();」を呼ぶ順番も、変更しました。

OnEraseBkgndで背景を描画すれば諸々問題にならないはずなんですが。
BOOL CTest1Dlg::OnEraseBkgnd(CDC* pDC)のpDCに描画してもダメですか?

> 「その2」は、できればダミーのボタンコントロールを配置しないで
> 解決させたいので、引き続きアドバイスをお待ちします。

とりあえずで良ければ、リードオンリーにしてメインダイアログの外(見えないところ)
に移動するというのはいかがでしょう?

maru 2008/12/10(水) 09:11:04
> 「その2」は、できればダミーのボタンコントロールを配置しないで
> 解決させたいので、引き続きアドバイスをお待ちします。

だ〜か〜ら〜、金魚ちゃん さんのいう通り
> キーボードのフォーカスが外れるからじゃないの。
> だから SW_SHOW したときに、エディットボックスにフォーカスを設定。
だけじゃないの。

PATIO 2008/12/10(水) 10:25:31
> 本当は、きっちり勉強した方がいいのかも…と、不安に思っているのですが、
> 「とにかく作るのが先決!マズい部分は後で直そう!」という方針で、
> 勉強を後回しにしちゃっている状態です。(もしかして、危険でしょうか…?)

他の所は皆さんが書かれているので上記の所に反応しておきます。
後回しというのはいつか勉強すると言う意味なので、
いつまで後回しにするの?と言う話になります。
ある程度、形になる物が出来るまではやり方を覚える方法で
やった方が退屈しないので長続きすると言うのは確かなんですが、
やり方が大体わかったのであれば、今度はなんでこうすると動くのか?
とか、このコードは実際にはこう言うことをしているんだとか、
コードのパターンではなくて細かい動作の仕組みとかを覚えた方が良いです。
やり方をパターンで覚えても応用が利きませんからそこで止まってしまいます。
今後のことを考えるのであれば、Windowsの仕組み(どういう流れで動いて
いるのか)をきちんと理解した上でMFCのクラスやメソッドとの関連を
理解するようにした方が良いと思いますよ。
MFCはWindowsの仕組みをうまく使う為のフレームワーク(雛形)を提供して
くれています。このフレームワークを理解して使いこなせるようになると
色々と処理の幅が広がりますよ。

たいちう 2008/12/10(水) 14:27:42
> 現状、それでもそれなりのものは作れてる自負はあるので、
> 「まっいっか」で通しちゃってますが…。

作れていないから質問したのでは?
それとも、この質問で作ろうとしているのは、
それなり以上のものに該当するのですか?

PATIOさんがMFCについて触れています。
SDKのプログラムもかじっておくと、MFCの理解も深まりますよ。

# 見下されているように感じたら御容赦を。私も大した実力ではありません。
# 妙な自信を持つよりも、自分に何が欠けているのか冷徹に分析し、
# それを強化することが上達の最良の方法ではないか、という意図です。
#
# 何となく書いてみて「やった動いた!」というレベルは、
# 卒業しているんでしょ?

まに 2008/12/10(水) 21:05:09
[[解決]]
> 効用を見る限りOnPaintの中ではCPaintDCを使うのが必須でしょう。

そうなんですね。
実はISLeさんに言われて参考書をもう一度見直したら、
OnPaintの中では「CPaintDC」を使っていたので、
それをマネしたらうまくいった…って感じなんです。

参考書をもう一度よく見直したら、
「CPaintDCはBeginPaint/EndPaintがカプセル化されている」
ということが分かりました。
ただ、それ以上の記述がないので、
「BeginPaint/EndPaintはどんな時にどう使うのか?」とか、
「なんで他の関数内で描画するときはCClientDCを使うのに、
OnPaintの中ではCPaintDCを使うのか?」とか、
疑問に思うことが出てきてしまいました。

「CDialog::OnPaint();」を呼ぶ順番についても、
参考書の中で、OnPaintの最初の方で呼んでたり、
最後の方で呼んでたりと記述がマチマチで、
「いったいどっちが正しい順番なんだろう?」って、疑問に思っています。
なんでこんなにややこしいんでしょう…?


> BOOL CTest1Dlg::OnEraseBkgnd(CDC* pDC)のpDCに描画してもダメですか?

「OnEraseBkgnd」のpDCに描画しても、うまくいきました。
ただし、その場合はデフォルトのコードの「return CDialog::OnEraseBkgnd(pDC);」を
コメントアウトして「return TRUE;」としないと、描画しても消えてしまうようです。


> とりあえずで良ければ、リードオンリーにしてメインダイアログの外
> (見えないところ)に移動するというのはいかがでしょう?

この方法は思いつきませんでした!この方法でしのぎます。
ありがとうございます。


> だ〜か〜ら〜、金魚ちゃん さんのいう通り
> > キーボードのフォーカスが外れるからじゃないの。
> > だから SW_SHOW したときに、エディットボックスにフォーカスを設定。
> だけじゃないの。

いえ、そんな汎ミスじゃありません。(><)
入力だってちゃんとできますから。
なのにキャレットだけが表示されなかったんです。


> 作れていないから質問したのでは?
> それとも、この質問で作ろうとしているのは、
> それなり以上のものに該当するのですか?

自分としては、今回質問させていただいたことは、
「それなり」の範疇に含まれるつもりでいました。
「画像表示」も「エディットボックスの操作」も「コントロールの表示/非表示」も、
すべて手持ちの参考書に載っていることで、
自分としては、(しくみは理解してないけど)「できるはず」だと思っていました。
にもかかわらず、これらを組み合わせると、なぜかできない。
なので助けを求めさせていただきました。
「でも、実際、作れてないってことなんだろ?」と言われれば、
返す言葉がないのですが………


> コードのパターンではなくて細かい動作の仕組みとかを覚えた方が良いです。

> # 妙な自信を持つよりも、自分に何が欠けているのか冷徹に分析し、
> # それを強化することが上達の最良の方法ではないか、という意図です。

はい、そうですね。
参考書に書かれていることは一通りマスターした気でいましたが、
「それだけでは足りない」「しくみをきちんと理解してない」という点は、
つくづく実感しました。

今は、自分の中の優先度的に勉強を後回しにしてますが、
「いよいよ勉強が必要!」と感じた時は、勉強を始めます。
その時には、お勧めの本などをお聞きするかもしれませんが、
よろしくお願いします。

Ban 2008/12/11(木) 02:09:18
> 「BeginPaint/EndPaintはどんな時にどう使うのか?」とか、

これらはWin32APIのひとつですから、使い方はSDKのルールです。
WM_PAINT (MFCでいえばOnPaintにマップされる)の中で使います。

> 「なんで他の関数内で描画するときはCClientDCを使うのに、
> OnPaintの中ではCPaintDCを使うのか?」とか、

BeginPaint以外にも、GetDCやGetWindowDC、CreateDC, CreateCompatibleDCなど、
用途に応じた複数のAPIがあり、それぞれに機能が違うため、
それらをラップしているMFCにおいても使うものが変わるのです。

先にあげられてるリンク先から参照できるCWnd::BeginPaintの仕様です。
http://msdn.microsoft.com/ja-jp/library/xyfxza1c.aspx

「BeginPaint メンバ関数を呼び出すのは、WM_PAINT メッセージに応答するときだけです。」
と書いてあります。更新フラグやカレットの操作などもするので、
WM_PAINTではこれを使うべきだし、これを呼ばねばならないのです。


> 「いったいどっちが正しい順番なんだろう?」って、疑問に思っています。

「どっちが正しい順番」という疑問はそもそも解決不能かと。
どちらでもいいか、どの順番にするかは、前後の命令によるものであって、
常に正しい順番なんてものはないわけで。

参考書の記述がバグってない限りはどちらも正しい記述でしょうし、
その参考書の記述の妥当性は、個々の処理やAPIについてMSDNなどで確認し、
何をやっているか正しく理解した上で、初めて判断できうるものです。

仕様確認(MSDN)や、動作の理解なしに引用で書いてると、今後もはまると思いますよ。

まぁ、MFCってのは多分、本質的にWin32を知らないと使いこなせないものだと思ってます。
「知ってる人が多少楽するためのもの」であって、Win32を調べずにまともなものが書けたら
よほど運がいいか、センスがいいか…単に問題に気づけないだけか、と思います。

ISLe 2008/12/11(木) 02:49:18
> 「OnEraseBkgnd」のpDCに描画しても、うまくいきました。
> ただし、その場合はデフォルトのコードの「return CDialog::OnEraseBkgnd(pDC);」> コメントアウトして「return TRUE;」としないと、描画しても消えてしまうようで
す。

 自前で背景を消去したときはTRUEを返す必要がある。
 デフォルトではウィンドウクラスに登録した背景ブラシで塗り潰される。

 MSDNライブラリでOnEraseBkgndのページを見るとそんなことが書いてあります。
 http://msdn.microsoft.com/ja-jp/library/a0a52fkz.aspx


 余談ですが、ゲームソフトのように常にクライアント領域全体を使って描画するとき
はウィンドウクラスの背景ブラシをNULLにしOnEraseBkgnd(WM_ERASEBKGND)でTRUEを返す
だけにすると無意味な背景消去処理を省けるので若干高速化できたりします。

まに 2008/12/11(木) 20:48:31
みなさん、本当にありがとうございました。

しくみをちゃんと理解してないのに、「できるはず」と思っていたのは
自惚れだったと実感しました。
せっかくアドバイスいただいたのに、きちんと理解できないことがあったりして、
本当にすみませんでした。
にもかかわらず根気よくアドバイスいただき、本当に感謝しております。

今まで、「なんとかうまくいってるから大丈夫」と思って
そのまま作り続けていたのですが、
もしかしたら問題ばかりの危険なコードだらけかもしれませんので、
どのみち勉強は必ず行うようにします。

> 余談ですが、ゲームソフトのように常にクライアント領域全体を使って
> 描画するときはウィンドウクラスの背景ブラシをNULLにし
> OnEraseBkgnd(WM_ERASEBKGND)でTRUEを返すだけにすると
> 無意味な背景消去処理を省けるので若干高速化できたりします。

ありがとうございます。
今まで、OnPaint以外のところで描画するとき、
Invalidate関数を使うとなぜかチラつきが発生していたので
「CClientDC myDC(this);」で作成したmyDCに描画してたのですが、
その方法を試したらチラつきが発生しなくなりました。

古いスレッドには追加発言できません。