Word Interop Batch Printing - c#

I have an application that creates multiple (regularly 1000+) Word files and then prints them.
I have created the below code that, once the PrintDialog is Ok'd proceeds to print the documents that reside in a folder. However recently we've had some strange behaviour following some Office updates (its Office 2010, which we cannot upgrade). The screen literally flickers, like a really bad graphics problem, then Word crashes after about 500 prints. This hasn't happened before and has been running for probably 12 months+ without issue.
As you can see, the application opens one instance of Word, then uses that to open>print>close each document. I can't see how this causes a problem, as Word only ever has one document open at a time. The memory size of winword.exe also doesn't increase significantly beyond the size of when its first opened too, so the Office updates don't initially appear to have introduced a memory leak.
Is there a more efficient way of doing this?
if (printDialog.ShowDialog() == DialogResult.OK)
{
int c = 1;
int total = directoryInfo.GetFiles().Count();
// Set the Progress bar details
App.Current.Dispatcher.Invoke((Action)delegate { MainWindow.ui_progressBar.Maximum = directoryInfo.GetFiles().Length; });
// loop through the files in order
foreach (FileInfo report in directoryInfo.GetFiles().OrderBy(x => x.FullName))
{
UpdateUI("Printing " + c.ToString() + " / " + total.ToString());
Microsoft.Office.Interop.Word.Document reportFile = wordApp.Documents.Add(report.FullName);
wordApp.ActivePrinter = printDialog.PrinterSettings.PrinterName;
wordApp.ActiveDocument.PrintOut();
reportFile.Close(SaveChanges: false);
reportFile = null;
PrinterCounter++;
App.Current.Dispatcher.Invoke((Action)delegate { MainWindow.ui_progressBar.Value = PrinterCounter; });
c++;
}
}

Related

Microsoft.Office.Interop.Word .doc/.docx lines count C#

I want to count the total number of lines of the word document (.doc /.docx).
In my class, I added a reference to the COM library Microsoft.Office.Interop.Word through which I am counting the document's total word count.
With reference to the this Lines.Count Property documentation, the library also provides a lines count option in the latest version.
But unfortunately, I am unable to find the Lines interface or property in the whole library.
Is there any other way to get the total number of lines of the MS Word document as shown in the image below?
Click here to view image
Method for words count (just for reference)
public int GetWordsCountFromWordFile(string wordFile)
{
try
{
if (!string.IsNullOrEmpty(wordFile))
{
var application = new Application();
var document = application.Documents.Open(wordFile, ReadOnly: true);
int count = document.Words.Count;
document.Close();
return count;
}
return 0;
}
catch (Exception ex)
{
LogWriter.ErrorLogWriter(nameof(Client), nameof(TaskHelper), nameof(GetWordsCountFromWordFile), "int", ex.Message);
return 0;
}
}
Answering my own question because I found the easiest and shortest working solution to the problem, exactly like I wanted it.
I added up two more lines of code in the method provided in my question and I got accurate results.
int lines = document.ComputeStatistics(WdStatistic.wdStatisticLines, true);
application.Quit(WdSaveOptions.wdDoNotSaveChanges);
Note:
Sending true parameter in ComputeStatistics method to include footnotes and endnotes.
Quit will stop the save changes window from opening and will close the MS Word process running in the background.

Network printers ignoring number of copies sent programatically

