3

私の Delphi / C++Builder アプリには、ユーザーがプロット要素をドラッグしてプロットを操作できるようにする OnMouseMove ハンドラがあります。(VCL の OnDragOver などを使用する代わりに、必要なドラッグ アンド ドロップ ロジックを手動で実装しました。)

OnMouseMove イベントは、プロットの現在の状態に基づいて、メイン フォームといくつかの子フォームの両方を更新します。ただし、マウスを動かしている限り、フォームとその各子フォームで Repaint を手動で呼び出さない限り、メイン フォームも子フォームも更新された状態を実際に再描画しません。再描画が必要な子フォームを見落としやすいため、これはやや脆弱です。

マウスの動きを止めるとすぐに、フォームが期待どおりに再描画されるため、コントロールが期待どおりに無効化されているように見えます.OnMouseMoveイベント/ WM_MOUSEMOVEメッセージが着信している限り、コントロールは再描画されません.(非常にゆっくりとドラッグすると、その後、画面も期待どおりに再描画されます。)

個々の子フォームのコントロールは、個別に再描画しない限り再描画されない可能性があるため、各フォームで手動で Repaint を呼び出すだけでは必ずしも十分ではありません。(たとえば、TEdit は、親の TForm の Repaint を呼び出すと新しい値を表示しますが、無効にした TRadioButton は、それ自体の Repaint を呼び出さない限り、無効に見えません。)

Repaint を呼び出す必要があるのはなぜですか? マウスをドラッグしている間、Windows がアプリのウィンドウを自動的に再描画しないのはなぜですか? Repaint を呼び出す必要があるウィンドウを手動で列挙するよりも、ウィンドウを再描画するより良い方法はありますか?

簡単なテスト アプリケーションをいじってみると、OnMouseMove イベントが遅すぎて、アプリケーションが WM_MOUSEMOVE で忙しすぎて WM_PAINT メッセージがディスパッチされないことが問題なのだろうか? これが実際に当てはまるかどうか、またはそうである場合、これについて何をすべきかはわかりません。

ここに、私が何をしているかを説明するための (過度に単純化されていないことを願っています) コードを示します。GraphArea は、Canvas にプロットが含まれる TImage です。

void __fastcall TMachineForm::GraphAreaMouseDown(TObject *Sender,
    TMouseButton Button, TShiftState Shift, int X, int Y)
{
    if (IsNearAdjustableObject(X, Y)) {
        is_adjusting = true;
    }
}

void TMachineForm::GraphAreaMouseMove(TObject *Sender,
    TShiftState Shift, int X, int Y)
{
    if (is_adjusting) {
        AdjustObject(X, Y);

        /* Draws to the GraphArea TImage by calling GraphArea->Canvas methods */
        RedrawGraphArea();

        /* Updates several standard VCL controls on ChildForm1 and ChildForm2;
         * e.g., ChildForm1->Edit1->Text = CalculatedValue(); */
        NotifyChildForm1OfAdjustment();
        NotifyChildForm2OfAdjustment();

        /* This is where I have to manually call Repaint. I don't know why. */
        GraphArea->Repaint();
        ChildForm1->Repaint();
        ChildForm2->Repaint();
    }
}

void TMachineForm::GraphAreaMouseUp(TObject *Sender,
    TMouseButton Button, TShiftState Shift, int X, int Y)
{
    is_adjusting = false;
}
4

3 に答える 3

4

かつて、VCLのドラッグアンドドロップを使用するアプリケーションでも同じ動作に気づきました。どういうわけか、WM_PAINT自分自身を投稿したり呼び出したりした結果Invalidateのメッセージは、メッセージキューの一番上に到達しません。

の代わりに、子の塗り直しをより適切に処理する必要があるものをRepaint使用することをお勧めします。Update

于 2012-07-31T23:48:10.803 に答える
2

Raymond Chenが、WM_PAINTメッセージがどのように機能するかを説明します。ウィンドウを無効にすると(ウィンドウを無効にするVCLメソッドまたはプロパティを設定するか、手動で呼び出すかどうかに関係なく) 、次回の呼び出し時にメッセージを配信する必要があり、メッセージが利用できないInvalidateことを示すフラグが効果的に設定されます。WM_PAINTGetMessage

私の知る限り、メッセージが十分に高速に生成され(たとえば、WM_MOUSEMOVEメッセージ)、これらのメッセージの処理に十分な時間がかかる場合、メッセージキューが空になることはないため、WM_PAINTメッセージが配信されることはありません。

解決策は、手動で呼び出すUpdate(よりも少しパフォーマンスが良いはずですRepaint)などです。追加の考慮事項:

  • 所有しているすべてのフォームの再描画:ここではクリーンな解決策が見つからないため、所有しているフォームを手動で追跡し、手動でRepaintを呼び出します。(必要に応じて、TFormのコンポーネントを反復処理してTFormを探すこともできますが、それによって測定可能なオーバーヘッドが追加されます。)
  • 子コントロールの処理(私の例では無効として自分自身を再描画しないTRadioButtonなど):Repaintまたはを呼び出す代わりにUpdate、を使用しますRedrawWindow。これにより、子ウィンドウにも自分自身を再描画するように指示できます。
    RedrawWindow(ChildForm1-> Handle、NULL、NULL、
        RDW_UPDATENOW | RDW_ALLCHILDREN);
    
于 2012-08-01T14:55:41.580 に答える
2

Repaint()呼び出されたコントロールの即時再描画を実行します。調整ロジックが GraphicArea と ChildForms に変更を加えて、新しい値で再描画する必要がある可能性がありますが、実際には再描画する必要があることを認識していないため、再描画しません。手動で再描画をトリガーしない限り、変更が表示されない理由はこれで説明できます。

Invalidate()の代わりに使用することをお勧めしRepaint()ます。 Invalidate()コントロールを再描画する必要があることを OS に通知しますが、実際にはまだ描画を実行しません。これにより、OS は独自のタイミングでペイントを管理できるようになり、コントロールは、ユーザーから直接ではなく、通常どおり OS からペイント要求を受け取ります。

于 2012-07-31T22:42:49.370 に答える