Setting the font for a windows title bar

Is there an easy way to set the font used in a window's title bar.  I do not want to change the overall system properties, just the font in certain windows.  I would prefer to avoid using non-client draw messages.

Any suggestions?
LVL 2
jstolanAsked:
Who is Participating?
 
piano_boxerCommented:
Here is some MFC code for drawing you own titlebar:
---------------------------------------------------

BOOL g_fDrawFancyCaption = TRUE;

/////////////////
// Paint non-client area: First let Windows paint, then I'll paint over it.
//
// I use the relatively obscure and poorly documented update region feature
// to prevent Windows from painting the title text. This is to get totally
// flicker-free painting when the user sizes the window. Note that Windows
// sometimes uses WPARAM=1.
//

void CMainFrame::OnNcPaint()
{
    if(!g_fDrawFancyCaption)
    {
        CMDIFrameWnd::OnNcPaint();
        return;
    }

    MSG& msg = AfxGetThreadState()->m_lastSentMsg;
    HRGN hRgn = (HRGN)msg.wParam;

    CCaptionRect rc(*this);     // caption rectangle in window coords
    CRect rcWin;                    // window rect
    GetWindowRect(&rcWin);      // .. get window rect
    rc += rcWin.TopLeft();      // convert caption rect to screen coords

    // Don't bother painting if the caption doesn't lie within the region.
    //
    if (msg.wParam > 1 && !::RectInRegion((HRGN)msg.wParam, &rc)) {
        CFrameWnd::OnNcPaint(); // just do default thing
        return;                     // and quit
    }

    // Exclude caption from update region
    //
    HRGN hRgnCaption = ::CreateRectRgnIndirect(&rc);
    HRGN hRgnNew     = ::CreateRectRgnIndirect(&rc);
    if (msg.wParam > 1) {
        // wParam is a valid region: subtract caption from it
        ::CombineRgn(hRgnNew, hRgn, hRgnCaption, RGN_DIFF);
    } else {
        // wParam is not a valid region: create one that's the whole
        // window minus the caption bar
        HRGN hRgnAll = ::CreateRectRgnIndirect(&rcWin);
        CombineRgn(hRgnNew, hRgnAll, hRgnCaption, RGN_DIFF);
        DeleteObject(hRgnAll);
    }

    // Call Windows to do WM_NCPAINT with altered update region
    //
    WPARAM savewp = msg.wParam; // save original wParam
    msg.wParam = (WPARAM)hRgnNew;   // set new region for DefWindowProc
    CFrameWnd::OnNcPaint();         // call it
    DeleteObject(hRgnCaption);      // clean up
    DeleteObject(hRgnNew);          // ...
    msg.wParam = savewp;                // restore original wParam

    PaintCaption();                 // Now paint my special caption
}

BOOL CMainFrame::OnNcActivate(BOOL bActive)
{
    if(!g_fDrawFancyCaption)
        return CMDIFrameWnd::OnNcActivate(bActive);


    // Mimic MFC kludge to stay active if WF_STAYACTIVE bit is on
    //
    if (m_nFlags & WF_STAYACTIVE)
        bActive = TRUE;
    if (!IsWindowEnabled())         // but not if disabled
        bActive = FALSE;

    if (bActive==m_bActive)
        return TRUE;                    // nothing to do

    DefWindowProc(WM_NCACTIVATE, bActive, 0L);

    m_bActive = bActive;        // update state
    SendMessage(WM_NCPAINT);    // paint non-client area (frame too)
   
    return TRUE;                    // done OK
}


void CMainFrame::OnSettingChange(UINT uFlags, LPCTSTR lpszSection)
{
    OnSysColorChange();
}

void CMainFrame::OnSysColorChange()
{
    if(!g_fDrawFancyCaption)
    {
        CMDIFrameWnd::OnSysColorChange();
        return;
    }

    m_fontCaption.DeleteObject();   // generate new fonts
    m_rcCaption.SetRectEmpty(); // will force new bitmap to be created
}


