c++winapi

How to use IPrintDialogServices interface when using IPrintDialogCallback::SelectionChange() to get and change printer settings?


Tell me please how to use IPrintDialogServices interface when using IPrintDialogCallback::SelectionChange to get and change printer settings.

I'm trying to use this construct, but I don't understand how to initialize the IPrintDialogServices:

struct PrintDialogCallback : public IPrintDialogCallback
{
public:
    PrintDialogCallback()
    {}
private:
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) noexcept final {
        if (riid == IID_IUnknown || riid == IID_IPrintDialogCallback) {
            *ppv = static_cast<IPrintDialogCallback*>(this);
            AddRef();
            return S_OK;
        }
        *ppv = nullptr;
        return E_NOINTERFACE;
    }
    virtual ULONG STDMETHODCALLTYPE AddRef() noexcept final {
        return 1;
    }
    virtual ULONG STDMETHODCALLTYPE Release() noexcept final {
        return 1;
    }
    virtual HRESULT STDMETHODCALLTYPE HandleMessage(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam,
                                                       LRESULT *pResult) noexcept final {
        return S_FALSE;
    }
    virtual HRESULT STDMETHODCALLTYPE InitDone() noexcept final {
        return S_FALSE;
    }
    virtual HRESULT STDMETHODCALLTYPE SelectionChange() noexcept final {
        IPrintDialogServices *pServices = nullptr;
        // ? how to init pServices ?
        if (pServices) {
            wchar_t printerName[256] = {};
            UINT psize = ARRAYSIZE(printerName);
            HRESULT hr = pServices->GetCurrentPrinterName(printerName, &psize);
            if (SUCCEEDED(hr)) {
                wprintf(L"Selected printer: %s\n", printerName);
            }
            // Other settings
            // DEVMODE *pDevMode = ...;
            // UINT pcbSize = sizeof(DEVMODE);
            // hr = pServices->GetCurrentDevMode(pDevMode, &pcbSize);
            // if (SUCCEEDED(hr) && pDevMode) {
            //     wprintf(L"Printer paper size: %d\n", pDevMode->dmPaperSize);
            // }
            pServices->Release();
        }
        return S_FALSE;
    }
};

UPDATE

Please tell me how to change the duplex mode for the selected printer?

I'm trying to use this construct but it is not working:

struct PrintDialogCallback : public IPrintDialogCallback, public IObjectWithSite
{
public:
    PrintDialogCallback()
    {}
private:
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) noexcept final {
        if (riid == IID_IUnknown || riid == IID_IPrintDialogCallback) {
            *ppv = static_cast<IPrintDialogCallback*>(this);
            AddRef();
            return S_OK;
        } else
        if (riid == IID_IObjectWithSite) {
            *ppv = static_cast<IObjectWithSite*>(this);
            AddRef();
            return S_OK;
        }
        *ppv = nullptr;
        return E_NOINTERFACE;
    }
    virtual ULONG STDMETHODCALLTYPE AddRef() noexcept final {
        return 1;
    }
    virtual ULONG STDMETHODCALLTYPE Release() noexcept final {
        return 1;
    }
    virtual HRESULT STDMETHODCALLTYPE HandleMessage(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam,
                                                       LRESULT *pResult) noexcept final {
        return S_FALSE;
    }
    virtual HRESULT STDMETHODCALLTYPE InitDone() noexcept final {
        return S_FALSE;
    }
    virtual HRESULT STDMETHODCALLTYPE SelectionChange() noexcept final {
        if (m_pServices) {
            WCHAR printerName[MAX_PATH];
            UINT nameLen = ARRAYSIZE(printerName);
            if (SUCCEEDED(m_pServices->GetCurrentPrinterName(printerName, &nameLen)) && nameLen > 0) {
                HANDLE hPrinter = NULL;
                if (OpenPrinter(printerName, &hPrinter, NULL)) {
                    UINT devModeSize = 0;
                    if (SUCCEEDED(m_pServices->GetCurrentDevMode(nullptr, &devModeSize)) && devModeSize > 0) {
                        HGLOBAL hDevMode = GlobalAlloc(GHND, devModeSize);
                        LPDEVMODE pDevMode = (LPDEVMODE)GlobalLock(hDevMode);
                        if (SUCCEEDED(m_pServices->GetCurrentDevMode(pDevMode, &devModeSize))) {
                            if (pDevMode->dmFields & DM_DUPLEX) {
                                pDevMode->dmDuplex = DMDUP_VERTICAL;
                                LONG res = DocumentProperties(NULL, hPrinter, printerName, pDevMode, pDevMode, DM_IN_BUFFER | DM_OUT_BUFFER);
                            }
                        }
                        GlobalUnlock(hDevMode);
                        GlobalFree(hDevMode);
                    }
                    ClosePrinter(hPrinter);
                }
            }
        }
        return S_FALSE;
    }
    virtual HRESULT STDMETHODCALLTYPE SetSite(IUnknown *pSite) noexcept final {
        if (m_pServices) {
            m_pServices->Release();
            m_pServices = nullptr;
        }
        if (m_pSite) {
            m_pSite->Release();
            m_pSite = nullptr;
        }
        if (pSite) {
            m_pSite = pSite;
            m_pSite->AddRef();
            m_pSite->QueryInterface(IID_PPV_ARGS(&m_pServices));
        }
        return S_OK;
    }
    virtual HRESULT STDMETHODCALLTYPE GetSite(REFIID riid, void **ppvSite) noexcept final {
        if (m_pSite)
            return m_pSite->QueryInterface(riid, ppvSite);
        *ppvSite = nullptr;
        return E_NOINTERFACE;
    }

    IUnknown *m_pSite = nullptr;
    IPrintDialogServices* m_pServices = nullptr;
};

