However, sometimes we need to create image starting from the DDB. If we use DDB to record image data, it would be very simple because we don't have to worry about the actual data format and palette realization. However, there are two problems: 1) If we want to save the image data to a file, we still need to convert it to DIB format. 2) If the hardware is a palette device, the entries of the system palette may actually change (e.g., when another application realizes its own logical palette). In this case, the colors contained in our DDB may not be correctly mapped if we do not implement logical palette for it.
A very typical application of this kind is the screen capturing application. Screen capture can be implemented by copying images between the desktop window DC and our own memory DC. We can call function CDC::BitBlt(...) to copy the images. In this case, the memory DC must select a DDB rather than DIB. If the system uses palette device, this may cause potential problem. Think about the following situation: we use screen capturing application to make a snapshot of the desktop screen, and currently there is a graphic editor opened with a colorful image being displayed. As long as the system palette remains unchanged, the captured image can be displayed in its original colors. Suppose the graphic editor is closed and this causes the system palette to change, the captured image will also change accordingly.
To prevent this from happening, when capturing the screen, we need to copy not only the image bits, but also the colors contained in the current system palette. Then we can use them to create a logical palette, and convert the DDB to DIB data. In the client window, we can display the image using DIB data instead of captured DDB data. By doing this, when the system palette changes, we can re-match the logical palette to achieve the best effect.
Samples in this chapter are specially designed to work on 256-color palette device. To customize them for non-palette devices, we can just eleminate logical palette creation and realization procedure.
12.1 Capturing the Whole Screen
Capture
Making capture is very simple. We already have a lot of experience of creating bitmap in memory, selecting it into a memory DC, and copying it to the client window. We can make a screen capture by reversing the above procedure. The following lists the necessary steps for making a screen capture: 1) Create a blank bitmap in the memory. 2) Create a memory DC that is compatible with the window DC. 3) Select the memory bitmap into the memory DC. 4) Obtain a valid window DC. 5) Copy the image from the window DC to the memory DC. Here the window DC can be either the desktop window DC or a client window DC. In the former case, the whole screen will be captured. In the later case, only the client window will be captured.
Sample 12.1\GDI demonstrates how to make screen capture. It is a standard SDI application generated by Application Wizard, whose view class is based on CScrollView. In the sample, function CGDIView:: Capture() is added to capture the whole desktop screen and store the image in a CBitmap type variable.
Under Windows(, desktop window is the parent of the all windows in the system, and its pointer can be obtained by calling function CWnd::GetDesktopWindw(). With this pointer, we can create a DC and use it to draw anything on the desktop (Be careful with this feature, generally an application should not draw outside its own window). Of course, we can also copy a bitmap to the desktop window.
The following is the implementation of function CGDIView::Capture() that shows how to copy the whole screen and store the image in a CBitmap type variable:
(Code omitted)
In the above function, first desktop window DC is created from the pointer of desktop window (By calling function CWnd::GetDesktopWindow()), then the dimension of the desktop window is retrieved and stored in variable rect; next the memory DC and blank memory bitmap are created and the bitmap is selected into the memory DC; finally function CDC::BitBlt(...) is called to capture the whole screen.
Of course we can use the bitmap (bmpCap) and memory DC (dcMem) created here to display the captured image. However, because there is no logical palette associated with the bitmap, if the colors contained in the system palette change, the captured image may not be displayed correctly. So before displaying the image, we need to first convert it to DIB and implement logical palette.
Converting DDB to DIB
Like other samples, after DDB is converted to DIB we'd like to store it in document so that the image can be easily saved to file through serialization. Also, a logical palette is created in the constructor of the document, it will be used to display captured image later. The entries of this palette is not initialized in the constructor. The variables used for storing DIB and logical palette are CGDIDoc::m_hDIB and CGDIDoc::m_palDraw respectively. Like other samples, here we also have corresponding functions in CGDIDoc that makes two variables accessible from outside the document.
In CGDIDoc, function CGDIDoc::ConvertDDBtoDIB(...) is implemented to convert captured DDB format image to DIB. The procedure is almost the same with what we implemented in sample 10.4\GDI (There is also a function CGDIDoc::ConvertDDBtoDIB(...) there), the only difference is where the logical palette comes from. In sample 10.4\GDI, whenever we open a DIB image, we can obtain its color table, which can be used to create the logical palette. Here, we do not have such kind of color table and must create a logical palette from the colors contained in the current system palette and use it to generate the color table for DIB image.
Obtaining colors contained in the system palette is not difficult. Remember in sample 8.9\GDI, we can actually monitor the system palette all the time. To create a palette whose entries are always synchronized to the entries of the system palette, we can set member peFlags of structure PALETTEENTRY to flag PC_EXPLICIT when creating the logical palette (Also, set lower two bytes of structure PALETTEENTRY to an index to the system palette). The logical palette (More accurately, the entries with flag PC_EXPLICT) created this way will not be mapped to the system palette by looking up the same (or nearest) colors or filling empty entries. Instead, each entry will always be mapped to a fixed entry contained in the system palette. If the colors in the system palette change, the corresponding colors in the logical palette will also change.
We need to create this kind of logical palette right after the image is captured (Before the system palette changes) so that the colors contained in the captured image will be represented by the system palette. Although we can use this palette to obtain the correct color table we will use, it can not be used for later DIB displaying. The reason is that the colors contained in these entries may change constantly. We need to create a logical palette using the color table obtained this way (Member peFlags of the palette entries should be set to NULL).
After the logical palette with PC_EXPLICIT flag entries is created, we can select it into DC and realize it. Then we can allocate enough buffers to store BITMAPINFOHEADER type object and color table. When calling function ::GetDIBits(...), we can pass NULL to its lpvBits parameter, this will cause the bitmap header and the correct color table to be filled into the buffers allocated before.
Now that we have the correct color table, we can use it to create a logical palette with member peFlags set to NULL. In the sample, since an uninitialized logical palette is created at the beginning, we can just fill the palette entries after each capturing.
The rest part of DDB-to-DIB conversion is the same with that of sample 10.4\GDI: we just need to calculate the image size, reallocate the buffers, and call ::GetDIBits(...) again to receive actual bitmap bit values.
The following is the implementation of function CGDIDoc::ConvertDDBtoDIB(...), the input parameter is a CBitmap type pointer, and the returned value is the handle of global memory that contains DIB data:
(Code omitted)
New Command
In sample 12.1\GDI, a new command Capture | Go! is added to mainframe menu IDR_MAINFRM. This command is handled by function CGDIDoc::OnCaptureGo(). Within the function, we first minimize the application by calling function CMainFrame::ShowWindow(...) using SW_SHOWMINIMIZED flag. Then we call function CGDIView::PrepareCapture() to set the timer (This function contains only one statement).
Because we will minimize our application window, the capture should be delayed for a few seconds after the user executes Capture | Go! command. Function CGDIView::PrepareCapture() does nothing but setting up a timer with time out period set to 2 seconds. Function CGDIView::Capture() is called when the timer times out. Within function CGDIView::Capture(), after the capture is made, function CGDIDoc:: GetCaptureBitmap(...) will be called to convert DDB to DIB and update the client window (Within this function, CGDIDoc::ConvertDDBtoDIB(...) will be called).
In CGDIView::OnDraw(...), we use function ::SetDIBitsToDevice(...) to display DIB data. This is the same with sample 10.4\GDI.
Figure 12-1 shows the time sequence of function calling.
(Figure 12-1 omitted)
12.2 Capturing a Specified Window
One problem of the sample implemented in the previous section is that it can capture only the whole desktop window. To improve it, we will add some new functions so that the user can specify any window for capturing.
Picking Up a Window
Often there are many windows contained in the desktop window. Each application may have one mainframe window and several child windows. To let the user pick up a window with mouse clicking, we must find a way to detect if there is a window under the current mouse cursor.
We can call function CWnd::WindowFromPoint(...) to retrieve the window under current mouse cursor. If there is such a window, its handle will be returned by this function. Otherwise the function will return NULL.
We also need to monitor mouse movement and respond to its activities when the user is selecting a window. We all know that this can be implemented by handling mouse related messages: WM_LBUTTONDOWN, WM_MOUSEMOVE, and WM_LBUTTONUP. Also we must be able to receive these messages even if the mouse cursor is outside our application window. To implement this, we need to set window capture. By doing so, the user can hold the left button and move it anywhere to pick up window. As long as the left button is held down, we can receive WM_MOUSEMOVE messages (The capture will be released by the system if the left button is released).
Another issue is how to indicate the selected window. To make the application easier to use, we need to put some indication on the window that is being selected. From the previous section we know that the DC of the desktop window can be obtained and used to draw anything anywhere on the screen. With the desktop window DC, we can reverse the selected window when the mouse cursor is over it, and resume it as the mouse cursor leaves the window.
The drawing mode can be set by calling function CDC::SetROP2(...) using R2_NOT flag. After this if we draw a rectangle (By calling function CDC::Rectangle(...)) using the window's position and size, the whole window will be reversed. Calling this function twice using the same parameter will resume the original window.
One thing we must pay attention to is that the application itself also has a mainframe window, and therefore will be selected for capturing if the mouse cursor is over it. This is not a desirable feature. To solve this problem, after function CWnd::WindowFromPoint(...) is called, we must check if the window returned by this function belongs to the application itself.
Because the windows can overlap one another, when the mouse cursor is over one window, we should not reverse the overlapped part (Figure 11-2). To solve this problem, we need to use region. We can create a region that contains only the non-overlapped part of a window, which can be selected by the desktop window DC. By doing this, the overlapped portion of the window will not be reversed when the function CDC::Rectangle(...) is called.
After the user has selected a specific window and executed Capture | Go! command, we can find out the size and position of this window, start timer, call function CDC::BitBlt(...) to make a snapshot of the window and store the data to the memory bitmap.
Dialog Box IDD_DIALOG_SELECT
Sample 12.2\GDI is based on sample 12.1\GDI. The new sample allows the user to select a specified window for making snapshot. First a dialog box is added to the application, it will be used to let the user select a window. The new dialog template is IDD_DIALOG_SELECT, and the class associated with it is CSelDlg. To make the interface user friendly, mouse cursor will be changed when the user is selecting a window (With left button held down). In the sample, a cursor resource IDC_CURSOR_SELECT is added for this purpose. The cursor is loaded in the constructor of CSelDlg and its handle is stored in variable CSelDlg::m_curSelect. In the dialog box template, an icon that contains the cursor is displayed. If the user click on this icon, it will be changed to a blank icon and at the same time, the cursor will be customized to IDC_CURSOR_SELECT. If the user releases the mouse button, everything will be resumed. This will give the user a feeling that the mouse clicking actually picks up the cursor (Figure 12-3).
The icons displayed in the dialog box are also stored as resources. Their IDs are IDI_ICON_CURSOR and IDI_ICON_BLANK. Also, they are loaded in the class constructor and are displayed in the dialog box by calling function CStatic::SetIcon(...). The control used to display the icon is a static control (Actually it is added as a "Picture" control). In the property sheet "Picture Properties", we can choose the type of images that will be displayed, such as bitmap or icon (Figure 12-4).
Messages WM_LBUTTONDOWN, WM_LBUTTONUP and WM_MOUSEMOVE are handled to let the user select a window. When the left button is pressed down, we check if it hits the icon contained in the dialog box. If so, we set window capture for the dialog box, change the mouse cursor, and change icon IDI_ICON_CURSOR to IDI_ICON_BLANK:
(Code omitted)
Variable CSelDlg::m_hWnd is used to store the handle of the selected window. Also, variable CSelDlg::m_rectSelect is used to indicate the rectangle of the previously selected window. If this rectangle is empty, no window is currently being reversed.
As the mouse moves, we will keep on receiving WM_MOUSEMOVE messages. In the sample, function CSelDlg::DrawSelection(...) is implemented to handle this message. Within this function, first we create a region that contains all of the desktop window excluding the area occupied by the application window. We select this region into the desktop window DC before reversing any window. By doing this, if the selected window is overlapped by the application window, the reversing effect does not apply to the application window. The following portion of function CSelDlg::DrawSelection(...) shows how the region is created:
(Code omitted)
In order to resume the reversed window, the drawing mode should be set to R2_NOT, which will reverse all the pixels contained in the rectangle when function CDC::Rectangle(...) is called. The drawing mode can be set by calling function CDC::SetROP2(...):
(Code omitted)
Then we call function CWnd::WindowFromPoint(...) to see if the current mouse cursor is over any window. We use the returned pointer to retrieve the handle of that window, and compare it with the handles of our application windows (both mainframe window and dialog box window). If there is a match, we should not draw the rectangle because the cursor is over the application window (In this case, if there exists a window that has been reversed, we should resume it). Otherwise, we further compare it with the handle stored in CSelDlg::m_hWnd, if they are the same, we don't do anything because the window under the cursor has been reversed. If not, this means a new window is being selected and we should resume the old reversed window (If there exists such a window) then reverse the newly selected one:
(Code omitted)
In function CGDIView::Capture(), we need to first obtain handle CGDIDoc::m_hWnd from the document, if it is not a valid window handle, we still capture the whole desktop screen. Otherwise we use this handle to find out the rectangular area of the window and make the snapshot.
12.3 Simple Printing
Although we didn't write a single line of code to implement printing feature, all our SID or MDI samples have the default printing functionality. This includes default printer set up, print preview, and printing the client window. Like display, printer is another type of graphic device that can be used to output drawings. Its interface to the software programmer is similar to that of display: instead of writing code to control the hardware directly, we can use DC to output drawings to the printers. Actually we can call member functions of class CDC to output dot, line, curve, rectangle and bitmaps to a printer.
Mapping Mode
However, there are some differences between printing devices and display devices. One main difference is that two devices may have different capabilities. Because all displays have similar sizes and resolutions, it is relatively convenient to measure everything on the screen using pixel. For example, it doesn't make much difference if we display a 256(256 bitmap on an 800(600 display or a 1024(768 display. Since every window is able to display an object that is larger than the dimension of its client area (using scroll bars), it is relatively easy to base every drawing on the minimum possible unit ¾ pixel.
For printers, this is completely different. There are many types of printers in the world, whose resolutions are remarkably different from one another. For example, there are line printers, one pixel on this kind of printers may be 0.1mm(0.1mm; also, there are many types of laser printers, whose resolution can be 600dpi, 800dpi or even denser. If we display a 256(256 image on the two types of printers, their sizes will vary dramatically.
Anther difference between printing devices and display devices is that when doing the printing, it is desirable to make sure that all the outputs fit within the device. For a window, since we can customize the total scroll size, it doesn't matter what the actual output dimension is (The scroll size can always be set to the dimension of output drawings). For the printer, we need to either scale the output to let it fit within the device or print the output on separate papers.
In order to handle this complicated situation, under Windows(, OS and devices have some common agreements. When we draw a dot, copy a bitmap to device, everything is actually based on logical units (pixels). By default, the size of one logical unit is mapped to one minimum physical pixel on the device, however, this can be changed. Actually class CDC has a function that let us customize it:
virtual int CDC::SetMapMode(int nMapMode);
Parameter nMapMode specify how to map one logical unit to physical units of the target device. By default it is set to MM_TEXT, which maps one logical unit to one physical unit. It can also be set to one of the following parameters:
(Table omitted)
Instead of mapping one logical unit to a fixed number of pixels, it is mapped to a fixed size on the target device. It is the device driver's task to figure out the actual number of pixels that should be used for drawing one logical pixel. By doing this type of mappings, the output will have the same dimension no matter what type of target device we use.
There is one difference between MM_TEXT mapping mode and other modes: for MM_TEXT mode, the positive y axis points downward. For other mapping modes, the positive y axis points upward. So if we decide to use one of the mapping mode listed above, and the origin of the bitmap is still the same, we need to use negative values to reference a pixel's vertical position (Figure 12-5).
Converting between Logical and Device Units
Sometimes we need to implement the conversion between logical unit and actual device unit. Class CDC has a bunch of functions that allow us to do the conversion between two coordinate systems. For example, CDC::LPtoDP(...) allows us to convert a point (POINT type variable) or a size (CSize type variable) measured in logical unit coordinate system to device coordinate system. And CDC::DPtoLP(...) does the reverse.
Implementing Print
Actually it is easy to implement printing for applications generated from the Application Wizard. When the user executes File | Print or File | Print Preview command, a series of printing messages will be sent to the application. Upon receiving these messages, the frame window finds out the current active view, and call that view's CView::OnPrint(...) function to output drawings to the target device.
By default, CView::OnPrint(...) does nothing but calling function CView::OnDraw(...), so everything contained in the client window (view) will also be output to the printer. We can experiment this with the sample already implemented. For example, after executing sample 12.2\GDI, if we make a snapshot and execute File | Print command, the captured image will be sent to the printer. The actual size of the output image depends on the type of printer because in CGDIView::OnDraw(...), we didn't set the mapping mode so the default mode MM_TEXT is used.
We must know the resolution of the target device so that we can either scale the output to let it fit into the device or we can manage to print one image on separate papers. One way of obtaining the target device's resolution is to call function CDC::GetDeviceCap(...) using HORZRES and VERTRES parameters. The returned value of this function will be the horizontal or vertical resolution of the target device, measured in its device unit. Besides this, we can also use LOGPIXELSX and LOGPIXELSY to retrieve the number of pixels per logical inch in the target device for both horizontal and vertical directions. Since the width and height of a minimum pixel in the target device may not be the same, the above two parameters may be different.
Scaling the Image before Printing
Sample 12.3\GDI is based on sample 12.2\GDI. In this sample, when doing the printing, the output is scaled so that it can fit within one sheet of paper no matter what the resolution of target device is. In order to implement this, we need to calculate the proportion between the logical unit and the physical unit of the target device before the image is output to the device.
For example, suppose we have an image whose logical dimension is x(y, and want to output it to the target device whose physical resolution is px(py. We further assume that the aspect ratio of a minimum physical pixel on the target device is rx : ry. This is illustrated by Figure 12-6:
In Figure 12-6, x=4, y=2, px=12, py=9, rx : ry = 1:2. First we choose px as the actual width of the output image. In this case, we need to map 4 logical units to 12 physical units in the horizontal direction. So the horizontal mapping ratio is selected as 1:3. If we do the same thing in the vertical direction, we will have a 12(6 image on the target device. However, because a basic pixel on the target device is not square, the proportion of the output image will change if we do not take it (rx : ry ratio) into consideration. To compensate for this, we need to map one logical unit to (px/x) ((rx/ry) pixels in the vertical direction. In this sample, we will have a 12X3 image on the target device.
If we have a very tall image (For example, in Figure 12-6, if x=2, y=9), such mapping may cause some portion of the image unable to fit into the target device. So after calculating the mapping using the above method, we need to check the vertical physical size and see if it is greater than py (if y((px/x) ((rx/ry) > py). If so, we need to calculate the mapping again by first setting the vertical size to py and then calculating the horizontal size using the same method.
Displaying or Printing?
In function CView::OnDraw(...), this mapping is unnecessary if the target device is a display rather than a printer. To find out if this function is being called for printer, we can call function CDC::IsPrinting(). If the returned value is FALSE, the function is called to output drawings to display. Otherwise it is called to output drawings to printer.
Function CGDIView::OnDraw(...)
The following portion of function CGDIView::OnDraw(...) shows how to scale the image dimension so that it will fit within the target device before being output to the printer. Since the image must be scaled before being printed, we call function ::StretchDIBits(...) to implement printing and still use ::SetDIBitsToDevice(...) for painting the client window:
(Code omitted)
The physical resolution of the target device is retrieved and stored in variables cxPage and cyPage, and the number of pixels per logical inch are retrieved and stored in variables cxInch and cyInch, which can be used to calculate the aspect ratio of a basic pixel on the target device. The logical dimension of the image is stored in members lpBi->bmiHeader.biWidth and lpBi->bmiHeader.biHeight. With the above parameters, it is easy to figure out the actual physical size of the output image. The output dimension is stored in variable rcDest, and function ::StretchDIBts(...) is called to output the captured image to printer.
12.4 Fixed Scale Printing
The solution in the previous section scales image so that it will always fit within the target device. However, this is not he best solution. For example, if we need to print a small button image, the image will be enlarged to fit the paper; if we want to print a very big image, it will be shrunk and we will inevitably lose some details.
Printing Related Functions
To let the printing output always have the same fidelity, we need to call function CDC::SetMapMode(...) to map one logical unit to a fixed value. For example, if we map one logical unit to 0.1mm, a 256(256 image will always have a 25.6(25.6cm2 dimension, no matter what type of target device we use. When doing the printing, we can further call function CDC::StretchBlt(...) or ::StretchDIBits(...) to scale one logical unit to multiple of 0.1 millimeters. For example, if we set one logical unit to 0.1 mm and call ::StretchDIBits(...) to scale the image to three times of its original dimension, one logical unit will ultimately equal to 0.3 mm.
Usually printing is handled in function CView::OnPrint(...). If we do not override it, function CView::OnDraw(...) (Or its overridden function) will be called to implement the default printing. The advantage of using CView::OnPrint(...) instead of CView::OnDraw(...) is that function CView::OnPrint(...) has two parameters, one is a pointer to the target device context, the other is a CPrintInfo type pointer that brings us a lot of information of the printing device. We will see how to use this parameter in later sections.
Before printing begins, function CView::OnBeginPrinting(...) will be called. A CDC type pointer and a CPrintInfo type pointer will be passed to this function, from which we can obtain the information of current printing status. If we need to prepare something before the printing starts, we can override this function. This function is necessary because there exist some applications whose printing output is different from what is displayed in the client window. For example, if we are programming a video editing application, what can be displayed in the client window is usually one of a series of images. When the user does the printing, we actually want to print all the images. In this case, we must create either DIB or DDB data before the printing starts. Function CView::OnBeginPrinting(...) is a best place to implement such kind of preparation: we can create GDI objects, allocate memory, set device mapping modes, and so on. Since the GDI objects and memory prepared here are solely used for printing, after the printing is done, we must destroy them. Function CView::OnEndPrinting(...) is designed for this purpose, it will be called when the printing task is over.
When the printing is undergoing, function CView::OnPrint(...) will be called. We can use CDC type pointer passed to this function to output objects to the printing device, this is the same with outputting objects to display device.
Sample 12.4\GDI
Sample 12.4\GDI is based on sample 12.3\GDI. In this sample the printing is handled in function CGDIView::OnPrinting(...), and CGDIView::OnDraw(...) is only responsible for painting the client window. Since we can use the DIB stored in the document for printing, we do not need to do any preparation in function CGDIView::OnBeginPring(...). Within function CGDIView::OnPrinting(...), we first need to obtain the DIB and palette from the document, and set the mapping mode to MM_LOMETRIC, which will map one logical unit to 0.1 mm. Then we need to select the palette into the target DC, call function ::StretchDIBits(...) to copy the image to target device. Please note that after the mapping mode is set to MM_LOMETRIC, the direction of y axis is upward. When calling function ::StretchDIBits(...), we must set the output vertical dimension on the target device to a negative value if the origin is still located at (0, 0):
(Code omitted)
Now no matter what type of printer we use, the output dimension will be the same. The only difference between the output from two different types of printers may be the image quality: for printers with high DPIs, we will see a smooth image; for printers with low DPIs, we will see undesirable image.
Although the printing ratio is fixed for this sample, the user can still modify it through File | Print Setup... command. In the Print Setup dialog box, the user can also select printer, paper size and the printing ratio. The maximum printing ratio that can be set by the user is 400%. This may cause the output image unable to fit within the target device. In this case, we need to print one image on separate pages.
12.5 Printing on Separate Pages
If we want to output an image on separate papers, there are two situations: 1) Number of required pages is known before the printing starts. 2) Number of required pages has to be decided after the printing starts. For different situations, we need to use different approaches.
Number of required Pages is Known Beforehand
Sample 12.5-1\GDI is based on sample 12.4\GDI and demonstrates how to implement printing when the total number of pages is known beforehand.
For some applications, the number of required pages for printing is fixed. For this situation, we need to override function CView::OnPreparePrinting(...), and call function CPrintInfo::SetMaxPage(...) to set the page range. By doing this, when the printing is being processed, function CView:OnPrint(...) will be called repeatedly until all the pages are printed out. Within CView::OnPrint(...), the page information can be obtained from member CPrintInfo::m_nCurPage (The second parameter of this function is a pointer to class CPrintInfo), which stands for the current page number that is being printed. According to this number, we can output different contents to different pages. The printing will be stopped after all the pages are printed out.
For example, suppose whenever we want to print out the captured image, we'd like to make two copies, one with 100% ratio and one with 200% ratio. In this case the number of pages is determined beforehand. Before the printing begins, we can set the number of pages in function CGDIView::OnPreparePrinting(...) as follows:
(Code omitted)
By doing this, if the user executes printing command, in the popped up dialog box, the total number of pages will be set to 2. The user can choose to print any of the pages or both of them. In function CGDIView::OnPrint(...), we need to check which page is being printed and call function ::StretchDIBits(...) using the corresponding ratio:
(Code omitted)
Setting Number of Pages Just Before Printing Starts
However this is not a normal case. Because the user can actually change the printing ratio, the number of pages actually needed depends on print settings. For example, when the user set the printing ratio to 400%, the image that can originally fit into one page (when the ratio is 100%) often requires more than one page now. So the actual number of pages needed depends on the printing ratio, which can range from 25% to 400%.
One solution to this problem is to calculate the actual number of pages just before the printing starts. At this time, the print setting will not be changed any more, so we can calculate the number of required pages and call function CPrintInfo::SetMaxPage(...) to set the page range.
This can be done in either function CView::BeginPrinting(...) or in function CView:: OnPrepareDC(...). The first function will be called just before the print job begins, and the second function will be called before CView::BeginPrinting(...) is called when the printing DC needs to be prepared. Please note that CView::OnPrepareDC(...) will also be called for preparing display DC, to distinguish between the two situations, we can check if parameter pInfo is NULL, if not, it is called for the printing job.
The number of required pages can be calculated by retrieving the device resolution (need to be converted to logical unit) and comparing it with the image size. If the image size is greater than the device resolution, we can print one portion at a time until the whole image is output to the target device.
Sample 12.5-2\GDI is based on sample 12.4\GDI, it demonstrates how to print the captured image using this method. In the sample, first function CGDIView::OnPrepareDC(...) is overridden, within which the number of required pages is calculated as follows:
(Code omitted)
Here, the device resolution is retrieved by calling function CDC::GetDeviceCaps(...). Because the device mapping mode is MM_LOMETRIC, which may cause the values returned by this function to be negative for vertical dimensions, we need to use absolute value when doing the calculation.
Within function CGDIView::OnPrint(...), we print the corresponding portion of the image according to the current page number. This procedure can be illustrated in Figure 12-7.
The shaded area represents the image. It is divided into horizontal and vertical cells, each cell has a dimension that is the same with target device resolution. To draw the image, we need nine pages, each page print one cell that is labeled (v, u). First we need to calculate the cell label from the page number:
v = (page number - 1)/(number of horizontal cells)
u = (page number -1)%(number of horizontal cells)
Here the page number starts from 1. The next step is to calculate the position and the dimension of the cell. Obviously, the origin of a cell rectangle can be calculated as follows:
origin X = u((device horizontal resolution)
origin Y = v((device vertical resolution)
If the cell is not the one located right-most or bottom-most, the horizontal and vertical sizes of the cell can be determined from the resolution of target device. If it is located right-most (i.e., cells (0, 2), (1, 2) and (2, 2) in Figure 12-7), the horizontal size can be calculated using the following formulae:
size X = image horizontal size ( u ( ( cell horizontal size )
Similarly, if the cell is located bottom-most, the cell's height can be calculated using the following formulae:
size Y = image vertical size ( v ( ( cell vertical size )
The following shows the procedure of calculating the dimension of a cell in function CGDIView::OnPrint(...) (Sample 12.5-2\GDI):
(Code omitted)
Variables nRepX and nRepY are used to store the number of cells in horizontal and vertical directions, and variable rectDC stores the device resolution. When a cell is copied, its dimension is calculated and stored to variable rect. The following portion of function CGDIView::OnPrint(...) shows how a cell is output to the device:
(Code omitted)
Calculating the Number of Pages when the Printing Is Undergoing
Sample 12.5-3\GDI uses an alternate method to calculate the number of required pages for printing. It is based on sample 12.4\GDI. Instead of calculating the number of pages before printing begins, we can set the number of pages to the maximum value then start printing. Each time a page is printed, we check if all of the image has been output to the printer. If so, we can stop printing.
The advantage of this method is that the actual number of required pages need not to be decided beforehand. This is especially useful for the situation when data cannot be formatted before being printed.
By applying this method, we do not need to calculate the number of required pages in function CGDIView::OnPrepareDC(...). Instead, we can first set it to the maximum possible value in function CGDIView::OnPreparePrinting(...):
(Code omitted)
Please note that we must use 0xFFFFFFFE instead of 0xFFFFFFFF to set the maximum page range, the latter will result in printing only the first page.
If we do not add further control, the printing will not stop until 0xFFFFFFFE pages have been printed. To stop printing after all the image has been printed out, we need to calculate the total required number of pages and check if the page currently being printed is the last page in function CGDIView::OnPrint(...). If so, we set the page range again to stop printing:
(Code omitted)
In our case the number of pages can actually be decided before the printing begins, so it seems not necessary to stop printing this way. However, for applications that the number of pages cannot be decided beforehand, this is the only method to implement multiple-page printing.
12.6 Customizing Print Dialog Box
In this section, we will discuss the topics on how to enhance the user interface for implementing print set up, which has nothing to do with GDI.
Customizing Common Controls
One customization we want to make is to disable radio buttons labeled "Pages" and "Selection", along with the edit boxes labeled "from:" and "to:" after the user executes "File | Print" command (Figure 12-8). This dialog box is implemented by print common dialog box, which was not discussed in Chapter 7. The purpose of this dialog box is to let the user setup the printer before the printing task starts. In MFC, the class used to implement this dialog box is CPrintDialog.
In standard SDI and MDI applications, we don't need to add anything in order to include the print dialog box. The print dialog box can be used to do either print setup or printer setup. The constructor of CPrintDialog must have at least one input parameter, which indicates if the dialog box should be implemented for print setup or printer setup:
(Code omitted)
Since the initialization procedure of the print dialog box is implemented within other MFC member functions, as a programmer, we can only modify the dialog box after it is created. Remember in function CGDIView::OnPreparePrinting(...), one of the input parameters is a CPrintInfo type pointer. The print dialog box is embedded in this class. The member used to store the dialog box is CPrintDialog type pointer CPrintInfo::m_pPD. We can modify any of its member to change the dialog box style before function CView::DoPreparePrinting(...) is called.
Class CPrintDialog contains a PRINTDLG type object m_pd that allows us to customize the style of the dialog box. Here structure PRINTDLG is similar to OPENFILENAME structure of class CFileDialog. It also has a member Flags that allows us to specify the styles of the print dialog box. Two flags we will use in the sample are listed in the following table:
(Table omitted)
Sample 12.6-1\GDI is based on sample 12.5-3\GDI, it demonstrates how to disable these controls. The following is a portion of function CGDIView::OnPreparePrinting(...) of sample 12.6-1\GDI showing how the styles of the print dialog box are customized:
(Code omitted)
Other styles can also be customized by using this method.
Using Custom Dialog Template
Like other common dialog boxes, we can use our own dialog template to replace the standard one. This procedure is similar to that of other common dialog boxes. To use custom dialog template, we need to make changes to the default values of the following members contained in structure PRINTDLG: 1) Enable PD_ENABLEPRINTTEMPLATE flag. 2) Assign the custom dialog template name to member lpPrintTemplateName. 3) Assign the application instance handle to member hInstance.
Sample 12.6-2\GDI is based on sample 12.5-3\GDI and demonstrates how to use programmer- provided dialog template to implement print dialog box.
The first step of implementing customized print dialog box is to add a new dialog template. We can copy this dialog template from file "Commdlg.dll" and modify it. Please note that we cannot delete the original controls contained in the template. If we want to hide certain controls, we can either move them out of the template, or disable them in the dialog box's initialization stage. In sample 12.6-2\GDI, the new dialog template is PRINTDLG. For the purpose of demonstration, no change is made to the original template.
The next step is to derive a class from CPrintDialog. If we double click on the dialog template, we will be prompted to add a new class for it. Since CPrintDialog is not in the list of base classes, we can first choose CDialog as the base class and change all the keywords CDialog to CPrintDialog later. Please note that the constructors of CPrintDialog and CDialog are different, so if we choose automatic method, we also need to change the constructor created by the Class Wizard. In the sample, the new class is CPrnDlg. There is no new variable or function added to it because we don't want to make further modification. The only thing implemented in the new class is that flags PD_NOPAGENUMS and PD_NOSELECTION are set in the constructor so that the radio buttons and edit boxes will be disabled. This is the same with sample 12.6-1\GDI.
The place where we can use this dialog box is still in function CGDIView::OnPreparePrinting(...). Since there is a default print dialog box implemented, we must delete the old one before we can use our own. After we allocate memory and implement a new print dialog box, we must assign it to member CPrintInfo::m_pPD. Also, we must set template name, instance, and enable PD_ENABLEPRINTTEMPLATE flag. The following shows how the customized print dialog is implemented in function CGDIView:: OnPreparePrinting(...):
(Code omitted)
With the above implementation, the print dialog box will use the custom dialog template PRINTDLG.
Summary
1) To capture the screen, we need to obtain a DC of the desktop window, prepare blank memory bitmap, and call function CDC::BitBlt(...) to make the copy.
2) For palette devices, we must obtain the system palette after a snapshot is taken. This can be implemented by creating logical palette using PC_EXPLICIT flags.
3) One logical unit can be mapped to different size on the target device. If we use MM_TEXT mode, one logical unit will be mapped to one physical unit. We can also use other mapping modes to map one logical unit to an absolute length.
4) To fit the output within the target device, we need to know the resolution of the device, along with the number of pixels contained in one inch for both horizontal and vertical directions. These parameters can be obtained by calling function CDC::GetDeviceCaps(...) and using flags HORZRES, VERTRES, LOGPIXELSX and LOGPIXELSY.
5) If we want the output image to have the same size on any type of target devices, we need to use a mapping mode other than MM_TEXT.
6) If the number of pages is fixed for printing output, we can call CPrintInfo::SetMaxPage(...) in function CView::OnPreparePrinting(...) to set the page range. The page number will be passed to function CView::OnPrint(...) to let us customize the print output.
7) If the number of pages can only be decided just before the printing starts, we can call CPrintInfo::SetMaxPage(...) in function CView::BeginPrinting(...) or CView::OnPrepareDC(...).
8) If the number of pages must be decided when the printing is undergoing, we can set the page range to its maximum value before the printing starts, and call CPrintInfo::SetMaxPage(...) to stop printing dynamically in function CView::OnPrint(...).