//////////////////
// Paint custom caption. Flag tells whether active or not.
// Just blast the bitmap to the title bar, but not iif minimized (iconic).
//
void CMainFrame::PaintCaption(BOOL bActive)
{
    if (!IsIconic())
    {
        CWindowDC dcWin(this);
        CDC dc;
        dc.CreateCompatibleDC(&dcWin);
        CBitmap* pOldBitmap = dc.SelectObject(GetCaptionBitmap(bActive));
        const CRect& rc = m_rcCaption;
        dcWin.BitBlt(rc.left,rc.top,rc.Width(),rc.Height(),&dc,0,0,SRCCOPY);
    }
}

//////////////////
// Helper function to compute the luminosity for an RGB color.
// Measures how bright the color is. I use this so I can draw the caption
// text using the user's chosen color, unless it's too dark. See MSDN for
// definition of luminosity and how to compute it.
//
static int GetLuminosity(COLORREF color)
{
#define HLSMAX 240  // This is what Display Properties uses
#define RGBMAX 255  // max r/g/b value is 255
    int r = GetRValue(color);
    int g = GetGValue(color);
    int b = GetBValue(color);
    int rgbMax = max( max(r,g), b);
    int rgbMin = min( min(r,g), b);
    return (((rgbMax+rgbMin) * HLSMAX) + RGBMAX ) / (2*RGBMAX);
}

#define COLOR_WHITE RGB(255,255,255)
#define COLOR_BLACK RGB(0,0,0)
#define NCOLORSHADES 64     // this many shades in gradient

//////////////////
// Helper to paint rectangle with a color.
//
static void PaintRect(CDC& dc, int x, int y, int w, int h, COLORREF color)
{
    CBrush brush(color);
    CBrush* pOldBrush = dc.SelectObject(&brush);
    dc.PatBlt(x, y, w, h, PATCOPY);
    dc.SelectObject(pOldBrush);
}

