プログレスコントロールにテキストを表示する。

プログレスコントロールにはSetWindowText()を使ってもテキストは表示できません。
そこでDrawText()を使って直接表示してやります。

表示するタイミングはコントロールが再描写された時、つまりWM_PAINTが送られた時点で表示します。
このメッセージはコントロールに送られる為、コントロールをサブクラス化して取得する必要があります。
クラスの新規作成で基本クラスにCProgressCtrlを選択しクラスを作成してください。
ここではクラス名をCTextProgressCtrlと付けることにします。
そして、OnPaint()のメッセージハンドラを作成し中身を実装します。
単にコントロール内にテキストを表示するだけであればクライアント領域を取得し
DrawText()で書き込んでやればいいような気もしますがそれでは上手くいきません。
まず、OnPaint()のメッセージハンドラを作成するとバーが表示されなくなります。
原因はデフォルトのウィンドウ プロシージャを実行しなくなるわけですから
そこでやってたバーの描写処理もされなくなると言うわけですね。
ですので代わりにバーの描写処理もする必要があります。
それからバーの表示されている部分と背景の部分でテキストの表示色が同じだと
とても見難いので表示色を変えてやるといいでしょう。
そしてもう一つ、デバイスコンテキストにはデフォルトでシステムのフォントが設定されているので
そのまま使うと文字が変になってしまいます。これも設定しなければなりません。

では、細かな説明は後にしてまずはソースです。


void CTextProgressCtrl::OnPaint() 
{
    CPaintDC dc(this);

    int nPos, nLower, nUpper;
    double dRatio;
    CRgn rgn;
    CRect rcClient, rcText;
    CString strText;

    // 表示形式や色の設定
    UINT nFormat              = DT_CENTER | DT_VCENTER | DT_SINGLELINE;
    COLORREF clrText          = ::GetSysColor(COLOR_WINDOWTEXT);
    COLORREF clrHighlightText = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
    COLORREF clrBar           = ::GetSysColor(COLOR_HIGHLIGHT);
    COLORREF clrBkgnd         = ::GetSysColor(COLOR_3DFACE);

    // プログレスコントロールのデータを取得
    GetWindowText(strText);
    GetRange(nLower, nUpper);
    GetClientRect(rcClient);
    rcText = rcClient;
    rcText.DeflateRect(1, 1);
    dRatio = (double)(GetPos() - nLower) / (nUpper - nLower);
    nPos = (int)((rcText.right - 1) * dRatio + 0.5);

    // デバイスコンテキストの設定
    CFont* pOldFont = dc.SelectObject(GetFont());
    dc.SetBkMode(TRANSPARENT);

    // バーの部分を描写
    dc.SetTextColor(clrHighlightText);
    dc.IntersectClipRect(1, 1, nPos + 1, rcText.bottom);
    dc.FillSolidRect(rcClient, clrBar);
    dc.DrawText(strText, rcText, nFormat);

    // 背景部分を描写
    dc.SetTextColor(clrText);
    rgn.CreateRectRgnIndirect(&rcClient);
    dc.SelectClipRgn(&rgn, RGN_XOR);
    dc.FillSolidRect(rcClient, clrBkgnd);
    dc.DrawText(strText, rcText, nFormat);

    dc.SelectObject(pOldFont);
}

まず最初に表示形式や色の設定を行っています。
nFormatDrawText()で表示する際どのように表示するかを指定しています。
DT_CENTERは左右中央揃え、DT_VCENTERは上下中央揃え、DT_SINGLELINEは一行のみと言う意味です。
他のスタイルはMSDNでDrawText()を参考にしてください。
clrForeText以下はそれぞれテキストの表示色に反転色、バーの表示色に背景色です。
それぞれGetSysColor()で標準の色を取得しています。
この値たちはメンバ変数として定義しユーザがいつでも変更できるようにしておくと後々便利です。

次にプログレスコントロールのデータを取得しています。
GetWindowText()は設定されているテキストを取得します。
これは後で表示するテキストになります。
GetRange()はプログレスコントロールの範囲を取得。
GetClientRect()はプログレスコントロールのクライアント領域(大きさ)を取得。
DeflateRect()はバーの表示領域及びテキストの表示領域を取得。
そしてGetPos()でプログレスバーの現在位置を取得し、上の3つで取得した数値とで計算し
ピクセル単位の現在位置を取得させています。
計算式はソースをご覧ください。

次にデバイスコンテキストの設定です。
まず、SelectObject()GetFont()で取得した現在のウィンドウのフォントを設定します。
このときの戻り値は後で元に戻す為にとっておいてください。
それからSetBkMode()でテキストを描写した際に背景色を通過するように設定します。

