8

This seems like such a simple need, but for some reason I cannot find how I can accomplish this. I have code like this:

Microsoft.Office.Interop.Word.Application word = new Microsoft.Office.Interop.Word.Application();
MemoryStream documentStream = getDocStream();
FileInfo wordFile = new FileInfo("c:\\test.docx");
object fileObject = wordFile.FullName;
object oMissing = System.Reflection.Missing.Value;
Microsoft.Office.Interop.Word.Document doc = wordInstance.Documents.Open(ref fileObject, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing);
doc.Activate();
doc.PrintOut(oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing);

I need to have a config setting drive which printer and tray are used. After searching around I found Microsoft.Office.Interop.Word.Application.ActivePrinter which is a settable string property that the documentation says takes "the name of the active printer", but I don't know what it means for a printer to the the "Active Printer", especially when I have two of them. How can this be accomplished?

4

3 回答 3

16

TL;DR您无法打印到特定的打印机。您必须将默认打印机更改为您想要使用的打印机。然后正常打印。

自从我们第一次在该领域开展工作以来,情况可能发生了巨大变化,但我们无法找到任何方法来打印到特定的打印机。因此,我们所做的就是将系统的默认打印机更改为我们想要的,在该计算机上打印我们想要的所有文档,然后将其更改回原来的样子。(实际上,我们放弃了将其更改回来,因为没有其他任何东西期待任何特定的默认打印机,所以没关系)。

简短的回答:

Microsoft.Office.Interop.Word._Application _app = [some valid COM instance];
Microsoft.Office.Interop.Word.Document doc = _app.Documents.Open(ref fileName, ...);
doc.Application.ActivePrinter = "name of printer";
doc.PrintOut(/* ref options */);

但是,我们发现这非常不可靠!请阅读以获得更多详情:

如果您还没有这样做,我强烈建议您构建自己的包装类来处理_Document_Application. 现在可能不像以前那么糟糕(dynamic对我们来说不是一个选择),但这仍然是一个好主意。您会注意到其中缺少某些美味的代码......我试图专注于与您所要求的内容相关的代码。此外,这个DocWrapper类是代码的许多独立部分的合并——请原谅这种混乱。最后,如果您认为异常处理很奇怪(或者只是抛出异常的糟糕设计)——请记住,我正在尝试将许多地方的部分代码放在一起(同时也忽略了我们自己的自定义类型)。阅读代码中的注释,它们很重要。

class DocWrapper
{
  private const int _exceptionLimit = 4;

  // should be a singleton instance of wrapper for Word
  // the code below assumes this was set beforehand
  // (e.g. from another helper method)
  private static Microsoft.Office.Interop.Word._Application _app;

  public virtual void PrintToSpecificPrinter(string fileName, string printer)
  {
    // Sometimes Word fails, so needs to be restarted.
    // Sometimes it's not Word's fault.
    // Either way, having this in a retry-loop is more robust.
    for (int retry = 0; retry < _exceptionLimit; retry++)
    {
      if (TryOncePrintToSpecificPrinter(fileName, printer))
        break;

      if (retry == _exceptionLimit - 1) // this was our last chance
      {
        // if it didn't have actual exceptions, but was not able to change the printer, we should notify somebody:
        throw new Exception("Failed to change printer.");
      }
    }
  }

  private bool TryOncePrintToSpecificPrinter(string fileName, string printer)
  {
    Microsoft.Office.Interop.Word.Document doc = null;

    try
    {
      doc = OpenDocument(fileName);

      if (!SetActivePrinter(doc, printer))
        return false;

      Print(doc);

      return true; // we did what we wanted to do here
    }
    catch (Exception e)
    {
      if (retry == _exceptionLimit)
      {
        throw new Exception("Word printing failed.", e);
      }
      // restart Word, remembering to keep an appropriate delay between Quit and Start.
      // this should really be handled by wrapper classes
    }
    finally
    {
      if (doc != null)
      {
        // release your doc (COM) object and do whatever other cleanup you need
      }
    }

    return false;
  }

  private void Print(Microsoft.Office.Interop.Word.Document doc)
  {
    // do the actual printing:
    doc.Activate();
    Thread.Sleep(TimeSpan.FromSeconds(1)); // emperical testing found this to be sufficient for our system
    // (a delay may not be required for you if you are printing only one document at a time)
    doc.PrintOut(/* ref objects */);
  }

  private bool SetActivePrinter(Microsoft.Office.Interop.Word.Document doc, string printer)
  {
    string oldPrinter = GetActivePrinter(doc); // save this if you want to preserve the existing "default"

    if (printer == null)
      return false;

    if (oldPrinter != printer)
    {
      // conditionally change the default printer ...
      // we found it inefficient to change the default printer if we don't have to. YMMV.
      doc.Application.ActivePrinter = printer;
      Thread.Sleep(TimeSpan.FromSeconds(5)); // emperical testing found this to be sufficient for our system
      if (GetActivePrinter(doc) != printer)
      {
        // don't quit-and-restart Word, this one actually isn't Word's fault -- just try again
        return false;
      }

      // successful printer switch! (as near as anyone can tell)
    }

    return true;
  }

  private Microsoft.Office.Interop.Word.Document OpenDocument(string fileName)
  {
    return _app.Documents.Open(ref fileName, /* other refs */);
  }

  private string GetActivePrinter(Microsoft.Office.Interop.Word._Document doc)
  {
    string activePrinter = doc.Application.ActivePrinter;
    int onIndex = activePrinter.LastIndexOf(" on ");
    if (onIndex >= 0)
    {
      activePrinter = activePrinter.Substring(0, onIndex);
    }
    return activePrinter;
  }
}
于 2012-06-20T22:42:21.457 回答
2

有一种方法可以指定打印机,但不要将其设置为系统默认值(我使用 C++/CLR,但它应该可以移植到 C#):

String^ printername = "...";
auto wordapp= gcnew Microsoft::Office::Interop::Word::Application();
if (wordapp != nullptr)
{
  cli::array<Object^>^ argValues= gcnew cli::array<Object^>(2);
  argValues[0]= printername;
  argValues[1]= 1;
  cli::array<String^>^ argNames= gcnew cli::array<String^>(2);
  argNames[0]= "Printer";
  argNames[1]= "DoNotSetAsSysDefault";
  Object^ wb= wordapp->WordBasic;
  wb->GetType()->InvokeMember( "FilePrintSetup", System::Reflection::BindingFlags::InvokeMethod, nullptr, wb, argValues, nullptr, nullptr, argNames);
}
于 2016-02-15T14:35:36.703 回答
1

我继承了一个使用 Word.Application 的 C# 项目。因为它是一个 C# 项目,而且翻译看起来很简单,所以我使用了 @Lars C++ 代码,并在我的项目中添加了一个 C# 翻译方法。在这里发布他的代码的直接翻译,希望能让某人的生活更轻松一些:

string printername = "...";
var wordapp = new Microsoft.Office.Interop.Word.Application();
if (wordapp != null)
{
    object[] argValues = new object[2];
    argValues[0] = printername;
    argValues[1] = 1;
    string[] argNames = new string[2];
    argNames[0] = "Printer";
    argNames[1] = "DoNotSetAsSysDefault";
    var wb = wordapp.WordBasic;
    wb.GetType().InvokeMember("FilePrintSetup", System.Reflection.BindingFlags.InvokeMethod, null, wb, argValues, null, null, argNames);
}
于 2018-11-09T22:31:02.303 回答