//////////////////
// Get the appropriate bitmap whether active/inactive. If the size of
// the caption rect has changed since the last time it was generated,
// generate it again.
//
// This is the function that actually does the shading. It creates a
// bitmap that's used to paint the caption. It looks horrible, but it's
// just a lot of bit-twiddling GDI stuff.
//
CBitmap* CMainFrame::GetCaptionBitmap(BOOL bActive)
{
    CBitmap& bm = m_bmCaption[bActive!=0]; // one of two bitmaps
    CCaptionRect rcCap(*this);                  // caption rectangle in win coords
    if (rcCap != m_rcCaption) {             // if changed since last time:
        m_bmCaption[0].DeleteObject();      // delete both bitmaps,
        m_bmCaption[1].DeleteObject();      // ..they are bad
        m_rcCaption = rcCap;                        // note for next time
    }
   
    if (bm.m_hObject)
        return &bm;                                // already have bitmap; return it.
   
    // Either the size changed or the bitmap was never created. Create it.
    //
    rcCap -= rcCap.TopLeft(); // convert caption rectangle origin to (0,0)
    int w = rcCap.Width();
    int h = rcCap.Height();
    int cxIcon = GetSystemMetrics(SM_CXSIZE);
    int cyIcon = GetSystemMetrics(SM_CYSIZE);

    // Create bitmap same size as caption area and select into memory DC
    //
    CWindowDC dcWin(this);
    CDC dc;
    dc.CreateCompatibleDC(&dcWin);
    bm.DeleteObject();
    bm.CreateCompatibleBitmap(&dcWin, w, h);
    CBitmap* pOldBitmap = dc.SelectObject(&bm);

    // Paint shaded background. Note all painting is into memory DC,
    // not window, so there's no flicker.
    //
    if (!bActive) {
        // Inactive caption: don't do shading, just fill w/bg color
        PaintRect(dc, 0, 0, w, h, GetSysColor(COLOR_INACTIVECAPTION));

    } else {
        // Active caption: do shading
        //
        COLORREF clrBG = GetSysColor(COLOR_ACTIVECAPTION); // background color
        int r = GetRValue(clrBG);               // red..
        int g = GetGValue(clrBG);               // ..green
        int b = GetBValue(clrBG);               // ..blue color vals
        int x = 5*rcCap.right/6;                // start 5/6 of the way right
        int w = x - rcCap.left;                 // width of area to shade
        int xDelta= max(w/NCOLORSHADES,1);  // width of one shade band

        // Paint far right 1/6 of caption the background color
        PaintRect(dc, x, 0, rcCap.right-x, h, clrBG);

        // Compute new color brush for each band from x to x + xDelta.
        // Excel uses a linear algorithm from black to normal, i.e.
        //
        //      color = CaptionColor * r
        //
        // where r is the ratio x/w, which ranges from 0 (x=0, left)
        // to 1 (x=w, right). This results in a mostly black title bar,
        // since we humans don't distinguish dark colors as well as light
        // ones. So instead, I use the formula
        //
        //      color = CaptionColor * [1-(1-r)^2]
        //
        // which still equals black when r=0 and CaptionColor when r=1,
        // but spends more time near CaptionColor. For example, when r=.5,
        // the multiplier is [1-(1-.5)^2] = .75, closer to 1 than .5.
        // I leave the algebra to the reader to verify that the above formula
        // is equivalent to
        //
        //      color = CaptionColor - (CaptionColor*(w-x)*(w-x))/(w*w)
        //
        // The computation looks horrendous, but it's only done once each
        // time the caption changes size; thereafter BitBlt'ed to the screen.
        //
        while (x > xDelta) {                        // paint bands right to left
            x -= xDelta;                            // next band
            int wmx2 = (w-x)*(w-x);             // w minus x squared
            int w2  = w*w;                          // w squared
            PaintRect(dc, x, 0, xDelta, h,  
                RGB(r-(r*wmx2)/w2, g-(g*wmx2)/w2, b-(b*wmx2)/w2));
        }

        PaintRect(dc,0,0,x,h,COLOR_BLACK);  // whatever's left ==> black
    }

    // Draw icon and caption buttons.
    // These are all drawn inside a rectangle of dimensions SM_C[XY]SIZE.
    //
    CRect rcButn(0,0,cxIcon,cyIcon);

    // Within the basic button rectangle, Windows 95 uses a 1 or 2 pixel border
    // Icon has 2 pixel border on left, 1 pixel on top/bottom, 0 right
    rcButn.DeflateRect(0,1);
    rcButn.left += 2;
    DrawIconEx(dc.m_hDC, rcButn.left, rcButn.top,
        (HICON)GetClassLong(m_hWnd, GCL_HICONSM),
        rcButn.Width(), rcButn.Height(), 0, NULL, DI_NORMAL);


    // Close box has a 2 pixel border on all sides but left, which is zero
    rcButn.DeflateRect(0,1);                // shrink top/bottom 1 more
    rcButn += CPoint(w-cxIcon-2,0);     // move right
    dc.DrawFrameControl(&rcButn, DFC_CAPTION, DFCS_CAPTIONCLOSE);


    DWORD dwStyle = GetStyle();

    // Max/restore button is like close box; just shift rectangle left
    //
    if (dwStyle & WS_MAXIMIZEBOX) {
        rcButn -= CPoint(cxIcon,0);
        dc.DrawFrameControl(&rcButn, DFC_CAPTION,
            IsZoomed() ? DFCS_CAPTIONRESTORE : DFCS_CAPTIONMAX);
    }

    // Minimize button has 2 pixel border on all sides but right.
    //
    if (dwStyle & WS_MINIMIZEBOX) {
        rcButn -= CPoint(cxIcon-2,0);
        dc.DrawFrameControl(&rcButn, DFC_CAPTION, DFCS_CAPTIONMIN);
    }


    // Now draw text. First Create fonts if needed
    //
    if (!m_fontCaption.m_hObject)
        CreateFonts();
    CFont* pOldFont = (CFont*)dc.SelectObject(&m_fontCaption);

   
    // Paint "ACME TEXT" using ACME font, always white
    //
    CRect rcText = rcCap;                    // caption rectangle
    rcText.left += cxIcon+2;                 // start after icon
    rcText.right = rcButn.left-2;            // don't draw past buttons
    dc.SetBkMode(TRANSPARENT);



    // Now paint window title (caption)
    //
    if (rcText.right > rcText.left)
    {       // if still room:
        COLORREF clrText;                           // text color
        if (bActive) {
            // Excel always uses white for title color, but I use the user's
            // selected color--unless it's too dark, then I use white.
            //
            clrText = GetSysColor(COLOR_CAPTIONTEXT);
            if (GetLuminosity(clrText) < 90) // good from trial & error
                clrText = COLOR_WHITE;
        } else
            clrText = GetSysColor(COLOR_INACTIVECAPTIONTEXT);

        // Paint the text. Use DT_END_ELLIPSIS to draw ellipsis if text
        // won't fit. Win32 sure is friendly!
        //
        dc.SetTextColor(clrText);
        CString s;
        GetWindowText(s);
        dc.DrawText(s, &rcText, DT_LEFT|DT_VCENTER|DT_SINGLELINE|DT_END_ELLIPSIS);
    }


    // Restore DC
    dc.SelectObject(pOldFont);
    dc.SelectObject(pOldBitmap);

    return &bm; // return bitmap to caller--whew!
}

