Home > Articles > Programming > User Interface (UI)

This chapter is from the book

Using the Printing Framework

As we've seen, wxPrinterDC can be created and used directly. However, a more flexible method is to use the wxWidgets printing framework to "drive" printing. The main task for the developer is to derive a new class from wxPrintout, overriding functions that specify how to print a page (OnPrintPage), how many pages there are (GetPageInfo), document setup (OnPreparePrinting), and so on. The wxWidgets printing framework will show the print dialog, create the printer device context, and call appropriate wxPrintout functions when appropriate. The same printout class can be used for both printing and preview.

To start printing, a wxPrintout object is passed to a wxPrinter object, and Print is called to kick off the printing process, showing a print dialog before printing the pages specified by the layout object and the user. For example:

// A global object storing print settings
wxPrintDialogData g_printDialogData;

// Handler for Print menu item
void MyFrame::OnPrint(wxCommandEvent& event)
{

    wxPrinter printer(& g_printDialogData);
    MyPrintout printout(wxT("My printout"));

    if (!printer.Print(this, &printout, true))
    {
        if (wxPrinter::GetLastError() == wxPRINTER_ERROR)
            wxMessageBox(wxT("There was a problem printing.\nPerhaps your current printer is not set correctly?"), wxT("Printing"), wxOK);
        else
            wxMessageBox(wxT("You cancelled printing"),
                         wxT("Printing"), wxOK);
    }
    else
    {
        (*g_printDialogData) = printer.GetPrintDialogData();
    }
}

Because the Print function returns only after all pages have been rendered and sent to the printer, the printout object can be created on the stack.

The wxPrintDialogData class stores data related to the print dialog, such as the pages the user selected for printing and the number of copies to be printed. It's a good idea to keep a global wxPrintDialogData object in your application to store the last settings selected by the user. You can pass a pointer to this data to wxPrinter to be used in the print dialog, and then if printing is successful, copy the settings back from wxPrinter to your global object, as in the previous example. (In a real application, g_printDialogData would probably be a data member of your application class.) See Chapter 8, "Using Standard Dialogs," for more about print and page dialogs and how to use them.

To preview the document, create a wxPrintPreview object, passing two printout objects to it: one for the preview and one to use for printing if the user requests it. You can also pass a wxPrintDialogData object so that the preview picks up settings that the user chose earlier. Then pass the preview object to wxPreviewFrame, call the frame's Initialize function, and show the frame. For example:

// Handler for Preview menu item
void MyFrame::OnPreview(wxCommandEvent& event)
{
    wxPrintPreview *preview = new wxPrintPreview(
                             new MyPrintout, new MyPrintout,
                             & g_printDialogData);
    if (!preview->Ok())
    {
        delete preview;
        wxMessageBox(wxT("There was a problem previewing.\nPerhaps your current printer is not set correctly?"),
                     wxT("Previewing"), wxOK);
        return;
    }

    wxPreviewFrame *frame = new wxPreviewFrame(preview, this,
                             wxT("Demo Print Preview"));
    frame->Centre(wxBOTH);
    frame->Initialize();
    frame->Show(true);
}

When the preview frame is initialized, it disables all other top-level windows in order to avoid actions that might cause the document to be edited after the print or preview process has started. Closing the frame automatically destroys the two printout objects. Figure 5-9 shows the print preview window, with a control bar along the top providing page navigation, printing, and zoom control.

05fig09.gif

Figure 5-9 Print preview window

More on wxPrintout

When creating a printout object, the application can pass an optional title that will appear in the print manager under some operating systems. You will need to provide at least GetPageInfo, HasPage, and OnPrintPage, but you can override any of the other methods below as well.

GetPageInfo should be overridden to return minPage, maxPage, pageFrom, and pageTo. The first two integers represent the range supported by this printout object for the current document, and the second two integers represent a user selection (not currently used by wxWidgets). The default values for minPage and maxPage are 1 and 32,000, respectively. However, the printout will stop printing if HasPage returns false. Typically, your OnPreparePrinting function will calculate the values returned by GetPageInfo and will look something like this:

void MyPrintout::GetPageInfo(int *minPage, int *maxPage,
                             int *pageFrom, int *pageTo)
{
    *minPage = 1; *maxPage = m_numPages;
    *pageFrom = 1; *pageTo = m_numPages;
}

