【问题标题】:Delphi Check for Default Printer FailingDelphi 检查默认打印机失败
【发布时间】:2015-12-17 16:27:22
【问题描述】:

当有人尝试打印但没有设置默认打印机时,我使用下面的代码来防止出现异常。我收到了一个用户的报告,该用户使用 citrix 远程使用该软件,该用户将网络打印机作为默认打印机。它在调用 GetPrinter 时引发异常,并显示消息“当前未选择默认打印机”。他们从其他应用程序打印没有问题。这里出了什么问题?

function CheckForDefaultPrinter: boolean;
var
  FDevice:     PChar;
  FDriver:     PChar;
  FPort:       PChar;
  FHandle:     THandle;
  CurrentPrinterName: string;
begin
  //ensure default printer selected - bypass printer.pas bug
  Printer.PrinterIndex := Printer.PrinterIndex;
  GetMem (FDevice, 255);
  GetMem (FDriver, 255);
  GetMem (FPort, 255);
  try
    try
      Printers.Printer.GetPrinter(FDevice, FDriver, FPort, FHandle);
    except
      on E:Exception do
        ShowMessage(E.Message);
    end;
    CurrentPrinterName := FDevice;
  finally
    if FDevice <> nil then FreeMem (FDevice, 255);
    if FDriver <> nil then FreeMem (FDriver, 255);
    if FPort <> nil then FreeMem (FPort, 255);
  end;

  if CurrentPrinterName = '' then
  begin
    MessageDlg('You do not have a default printer defined.' +
               #13#13 + 'Please select a printer before running a report.'+
               #13#13 + 'Or the default printer name is blank. Please assign the printer a name.',
               mtError,[mbOK],0);
    Result:= False;
  end
  else
    Result:= True;
end;

【问题讨论】:

  • Printer.PrinterIndex 的值是多少?是的,当系统中没有默认打印机时很有可能,这是绝对有效的情况,您也必须检查它。您在哪里检查用户没有任何默认打印机?如果没有,您应该向用户显示打印机列表并要求选择一个,至少可以使用带有 TRAdioGroup 的简单表单,或者更好的是标准 Windows PrinterSelect 对话框
  • 您的堆分配很奇怪。为什么要在最后测试分配?你知道会的。为什么堆分配?堆栈有什么问题?这也是 RTL 函数,无法知道要为其分配多少空间。
  • @Arioch 'Printer.PrinterIndex 实际上应该设置为 -1 以检查默认打印机,对吗?将其设置为自身似乎没有用。这可能是问题的一部分。我不记得printer.pas 错误是什么或如何绕过它。
  • 当您可以调用Printer.GetPrinter 时,问题已经出现(它会在您第一次访问Printer 时发生,即使在上面的代码中这是对GetPrinter 的调用)。而是直接调用WinAPI函数GetDefaultPrinter
  • @fullerm 我再次问你 - 当系统中根本没有默认打印机时,你的代码应该做什么?

标签: delphi printing


【解决方案1】:

在您发布的代码可以检查之前发生错误,因为它在第一次访问Printer时失败。

在尝试使用全局 Printer 之前,您应该直接使用 WinAPI GetDefaultPrinter 函数来查看是否存在默认打印机。这是一个示例控制台应用程序(它使用了比最新版本的 Delphi 中包含的更简单的函数声明)来演示如何这样做。该示例在 XE 10 Seattle 中编译并在 Windows 7 64 位上进行了测试。

program Project1;

{$APPTYPE CONSOLE}

uses
  System.SysUtils, WinAPI.Windows;

function GetDefaultPrinter(Buffer: PChar; var BufferSize: DWord): BOOL; stdcall;
  external 'winspool.drv' name 'GetDefaultPrinterW'; // GetDefaultPrinterA on pre-Unicode Delphi versions

var
  Buff: string;
  BuffSize, Err: DWord;
begin
  // Get size of buffer needed. 
  GetDefaultPrinter(nil, BuffSize);
  SetLength(Buff, BuffSize);
  // If this call fails, and GetLastError returns
  // ERROR_FILE_NOT_FOUND, there is no default printer assigned.
  if GetDefaultPrinter(PChar(Buff), BuffSize) then
    WriteLn('Default printer: ', Buff)
  else
  begin
    Err := GetLastError();
    if Err = ERROR_FILE_NOT_FOUND then
      WriteLn('No default printer assigned')
    else
      WriteLn('Failed. Error: ', Err);
  end;
  ReadLn;
end.

请注意,根据文档,返回值包括终止 NULL (#0)。要删除它,只需在调用 GetDefaultPrinter 返回后使用 SetLength(Buff, Length(Buff) - 1)

【讨论】:

  • 根据 Win API 文档,GetDefaultPrinter 在使用 nil 缓冲区调用时总是失败。因此,应该更改第一个 if 语句。除此之外,这是一个很好的答案。
  • @fullerm:有趣的是 - 几分钟前我下班开车回家,并想到了那个确切的问题。当我回到家时,我打算再次检查我的答案,发现你的评论提到了同样的事情。 :-) 更正了评论。谢谢。
  • @KenWhite 你好,Ken,我添加了一个单独的部分 Delphi 10.1 Berlin,基于 answer/comment。如果您认为它不正确或不合适,请查看并删除它。最好的祝福。阅读后将此评论标记为不再需要。
  • @Trenton_M:你不能抄袭别人的答案并将其添加到我的答案中。那是不恰当的编辑。我已经回滚了。我在更合适的注释中进行了编辑,以将 RodD 提供的信息包含在另一个答案中。
  • @RodD 将其作为对答案的评论发布,“所以要明确的是,这不是一个单独的答案,而是对答案 #2 的评论/更正”,这可能会在审查中被删除,不是答案。他们写的,就在你的帖子下面。此外,抄袭是在不注明出处的情况下将别人的作品归功于自己,而我没有这样做。此外,还有许多社区维护答案的示例,随着新版本或方法的发布而增长。社区维护的答案有很好的先例。
【解决方案2】:

在 Windows 上,您可以假设如果有超过 0(零)台打印机,则存在默认打印机。 除非您的应用程序作为服务运行,否则此假设是安全的。

所以你只需要这个:

if (Printer.Printers.Count=0) then ShowMessage('Please install a printer before attempting to print.');

您需要在访问打印机对象上的大多数其他属性/方法之前进行此检查。

如果您的应用程序作为服务运行,则不会有默认打印机。在您在代码中分配值之前,此处的 PrinterIndex 将为 -1。

【讨论】:

  • On Windows you can assume that if there is more than 0 (zero) printers then there is a default printer 不,你不能
  • 这并不总是有效,因为访问Printer 会导致异常,然后才能访问Printers.Count。见qc.embarcadero.com/wc/qcmain.aspx?d=100256。如果至少安装了一台打印机,您也不能假设存在默认值。
【解决方案3】:

我使用 Delphi XE6、Win7Pro 测试了您的代码。只是换了一行

GetMem (FPort, 255);
 try
  try
    Printer.GetPrinter(FDevice, FDriver, FPort, FHandle); // HERE

except
  on E:Exception do
    ShowMessage(E.Message);
end;

它默认使用联网打印机,即使在拔下电缆的情况下也是如此。

【讨论】:

  • 您刚刚删除了对打印机单元的引用。也许在 XE6 中 GetPrinter 位于不同的单元中,但在旧版本的 Delphi 中,删除单元引用将无效。
  • 如果打印机在使用子句中,删除Printers. 没有任何意义。此外,拔下网络打印机并不会使其不再是默认打印机;它只是意味着默认打印机处于脱机状态。您的回答对问题的影响为零。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多