//////////////////
// Helper function to build the fonts I need.
//
void CMainFrame::CreateFonts()
{
    // Get current system caption font, just to get its size
    //
    NONCLIENTMETRICS ncm;
    ncm.cbSize = sizeof(ncm);
    VERIFY(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0));
    m_fontCaption.CreateFontIndirect(&ncm.lfCaptionFont);
}

//////////////////
// CCaptionRect: Constructor conputes caption rectangle in window coords.
//
CCaptionRect::CCaptionRect(const CWnd& wnd)
{
    // Get size of frame around window
    DWORD dwStyle = wnd.GetStyle();
    CSize szFrame = (dwStyle & WS_THICKFRAME) ?
        CSize(GetSystemMetrics(SM_CXSIZEFRAME),
               GetSystemMetrics(SM_CYSIZEFRAME)) :
        CSize(GetSystemMetrics(SM_CXFIXEDFRAME),
                GetSystemMetrics(SM_CYFIXEDFRAME));

    int cxIcon = GetSystemMetrics(SM_CXSIZE); // width of caption icon/button

    // Compute rectangle
    wnd.GetWindowRect(this);        // window rect in screen coords
    *this -= CPoint(left, top); // shift origin to (0,0)
    left  += szFrame.cx;                // frame
    right -= szFrame.cx;                // frame
    top   += szFrame.cy;                // top = end of frame
    bottom = top + GetSystemMetrics(SM_CYCAPTION)  // height of caption
        - GetSystemMetrics(SM_CYBORDER);                  // minus gray shadow border
}

LRESULT CMainFrame::OnSetText(WPARAM wParam, LPARAM lParam)
{
    if(!g_fDrawFancyCaption)
        return Default();

    // Turn WS_VISIBLE style off before calling Windows to
    // set the text, then turn it back on again after.
    //
    DWORD dwStyle = GetStyle();
    if (dwStyle & WS_VISIBLE)
        SetWindowLong(m_hWnd, GWL_STYLE, dwStyle & ~ WS_VISIBLE);
    LRESULT lRet = DefWindowProc(WM_SETTEXT, wParam, lParam);
    if (dwStyle & WS_VISIBLE)
        SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);

    m_rcCaption.SetRectEmpty();          // invalidate caption
    SendMessage(WM_NCPAINT);    // paint non-client area (frame too)
    PaintCaption();                          // ..and paint it
    return lRet;
}

0
 
BelgaratCommented:
Sorry, it seems that there is not a message/system call that would change font in window's caption. Try sending WM_SETFONT, but I suppose that only standard window classes (controls & dialog boxes) will accept it.
The only way I think about is to handle WM_NCPAINT in your program.
0
 
topolCommented:
I can send you the code, but it's in Visual Basic 5, but it uses API -- so you will easily convert it to C
OK?
0
Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
jstolanAuthor Commented:
To topol

If I can adapt it, I will gladly give you the points.  My E-mail is

jstolan@ossconsulting.com

PS to Belgarat WM_SETFONT doesn't work
0
 
BelgaratCommented:
topol: can you send me your code too ? e-mail is svatopluk.dedic@st.mff.cuni.cz.
Thanks in advance :-)
0
 
shaigCommented:
topol: can you send me your code too ? e-mail is shaig@inter.net.il
Thanks in advance :-)
0
 
jstolanAuthor Commented:
Thanks piano_boxer, that's just what I was looking for.  I must admit it was a little more complicated than I thought.  I was able to simplify your sample quite a bit, as all I really needed was to change the font so Kanji characters would be guaranteed to be displayed.

Topol, I got your E-mail.  But in fairness, piano-boxer's example was more appropriate since it was in MFC.  If you would like some points, leave a message here and I will put up a special question for you.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.