HasPage must return false if the argument is outside the current page range. Often its implementation will look like this, where m_numPages has been calculated in OnPreparePrinting:

bool MyPrintout::HasPage(int pageNum)
{
    return (pageNum >= 1 && pageNum <= m_numPages);
}

OnPreparePrinting is called before the print or preview process commences, and overriding it enables the application to do various setup tasks, including calculating the number of pages in the document. OnPreparePrinting can call wxPrintout functions such as GetDC, GetPageSizeMM, IsPreview, and so on to get the information it needs.

OnBeginDocument is called with the start and end page numbers when each document copy is about to be printed, and if overridden, it must call the base wxPrintout::OnBeginDocument function. Similarly, wxPrintout::OnEndDocument must be called if overridden.

OnBeginPrinting is called once for the printing cycle, regardless of the number of copies, and OnEndPrinting is called at the end.

OnPrintPage is passed a page number, and the application should override it to return true if the page was successfully printed (returning false cancels the print job). This function will use wxPrintout::GetDC to get the device context to draw on.

The following are the utility functions you can use in your overridden functions, and they do not need to be overridden.

IsPreview can be called to determine whether this is a real print task or a preview.

GetDC returns a suitable device context for the current task. When printing, a wxPrinterDC will be returned, and when previewing, a wxMemoryDC will be returned because a preview is rendered into a bitmap via a memory device context.

GetPageSizeMM returns the size of the printer page in millimeters, whereas GetPageSizePixels returns the size in pixels (the maximum resolution of the printer). For a preview, this will not be the same as the size returned by wxDC::GetSize, which will return the preview bitmap size.

GetPPIPrinter returns the number of pixels per logical inch for the current device context, and GetPPIScreen returns the number of pixels per logical inch of the screen.

Scaling for Printing and Previewing

When drawing on a window, you probably don't concern yourself about scaling your graphics because displays tend to have similar resolutions. However, there are several factors to take into account when drawing to a printer:

  • You need to scale and position graphics to fit the width of the page, and break the graphics into pages if necessary.
  • Fonts are based on screen resolution, so when drawing text, you need to set a scale so that the printer device context matches the screen resolution. Dividing the printer resolution (GetPPIPrinter) by the screen resolution (GetPPIScreen) can give a suitable scaling factor for drawing text.
  • When rendering the preview, wxWidgets uses a wxMemoryDC to draw into a bitmap. The size of the bitmap (returned by wxDC::GetSize) depends on the zoom scale, and an extra scale factor must be calculated to deal with this. Divide the size returned by GetSize by the actual page size returned by GetPageSizePixels to get this scale factor. This value should be multiplied by any other scale you calculated.

You can use wxDC::SetUserScale to let the device context perform the scaling for subsequent graphics operations and wxDC::SetDeviceOrigin to set the origin (for example, to center a graphic on a page). You can keep calling these scaling and device origin functions for different parts of your graphics, on the same page if necessary.

The wxWidgets sample in samples/printing shows how to do scaling. The following example shows a function adapted from the printing sample, which scales and positions a 200x200 pixel graphic on a printer or preview device context.

void MyPrintout::DrawPageOne(wxDC *dc)
{
    // You might use THIS code if you were scaling
    // graphics of known size to fit on the page.

    // We know the graphic is 200x200. If we didn't know this,
    // we'd need to calculate it.
    float maxX = 200;
    float maxY = 200;

    // Let's have at least 50 device units margin
    float marginX = 50;
    float marginY = 50;

    // Add the margin to the graphic size
    maxX += (2*marginX);
    maxY += (2*marginY);

    // Get the size of the DC in pixels
    int w, h;
    dc->GetSize(&w, &h);

    // Calculate a suitable scaling factor
    float scaleX=(float)(w/maxX);
    float scaleY=(float)(h/maxY);

    // Use x or y scaling factor, whichever fits on the DC
    float actualScale = wxMin(scaleX,scaleY);

    // Calculate the position on the DC for centring the graphic
    float posX = (float)((w - (200*actualScale))/2.0);
    float posY = (float)((h - (200*actualScale))/2.0);

    // Set the scale and origin
    dc->SetUserScale(actualScale, actualScale);
    dc->SetDeviceOrigin( (long)posX, (long)posY );

    // Now do the actual drawing
    dc.SetBackground(*wxWHITE_BRUSH);
    dc.Clear();
    dc.SetFont(wxGetApp().m_testFont);

    dc.SetBackgroundMode(wxTRANSPARENT);

    dc.SetBrush(*wxCYAN_BRUSH);
    dc.SetPen(*wxRED_PEN);

    dc.DrawRectangle(0, 30, 200, 100);

    dc.DrawText( wxT("Rectangle 200 by 100"), 40, 40);

    dc.SetPen( wxPen(*wxBLACK,0,wxDOT_DASH) );
    dc.DrawEllipse(50, 140, 100, 50);
    dc.SetPen(*wxRED_PEN);

    dc.DrawText( wxT("Test message: this is in 10 point text"),
                 10, 180);
}