Solution

  • Per the PRINTDLGEX documentation:

    lpCallback

    Type: LPUNKNOWN

    A pointer to an application-defined callback object.

    The object should contain the IPrintDialogCallback class to receive messages for the child dialog box in the lower portion of the General page.

    The callback object should also contain the IObjectWithSite class to receive a pointer to the IPrintDialogServices interface. The PrintDlgEx function calls IUnknown::QueryInterface on the callback object for both IID_IPrintDialogCallback and IID_IObjectWithSite to determine which interfaces are supported.

    If you do not want to retrieve any of the callback information, set lpCallback to NULL.

    And per the IPrintDialogCallback::InitDone() documentation:

    If your callback object implements the IObjectWithSite interface, the PrintDlgEx function calls the IObjectWithSite::SetSite method to pass an IPrintDialogServices pointer to the callback object. The PrintDlgEx function calls the IObjectWithSite::SetSite method before calling the InitDone method. This enables your InitDone implementation to use the IPrintDialogServices methods to retrieve information about the currently selected printer.

    For example:

    struct PrintDialogCallback : public IObjectWithSite, public IPrintDialogCallback
    {
    private:
        IUnknown* m_Site = nullptr;
        IPrintDialogServices* m_Services = nullptr;
    
    public:
        PrintDialogCallback() = default;
        ~PrintDialogCallback() { SetSite(nullptr); }
    
        virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) noexcept final {
            if (riid == IID_IUnknown) {
                *ppv = static_cast<IUnknown*>(static_cast<IPrintDialogCallback*>(this));
                AddRef();
                return S_OK;
            }
            else if (riid == IID_IObjectWithSite) {
                *ppv = static_cast<IObjectWithSite*>(this);
                AddRef();
                return S_OK;
            }
            else if (riid == IID_IPrintDialogCallback) {
                *ppv = static_cast<IPrintDialogCallback*>(this);
                AddRef();
                return S_OK;
            }
            *ppv = nullptr;
            return E_NOINTERFACE;
        }
    
        virtual ULONG STDMETHODCALLTYPE AddRef() noexcept final {
            return 1;
        }
    
        virtual ULONG STDMETHODCALLTYPE Release() noexcept final {
            return 1;
        }
    
        virtual HRESULT STDMETHODCALLTYPE GetSite(REFIID riid, void **ppvSite) noexcept final {
            if (m_Site)
                return m_Site->QueryInterface(riid, ppvSite);
            *ppvSite = nullptr;
            return E_NOINTERFACE;
        }
    
        virtual HRESULT STDMETHODCALLTYPE SetSite(IUnknown *pUnkSite) noexcept final {
            if (m_Services) {
                m_Services->Release();
                m_Services = nullptr;
            }
            if (m_Site) {
                m_Site->Release();
                m_Site = nullptr;
            }
            if (pUnkSite) {
                m_Site = pUnkSite;
                m_Site->AddRef();
                m_Site->QueryInterface(IID_PPV_ARGS(&m_Services));
            }
            return S_OK;
        }
    
        virtual HRESULT STDMETHODCALLTYPE HandleMessage(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam,
                                                           LRESULT *pResult) noexcept final {
            return S_FALSE;
        }
    
        virtual HRESULT STDMETHODCALLTYPE InitDone() noexcept final {
            return S_FALSE;
        }
    
        virtual HRESULT STDMETHODCALLTYPE SelectionChange() noexcept final {
            if (m_Services) {
                wchar_t printerName[256] = {};
                UINT psize = ARRAYSIZE(printerName);
                HRESULT hr = m_Services->GetCurrentPrinterName(printerName, &psize);
                if (SUCCEEDED(hr)) {
                    wprintf(L"Selected printer: %s\n", printerName);
                }
                // Other settings
                // DEVMODE *pDevMode = ...;
                // UINT pcbSize = sizeof(DEVMODE);
                // hr = m_Services->GetCurrentDevMode(pDevMode, &pcbSize);
                // if (SUCCEEDED(hr) && pDevMode) {
                //     wprintf(L"Printer paper size: %d\n", pDevMode->dmPaperSize);
                // }
                // ...
            }
            return S_FALSE;
        }
    };