近日因项目需要接触了打印机,自定义纸张的大小问题困扰了我一个多星期,还以为XP系统无法自定义纸张,后来在网上搜索时无意中发现,打印机都有最小纸张的标准,如果小于或大于这个标准,就算自定义好了页面,都无法选择它(不会显示出来),如果用程序强制设置打印的话,它会按A4的标准走纸,比如我用的Epson LQ-300K+这款打印机,最小纸张是10CM*10.16CM,而我要的宽度正好小于10CM,后来我改成10CM后,就能正常打印了。使用下面第一段例子就可以搞定了!
转自:http://delphi.ktop.com.tw/board.php?cid=30&fid=100&tid=75503
在
WindowsNT/200
環境下要自訂紙張尺寸所使用的方法與
Win9x
不同,
你必須先為目前的印表機定義一個自訂的
"Form"
(呼叫
API: AddForm
,
此
API
宣告於
WinSpool
單元中),然後把這個
Form
的名稱設定給
DEVMODES
結構中的
dmFormName
欄位。以下的函式可以直接拿來使用:
uses
Windows, WinSpool, Printers;
(*------------------------------------------------------
Define a new Form (WinNT/2000 only).
If FormName already exists, do nothing and return.
If failed, an exception will be raised.
------------------------------------------------------*)
procedure
PrnAddForm(
const
FormName:
string
; PaperWidth, PaperLength: integer);
var
PrintDevice, PrintDriver, PrintPort :
array
[
0
..
255
]
of
Char;
hDMode : THandle;
hPrinter: THandle;
FormInfo: TFormInfo1;
PaperSize: TSize;
PaperRect: TRect;
errcode: integer;
s:
string
;
begin
Printer.GetPrinter(PrintDevice, PrintDriver, PrintPort, hDMode);
OpenPrinter(PrintDevice, hPrinter,
nil
);
if
hPrinter =
0
then
raise
Exception.Create(
'Failed to open printer!'
);
FormInfo.Flags := FORM_USER;
FormInfo.pName := PChar(FormName);
PaperSize.cx := PaperWidth;
PaperSize.cy := PaperLength;
PaperRect.Left :=
0
;
PaperRect.Top :=
0
;
PaperRect.Right := PaperWidth;
PaperRect.Bottom := PaperLength;
FormInfo.Size := PaperSize;
FormInfo.ImageableArea := PaperRect;
if not
AddForm(hPrinter,
1
, @FormInfo)
then
begin
errcode := GetLastError;
if
errcode <> ERROR_FILE_EXISTS
then
// Form name exists?
begin
case
errcode
of
ERROR_ACCESS_DENIED: s :=
'Access is denied'
;
ERROR_INVALID_HANDLE: s :=
'The handle is invalid'
;
ERROR_NOT_READY: s :=
'The device is not ready'
;
ERROR_CALL_NOT_IMPLEMENTED:
s :=
'Function "AddForm" is not supported on this system'
;
else
s :=
'Failed to add a Form (paper) name!'
;
end
;
raise
Exception.Create(s);
end
;
end
;
ClosePrinter(hPrinter);
end
;
(*
Set custom paper size for WinNT/2000.
Make sure FormName is supported by current printer,
You can call PrnAddForm to define a new Form.
*)
procedure
PrnSetPaperSizeNT(FormName:
string
; PaperWidth, PaperLength: integer);
var
Device, Driver, Port:
array
[
0
..
80
]
of
Char;
DevMode: THandle;
pDevmode: PDeviceMode;
begin
// Get printer device name etc.
Printer.GetPrinter(Device, Driver, Port, DevMode);
// force reload of DEVMODE
Printer.SetPrinter(Device, Driver, Port,
0
) ;
// get DEVMODE handle
Printer.GetPrinter(Device, Driver, Port, DevMode);
if
DevMode <>
0
then
begin
// lock it to get pointer to DEVMODE record
pDevMode := GlobalLock( DevMode );
if
pDevmode <>
nil then
try
with
pDevmode^
do
begin
// modify form
StrLCopy( dmFormName, PChar(FormName), CCHFORMNAME-
1
);
// tell printer driver that dmFormname field contains
// data it needs to inspect.
dmPaperWidth := PaperWidth;
dmPaperLength := PaperLength;
dmFields := dmFields
or
DM_FORMNAME
or
DM_PAPERWIDTH
or
DM_PAPERLENGTH;
end
;
finally
GlobalUnlock( Devmode );
// unlock devmode handle.
end
;
end
;
{ If }
end
;
procedure
TForm1.Button1Click(Sender: TObject);
begin
PrnAddForm(
edFormName.Text,
StrToInt(edPaperWidth.Text),
StrToInt(edPaperLength.Text)
);
PrnSetPaperSizeNT(
edFormName.Text,
StrToInt(edPaperWidth.Text),
StrToInt(edPaperLength.Text)
);
Printer.BeginDoc;
Printer.Canvas.TextOut(
10
,
10
,
'Printer test!'
);
Printer.EndDoc;
end
;
在
Delphi
帮助中,
AddForm
定义如下:
BOOL AddForm(
HANDLE hPrinter,
// handle to printer object
DWORD Level,
// data-structure level
LPBYTE pForm
// pointer to form info. data structure
);
下面是我在
Delphi
中定义的自定义函数
AddPaper()
:
function
AddPaper(PaperName: PChar;fPaperWidth,fPaperHeigth: Double):
String
;
var
PrintDevice, PrintDriver, PrintPort :
array
[
0
..
255
]
of
Char;
hDMode : THandle;
hPrinter: THandle;
FormInfo: TForminfo1;
PaperSize: TSize;
PaperRect: TRect;
PaperWidth,PaperHeigth: Integer;
function
Zlxs(S:
String
;nWs: Integer):
String
;
//
整理小数位,并转化成厘米
begin
Try
Result:=FloatToStr(StrToFloat(S));
If
pos(
'.'
,Result)>
0
then
Result:=Copy(Result,
1
,pos(
'.'
,Result)+
2
);
Result:=FloatToStr(StrToFloat(Result)*
10000
);
Except
Result:=
'0'
;
end
;
end
;
begin
PaperWidth:=StrToInt(Zlxs(FloatToStr(fPaperWidth),
3
));
Paperheigth:=StrToInt(Zlxs(FloatToStr(fPaperheigth),
3
));
//
判断是否安装打印机,并得到默认打印机的句柄
Printer.GetPrinter(PrintDevice, PrintDriver, PrintPort, hDMode);
OpenPrinter(PrintDevice, hPrinter,
nil
);
if
hPrinter=
0
then
begin
Result:=
'
没有安装打印机!
'
;
Exit;
end
;
//
定义结构
FormInfo.Flags:=FORM_USER;
FormInfo.pName:=PChar(PaperName);
PaperSize.cx:=PaperWidth;
PaperSize.cy:=PaperHeigth;
PaperRect.Left:=
0
;
PaperRect.Top:=
0
;
PaperRect.Right:=PaperSize.cx;
PaperRect.Bottom:=PaperSize.cy;
FormInfo.Size:=PaperSize;
FormInfo.ImageableArea:=PaperRect;
AddForm(hPrinter,
1
,@FormInfo);
//
添加纸张
ClosePrinter(hPrinter);
end
;
3
个参数:
PaperName:
你给纸张命的名(操作系统中叫描述格式),
fPaperWidth
:纸张宽度
,fPaperHeigth
:纸张高度。如果没有安装打印机,返回提示信息。如果已经有同样名称的纸张,函数不起作用,建议大家最好在名称中加入
“_”
,因为很少有这样命名的纸张,你的程序用你的专用纸张也不为过吧(谁叫
Windows
不提供,而我们偏偏又要用呢)?里面还有一个函数:
Zlxs
,这是用来整理小数的,经过试验,加入的纸张采用的单位是厘米时宽度用
10000
时只有
1
厘米,大家输入的往往是以厘米为单位的,且带小数,所以得用一个函数来将浮点数转换成整数。当然首先还得在
uses
段中加入
Printers,winspool
引用。以上代码在
D5,D6+ Win
2000
中运行通过。将这个函数加入管理系统中,在打印之前调用生成专用纸张,省时又省力。
这种方法应该是处理自定义纸张问题的正解,通用性强,也不会浪费打印机的链式(牵引)走纸功能。大家可以根据各自编程工具的方法进行定义,也可以做成
.dll
文件,这样不支持结构的编程工具,如
VFP
等也能使用了。
4
、
其他小技巧:
在使用
OKI
打印机时,我们有时会想把一行比较长的数据打在一张
“US Std Fanfold”
纸上,但
“US Std Fanfold”
宽度
37.78
cm
,象
OKI5330
之类的打印机宽度有限,这么宽的纸放不下啊。我们可以采用自定义的方法实现,首先定义新格式,然后将
“US Std Fanfold”
的宽和高反过来,命个名:
“US Std Fanfold(
纵向
)”
,然后在报表设计中使用这张纸并采用横向打印就行了。
procedure
UpdatePrint(Awidth,Aheight:integer);
const
CustomFormName =
'ZJ Defined'
;
function
Win95SetForm(PDevMode: PDeviceMode): Boolean;
begin
Printer.PrinterIndex := Printer.PrinterIndex;
PDevMode.dmFields := PDevMode.dmFields
or
DM_PAPERSIZE;
PDevMode.dmPaperSize :=
256
;
PDevMode.dmFields := PDevMode.dmFields
or
DM_PAPERWIDTH;
PDevMode.dmPaperWidth := AWidth;
PDevMode.dmFields := PDevMode.dmFields
or
DM_PAPERLENGTH;
PDevMode.dmPaperLength := AHeight;
Printer.PrinterIndex := Printer.PrinterIndex;
Result := True;
end
;
function
WinNTSetForm(PDevMode: PDeviceMode;
Device: PChar; Port: PChar): Boolean;
var
hPrinter: THandle;
pForm: Pointer;
cbNeeded: DWORD;
cReturned: DWORD;
FormInfo1: TFormInfo1;
begin
Result := False;
if
OpenPrinter(Device, hPrinter,
nil
)
then
begin
pForm :=
nil
;
EnumForms(hPrinter,
1
, pForm,
0
, cbNeeded, cReturned);
GetMem(pForm, cbNeeded);
//
取
pForm
的大小并分配内存
try
if
EnumForms(hPrinter,
1
, pForm, cbNeeded, cbNeeded, cReturned)
then
begin
if
DeleteForm(hPrinter, PChar(CustomFormName))
then
Dec(cReturned);
//
删除旧的
Form
with
FormInfo1
do
begin
Flags :=
0
;
pName := PChar(CustomFormName);
Size.cx := AWidth *
100
;
Size.cy := AHeight *
100
;
with
ImageAbleArea
do
begin
Left :=
0
;
Top :=
0
;
Right := Size.cx;
Bottom := Size.cy;
end
;
end
;
if
AddForm(hPrinter,
1
, @FormInfo1)
then
begin
Printer.PrinterIndex := Printer.PrinterIndex;
PDevMode.dmFields := PDevMode.dmFields
or
DM_PAPERSIZE;
PDevMode.dmPaperSize := cReturned +
1
;
Printer.PrinterIndex := Printer.PrinterIndex;
Result := True;
end
;
end
;
finally
FreeMem(pForm);
end
;
end
;
end
;
var
Device, Driver, Port:
array
[
0
..
127
]
of
char;
hDevMode: THandle;
PDevMode: PDeviceMode;
begin
Printer.GetPrinter(Device, Driver, Port, hDevMode);
if
hDevMode <>
0
then
begin
PDevMode := GlobalLock(hDevMode);
try
if
(Win32Platform = VER_PLATFORM_WIN32s)
or
(Win32Platform = VER_PLATFORM_WIN32_WINDOWS)
then
Win95SetForm(PDevMode)
else if
Win32Platform = VER_PLATFORM_WIN32_NT
then
WinNTSetForm(PDevMode, Device, Port);
finally
GlobalUnlock(hDevMode);
end
;
end
end
;
将《
Delphi
中票据凭证的精确打印》一文中关于设置打印纸张长、宽的内容贴上来,供你参考
file
:
//
设置纸张高度
-
单位:
mm
procedure
SetPaperHeight(Value:integer);
var
Device :
array
[
0
..
255
]
of
char;
Driver :
array
[
0
..
255
]
of
char;
Port :
array
[
0
..
255
]
of
char;
hDMode : THandle;
PDMode : PDEVMODE;
begin
file
:
//
自定义纸张最小高度
127mm
if
Value <
127
then
Value :=
127
;
file
:
//
自定义纸张最大高度
432mm
if
Value >
432
then
Value :=
432
;
Printer.PrinterIndex := Printer.PrinterIndex;
Printer.GetPrinter(Device, Driver, Port, hDMode);
if
hDMode <>
0
then
begin
pDMode := GlobalLock(hDMode);
if
pDMode <>
nil then
begin
pDMode^.dmFields := pDMode^.dmFields
or
DM_PAPERSIZE
or
DM_PAPERLENGTH;
pDMode^.dmPaperSize := DMPAPER_USER;
pDMode^.dmPaperLength := Value *
10
;
pDMode^.dmFields := pDMode^.dmFields
or
DMBIN_MANUAL;
pDMode^.dmDefaultSource := DMBIN_MANUAL;
GlobalUnlock(hDMode);
end
;
end
;
Printer.PrinterIndex := Printer.PrinterIndex;
end
;
file
:
//
设置纸张宽度:单位
--mm
Procedure
SetPaperWidth(Value:integer);
var
Device :
array
[
0
..
255
]
of
char;
Driver :
array
[
0
..
255
]
of
char;
Port :
array
[
0
..
255
]
of
char;
hDMode : THandle;
PDMode : PDEVMODE;
begin
file
:
//
自定义纸张最小宽度
76mm
if
Value <
76
then
Value :=
76
;
file
:
//
自定义纸张最大宽度
216mm
if
Value >
216
then
Value :=
216
;
Printer.PrinterIndex := Printer.PrinterIndex;
Printer.GetPrinter(Device, Driver, Port, hDMode);
if
hDMode <>
0
then
begin
pDMode := GlobalLock(hDMode);
if
pDMode <>
nil then
begin
pDMode^.dmFields := pDMode^.dmFields
or
DM_PAPERSIZE
or
DM_PAPERWIDTH;
pDMode^.dmPaperSize := DMPAPER_USER;
file
:
//
将毫米单位转换为
0.1mm
单位
pDMode^.dmPaperWidth := Value *
10
;
pDMode^.dmFields := pDMode^.dmFields
or
DMBIN_MANUAL;
pDMode^.dmDefaultSource := DMBIN_MANUAL;
GlobalUnlock(hDMode);
end
;
end
;
Printer.PrinterIndex := Printer.PrinterIndex;
end
;
设定纸张大小
Procedure
PrintPapersize(Width,Length:integer);
var
Device :
array
[
0
..cchDeviceName -
1
]
of
Char;
Driver :
array
[
0
..(MAX_PATH -
1
)]
of
Char;
Port :
array
[
0
..
32
]
of
Char;
hDMode : THandle;
pDMode : PDevMode;
begin
Printer.GetPrinter(Device,Driver,Port,hDMode);
if
hDMode <>
0
then
begin
pDMode := GlobalLock(hDMode);
if
pDMode <>
nil then
begin
pDMode^.dmPaperSize :=
256
;
pDMode^.dmPaperLength :=Length ;
pDMode^.dmPaperWidth := Width;
pDMode^.dmFields :=pDMode^.dmFields
or
DM_PAPERSIZE;
pDMode^.dmFields :=pDMode^.dmFields
or
DM_PAPERLENGTH;
pDMode^.dmFields :=pDMode^.dmFields
or
DM_PAPERWIDTH;
ResetDC(Printer.Handle,pDMode^);
GlobalUnlock(hDMode);
end
;
end
;
end
;
uses
WinSpool, Printers, Windows;
function
CustomAddForm (
const
Name:
String
;
const
Width, Height:Double;
const
PrinterName:
String
):Boolean;
var
FormInfo1: TFormInfo1;
pFormInfo: PFormInfo1;
hPrinter : THandle;
begin
Result := False;
if
OpenPrinter(PChar(PrinterName),hPrinter,
NIL
)
then
begin
with
FormInfo1
do
begin
Flags :=
0
;
pName := PAnsiChar(Name);
Size.cx := Trunc(Width*
1000
);
Size.cy := Trunc(Height*
1000
);
ImageableArea.Left :=
0
;
ImageableArea.Top :=
0
;
ImageableArea.Bottom := Size.cy;
ImageableArea.Right := Size.cx;
end
;
pFormInfo := @FormInfo1;
Result := AddForm(hPrinter,
1
,pFormInfo);
ClosePrinter(hPrinter);
end
;
end
;
有两个方法可以在
win2000
中设置自定义纸张:
1
、手工添加
在
“
控制面板
”
、
“
打印机和传真
”
中选中一台打印机,在
“
文件
”
菜单的
“
服务器属性
”
中创建新格式即可。
2
、程序动态修改
procedure
SetPaperSize(X, Y: Integer);
//
单位是
0.1mm
//
改变
devicemode
结构
var
Device:
array
[
0
..
255
]
of
char;
Driver:
array
[
0
..
255
]
of
char;
Port:
array
[
0
..
255
]
of
char;
hDMode: THandle;
PDMode: PDEVMODE;
begin
Printer.PrinterIndex := Printer.PrinterIndex;
Printer.GetPrinter(Device, Driver, Port, hDMode);
if
hDMode <>
0
then
begin
pDMode := GlobalLock(hDMode);
if
pDMode <>
nil then
begin
if
(x =
0
)
or
(y =
0
)
then
begin
{Set to legal}
pDMode^.dmFields := pDMode^.dmFields
or
dm_PaperSize;
{pDMode^.dmPaperSize := DMPAPER_LEGAL; changed by wulianmin}
pDMode^.dmPaperSize := DMPAPER_FANFOLD_US;
end
else
begin
{Set to custom size}
pDMode^.dmFields := pDMode^.dmFields
or
DM_PAPERSIZE
or
DM_PAPERWIDTH
or
DM_PAPERLENGTH;
pDMode^.dmPaperSize := DMPAPER_USER;
pDMode^.dmPaperWidth := x
{SomeValueInTenthsOfAMillimeter}
;
pDMode^.dmPaperLength := y
{SomeValueInTenthsOfAMillimeter}
;
end
;
{Set the bin to use}
pDMode^.dmFields := pDMode^.dmFields
or
DMBIN_MANUAL;
pDMode^.dmDefaultSource := DMBIN_MANUAL;
GlobalUnlock(hDMode);
end
;
end
;
end
;
我曾经用
Delphi
5.0
的
Printer
对象编写打印程序,在许多打印机上使用都没问题
(包括一些其他
EPSON
打印机),但是在
EPSON
460
上使用时不能打印,结果发现在这种
环境下,必须给
Printer
的
Title
属性赋值后,打印机才会真正去打印,具体方法如下:
Printer.BeginDoc;
Printer.Title :=
'
在这里给打印文档起个名字
'
;
{
打印的内容
}
;
Printer.EndDoc;