Sdílet prostřednictvím


Quickstart: Add printing to your app (DirectX)

[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]

You might want to add Direct2D print functionality to your Windows Store app using DirectX with C++. Here we'll show you how.

At a high level, to print from your Windows Store app using DirectX, your app must:

  • Use a multithreaded Direct2D factory to handle multiple threads.

  • Register for the Print contract in each display of the app that you want users to be able to print from.

  • Provide formatted content for print preview.

  • Provide formatted content to print.

Prerequisites

You must:

Instructions

Step 1: Open your app's source code for editing

This procedure shows how to open the Direct2D printing for Windows Store apps sample. If you are using your own app, open it in Visual Studio and skip to the next step.

Note  The code examples shown in this tutorial refer to the Direct2D printing for Windows Store apps sample. You might need to add more code from the sample app to use these examples in your own app.

 

  1. Go to the Direct2D printing for Windows Store apps sample and download the C++ example to your computer.
  2. In Visual Studio, click File > Open Project and go to the folder that contains the solution file of the sample app that you just downloaded.
  3. Select the D2DPrinting solution file and click Open.

Step 2: Build and test the app

  1. Click Build > Build Solution to build the app you are working on. Make sure that there are no error messages in the Output pane at the bottom of the screen.
  2. Click Debug > Start Without Debugging.
  3. Verify that, after a few seconds, the D2DPrinting app starts.
  4. If the app runs without error, return to Visual Studio and click Debug > Stop Debugging.

Step 3: Use a multithreaded Direct2D factory to handle multiple threads

Your first step is to determine whether you must handle multiple threads for printing in your app. In Windows Store apps, printing is always invoked in a separate thread. This means that an app uses one thread to display content and one or more working threads to preview or print the same content.

