| Based on the contents of a text file, I dynamically create controls in a subclass of CWnd. Using the following code in my Create method, I managed to create all the controls based on the message font the user selected in the Display Properties dialog.|
Now that I have the font, how can I determine what the height and width of the static control should be so that the text will be displayed in full?
ncm.cbSize = sizeof(NONCLIENTMETRICS);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
First of all, just so everyone knows what Werner is talking about, the Display Properties dialog is the dialog that's displayed when you right-click on the desktop and select Properties. The Appearance tab in the property sheet (see Figure 1) lets users make their menus magenta or their title bars teal.
Figure 1 Display Properties
You can call SystemParametersInfo with SPI_GETNONCLIENTMETRICS to find out what fonts the user has selected; SystemParametersInfo stuffs a NONCLIENTMETRICS struct with all the information you need (see Figure 2), including the LOGFONT structures for menu, message, and other fonts.
But once you have a font, how do you know how much screen space is needed for a given string of text? This is a common problem in WindowsÂ®. Fortunately, there's a function designed to draw the text where you want it: DrawText. This is the basic text-drawing function in Windows. It draws a string of text at a given position.
DrawText draws the text in the rectangle you specifyâ"but what if you don't know how big the rectangle should be? In that case, there's a handy option that computes the size for you.
CDC dc = ...;
CRect rc = ...;
CString str = "Hello";
dc.DrawText(str, &rc, 0);
Now DrawText doesn't actually draw the text; instead, it figures out how much space the text requires. DrawText alters the rectangle you pass so it's large enough for the text to fit. After the call to DrawText, rc.Width and rc.Height will return the width and height of the text. You can initialize your CRect with a desired upper-left corner (rc.left, rc.top) instead of (0,0). If you do, DrawText will add the text width and height to yield the lower-right corner.
dc.DrawText(str, &rc, DT_CALCRECT);
Of course, the preceding code snippets assume a device context with the font already selected. How do you do all that with just a LOGFONT? If you already have a DC because MFC passed it to you (for example, when MFC calls CView::OnDraw for a view or CWnd::OnDrawItem for an owner-draw item), you can use the DC supplied as your device context; otherwise, you have to call one of the CDC creation functions.
// create client DC
Since you don't intend to actually draw anything, it doesn't matter which device context you use. You can call any CDC-creation function as long as you create a DC that corresponds to the screenâ"as opposed to the printer or other device (unless, of course, you want a device context corresponding to the printer).
// create window DC
Once you have a device context, you have to create a font object and select it into the DC. This is standard Windows Graphics 101.
In order to create an application that has a different window shape like some MP3 players, I set the dialog background to TRANSPARENT (SetBkMode) and create a NULL pen in my WM_CTLCOLORDLG handler to paint the background of the dialog. Then I used TransparentBlt to put my bitmap onto the dialog. The dialog appears transparent at the beginning, but when I close other windows below this dialog, the repaint process maintains the previous window parts instead of showing the part of the window below. So, how do I create real transparency?
// create font
// select it into DC
CFont* pOldFont = dc.SelectObject(&font);
// compute text size
dc.DrawText(str, rc, DT_CALCRECT);
// restore old fontâ"required
Yap Ee Huey
SetBkMode does in fact make the background transparent when you paint, but Windows assumes that every window is rectangular. If you size your window, or close or move windows behind it, Windows will never bother to repaint the pixels under your window since it's assumed your window obscures them. Another way of saying this is that every window is responsible for painting its entire self, including the background. (As a matter of fact, Windows even sends a WM_ERASEBKGND message when it's time to erase your window's background.)
So how do those weird-shaped music players implement the weird shape? Not by using a TRANSPARENT paint mode (though they may do that, too), but by calling SetWindowRgn. This special function lets you create a nonrectangular window. You give SetWindowRgn a region (HRGN), which becomes the shape of your window. The region could be a rectangle, ellipse, polygon, rounded rectangle, or any combination of such shapes. Figure 3 shows a list of Windows functions you can use to create and manipulate regions. When you call SetWindowRgn, you rewrite the painting contract: now you're only responsible for painting inside your region. Windows takes care of painting outside it, as well as all the other "transparent" semantics such as mouse-clicking and changing the cursor.
I wrote a little program, NonRect, that shows how to use SetWindowRgn (see Figure 4). NonRect is a standard MFC app with one small but major modification: the main frame class, CMainFrame, has an OnSize handler that contains the following lines.
This produces a window with rounded corners, as shown in Figure 5.
rc -= rc.TopLeft(); //normalize
Figure 5 Nonrectangular Window
If you size the window, move it, or close other windows behind it, Windows behaves as if the corners are not there: it repaints the missing corners with the proper background, and if you click in the missing corner, Windows activates the window under yours (if there is one). Of course, a real nonrectangular window wouldn't have a title bar, menu, or border since these elements don't look right in a nonrectangular window. NonRect is just a dopey demo program that shows how SetWindowRgn works. If NonRect had been a real nonrectangular app, I'd have stripped all the menus, borders and other stuff, and done all the painting myselfâ"including all nonclient areas, too. In general, once you decide to use SetWindowRgn, you're totally on your own. You have to implement all your own menus, min/max buttons, borders (if any) and nonclient hit-testing. In particular, you'll have to override most, if not all, of the WM_NCXXX messages.
I have a tree control with a document pointer stored in the user section of the TVITEM. When the item is double-clicked, I need to activate that document from the list of open documents. So my question is: given a CDocument pointer, how do I make it active in an MDI app?
Martin MacRobert You can't activate a document; you can only activate a window. Implicit in your question is the assumption that each document has only one view/MDI child frame associated with it. In such a situation, activating the document is just a matter of chasing a few pointers. From the document, you can get its view.
Now pView is the first view associated with the document in question. This is assumed to be the one and only view for this document. If the document has more than one associated view (for example, if the user has opened two windows on the same document in an MDI app), you must decide which one to activate. The easiest solution is to arbitrarily use the first view in the list.
POSITION pos = pDoc->GetFirstViewPosition();
CView* pView = pDoc->GetNextView(pos);
Once you have the view, you have to get the MDI child frame containing it.
GetParentFrame gets the first parent frame of a given windowâ"the first parent window that is a CFrameWnd. Assuming your app is an MDI app, this should be the CMDIChildWnd containing the view. Once you have the MDI child frame, you can call pFrame->MDIActivate to activate it. Of course, you'll have to cast the pointer to CMDIChildWnd.
CFrameWnd *pFrame = pView->GetParentFrame();