In this code, we simply use wxDC::GetSize to get the preview or printer resolution so we can fit the graphic on the page. In this example, we're not interested in the points-per-inch printer resolution, as we might be if we were drawing text or lines of a specific length in millimeters, because the graphic doesn't have to be a precise size: it's just scaled to fit the available space.

Next, we'll show code that prints text at a size to match how it appears on the screen and that also draws lines that have a precise length, rather than simply being scaled to fit.

void MyPrintout::DrawPageTwo(wxDC *dc)
{
    // You might use THIS code to set the printer DC to roughly
    // reflect the screen text size. This page also draws lines of
    // actual length 5cm on the page.

    // Get the logical pixels per inch of screen and printer
    int ppiScreenX, ppiScreenY;
    GetPPIScreen(&ppiScreenX, &ppiScreenY);
    int ppiPrinterX, ppiPrinterY;
    GetPPIPrinter(&ppiPrinterX, &ppiPrinterY);

    // This scales the DC so that the printout roughly represents the
    // the screen scaling.
    float scale = (float)((float)ppiPrinterX/(float)ppiScreenX);

    // Now we have to check in case our real page size is reduced
    // (e.g. because we're drawing to a print preview memory DC)
    int pageWidth, pageHeight;
    int w, h;
    dc->GetSize(&w, &h);
    GetPageSizePixels(&pageWidth, &pageHeight);

    // If printer pageWidth == current DC width, then this doesn't
    // change. But w might be the preview bitmap width,
    // so scale down.
    float overallScale = scale * (float)(w/(float)pageWidth);
    dc->SetUserScale(overallScale, overallScale);

    // Calculate conversion factor for converting millimetres into
    // logical units.
    // There are approx. 25.4 mm to the inch. There are ppi
    // device units to the inch. Therefore 1 mm corresponds to
    // ppi/25.4 device units. We also divide by the
    // screen-to-printer scaling factor, because we need to
    // unscale to pass logical units to DrawLine.

    // Draw 50 mm by 50 mm L shape
    float logUnitsFactor = (float)(ppiPrinterX/(scale*25.4));
    float logUnits = (float)(50*logUnitsFactor);
    dc->SetPen(* wxBLACK_PEN);
    dc->DrawLine(50, 250, (long)(50.0 + logUnits), 250);
    dc->DrawLine(50, 250, 50, (long)(250.0 + logUnits));

    dc->SetBackgroundMode(wxTRANSPARENT);
    dc->SetBrush(*wxTRANSPARENT_BRUSH);

    dc->SetFont(wxGetApp().m_testFont);

    dc->DrawText(wxT("Some test text"), 200, 300 );
}

Printing Under Unix with GTK+

Unlike Mac OS X and Windows, Unix does not provide a standard way to display text and graphics onscreen and print it using the same API. Instead, screen display is done via the X11 library (via GTK+ and wxWidgets), whereas printing has to be done by sending a file of PostScript commands to the printer. Fonts are particularly tricky to handle; until recently, only a small number of applications have offered WYSIWYG (What You See Is What You Get) under Unix. In the past, wxWidgets offered its own printing implementation using PostScript that never fully matched the screen display.

From version 2.8, the GNOME Free Software Desktop Project provides printing support through the libgnomeprint and libgnomeprintui libraries by which most printing problems are solved. Beginning with version 2.5.4, the GTK+ port of wxWidgets can make use of these libraries if wxWidgets is configured accordingly and if the libraries are present. You need to configure wxWidgets with the --with-gnomeprint switch, which will cause your application to search for the GNOME print libraries at runtime. If they are found, printing will be done through these; otherwise, the application will fall back to the old PostScript printing code. Note that the application will not require the GNOME print libraries to be installed in order to run (there is no dependency on these libraries).

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020