If your app makes calls only to Direct2D, you can skip this section; no Microsoft Direct3D resources are accessed on multiple threads simultaneously and no resource-access conflicts occur. But if your app makes calls to both Direct2D and Direct3D, you must synchronize calls to the two APIs to accomplish Direct2D printing. To do this, your app must enable Direct2D factory locking, explicitly require a lock from Direct2D, and then apply that lock to control exclusive resource access during preview bitmap preparation. To correct multithreading:

  1. In your source code, make sure that the Direct2D factory uses the D2D1_FACTORY_TYPE_MULTI_THREADED flag.

    // These are the resources required independent of the device.
    void DirectXBase::CreateDeviceIndependentResources()
    {
        D2D1_FACTORY_OPTIONS options;
        ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));
    
    #if defined(_DEBUG)
        // If the project is in a debug build, enable Direct2D debugging via SDK Layers.
        options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
    #endif
    
        DX::ThrowIfFailed(
            D2D1CreateFactory(
                D2D1_FACTORY_TYPE_MULTI_THREADED,
                __uuidof(ID2D1Factory1),
                &options,
                &m_d2dFactory
                )
            );
    
  2. (Optional) Create an RAII (Resource Acquisition Is Initialization) class for manually acquiring and releasing the Direct2D lock.

    Note  We recommend that you use the RAII pattern to ensure that Direct2D lock acquisition and releasing always happens in pairs, even in error cases. This RAII class is not required, but using the Direct2D lock to guard Direct3D API calls is required.

     

    // RAII (Resource Acquisition Is Initialization) class for manually
    // acquiring/releasing the D2D lock.
    class D2DFactoryLock
    {
    public:
        D2DFactoryLock(_In_ ID2D1Factory* d2dFactory)
        {
            DX::ThrowIfFailed(
                d2dFactory->QueryInterface(IID_PPV_ARGS(&m_d2dMultithread))
                );
    
            m_d2dMultithread->Enter();
        }
    
        ~D2DFactoryLock()
        {
            m_d2dMultithread->Leave();
        }
    
    private:
        Microsoft::WRL::ComPtr<ID2D1Multithread> m_d2dMultithread;
    };
    
  3. Provide separate contexts for content display, preview, and printing to guarantee resource synchronization.

    Note  Direct2D resource calls are interleaved, so content to be printed might be partly altered by a display refresh. That is because calls to display, preview, and print may not be made at the same time. For more info about how to avoid making calls concurrently, see the "Ensuring Atomicity of Stateful Operations" section in Multithreaded Direct2D Apps.

     

    void PageRenderer::DrawPreviewSurface(
        _In_  float                             width,
        _In_  float                             height,
        _In_  float                             scale,
        _In_  D2D1_RECT_F                       contentBox,
        _In_  uint32                            desiredJobPage,
        _In_  IPrintPreviewDxgiPackageTarget*   previewTarget
        )
    {
        // We are accessing D3D resources directly without D2D's knowledge, so we
        // must manually acquire the D2D factory lock.
        //
        // Note: it's absolutely critical that the factory lock be released upon
        // exiting this function, or else the entire app will deadlock. This is
        // ensured via the following RAII class.
        D2DFactoryLock factoryLock(m_d2dFactory.Get());
    

Step 4: Register for the Print contract

  1. When your app registers for the Print contract, it can then access Windows printing. Get a PrintManager object and associate it to the PrintTaskRequested event. In this example, a new function called InitPrintManager() is added, to be called during app initialization.

    void DirectXApp::InitPrintManager()
    {
        // Call static method to get PrintManager object.
        m_printManager = Windows::Graphics::Printing::PrintManager::GetForCurrentView();
    
        // Start print task event.
        m_printManager->PrintTaskRequested +=
            ref new TypedEventHandler<PrintManager^, PrintTaskRequestedEventArgs^>(this, &DirectXApp::SetPrintTask);
    }
    
  2. Create a new class called CDocumentSource to interact with the Print Manager.

    Note  This new class retains printing-related values (such as page size and page margin), and generates pages for print preview and printing upon request from the Print Manager.

     

    void DirectXApp::SetPrintTask(
        _In_ PrintManager^ /*sender*/,
        _In_ PrintTaskRequestedEventArgs^ args
        )
    {
        PrintTaskSourceRequestedHandler^ sourceHandler =
            ref new PrintTaskSourceRequestedHandler([this](PrintTaskSourceRequestedArgs^ args)-> void{
                Microsoft::WRL::ComPtr<CDocumentSource> documentSource;
                DX::ThrowIfFailed(
                    Microsoft::WRL::MakeAndInitialize<CDocumentSource>(&documentSource, reinterpret_cast<IUnknown*>(m_renderer))
                    );
    
                // Here CDocumentSource is inherited from IPrintDocumentSource, cast it back to an object.
                IPrintDocumentSource^ objSource(
                    reinterpret_cast<IPrintDocumentSource^>(documentSource.Get())
                    );
    
                args->SetSource(objSource);
            });
    
        // Request initializing print task.
        PrintTask^ printTask = args->Request->CreatePrintTask(
            L"Direct2D image and effect printing sample",
            sourceHandler
            );
    }
    

Step 5: Provide formatted content for print preview

  • Generate preview bitmaps upon request from the Print Manager. In this example, the method CDocumentSource::MakePage() internally calls PageRenderer::DrawPreviewSurface() to draw to the page preview.

    // This sample only acts upon orientation setting for an example. The orientation is read from the user selection
    // in the Print Experience and is then used to reflow the content in a different way.
    IFACEMETHODIMP
    CDocumentSource::MakePage(
        _In_ uint32 desiredJobPage,
        _In_ float  width,
        _In_ float  height
        )
    {
        HRESULT hr = (width > 0 && height > 0) ? S_OK : E_INVALIDARG;
    
        // When desiredJobPage is JOB_PAGE_APPLICATION_DEFINED, it means a new preview begins. If the implementation here is by an async way,
        // for example, queue MakePage calls for preview, app needs to clean resources for previous preview before next.
        // In this sample, we will reset page number if Paginate() has been called.
        if (desiredJobPage == JOB_PAGE_APPLICATION_DEFINED && m_paginateCalled)
        {
            desiredJobPage = 1;
        }
    
        if (SUCCEEDED(hr) && m_paginateCalled)
        {
            // Calculate the size of preview surface, according to desired width and height.
            Windows::Foundation::Size previewSize;
            float scale = TransformedPageSize(width, height, &previewSize);
    
            try
            {
                m_renderer->DrawPreviewSurface(
                    previewSize.Width,
                    previewSize.Height,
                    scale,
                    m_imageableRect,
                    desiredJobPage,
                    m_dxgiPreviewTarget.Get()
                    );
            }
    

Step 6: Provide formatted content to print

  • Initiate and complete the actual print job upon request from the Print Manager. In the example below, CDocumentSource::MakeDocument() internally calls PageRenderer::CreatePrintControl(), PrintPage(), and ClosePrintControl() to create a Direct2D print control, draw and add a page, and then close the print control.

    IFACEMETHODIMP
    CDocumentSource::MakeDocument(
        _In_ IInspectable*                docOptions,
        _In_ IPrintDocumentPackageTarget* docPackageTarget
        )
    {
        if (docOptions == nullptr || docPackageTarget == nullptr)
        {
            return E_INVALIDARG;
        }
    
        // Get print settings from PrintTaskOptions for printing, such as page description, which contains page size, imageable area, DPI.
        // User can obtain other print settings in the same way, such as ColorMode, NumberOfCopies, etc., which are not shown in this sample.
        PrintTaskOptions^ option = reinterpret_cast<PrintTaskOptions^>(docOptions);
        PrintPageDescription pageDesc = option->GetPageDescription(1); // Get the description of the first page.
    
        // Create a print control properties and set DPI from PrintPageDescription.
        D2D1_PRINT_CONTROL_PROPERTIES printControlProperties;
    
        printControlProperties.rasterDPI  = (float)(min(pageDesc.DpiX, pageDesc.DpiY)); // DPI for rasterization of all unsupported D2D commands or options
        printControlProperties.colorSpace = D2D1_COLOR_SPACE_SRGB;                  // Color space for vector graphics in D2D print control.
        printControlProperties.fontSubset = D2D1_PRINT_FONT_SUBSET_MODE_DEFAULT;    // Subset for used glyphs, send and discard font resource after every five pages
    
        HRESULT hr = S_OK;
    
        try
        {
            // Create a new print control linked to the package target.
            m_renderer->CreatePrintControl(
                docPackageTarget,
                &printControlProperties
                );
    
            // Calculate imageable area and page size from PrintPageDescription.
            D2D1_RECT_F imageableRect = D2D1::RectF(
                pageDesc.ImageableRect.X,
                pageDesc.ImageableRect.Y,
                pageDesc.ImageableRect.X + pageDesc.ImageableRect.Width,
                pageDesc.ImageableRect.Y + pageDesc.ImageableRect.Height
                );
    
            D2D1_SIZE_F pageSize = D2D1::SizeF(pageDesc.PageSize.Width, pageDesc.PageSize.Height);
    
            // Loop to add page command list to d2d print control.
            for (uint32 pageNum = 1; pageNum <= m_totalPages; ++pageNum)
            {
                m_renderer->PrintPage(
                    pageNum,
                    imageableRect,
                    pageSize,
                    nullptr // If a page-level print ticket is not specified here, the package print ticket is applied for each page.
                    );
            }
        }
        catch (Platform::Exception^ e)
        {
            hr = e->HResult;
    
            if (hr == D2DERR_RECREATE_TARGET)
            {
                // In case of device lost, the whole print job will be aborted, and we should recover
                // so that the device is ready when used again. At the same time, we should propagate
                // this error to the Print Dialog.
                m_renderer->HandleDeviceLost();
            }
        }
    
        // Make sure to close d2d print control even if AddPage fails.
        HRESULT hrClose = m_renderer->ClosePrintControl();
    
        if (SUCCEEDED(hr))
        {
            hr = hrClose;
        }
    
        return hr;
    }
    

Remarks

Summary and next steps

In this Quickstart, you added basic Direct2D printing to your app.

The next tutorial, How to format content for printing, shows in detail how to provide formatted content to print. Your app can give users even more print options, as How to retrieve and change print settings demonstrates.

And for more printing scenarios that are available in Direct2D printing, like error handling, see the Direct2D printing for Windows Store apps sample app.

Direct2D printing from Windows Store apps

Printing and Command Lists

Printing

Improving the performance of Direct2D apps (Windows)

How to format content for printing

How to retrieve and change print settings