Open Hardware Station sample from Retail SDK. Path of Retail SDK will be something like below:
C:\RetailSDK\Code\SampleExtensions\HardwareStation\SampleHardwareStation.sln
Create new project in this solution "MyNewWindowsPrinterExtension".
Right click on "SampleHardwareStation" Solution and select "Class Library (Portable)":
Now select following targets and click OK:
- .NET Framework 4.5
- Windows 8
- ASP.NET Core 1.0
- Windows Phone 8.1
Now you can see your new project in this solution:
Now add new class "WindowsPrinter".
Add below references in your newly created project:
Now add below code in your class:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Printing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Dynamics.Commerce.HardwareStation;
using Microsoft.Dynamics.Commerce.HardwareStation.Peripherals;
using Microsoft.Dynamics.Commerce.Runtime.Handlers;
using Microsoft.Dynamics.Commerce.Runtime.Messages;
using Microsoft.Dynamics.Retail.Diagnostics;
namespace MyNewWindowsPrinterExtension
{
public class WindowsPrinter : INamedRequestHandler
{
private const string BarCodeRegEx = "<B: (.+?)>";
private const string NormalTextMarker = "|1C";
private const string BoldTextMarker = "|2C";
private const string DoubleSizeNormalTextMarker = "|3C";
private const string DoubleSizeBoldTextMarker = "|4C";
private const string ExceptNormalMarkersRegEx = @"\|2C|\|3C|\|4C";
private const string AllMarkersForSplitRegEx = @"(\|1C|\|2C|\|3C|\|4C)";
private List<TextPart> parts;
private int printLine;
private Barcode barCode = new BarcodeCode39();
private DefaultLogo defaultLogo = new DefaultLogo();
/// <summary>
/// Gets the esc marker.
/// </summary>
public static string EscMarker
{
get
{
return "";
}
}
/// <summary>
/// Gets the unique name for this request handler.
/// </summary>
public string HandlerName
{
get { return PeripheralType.Windows; }
}
/// <summary>
/// Gets the collection of supported request types by this handler.
/// </summary>
public IEnumerable<Type> SupportedRequestTypes
{
get
{
return new[]
{
typeof(OpenPrinterDeviceRequest),
typeof(PrintPrinterDeviceRequest),
typeof(ClosePrinterDeviceRequest)
};
}
}
/// <summary>
/// Gets or sets the printer name.
/// </summary>
protected string PrinterName { get; set; }
/// <summary>
/// Represents the entry point for the printer device request handler.
/// </summary>
/// <param name="request">The incoming request message.</param>
/// <returns>The outgoing response message.</returns>
public Response Execute(Request request)
{
ThrowIf.Null(request, "request");
Type requestType = request.GetType();
if (requestType == typeof(OpenPrinterDeviceRequest))
{
var openRequest = (OpenPrinterDeviceRequest)request;
this.Open(openRequest.DeviceName);
}
else if (requestType == typeof(PrintPrinterDeviceRequest))
{
var printRequest = (PrintPrinterDeviceRequest)request;
this.Print(
printRequest.Header,
printRequest.Lines,
printRequest.Footer);
}
else if (requestType == typeof(ClosePrinterDeviceRequest))
{
// Do nothing. Just for support of the close request type.
}
else
{
throw new NotSupportedException(string.Format("Request '{0}' is not supported.", requestType));
}
return new NullResponse();
}
private static bool DrawBitmapImage(PrintPageEventArgs e, byte[] defaultLogoBytes, float contentWidth, ref float y)
{
using (MemoryStream ms = new MemoryStream(defaultLogoBytes))
{
var image = Image.FromStream(ms);
if (y + image.Height >= e.MarginBounds.Height)
{
// No more room - advance to next page
e.HasMorePages = true;
return false;
}
float center = ((contentWidth - image.Width) / 2.0f) + e.MarginBounds.Left;
if (center < 0)
{
center = 0;
}
float top = e.MarginBounds.Top + y;
e.Graphics.DrawImage(image, center, top);
y += image.Height;
return true;
}
}
/// <summary>
/// Opens a peripheral.
/// </summary>
/// <param name="peripheralName">Name of the peripheral.</param>
private void Open(string peripheralName)
{
this.PrinterName = peripheralName;
}
/// <summary>
/// Print the data on printer.
/// </summary>
/// <param name="header">The header.</param>
/// <param name="lines">The lines.</param>
/// <param name="footer">The footer.</param>
private void Print(string header, string lines, string footer)
{
string textToPrint = header + lines + footer;
if (!string.IsNullOrWhiteSpace(textToPrint))
{
using (PrintDocument printDoc = new PrintDocument())
{
printDoc.PrinterSettings.PrinterName = this.PrinterName;
string subString = textToPrint.Replace(EscMarker, string.Empty);
var printText = subString.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
this.parts = new List<TextPart>();
foreach (var line in printText)
{
var lineParts = TextLogoParser.Parse(line);
if (null != lineParts)
{
this.parts.AddRange(lineParts);
}
}
this.printLine = 0;
printDoc.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(this.PrintPageHandler);
#if DEBUG
if ("Microsoft XPS Document Writer" == this.PrinterName)
{
printDoc.PrinterSettings.PrintFileName = Path.Combine(Path.GetTempPath(), "HardwareStation_Print_Result.xps");
printDoc.PrinterSettings.PrintToFile = true;
NetTracer.Information(string.Format(CultureInfo.InvariantCulture, "Look for XPS file here: {0}", printDoc.PrinterSettings.PrintFileName));
}
#endif
try
{
printDoc.Print();
}
catch (InvalidPrinterException)
{
throw new PeripheralException(PeripheralException.PeripheralNotFound);
}
}
}
}
/// <summary>
/// Prints the page.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="PrintPageEventArgs" /> instance containing the event data.</param>
private void PrintPageHandler(object sender, PrintPageEventArgs e)
{
const int LineHeight = 10;
const string TextFontName = "Courier New";
const int DoubleSizeTextFontSize = 14;
e.HasMorePages = false;
using (Font textFont = new Font(TextFontName, TextFontSize, FontStyle.Regular))
using (Font textFontBold = new Font(TextFontName, TextFontSize, FontStyle.Bold))
using (Font doubleSizeTextFont = new Font(TextFontName, DoubleSizeTextFontSize, FontStyle.Regular))
using (Font doubleSizeTextFontBold = new Font(TextFontName, DoubleSizeTextFontSize, FontStyle.Bold))
{
float y = 0;
float dpiXRatio = e.Graphics.DpiX / 96f; // 96dpi = 100%
float dpiYRatio = e.Graphics.DpiY / 96f; // 96dpi = 100%
// This calculation isn't exactly the width of the rendered text.
// All the calculations occurring in the rendering code of PrintTextLine. It almost needs to run that code and use e.Graphics.MeasureString()
// the first time to get the true contentWidth, then re-run the same logic using the true contentWidth for rendering.
//
// For now, the rendering is close, but it's not 'exact' center due to the mismatch in estimated vs. true size
float contentWidth = this.parts.Where(x => x.TextType == TextType.Text)
.Select(p => p.Value)
.Max(str => str.Replace(NormalTextMarker, string.Empty)
.Replace(BoldTextMarker, string.Empty)
.Replace(DoubleSizeNormalTextMarker, string.Empty)
.Replace(DoubleSizeBoldTextMarker, string.Empty)
.Length)
* dpiXRatio; // Line with max length = content width
for (; this.printLine < this.parts.Count; this.printLine++)
{
var part = this.parts[this.printLine];
if (part.TextType == TextType.Text)
{
if (!this.PrintTextLine(
e,
LineHeight,
textFont,
textFontBold,
doubleSizeTextFont,
doubleSizeTextFontBold,
dpiYRatio,
contentWidth,
dpiXRatio,
part.Value,
ref y))
{
return;
}
}
else if (part.TextType == TextType.LegacyLogo)
{
byte[] defaultLogoBytes = this.defaultLogo.GetBytes();
if (!DrawBitmapImage(e, defaultLogoBytes, contentWidth, ref y))
{
return;
}
}
else if (part.TextType == TextType.LogoWithBytes)
{
byte[] image = TextLogoParser.GetLogoImageBytes(part.Value);
if (!DrawBitmapImage(e, image, contentWidth, ref y))
{
return;
}
}
}
}
}
private bool PrintTextLine(
PrintPageEventArgs e,
int lineHeight,
Font textFont,
Font textFontBold,
Font doubleSizeTextFont,
Font doubleSizeTextFontBold,
float dpiYRatio,
float contentWidth,
float dpiXRatio,
string line,
ref float y)
{
// it always use one line height
// because the Report designer use a col and a line to set position for all conrtols
if (y + lineHeight >= e.MarginBounds.Height)
{
// No more room - advance to next page
e.HasMorePages = true;
return false;
}
bool hasFontModificator = (line.Length > 0) && Regex.IsMatch(line, ExceptNormalMarkersRegEx, RegexOptions.Compiled);
if (hasFontModificator)
{
// Text line printing with bold Text in it.
float x = 0;
Font currentFont = textFont;
int sizeFactor = 1;
string[] subStrings = Regex.Split(line, AllMarkersForSplitRegEx, RegexOptions.Compiled);
foreach (string subString in subStrings)
{
switch (subString)
{
case NormalTextMarker:
currentFont = textFont;
sizeFactor = 1;
break;
case BoldTextMarker:
currentFont = textFontBold;
sizeFactor = 2;
break;
case DoubleSizeNormalTextMarker:
currentFont = doubleSizeTextFont;
sizeFactor = 1;
break;
case DoubleSizeBoldTextMarker:
currentFont = doubleSizeTextFontBold;
sizeFactor = 2;
break;
default:
if (!string.IsNullOrEmpty(subString))
{
e.Graphics.DrawString(subString, currentFont, Brushes.Black, x + e.MarginBounds.Left, y + e.MarginBounds.Top);
x = x + (subString.Length * sizeFactor * 6);
}
break;
}
}
}
else
{
// Text line printing with no bold Text and no double size Text in it.
string subString = line.Replace(NormalTextMarker, string.Empty);
Match barCodeMarkerMatch = Regex.Match(subString, BarCodeRegEx, RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (barCodeMarkerMatch.Success)
{
// Get the receiptId
subString = barCodeMarkerMatch.Groups[1].ToString();
using (Image barcodeImage = barCode.Create(subString, e.Graphics.DpiX, e.Graphics.DpiY))
{
if (barcodeImage != null)
{
float barcodeHeight = barcodeImage.Height / dpiYRatio;
if (y + barcodeHeight >= e.MarginBounds.Height)
{
// No more room - advance to next page
e.HasMorePages = true;
return true;
}
// Render barcode in the center of receipt.
e.Graphics.DrawImage(barcodeImage, ((contentWidth - (barcodeImage.Width / dpiXRatio)) / 2) + e.MarginBounds.Left, y + e.MarginBounds.Top);
y += barcodeHeight;
}
}
}
else
{
e.Graphics.DrawString(subString, textFont, Brushes.Black, e.MarginBounds.Left, y + e.MarginBounds.Top);
}
}
y = y + lineHeight;
return true;
}
}
}