The setup - a local Windows 10 PC has multiple network printers installed. A GUI C# WinForm application (.NET) is constantly running in the background, and occasionally downloads a PDF file from a predefined URL (read from an *.ini file).
The problem occurs when the said PDF file is printed. Instead of accepting the number of copies sent from the application, the printer keeps printing just one copy of the file.
This is the relevant part of my code:
string webPrinter = "HP LaserJet PCL 6"; // is set in another part of the code
string iniFilePrinter = "hp LaserJet 1320 PCL 5"; // is set in another part of the code - read from the ini file
string dirName = "C:\\mydir";
string newDocName = "mydoc.pdf";
short numCopies = 1;
if(event1 == "event1") { // taken from another part of the code
numCopies = webNumCopies; // taken from another part of the code
} else if(event2 == "event2") {
numCopies = iniNumCopies; // taken from another part of the code - read from the ini file
}
var path = dirName + "\\" + newDocName;
try
{
using (var document = PdfiumViewer.PdfDocument.Load(path))
{
using (var printDocument = document.CreatePrintDocument())
{
System.Drawing.Printing.PrinterSettings settings = new System.Drawing.Printing.PrinterSettings();
string defaultPrinterName = settings.PrinterName;
printDocument.DocumentName = newDocName;
printDocument.PrinterSettings.PrintFileName = newDocName;
printDocument.PrinterSettings.Copies = numCopies;
printDocument.PrintController = new System.Drawing.Printing.StandardPrintController();
printDocument.PrinterSettings.PrinterName = webPrinter;
MessageBox.Show("Before: " + printDocument.PrinterSettings.Copies.ToString() + " --- " + newDocName);
if (!printDocument.PrinterSettings.IsValid)
{
printDocument.PrinterSettings.PrinterName = iniFilePrinter;
if(!printDocument.PrinterSettings.IsValid)
{
printDocument.PrinterSettings.PrinterName = defaultPrinterName;
}
}
MessageBox.Show("After: " + printDocument.PrinterSettings.Copies.ToString() + " --- " + newDocName);
printDocument.Print();
}
}
}
catch (Exception ex) {
MessageBox.Show(ex.Message);
}
The exception has never been triggered, and the print succeeds on every single try. Changing the number of copies within if/else also happens when the conditions are met, and the MessageBox.Show() parts of the code do show the expected number of copies (2,3,7, anything but 1, when it's not supposed to be 1) immediatelly before invoking printDocument.Print().
I've also tried printing unrelated documents from various other programs (MS Word, various custom applications, PDF readers and the like), and the number of copies has always been 1. However, software like Google Chrome or FireFox manage to get things printed in the specified number of copies.
I was thinking that there might be something about the printer's setting which makes it ignore the number of copies sent. Based on that assumption, I've checked the settings of all of the printers, and have found that the number of copies is actually set to 1.
If that is indeed the cause of my problem, how can I bypass that setting (without actually changing it), the way that Google Chrome and Firefox seem to be able to do it? I know that I could probably change that limit programmatically (set it to my number of copies, and then change it back to the original value, once the printing has been completed), but that doesn't seem like the proper way of doing it.
EDIT
I've expanded my code by including a print dialog, like this:
PrintDialog printDlg = new PrintDialog();
printDlg.Document = printDocument;
printDlg.AllowSelection = true;
printDlg.AllowSomePages = true;
if (printDlg.ShowDialog() == DialogResult.OK)
{
printDocument.Print();
}
Still, the results are the same - even when the user changes the number of copies within the print dialog, the printer ignores them. The same code was tested on another (local) printer, connected to an unrelated Windows 10 PC, and there the number of copies from the dialog was not ignored.
I've also noticed that the print dialog from my application, and that from notepad.exe are different (image below). Is there a way for me to call up the same print dialog notepad.exe uses? The reason I'd like to do this, is because that one gets the job done (xy number of copies in the print dialog, xy number of copies printed).

Printing copies of document from form textbox

Is there a way to print the number of copies given in a textbox in a C# windows form application?
With my current code the document gets printed the required number of prints, but after the first print there is a dialog that says that the document already is opened and I have to open a copy. When I open this copy, the document prints again, but only if I accept to open the copy. Is there a way to print the document multiple times without getting the dialog of the copy of the document every time?
When the form is filled in correct, I want it to be stored on my hard drive and want it to print the number given in a textbox on a previous form. The value from this textbox is stored in the variable intAantalPoorten.
Thanks a lot!
Regards Bert
CreateWordDocument(#"N:\De wienes\Productieformulieren\Sjablonen\Poortblad.docx", #"N:\De wienes\Productieformulieren\Producties poortbladen\Poortblad " + txtKlantnaam.Text + "-" + txtReferentie.Text + ".docx");
ProcessStartInfo info = new ProcessStartInfo(#"N:\De wienes\Productieformulieren\Producties poortbladen\Poortblad " + txtKlantnaam.Text + "-" + txtReferentie.Text + ".docx");
for (intAAntalPrints = 0; intAAntalPrints <= intAantalPoorten; intAAntalPrints++)
{
info.Verb = "Print";
info.CreateNoWindow = true;
info.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(info);
}
You're probably not using the best programming model. The thing you're doing is relying on the windows shell to open the document and invoke the registered verb (as defined by Word in the local registry). This is the most primitive and least functional way to get the job done. For example, there's no way to change which printer you're printing to...or how many copies or which pages, etc.
Instead, there's a rich programming model that Word (and all the Office apps) provide. You can open documents, and print them specifying the number of copies that you want.
To get access to this programming model, you need to reference the .COM interop assembly for Word, which is:
...and the Office Core interop assembly:
Then, it's just a matter of driving Word to do what you want. For example:
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1: Form
{
public Form1( )
{
InitializeComponent( );
}
private void button1_Click( object sender, EventArgs e )
{
//--> assumes the textBox1.Text contains the file to open...
var app = new Microsoft.Office.Interop.Word.Application( );
var doc = app.Documents.Open( textBox1.Text, ReadOnly: true );
doc.PrintOut( Copies: 1 );
doc.Close( );
app.Quit( );
}
}
}
Note that this requires Word to be installed on the local machine. For more information, check out the word object model.

Code that can be used to iterate over every control on all reports in a Microsoft Access database?

Can anyone suggest c# code that can be used to iterate over every control on all reports in a Microsoft Access database? The reason for doing this is that I am converting reports from Microsoft Access to Reporting Services and I want to find all reports in access that has specific text in the control source property.
Currently I am using the Microsoft.Office.Interop.Access assemblies but the code I am using is not working. This is because Access API knowledge is limited.
private static void Main(string[] args)
{
OpenDatabase();
DisplayReportElements();
Console.ReadLine();
}
private static void OpenDatabase()
{
app = new Application();
app.OpenCurrentDatabase(#"database.mdb");
app.Visible = false;
//app.OpenCurrentDatabase(#"C:\DLDWorkspace\Truama\Skills Training.mdb");
}
public static void DisplayReportElements()
{
for (int i = 0; i < app.CurrentProject.AllReports.Count - 1; i++)
{
Report report = app.Reports[i];
foreach (Control control in report.Controls)
{
Console.WriteLine("{0} - {1}", report.FormName, control.Name);
ControlProperties(control);
}
}
}
The following code produces an exception with the message "The number you used to refer to the report is invalid." on line Report report = app.Reports[i];. To get around this I go through and Open each report by calling app.DoCmd.OpenReport in a loop. There is two problems with this. 1. It takes over 12 hours to process 300 reports. and 2. after about 300 (of 600) reports I get an Index is out of bounds somewhere in DisplayReportElements
To iterate through the reports and their controls from within an Access.Application your approach is correct. If you find that process to be too slow or otherwise troublesome then an alternative approach would be to dump all of the reports to text files using the Application.SaveAsText method ...
var app = new Application();
app.OpenCurrentDatabase(#"C:\Users\Public\Database1.accdb");
for (int i = 0; i < app.CurrentProject.AllReports.Count; i++)
{
string rptName = app.CurrentProject.AllReports[i].Name;
Console.WriteLine("Dumping [{0}] ...", rptName);
string fileSpec = #"C:\__tmp\ReportDump\" + rptName + ".txt";
app.SaveAsText(AcObjectType.acReport, rptName, fileSpec);
}
app.CloseCurrentDatabase();
app.Quit();
and then use your favorite text-searching tool to scan the files for lines that contain 'ControlSource =' followed by the string you want to find, e.g.,
ControlSource ="LastName"

How to launch a process which will open a text file in any editor and automatically move cursor to a certain line number?

From c#, I want to launch a process which will open a text file in any editor and automatically move cursor to a certain line number.
I can open a file using
Process.Start(#"c:\myfile.txt");
but I don't know how to move cursor at specific location in that file.
Answer with source code:
yes, I used notepad++
private void openLog() {
try {
// see if notepad++ is installed on user's machine
var nppDir = (string)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Notepad++", null, null);
if (nppDir != null) {
var nppExePath = Path.Combine(nppDir, "Notepad++.exe");
var nppReadmePath = Path.Combine(yourDirectory,fileName );
var line = 20;
var sb = new StringBuilder();
sb.AppendFormat("\"{0}\" -n{1}", nppReadmePath, lineNo);
Process.Start(nppExePath, sb.ToString());
} else {
string newPath = #"\\mySharedDrive\notpad++\bin\notepad++.exe";
Process.Start(newPath, #"\\" + filePath + " -n" + lineNo); // take exe from my shared drive
}
} catch (Exception e) {
Process.Start(#"\\" + FilePath); // open using notepad
}
}
Get Notepad++, then you can do this:
var nppDir = (string)Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Notepad++", null, null);
var nppExePath = Path.Combine(nppDir, "Notepad++.exe");
var nppReadmePath = Path.Combine(nppDir, "readme.txt");
var line = 20;
var sb = new StringBuilder();
sb.AppendFormat("\"{0}\" -n{1}", nppReadmePath, line);
Process.Start(nppExePath, sb.ToString());
In this example we get install path of n++ from the registry, build path to exe and readme.txt file, opens its own readme.txt file with cursor on line 20.
Using StringBuilder is more efficient than using multiple appends (explanation somewhere on SO).
The solution very heavily depends on which process/editor is opened on your system. That editor would have to have a developer API that you could use to access functionality such as setting ranges and altering the cursor position. For example, if the editor that is opened is Microsoft Word, you would use the Word Interop API to set a selection at a specific position. There is no universal way to do this in 'any editor' since each one has its own API (or no outward facing API at all).
Perhaps you are going this the wrong way. I'm not sure what you are trying to accomplish, but I think it would be alot easier to just open the text file in an editor that belongs to your application. Perhaps another form with a WYSIWYG editor control. That way you have full control on where the cursor will land in that editor. Otherwise, there are just way too many unknowns for anything feasibly workable.

Categories

Resources