Printing on the Brother QL Series [C# / WPF]

Written on 29.03.2024

Last updated on 08.08.2024

If you are just here for the solution and don't want to read my rant about C# printing and the way I came to my solution click here: (Jump to solution)

Using the predefined printing functions of C# is nearly always a nightmare if you want to do some "advanced" things.
Want to print a pdf file without the user interaction to chooses a printer? Nope not possible
Want to change the printer settings beyond the predefined variables (looking at you PageMediaSize)? Not possible

And this time wasn't different. I got the task to write a software that prints out labels of various sizes. When I heard that I already knew that this was going to be a hassle. So I started to create the labels as a UserControl and did my first test prints on a normal paper printer. The labels looked good, had the right size and the barcodes were readable. Alright I thought, let's print on the real thing. So I got myself a Brother QL820NWBC and a 62mm label roll. I opened my software, pressed print, choose the Brother printer and prayed to god that it would work.

It didn't work. The LED on the printer flashed red and I got the message "Wrong roll type". I checked the printer's manual and found out that you should specify the roll type in the printer settings.
I opened them up, changed the roll size to 62mm (infinite) and tried again. It printed! But only a part of it. To be specific only 12.7mm of 120mm. 12.7mm is the minimum and default length you can set the label to be.

I could just set the label length to my desired value but there is one problem. The project manager requested that the software should print labels of various lenghts. So I had to do it but how? Eventually I found out about b-pac which is a SDK to communicate with the Brother P-Touch software. P-Touch is a graphical label creator software and is the way to go if you want to print labels and modify their content. But I couldn't use it for multiple reasons.

1. The users should be able to print the label to any printer in the company not just Brother ones.

2. If you want to use the b-pac SDK, P-Touch and the SDK itself has to be installed on the client's PC.

3. The labels templates you want to print have to be on a shared network folder or locally stored

With b-pac ruled out I googled more and more and found the PageMediaSize setting of the PrintDialog.PrintTicket object. PageMediaSize, as far as I understand it is there to change the size of the PrintTicket you want to print. So exactly want I needed right? PageMediaSize has some predefined sizes (Microsoft documentation here) unfortunatly not the one I needed. I read that you could just use the PageMediaSizeName.Unknown value and specify your own page size. I did that (I tested with 62mm x 120mm). Pressed "Print" and now the printer wouldn't even print 12,7mm of my label. It showed the same error message as before "Wrong roll type". Weird I thought, so I switched the width and height around and tried again. No success.
Through the GetPrintCapabilitiesAsXml function of the PrintDialog.PrintQueue Property I got a XML file with the predefined sizes the brother printer can handle and print. So I formatted the xml with NotePad++ and the Pretty Print Plugin and scrolled through the seemingly endless list of print sizes. Until I found the 62mm one. 62000 was apperently the width the printer wanted. So I correted my width and height by multiplying them with 1000. Printed again, switch width and height around and still no success. At this point I was really pissed. I spent an entire workday with try and error debugging and still had no valid solution to show.

        
        print.PrintQueue.DefaultPrintTicket.PageMediaSize = new PageMediaSize(PageMediaSizeName.Unknown,62000,120000)
        
        

Through some miracle I found out about modifying the print ticket itself. As far as I understand it every print job is generated with a print ticket in which the print/page settings are defined. The print queue reads out the printer it gets assigned and creates a print ticket. In there we can change way more variables than we can with the given C# functions. So the solution I came up with is as follows:
Right before I print with the PrintDialog.Print function. I get the print ticket out of the PrintQueue and modify it so I can insert my label width. The code below is the function I use to change the width inside the print ticket.

        
private void ChangeLabelLength(PrintDialog print, double length){

//Save PrintTicket to %temp%

string tempPrintPath = Path.Combine(Path.GetTempPath(),"printTicket.xml"); using (FileStream s = new FileStream(tempPrintPath,FileMode.Create)) print.PrintTicket.SaveTo(s);

//Load PrintTicket and add required Namespace

XmlDocument doc = new XmlDocument(); doc.Load(tempPrintPath); var nmsp = new XmlNamespaceManager(doc.NameTable); nmsp.AddNamespace("psf", "http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework");

//Get Node where Height and Width Properties are stored and make some preparations

XmlNodeList featureNodes = doc.SelectNodes("/psf:PrintTicket/psf:Feature/psf:Option/psf:ScoredProperty", nmsp); if (featureNodes is null) return; foreach(XmlNode feature in featureNodes){ if(feature.Attributes!= null && feature.Attributes.Count > 0) { if(feature.Attributes.GetNamedItem("name")?.Value == "psk:MediaSizeHeight"){ XmlNode heightNodeReplacement = doc.CreateNode(XmlNodeType.Element,"psf", "ParameterRef", "http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework"); XmlAttribute heightNameAttribute = doc.CreateAttribute("name"); heightNameAttribute.Value = "psk:PageMediaSizeMediaSizeHeight"; heightNodeReplacement.Attributes.SetNamedItem(heightNameAttribute); feature.RemoveChild(feature.FirstChild); feature.AppendChild(heightNodeReplacement); } } }

//Set Length of Label

bool foundLengthParameter = false; XmlNodeList aNode = doc.SelectNodes("/psf:PrintTicket/psf:ParameterInit", nmsp); foreach (XmlNode node in aNode){ if (node.Attributes.GetNamedItem("name").Value == "psk:PageMediaSizeMediaSizeHeight"){ node.FirstChild.InnerText = length.ToString(); foundLengthParameter = true; break; } } if (!foundLengthParameter){ XmlNode parameterNode = doc.CreateNode(XmlNodeType.Element,"psf", "ParameterInit", "http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework"); XmlAttribute parameterNameAttribute = doc.CreateAttribute("name"); parameterNameAttribute.Value = "psk:PageMediaSizeMediaSizeHeight"; parameterNode.Attributes.SetNamedItem(parameterNameAttribute); XmlNode parameterValueNode = doc.CreateNode(XmlNodeType.Element,"psf" ,"Value", "http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework"); XmlAttribute parameterValueAttribute = doc.CreateAttribute("xsi:type"); parameterValueAttribute.Value = "xsd:integer"; parameterValueNode.Attributes.SetNamedItem(parameterValueAttribute); parameterValueNode.InnerText = length.ToString(); parameterNode.AppendChild(parameterValueNode);l doc.SelectSingleNode("/psf:PrintTicket", nmsp)?.AppendChild(parameterNode); } doc.Save(tempPrintPath);

//Finally overwrite existing PrintTicket with the modified one

using (FileStream ff = new FileStream(tempPrintPath, FileMode.Open)) print.PrintTicket = new PrintTicket(ff);


Please note that you can't change the width (rolltype (ex. 54mm, 64mm)) of the label, only the length.
I tried but the application throws an OutOfMemory Exception
So the right label length has to be specified in the Brother printer settings!!!
A printer can only have 1 rolltype anyway

NOTE: This method will only work on QL820NWB. If you're using an older model you have to create custom lengths in the brother settings and inject their ID into the printticket

I'm not gonna post the solution for that on here. If you are in dire need for it please write me a mail :)


If you have questions or improvements don't hesitate to contact me under: pessoa@cock.li