【发布时间】:2018-02-01 00:01:04
【问题描述】:
我正在使用 Embarcadero RAD Studio C++ builder XE7 编译器。在一个应用程序项目中,我同时使用 Windows GDI 和 GDI+ 来绘制多个设备上下文。
我的绘图内容是这样的:
在上面的示例中,文本背景和用户图片是使用 GDI+ 绘制的。用户图片也用圆角路径剪裁。所有其他项目(文本和表情符号)都是使用 GDI 绘制的。
当我在屏幕 DC 上绘制时,一切正常。
现在我想绘制一个打印机设备上下文。无论我用于测试的是 Windows 10 中可用的新“导出为 PDF”打印机设备。我准备我的设备上下文以通过这种方式在 A4 视口上绘制:
HDC GetPrinterDC(HWND hWnd) const
{
// initialize the print dialog structure, set PD_RETURNDC to return a printer device context
::PRINTDLG pd = {0};
pd.lStructSize = sizeof(pd);
pd.hwndOwner = hWnd;
pd.Flags = PD_RETURNDC;
// get the printer DC to use
::PrintDlg(&pd);
return pd.hDC;
}
...
void Print()
{
HDC hDC = NULL;
try
{
hDC = GetPrinterDC(Application->Handle);
const TSize srcPage(793, 1123);
const TSize dstPage(::GetDeviceCaps(hDC, PHYSICALWIDTH), ::GetDeviceCaps(hDC, PHYSICALHEIGHT));
const TSize pageMargins(::GetDeviceCaps(hDC, PHYSICALOFFSETX), ::GetDeviceCaps(hDC, PHYSICALOFFSETY));
::SetMapMode(hDC, MM_ISOTROPIC);
::SetWindowExtEx(hDC, srcPage.Width, srcPage.Height, NULL);
::SetViewportExtEx(hDC, dstPage.Width, dstPage.Height, NULL);
::SetViewportOrgEx(hDC, -pageMargins.Width, -pageMargins.Height, NULL);
::DOCINFO di = {sizeof(::DOCINFO), config.m_FormattedTitle.c_str()};
::StartDoc (hDC, &di);
// ... the draw function is executed here ...
::EndDoc(hDC);
return true;
}
__finally
{
if (hDC)
::DeleteDC(hDC);
}
}
在 StartDoc() 和 EndDoc() 函数之间执行的绘图函数与我在屏幕上使用的绘图函数完全相同。唯一的区别是我在整个页面上添加了一个全局剪切矩形,以避免在尺寸太大时绘图与页边距重叠,例如当我在第一个下多次重复上面的绘图时。 (这个是实验性的,后面我会加一个分页的过程,不过这个暂时不是问题)
这是我的剪辑功能:
int Clip(const TRect& rect, HDC hDC)
{
// save current device context state
int savedDC = ::SaveDC(hDC);
HRGN pClipRegion = NULL;
try
{
// reset any previous clip region
::SelectClipRgn(hDC, NULL);
// create clip region
pClipRegion = ::CreateRectRgn(rect.Left, rect.Top, rect.Right, rect.Bottom);
// select new canvas clip region
if (::SelectClipRgn(hDC, pClipRegion) == ERROR)
{
DWORD error = ::GetLastError();
::OutputDebugString(L"Unable to select clip region - error - " << ::IntToStr(error));
}
}
__finally
{
// delete clip region (it was copied internally by the SelectClipRgn())
if (pClipRegion)
::DeleteObject(pClipRegion);
}
return savedDC;
}
void ReleaseClip(int savedDC, HDC hDC)
{
if (!savedDC)
return;
if (!hDC)
return;
// restore previously saved device context
::RestoreDC(hDC, savedDC);
}
如上所述,我预计我的页面周围会有剪辑。然而结果只是一个空白页。如果我绕过剪切功能,所有打印都正确,除了绘图可能在页边距上重叠。另一方面,如果我在屏幕上的任意矩形上应用剪辑,一切正常。
我的剪辑有什么问题?为什么我启用后页面完全崩溃了?
【问题讨论】:
-
一些希望有用的提示:Clip(rect) 函数中的 rect 参数是什么,它使用什么单位,相对于什么?你确定在 draw 和 clip 之间使用兼容的单位吗?
-
一般来说
SelectClipRgn在这方面应该没有问题。你有SelectClipRgn(pCanvas->Handle, ...)和SelectClipRgn(hDC, ...)pCanvas->Handle和hDC是同一个东西吗?我不知道 Delphi,但似乎您创建了HRGN并立即将其删除,所以它不会做任何事情。您应该制作 MCVE 来演示问题。 -
你说得对,这是我的复制错误。实际上 pCanvas->Handle 是 C++ Builder 编译器提供的 TCanvas 对象的一个属性。该对象是 GDI 的包装器,而 Handle 属性实际上是设备上下文 (HDC)。当我复制函数时,为了清楚起见,我直接通过 HDC 更改了函数参数中的 TCanvas,但我省略了一个实例。我修改了源代码,谢谢你的意见。
-
@Niki:rect 参数是 C++ Builder 编译器提供的另一个对象(我在问题的开头提到了这个编译器的用法)。它只是一个包含 4 个参数(左、上、右、下)描述矩形的结构。 AFAIK 它是 GDI RECT 结构的包装器。 Clip 函数用于声明允许绘图的文档的矩形部分,即没有边距的页面。坐标系由视口函数定义。由于剪裁和绘图应用在相同的上下文中,它们的单位通常是兼容的。
-
@Barmak Shemirani:关于 HRGN,这不是 Delphi 对象,而是 GDI 句柄。关于删除,SelectClipRgn() 的 MS 文档说:“仅使用所选区域的副本。可以为任意数量的其他设备上下文选择区域本身,也可以将其删除。”据我了解,这意味着无论何时分配该区域都会在设备内部复制,并且可能会释放他的源区域,这就是我这样做的原因。
标签: printing gdi+ clipping gdi