无需用户干预即可使用免费的 CutePDF Writer
一个 C++/MFC 类,允许你无需用户干预即可使用免费的 CutePDF Writer
引言
本文介绍了一个类,它是一个针对免费 CutePDF Writer 缺陷的解决方案。该缺陷是“另存为”对话框总是弹出,因此无法在没有用户交互的情况下打印到 PDF 文件。这个类使得无需用户干预即可使用免费的 CutePDF Writer 成为可能。
背景
我有一个小型应用程序,计划每天早上 5:00 运行。它的工作是打印出前一天活动的摘要报告。我过去常常把它打印在纸上,但我发现这是浪费纸张,因为我通常只读一次然后就扔掉。所以我决定最好简单地打印到 PDF 文件中,如果需要的话,我可以制作一个硬拷贝。我的系统上已经安装了 CutePDF Writer,所以很自然地我使用了它。
我的第一次尝试,效果相当好,是简单地使用 FindWindow
API 找到“另存为”对话框,并通过向对话框发送 BN_CLICKED
命令来模拟点击保存按钮。但是这种方法有几个问题,包括 FindWindow
的不可靠性,以及如果另一个应用程序也碰巧同时打开一个另存为对话框会发生什么。
我遇到的主要问题是指定 CutePDF Writer 将 PDF 文件保存到的文件夹。只要没有其他应用程序也使用 CutePDF Writer 打印 PDF 文件,一切都很好,因为它总是将“另存为”对话框中的文件夹默认为任何应用程序使用的最后一个文件夹。然后我发现上次使用的文件夹保存在 Windows 注册表中,并且我意识到,通过更改该注册表值以指向我想保存 PDF 文件的文件夹,“另存为”对话框将使用我想要的文件夹,而不是其他随机应用程序上次使用的文件夹。
我还尝试修复实际找到正确的“另存为”对话框的问题。该对话框不是调用应用程序的子窗口,而是 *CPWSave.exe* 应用程序的子窗口。所以我使用 EnumWindows
API 列出系统上的所有顶级窗口。在回调函数中,我首先检查顶级窗口是否是对话框,方法是检查类名是否为“#32770
”,这是对话框的类名。如果是,那么我检查对话框是否由 *CPWSave.exe* 程序拥有。我可能会添加更多检查,但在这一点上,我可以相当确定找到的窗口是正确的“另存为”对话框。
BOOL CALLBACK CCutePDFWriter::GetSaveAsDialogProc(HWND hWnd, LPARAM lp)
{
BOOL result = TRUE;
TCHAR Buffer[MAX_PATH + 1] = {0};
GetClassName(hWnd, Buffer, _countof(Buffer));
// is this window a dialog?
if (_tcsicmp(Buffer, DIALOG_CLASS_NAME) == 0)
{
DWORD ProcessID = 0;
// Get the ID of the app that owns this dialog
GetWindowThreadProcessId(hWnd, &ProcessID);
HANDLE hProcess = OpenProcess (PROCESS_QUERY_INFORMATION
| PROCESS_VM_READ
, FALSE
, ProcessID);
if (NULL != hProcess)
{
// get the name of the exe file
GetModuleBaseName(hProcess, NULL, Buffer, _countof(Buffer));
// was this dialog created by the CPWSave program?
if (_tcsicmp(Buffer, EXE_FILE_NAME) == 0)
{
datastruct *dsp = (datastruct*)lp;
dsp->hWnd = hWnd;
dsp->ProcessID = ProcessID;
result = FALSE;
}
CloseHandle(hProcess);
}
}
return result;
}
我还想解决如果另一个应用程序已经在使用 CutePDF Writer 并且正在等待用户点击活动对话框的“保存”按钮会发生什么。我的解决方案是在类构造函数中查找一个活动对话框,如果找到一个,只需等待它关闭。这不是理想的解决方案,因为它会让使用该类的应用程序看起来挂起。
CCutePDFWriter::CCutePDFWriter(void)
: SavedDC(0)
, CPWProcessID(0)
{
// Check if the CutePDF Writer is already processing a file
HWND hWnd = GetSaveAsDialog();
if (NULL != hWnd)
{
// if it is then we wait for it to finish before we proceed
HANDLE ProcessHandle = OpenProcess(SYNCHRONIZE, FALSE, CPWProcessID);
WaitForSingleObject(ProcessHandle, INFINITE);
CloseHandle(ProcessHandle);
Using the Code
要使用代码,只需声明一个 CCutePDFWriter
类对象,调用它的 GetDC(LPCTSTR Folder)
方法来获取 PDF 打印机设备上下文。您在 GetDC
调用中指定 PDF 文件将保存在其中的文件夹的完整路径。如果该文件夹不存在,它将通过调用 SHCreateDirectoryEx
来创建。如果 GetDC
返回 NULL
,您可以通过调用 GetLastError()
来找出原因。文件的实际名称是在您调用 StartDoc()
函数时指定的。完成打印 PDF 文件后,您可以调用 ReleaseDC()
方法,或者让 CCutePDFWriter
对象超出范围。
CCutePDFWriter PDF_Printer;
CDC *pDC = PDF_Printer.GetDC(_T("C:\\My_PDF_Folder\\"));
pDC->StartDoc(_T("My_PDF_File"));
pDC->StartPage();
pDC->Ellipse(100, 200, 300, 400);
pDC->EndPage();
pDC->EndDoc();
PDF_Printer.ReleaseDC();
运行演示代码后,您将获得一个 PDF 文件,其中包含绘制在页面右上角的圆圈。该文件将是 *C:\My_PDF_Folder\My_PDF_File.pdf*。
缺点
此类确实有一些缺点。其中包括“另存为”对话框确实仍然会短暂弹出,但通常时间太短,不会打扰任何人,除了它会在一段时间内获取输入焦点这一事实。另一个是我使用 Sleep
函数来尝试避免竞争条件,因为我试图让 CutePDF writer 在此代码之前而不是之后更改注册表设置。
历史
- 2009 年 12 月 7 日 - 发布到 CodeProject