Squeezing more speed out of Windows print spooler - c#

Our customer and I are trying to find a way to eliminate our printing bottleneck in a labeling system my company integrated at their warehouse.
The way our system is set up, a box scans, my software does this:
PrintDocument pd = new PrintDocument();
pd.PrinterSettings.PrinterName = PrinterName;
pd.PrintController = new StandardPrintController(); //Prevent print windows
pd.DefaultPageSettings.Landscape = false;
pd.DefaultPageSettings.Margins = new Margins(0, 25, 25, 0);
pd.PrintPage += (sender, args) =>
{
Image i = Image.FromFile(#"D:\Product\Labels\" + LabelID + ".png");
Rectangle m = args.MarginBounds;
args.Graphics.DrawImage(i, m);
};
pd.Print();
which sends it to the spooler. The program runs on a rack server with Windows Server 2012, and we're printing shipping labels to five label tampers powered by Intermec PX4i label printers. Each print job is sent from a different thread, because our scanpoint processing is asynchronous. Right now, the fastest our system can handle is 5 boxes, one on each line, every 2.75 seconds, and they want to speed it up to 5 boxes every 2 seconds - the problem is, if we let it go any faster, print jobs start getting held, and spoolsv.exe starts eating up whatever CPU load my program and SQL Server aren't.
Unfortunately, our customer can only provide the monochrome .PNG image files for the labels (each about 27kb, which you wouldn't think would bog down a print spooler...) and not IPL/ZPL equivalents (which we could just send to the printers via TCP in a matter of milliseconds).
Is there a way - either in the code or in the print settings - that I can eliminate this bottleneck? Please let me know if there's anything on which you'd like me to elaborate.

Related

Enforce program settings

I have made a small c# winforms program which among other stuff, prints barcodes. On some client machines where it operates, some other software for printing barcodes overrides my settings for printing the barcode and it resizes just the barcode.
If I change the code just for that machine, it either repositions the image (makes it smaller as well), or stretches it so wide that it cuts off one third of it. Currently I am only using the printing settings to set the margins to 0 and set the paper mode to landscape in the print dialog, and the image size is fixed in the printing process(I used constants). Below is the code for when the user clicks the print button, and also two constants defined outside of it.
const int barcodeX = 570;
const int barcodeY = 135;
private void Print_Click(object sender, EventArgs e)
{
DocPrint.DocumentName = "Document";
elements = 0;
PrintDialog.Document = DocumentDrucker;
DocPrint.DefaultPageSettings.Landscape = true;
DocPrint.DefaultPageSettings.Margins.Top = 0;
DocPrint.DefaultPageSettings.Margins.Left = 0;
DocPrint.DefaultPageSettings.Margins.Right = 0;
DocPrint.DefaultPageSettings.Margins.Bottom = 0;
//DocumentDrucker.OriginAtMargins = false;
if (PrintDialog.ShowDialog() == DialogResult.OK)
DocPrint.Print();
}
Also the lines to encode and print the barcode in the printing process. The barcodePoint is where the barcode is positioned, on the paper.
b.Encode(TYPE.CODE128A, "SBD" + currentItem.Text.Substring(0, 3) + currentItem.Text.Substring(4), Color.Black, Color.Transparent, barcodeX, barcodeY);
graphic.DrawImage(b.EncodedImage, barcodePoint);
The program runs fine under any other circumstances, so I have identified the problem, I just do not know how to go around the other software's settings. I have already reinstalled the drivers for the printer (no success), as well logged in as a different user on the same computer, and that made my program work. So it has something to do with the software installed on it, I just don't know what.
At first I thought it was a problem with the drivers and the OS, but I tried it on other computers and it worked fine as well. The problem persists just on the computers with the software.
Is there any way to make my program force it's printing settings for the barcode image, since the software installed on the machines always uses it's own settings? Or to make sure that the settings in my code will not be changed?
Uninstalling the software is not an option, as it is used for other processes.
EDIT: I have run the program from both my user logon (no software) and the target machine user (both on the same machine). The settings were identical in both cases, the only thing different was that on the target machine, the software was installed. What are some ways to make sure that my settings are enforced, instead of an X program overwriting them?

Printing WPF controls without using PrintDialog isn't fully rendering on sizes larger than NorthAmericanLetter sized paper

I have run into a problem that seems to have little information on the web and has been giving me trouble the past two days. When I print to an 8.5 x 11" piece of paper, the output is correct on all printers. When I print to 11 x 17", the output is correct on all virtual (doPDF7, XPS Document Writer) printers. The output on an actual physical printer (Xerox WorkCentre 7535) only renders, from the top-left corner, 8.5 x 11" worth of my control onto a 11 x 17" piece of paper. My control is properly sized (dimension of the 11 x 17") it just doesn't get completely rendered.
I have a Print Preview control implemented where the user can select a printer (PrintQueue) and the settings (PrintCapabilities) for their selected printer. Changing these settings will show up during run time so the user knows exactly what they are printing.
When the user then wants to print their document, I prepare the selected PrintQueue by merging the ticket (their selections in the print preview) with the PrintQueue.CurrentJobSettings.CurrentPrintTicket:
public void Prepare(PrintTicket ticketToMerge)
{
// _printer is the PrintQueue
// I've also tried to merge with UserPrintTicket, DefaultPrintTicket, and created a PrintTicket using XPS Document Writer printer default print ticket
System.Printing.ValidationResult result = _printer.MergeAndValidatePrintTicket(_printer.CurrentJobSettings.CurrentPrintTicket, ticketToMerge);
_printer.CurrentJobSettings.CurrentPrintTicket = result.ValidatedPrintTicket;
_printer.Commit();
// _printer.Refresh(); // This doesn't help it seems
}
then I call to print my DocumentPaginator:
public void Print(DocumentPaginator paginator)
{
var writer = PrintQueue.CreateXpsDocumentWriter(_printer);
writer.Write(paginator, _printer.CurrentJobSettings.CurrentPrintTicket);
}
and the actual printer calls DocumentPaginator.GetPage:
public override DocumentPage GetPage(int pageNumber)
{
var view = new PrintingContent
{
// Create my control and make it the biggest size it can be without
// going into the margins
// Subtract the margins since they can't be printed in, these come from
// _printerCapabilities.PageImageableArea
Width = this.PageSize.Width - this.Margin.Left - this.Margin.Right,
Height = this.PageSize.Height - this.Margin.Top - this.Margin.Bottom,
// This might be a problem since the print preview control
// is also using "this" but haven't found any evidence to conclude this thought
DataContext = this
};
view.Measure(new Size(view.Width, view.Height));
view.Arrange(new Rect(new Point(), new Size(view.Width, view.Height)));
view.UpdateLayout();
var dg = UItilities.FindVisualChildren<DataGrid>(view).First();
// dg.ItemsSource = (File as ParameterCollectionShell).GetParametersByPage(pageNumber)
// This didn't work so I tried giving the datagrid a new instance of List - this might be causing problems?
var parameters = new List<ParameterViewModel>((File as ParameterCollectionShell).GetParametersByPage(pageNumber));
dg.ItemsSource = parameters;
// Doesn't seem to help
// view.Measure(new Size(view.Width, view.Height));
// view.Arrange(new Rect(new Point(), new Size(view.Width, view.Height)));
view.UpdateLayout();
//return new DocumentPage(view,
// this.PageSize,
// new Rect(new Point(), new Size(view.Width, view.Height)),
// new Rect(new Point(this.Margin.Left, this.Margin.Top),
// new Size(view.Width, view.Height)));
return new DocumentPage(view);
}
Of course I have debugged this and checked sizes to make sure they are what they are supposed to be. Everything works printing to an .xps document or .pdf; but why doesn't it work when being sent to a physical printer?
For what it's worth, there is a DataGrid in my control and I had trouble with *-size spacing columns in the past, but they are all fixed width so I'm not sure if its related or not.
It turns out that it must have been a printer driver issue. I re-installed the driver for the same printer and after doing so everything worked correctly.
I got in direct contact with Xerox's firmware team for the specific printer and they let me know that there was firmware updates for the printer and that it could be serviced to hopefully fix it. They couldn't pin point exactly what the issue is, but the issue is now in their bug system.
When in doubt, re-install a printers driver and try printing again when you're having rendering problems. I'm not sure what invokes this bad behavior but printing to any other printer worked correctly; that's how I narrowed it down to a driver issue.

PrintDocument.Print() prints very slowly to network printer [duplicate]

I have a web application hosted on server 'A' (SA) and a web service for printing hosted on server 'B' (SB). SA creates and image that needs printing and sends it to SB. When doing this, printing is fairly slow, around fifteen seconds. However, if I log into SB using remote desktop as the user from the webconfig of the app hosted on SA, then it will print in less than two seconds. It seems as if SB is starting something up when I log into it that is making it print faster. Any idea what this could be and if there's a way that I could keep this printing fast even if I'm not logged in?
Edit: Size of the image being printed is about 20 KB.
Here's the code from of the service that is hosted on SB:
public void PrintImage(Stream printImage, string printServer, string printer)
{
string printerName = String.Format(#"\\{0}\{1}", printServer, printer);
Image image = Image.FromStream(printImage);
PrintDocument printDocument = new PrintDocument();
PrinterSettings settings = new PrinterSettings();
settings.PrinterName = printerName;
printDocument.PrinterSettings = settings;
printDocument.PrintPage += (s, e) =>
{
e.Graphics.DrawImage(image, 0, 0);
};
printDocument.Print();
}
Thanks for taking time to read through this :)
We found that if we created a printer mapping on SB, it would execute just as fast without a remote desktop connection.
Note that printing from a web app (or a service) is generally unsupported. see msdn and this SO post.

PrintDocument.Print is slow unless user is logged in to the printing computer

I have a web application hosted on server 'A' (SA) and a web service for printing hosted on server 'B' (SB). SA creates and image that needs printing and sends it to SB. When doing this, printing is fairly slow, around fifteen seconds. However, if I log into SB using remote desktop as the user from the webconfig of the app hosted on SA, then it will print in less than two seconds. It seems as if SB is starting something up when I log into it that is making it print faster. Any idea what this could be and if there's a way that I could keep this printing fast even if I'm not logged in?
Edit: Size of the image being printed is about 20 KB.
Here's the code from of the service that is hosted on SB:
public void PrintImage(Stream printImage, string printServer, string printer)
{
string printerName = String.Format(#"\\{0}\{1}", printServer, printer);
Image image = Image.FromStream(printImage);
PrintDocument printDocument = new PrintDocument();
PrinterSettings settings = new PrinterSettings();
settings.PrinterName = printerName;
printDocument.PrinterSettings = settings;
printDocument.PrintPage += (s, e) =>
{
e.Graphics.DrawImage(image, 0, 0);
};
printDocument.Print();
}
Thanks for taking time to read through this :)
We found that if we created a printer mapping on SB, it would execute just as fast without a remote desktop connection.
Note that printing from a web app (or a service) is generally unsupported. see msdn and this SO post.

StackOverflowException with DataGridView

This is an odd one. I have a DataGridView. I'm setting its DataSource with a List containing objects of my own custom class. There are about 50,000 items in the list. I defined all of the columns I wanted to be visible in the Designer and set AutoGenerateColumns to false.
As soon as I set the DataSource to my list, it is immediately populated correctly. I can scroll up and down, select different rows. Everything is good. But when I scroll all of the way down and then let the window containing the DataGridView lose focus everything freezes up and after a short while the stack overflows as such:
System.Drawing.dll!System.Drawing.SafeNativeMethods.Gdip.GdipDeleteGraphics(System.Runtime.InteropServices.HandleRef graphics) + 0x2a bytes
System.Drawing.dll!System.Drawing.Graphics.Dispose(bool disposing) + 0x56 bytes
System.Drawing.dll!System.Drawing.Graphics.Dispose() + 0x12 bytes
System.Drawing.dll!System.Drawing.Font.GetHeight() + 0xc8 bytes
System.Drawing.dll!System.Drawing.Font.Height.get() + 0xb bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRow() + 0x44 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.Clone() + 0x44 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRowCollection.this[int].get(int index) + 0xa8 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridView.DataGridViewAccessibleObject.GetChild(int index) + 0xbd bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x76 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x83 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x83 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x83 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x83 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x83 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x83 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x83 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x83 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x83 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x83 bytes
System.Windows.Forms.dll!System.Windows.Forms.DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() + 0x83 bytes
...
For some reason the DataGridViewRow.DataGridViewRowAccessibleObject.Bounds.get() method is calling itself into oblivion. The whole stack seems rather strange to me. Why would Font.Height.get() ever call the DataGridViewRow?
EDIT:
I was asked for some code. This is the designer generated code for the DataGridView and its columns:
//
// dataGridView
//
this.dataGridView.AllowUserToAddRows = false;
this.dataGridView.AllowUserToDeleteRows = false;
this.dataGridView.AllowUserToOrderColumns = true;
this.dataGridView.AllowUserToResizeRows = false;
this.dataGridView.BackgroundColor = System.Drawing.SystemColors.Window;
this.dataGridView.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.Date,
this.Type,
this.Job,
this.Mix,
this.Entry});
this.dataGridView.Location = new System.Drawing.Point(8, 96);
this.dataGridView.Name = "dataGridView";
this.dataGridView.ReadOnly = true;
this.dataGridView.RowHeadersVisible = false;
this.dataGridView.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
this.dataGridView.Size = new System.Drawing.Size(1152, 504);
this.dataGridView.TabIndex = 10;
this.dataGridView.SelectionChanged += new System.EventHandler(this.dataGridView_SelectionChanged);
//
// Date
//
this.Date.DataPropertyName = "FormattedTime";
this.Date.HeaderText = "Date/Time";
this.Date.Name = "Date";
this.Date.ReadOnly = true;
//
// Type
//
this.Type.DataPropertyName = "FormattedType";
this.Type.FillWeight = 60F;
this.Type.HeaderText = "Type";
this.Type.Name = "Type";
this.Type.ReadOnly = true;
this.Type.Width = 60;
//
// Job
//
this.Job.DataPropertyName = "Job";
this.Job.FillWeight = 80F;
this.Job.HeaderText = "Job No.";
this.Job.Name = "Job";
this.Job.ReadOnly = true;
this.Job.Width = 80;
//
// Mix
//
this.Mix.DataPropertyName = "Mix";
this.Mix.FillWeight = 80F;
this.Mix.HeaderText = "Mix No.";
this.Mix.Name = "Mix";
this.Mix.ReadOnly = true;
this.Mix.Width = 80;
//
// Entry
//
this.Entry.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.Entry.DataPropertyName = "FormattedSummary";
this.Entry.HeaderText = "Entry";
this.Entry.Name = "Entry";
this.Entry.ReadOnly = true;
When time comes around to populate the grid view, I simply do a:
dataGridView.DataSource = myList;
Fix
No fix is known at this time.
Workaround
Disabling the Tablet PC Input Service (a.k.a. tabtip.exe) from the Services panel in Control Panel.
Details
A few weeks ago I contacted Microsoft Developer support about this issue. I corresponded with a support engineer on the WinForms team and we figured out that this issue was in some way caused by Accessibility.dll being loaded into my application whenever Microsoft's Tablet PC Input Service (tabtip.exe) was running. This service is present on at least all Windows 7 Enterprise installations but is generally only running if you have a tablet PC or have ever installed any sort of pen input device on your PC. I had used a Wacom Bamboo pen tablet on my PC in the past so it made sense why this service was running. Note that it was determined that neither the method I used to populate the DataGridView nor any of the properties I set had anything to do with the issue.
Accessibility.dll is a Microsoft library that allows certain peripherals (pen tablets and especially assistive devices) to more easily interact and get extra information about forms and the controls contained on forms. I'm not 100% sure on this but I believe that this library is loaded into every running Windows process automatically if such a peripheral is installed.
After examining a dump that I had provided, the Microsoft engineer was puzzled by the code path that the DataGridView decided to go down and found that Accessibility.dll was the culprit that allowed let it happen. He admitted that DataGridView should not be doing this and that this seemed like a problem on their end. However, he was unable to reproduce the problem on his end even after turning on the Tablet PC Input Service on both his PC and a freshly made Windows 7 VM. So while he was able to identify the key players that caused the problem on my PC he was not able to find root cause and hence unable to pursue it further.
This may be attributed to my particular pen tablet device (Wacom) or something else all together. Its unknown. If anyone else encounters this problem, please contact me. The engineer invited me to contact him if I was ever able to narrow down the cause. For now, just keeping the service off prevents the issue.
I have encountered a very similar scenario of a StackOverflowsException in DataGridView.
Conditions:
A WinForm hosting a datagridview control. DataGridView properties ReadOnly and VirtualMode set to true.
VirtualMode's OnCellValueNeeded implemented as per MSFT documentation (http://msdn.microsoft.com/en-us/library/2b177d6d.aspx)
Load a sizable amount of data (15 columns x 1 x 10^5 rows)
Behavior of datagridview is normal (i.e.: responsiveness of GUI, row and cell selection, vertical & horizontal scrolling) if displaying only 10 Columns X 30 rows [300 cells]. When I maximize the display such that the number of displayed cells is much larger, a StackOverflowsException in DataGridView is fired.
I, too, have an attached Wacom tablet to my PC.
The problem disappears if I stop the Tablet PC Input Service (tabtip.exe)
So...in my case, indeed, Franks workaround is in the right direction and supports the idea that 1. the Tablet PC Input Service is a culprit and 2. the number of displayed (active) cells in the screen has an effect on firing the exception.
First of all, thanks to #Frank Weindel for pointing to this WEIRD problem (only microsoft can come up with this).
For those who doesn't want to disable the "Tabtip.exe" as part of the requirement, say your application is for a tablet and only possible input method is Tabtip, there is an workaround.
You can kill the Tabtip at the event where the crash occurs. For me, it is when i select items at row beyond 60,000. This is what I did.
Private Sub DataGridView1_Scroll(sender As Object, e As ScrollEventArgs) Handles DataGridView1.Scroll
Call closeKeyboard()
End Sub
'When a user have to type something in textbox'
Private Sub Lookup_Tbox_MouseDown(sender As Object, e As MouseEventArgs) Handles Lookup_Tbox.MouseDown
Call OpenKeyboard()
End Sub
Public Sub OpenKeyboard()
System.Diagnostics.Process.Start("tabtip.exe")
End Sub
Public Sub closeKeyboard()
Dim proc() As System.Diagnostics.Process = Process.GetProcessesByName("tabtip")
For i As Integer = 0 To proc.Length - 1
proc(i).Kill()
Next i
End Sub
Hope it helps someone :) Happy fighting :)
I am not completly understand ur promblem but I think ur problem is slowness
of datagridview and memory
I Have a solution :
1:Break datagridview row into pages like only show maximum 100 row in Datagridview and next hundread in button or other control click it help to reduce ur video memory
2: Enable Double Buffred on to DataGridView
3: Use Suspendlayout and resume layout function to easily draw item
I had the same problem GridView with 50K records. Using the ScrollBar or simply moving large number of records caused the StackOverflowsException in DataGridView. After reading the above I simply stopped the virtual on screen keyboard (my screen is touch screen) Service description "Enables Touch Keyboard and Handwriting Panel pen and ink functionality"
As soon as this was stopped the GridView worked perfectly.
I had exact problem as described in question, after reading here and my own experimenting, this solved my problem:
private void dataGridView1_Scroll(object sender, ScrollEventArgs ex)
{
try
{
dataGridView1.Focus();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

Categories