ホーム | プログラミングTClock |


2003/11/23記。031123以降のソースに基づいています。

時計のサイズ調整

ここでは、TClock Lightのソース中で、タスクバーの時計のサイズを調整している部分について紹介します。すべてtcdll.tclockのソースに含まれます。

メッセージWM_USER+100

タスクバーのプログラムは、時計に必要なサイズを取得したいときには、時計ウィンドウにメッセージWM_USER+100を投げてきます。時計はこのメッセージに対し、上位16ビットに高さ、下位16ビットに幅を入れた整数を返します。この内部動作は、私がSpy++を使って時計の動きを調べているときに気づいたものです。

時計のサブクラス化用ウィンドウプロシージャでは、WM_USER+100が送られてきたら、関数OnCalcRectを呼び出して、時計の幅と高さを返します(dll/wndproc.c)。

        case (WM_USER+100):
            if(g_bNoClock) break;
            return OnCalcRect(hwnd);

関数OnCalcRectは次のようになります(dll/draw.c)。関数DrawClockとやっていることは似ています。表示に必要なテキストを作成し、関数GetClockTextSizeを呼び出してテキストに必要な幅(wtext)と高さ(htext)を得ます。幅と高さには数字を少し足して余裕を持たせ、「位置とサイズ」の「サイズの調整」で設定された幅(m_dwidth)や高さ(m_dheight)も足します。

変数g_bFitClock(サイズをトレイに合わせる)がオンになっていたら、トレイのサイズを取得して、幅か高さをトレイのサイズにします。

LRESULT OnCalcRect(HWND hwnd)
{
    SYSTEMTIME t;
    TEXTMETRIC tm;
    wchar_t s[BUFSIZE_FORMAT+BUFSIZE_DISP*2];
    int wclock, hclock;
    
    if(!m_hdcClock) return 0;
    
    if(!IsWindowVisible(hwnd)) return 0;
    
    GetTextMetrics(m_hdcClock, &tm);
    
    GetLocalTime(&t);
    
    if(g_sdisp2[0]) wcscpy(s, g_sdisp2);
    else if(g_sdisp1[0]) wcscpy(s, g_sdisp1);
    else MakeFormat(s, &t, g_format, BUFSIZE_FORMAT);
    
    if(g_scat1[0]) wcscat(s, g_scat1);
    if(g_scat2[0]) wcscat(s, g_scat2);
    
    GetClockTextSize(m_hdcClock, &tm, s, &wclock, &hclock);
    
    wclock += tm.tmAveCharWidth * 2 + m_dwidth;
    hclock += (tm.tmHeight - tm.tmInternalLeading) / 2 + m_dheight;
    if(hclock < 4) hclock = 4;
    
    if(wclock > m_ClockWidth) m_ClockWidth = wclock;
    
    if(g_bFitClock)
    {
        RECT rcTray, rcTaskbar;
        
        GetWindowRect(GetParent(hwnd), &rcTray);
        GetClientRect(GetParent(GetParent(hwnd)), &rcTaskbar);
        
        // horizontal task bar
        if(rcTaskbar.right - rcTaskbar.left >
            rcTaskbar.bottom - rcTaskbar.top)
        {
            hclock = rcTray.bottom - rcTray.top;
        }
        // vertical task bar
        else
            wclock = rcTray.right - rcTray.left;
    }
    
    return (hclock << 16) + wclock;
}

時計のテキストに必要な幅と高さを計算する関数GetClockTextSizeは、次のようになります。テキストの幅は一行ずつAPI GetTextExtentPoint32W(GetTextExtentPoint32のユニコード版)で取得しています。(dll/draw.c)。

void GetClockTextSize(HDC hdc, const TEXTMETRIC* ptm,
    const wchar_t* str, int *wout, int *hout)
{
    int w, h;
    int heightFont;
    const wchar_t *p, *sp, *ep;
    SIZE sz;
    
    p = str; w = 0; h = 0;
    
    heightFont = ptm->tmHeight - ptm->tmInternalLeading;
    while(*p)
    {
        sp = p;
        while(*p && *p != 0x0d) p++;
        ep = p;
        if(*p == 0x0d) p += 2;
        
        if(GetTextExtentPoint32W(hdc, sp, ep - sp, &sz) == 0)
            sz.cx = (ep - sp) * ptm->tmAveCharWidth;
        if(w < sz.cx) w = sz.cx;
        h += heightFont;
        
        if(*p) h += 2 + m_dlineheight;
    }
    
    *wout = w; *hout = h;
}

メッセージWM_WINDOWPOSCHANGING

メッセージWM_USER+100に対してサイズを返せば、タスクバーは時計の位置とサイズを自動的に調整してくれますが、思い通りのサイズにならないこともあります。そこで、メッセージWM_WINDOWPOSCHANGINGを処理して時計のサイズを強引に固定させます。(dll/wndproc.c)。

        case WM_WINDOWPOSCHANGING:  // size arrangement
            if(g_bNoClock) break;
            OnWindowPosChanging(hwnd, (LPWINDOWPOS)lParam);
            break;

WM_WINDOWPOSCHANGINGは、ウィンドウのサイズ変更が完了する前に送られてくるメッセージで、lParamで渡された構造体WINDOWPOSのメンバ変数を書き換えれば、サイズ変更を禁止できます。

WM_WINDOWPOSCHANGINGを処理する関数OnWindowPosChangingは次のとおりです(dll/wndproc.c)。関数OnCalcRectで時計のサイズを取得して、変更されようとしている幅や高さが時計の幅や高さよりも大きければ、構造体WINDOWPOSのメンバ変数を書き換えます。

変数g_bFitClock(サイズをトレイに合わせる)がオンになっていたら、何もしません。

void OnWindowPosChanging(HWND hwnd, LPWINDOWPOS pwp)
{
    DWORD dw;
    int h, w;
    
    if(g_bNoClock || g_bFitClock) return;
    
    if(!IsWindowVisible(hwnd) || (pwp->flags & SWP_NOSIZE))
        return;
    
    dw = OnCalcRect(hwnd);
    w = LOWORD(dw); h = HIWORD(dw);
    if(pwp->cx > w) pwp->cx = w;
    if(pwp->cy > h) pwp->cy = h;
}