次にバーの部分を描写します。
まずはSetTextColor()で表示するテキストの色を設定しています。
そしてIntersectClipRect()でバーの部分だけにクリッピング領域を設定して
FillSolidRect()でバーの部分を塗りつぶしDrawText()でテキストを表示しています。
クリッピング領域とはGDI の出力する領域、つまり塗りつぶしたり字を書いたりする事の出来る領域の事です。
これを設定する事とその範囲外には書き込めなくなります。
こうするとクライアント領域を塗りつぶそうとしてもバーの部分しか塗りつぶされなくて済みますし
テキストを出力した際、文字の途中でもそこで切ったり書き始めることが出来ます。

次に背景の部分を描写します。
ここでもまずテキストの色を設定してします。
そして背景部分のクリッピング領域を設定して書き出すわけですが、クライアント領域から
バーの部分を切り抜いた穴開きのクリッピング領域を作る必要があります。
そこでまず、CreateRectRgnIndirect()でクライアント領域全体のリージョンを作成します。
リージョンとは領域の事でどんな形の領域でも作成する事が出来るものです。
そしてそのリージョンをSelectClipRgn()でデバイスコンテキストにクリッピング領域を設定しますが
このとき2番目の引数を設定すると現在のクリッピング領域と指定した方法で結合します。
この場合は重なる部分を領域から外すわけですからRGN_XORを指定する事で
バーの部分を取り除いた穴開きのクリッピング領域が設定されます。
後は先ほどと同じように領域を塗りつぶしテキストを書き込みます。

最後に変更したフォントを元に戻せばお終いです。
おまけで、進行状況で%数を表示したいのであればプログレスコントロールのデータを取得する際に
テキストを取得しないでdRatioに率も入ってるのでsprintf()辺りで整形するといいかと思われます。

さて、これだけではまだ終わりではありません。
コントロールにSetWindowText()等でテキストを設定した際に画面を更新してやる必要があります。
# %表示をするだけで外部からテキストを設定しないのであれば必要ないですが。
それにはWM_SETTEXTを捕まえInvalidate()で更新してやればいいです。
ただし、メッセージハンドラの追加にはWM_SETTEXTは入っていない為自分で実装する必要があります。
幾つか方法はありますがここではメッセージマップを使ってやる事にします。
方法は以下の様に定義してやってください。


// TextProgressCtrl.h

class CTextProgressCtrl : public CProgressCtrl
{
        :
        :

    // 生成されたメッセージ マップ関数
protected:
    //{{AFX_MSG(CTextProgressCtrl)
    afx_msg void OnPaint();
    //}}AFX_MSG
    afx_msg LRESULT OnSetText(WPARAM wParam, LPARAM lParam);  // 追加

    DECLARE_MESSAGE_MAP()
};


// TextProgressCtrl.cpp

BEGIN_MESSAGE_MAP(CTextProgressCtrl, CProgressCtrl)
    //{{AFX_MSG_MAP(CTextProgressCtrl)
    ON_WM_PAINT()
    //}}AFX_MSG_MAP
    ON_MESSAGE(WM_SETTEXT, OnSetText)  // 宣言を追加
END_MESSAGE_MAP()


// 関数の定義を追加
LRESULT CTextProgressCtrl::OnSetText(WPARAM wParam, LPARAM lParam)
{
    LRESULT result = Default();
    Invalidate();
    return result;
}

このようにするとSetWindowText()等でWM_SETTEXTが発生するとOnSetText()が呼び出されます。
最初にDefault()を呼んでいるのは、デフォルトのウィンドウプロシージャを呼び出して
コントロールにテキストを設定してやる為です。

それから、WM_ERASEBKGNDが送られてきた時にTRUEを返すようにすると
バーのチラツキが無くなりいい感じになるのでやっておきましょう。


BOOL CTextProgressCtrl::OnEraseBkgnd(CDC* pDC) 
{
    return TRUE;
}

あとおまけで豆知識として、ダイアログにプログレスコントロールを配置するとデフォルトで
ウィンドウにProgress1とか名前が付けられ表示されると思いますが
コイツは.rcファイルを直接編集してやると名前を変えられます。

最後に、ここで説明したものを少し拡張したクラスのソースを置いておきます。
そのままプロジェクトに追加して使う事が出来ます。

CTextProgressCtrl.cpp
CTextProgressCtrl.h

< 戻る << HOME ©1999-2002 by Akky, All right reserved.