This is the description of my btPrint
demo app to print demo files to portable bluetooth label/receipt printers. I already did the same app for Android and now wanted to do the same for Windows phone 8.
The app starts with the main screen where you select a bluetooth printer, connect, select a demo file and let it print.
Similar to my BTprint4/Android, you will get a list of connected printers and a list of demo files.
The demo files are the same as for BTprint4
. There are files for printers ‘specking’ CSim, ESC/P, IPL (Intermec), Fingerprint (Intermec) and XSim. The files are described in an XML file which delivers some description and help text for the files.
Coding was harder than for Android, I am missing some API functions to access bluetooth devices. But coding was also similar, with layout files (WPF XAML) and code files. Async
/await
was also new for me and costs me some time to code. There is no sync access to files and resources, only async functions.
The layout designer of Visual Studio Express 2013 offers less layout options with the Phone 8 SDK than Android Dev Studio and the Android SDK. I am missing the FillParent
, etc. attributes to get a full dynamic layout. Something for future Phone SDKs?
As the visual designer was not helpful in some cases, I had to code the layout by hand editing the XAML file directly. As I use a dynamic created data source for the demo file list, the layout designer only shows an empty ListBox
.
I created another dummy page (dialog) to be able to get a bit visual design feedback.
I found a post about a custom listbox
entry and altered that to use a Grid
layout instead of the ugly StackPanel
. The button is filled with an image and the description and help text of a demo file. This data is taken of the XML file using a class to deserialize the XML. So I get a list of XML file objects with description and help text.
="1.0" ="ISO-8859-1"
<files>
<fileentry>
<shortname>PrintESC2plistBT</shortname>
<description>Intermec (BT,ESCP,2inch) Product List Print</description>
<help>Print 2inch list to an Intermec printer in ESCP</help>
<filename>escp2prodlist.prn</filename>
</fileentry>
<fileentry>
<shortname>PrintESCP3fieldserviceBT</shortname>
<description>Intermec (BT,ESCP,3inch) Field Service Print</description>
<help>Print 3inch field service to an Intermec printer in ESCP</help>
<filename>escp3fieldservice.prn</filename>
</fileentry>
...
This is evaluated to objects in a class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace btPrint4wp
{
[XmlRoot("files")]
public class demofilesXML
{
[XmlElement("fileentry")]
public fileentry[] fileentries;
public class fileentry
{
[XmlElement("shortname")]
public string shortname;
[XmlElement("description")]
public string filedescription;
[XmlElement("help")]
public string filehelp;
[XmlIgnore]
public string fileimage;
[XmlIgnore]
string _filename;
[XmlElement("filename")]
public string filename
{
get { return _filename; }
set {
_filename = value;
filetype = _filename.Substring(0, 4);
if (filetype.StartsWith("fp"))
fileimage = "images/fp.gif";
else if (filetype.StartsWith("csim"))
fileimage = "images/csim.gif";
else if (filetype.StartsWith("escp"))
fileimage = "images/escp.gif";
else if (filetype.StartsWith("ipl"))
fileimage = "images/ipl.gif";
else if (filetype.StartsWith("xsim"))
fileimage = "images/xsim.gif";
else
fileimage = "images/pb42.gif";
}
}
public string filetype;
}
public static demofilesXML XmlDeserialize(string s)
{
var locker = new object();
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(s));
var reader = XmlReader.Create(ms);
try
{
var xmlSerializer = new XmlSerializer(typeof(demofilesXML));
lock (locker)
{
var item = (demofilesXML)xmlSerializer.Deserialize(reader);
reader.Close();
return item;
}
}
catch
{
return default(demofilesXML);
}
finally
{
reader.Close();
}
}
}
}
As I first used a simple file list and then the XML based file list, I decided to write some Interfaces and classes to be able to either use the file based or XML based approach. So you will find an interface definition for demofile
and another for demofiles
and then classes implementing these interfaces called demofile
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace btPrint4wp
{
public interface IDemoFile
{
string filename { get; set; }
string filetype { get; set; }
string filedescription { get; set; }
string fileimage { get; set; }
string filehelp { get; set; }
}
public interface IDemoFiles
{
List demofiles { get; }
}
}
and the DemoFile
and DemoFiles
class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace btPrint4wp
{
public class DemoFile:IDemoFile
{
public string filename { get; set; }
public string filetype { get; set; }
public string filedescription { get; set; }
public string fileimage { get; set; }
public string filehelp { get; set; }
public DemoFile(string name, string description, string help)
{
filename = name;
filedescription = description;
filehelp = help;
filetype = filename.Substring(0, 4);
if (filetype.StartsWith("fp"))
fileimage = "images/fp.gif";
else if (filetype.StartsWith("csim"))
fileimage = "images/csim.gif";
else if (filetype.StartsWith("escp"))
fileimage = "images/escp.gif";
else if (filetype.StartsWith("ipl"))
fileimage = "images/ipl.gif";
else if (filetype.StartsWith("xsim"))
fileimage = "images/xsim.gif";
else
fileimage = "images/pb42.gif";
}
public override string ToString()
{
return filename;
}
}
public class DemoFiles : IDemoFiles
{
List<DemoFile> _demofiles = new List<DemoFile>();
public List<DemoFile> demofiles
{
get { return _demofiles; }
set { _demofiles = value; }
}
}
}
Then, there is a class to build demofile
objects by enumerating the files coming within the resources of the app:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
namespace btPrint4wp
{
public class DemoFilesFileBased:DemoFiles
{
public DemoFilesFileBased()
{
demofiles = new List<DemoFile>();
readList();
}
public async void readList()
{
StorageFolder InstallationFolder =
Windows.ApplicationModel.Package.Current.InstalledLocation; StorageFolder PrintfilesFolder = await InstallationFolder.GetFolderAsync("printfiles");
IReadOnlyList<StorageFile> filelist =
await PrintfilesFolder.GetFilesAsync();
IStorageItem[] files = filelist.ToArray();
List<string> filenamelist = new List<string>();
foreach (StorageFile i in filelist)
{
if (i.Name.EndsWith("prn"))
{
filenamelist.Add(i.Name);
DemoFile df = new DemoFile(i.Name, "n/a", "n/a");
demofiles.Add(df);
}
System.Diagnostics.Debug.WriteLine(i.Name);
}
}
}
}
The other class creates DemoFile
objects by parsing the XML data. We need to read the XML file and provide the content as string
to the de-serialize method. My install did not offer System.IO.FileIO
and so I used a byte buffer to read the file and converted that to a string
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using Windows.Storage;
namespace btPrint4wp
{
public class DemoFilesXMLBased:DemoFiles
{
StorageFolder InstallationFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
public demofilesXML myFiles = new demofilesXML();
public List<demofilesXML.fileentry> myDemoFilesXML = new List<demofilesXML.fileentry>();
public DemoFilesXMLBased()
{
demofiles.Clear();
init();
}
async void init()
{
myFiles = await deserialize("demofiles.xml");
myDemoFilesXML.Clear();
foreach (demofilesXML.fileentry f in myFiles.fileentries)
{
myDemoFilesXML.Add(f);
demofiles.Add(new DemoFile(f.filename, f.filedescription, f.filehelp));
}
}
async Task<demofilesXML> deserialize(string _filename)
{
StorageFolder PrintfilesFolder =
await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("printfiles");
StorageFile printfile = await PrintfilesFolder.GetFileAsync(_filename);
if (printfile != null)
{
Stream stream = await ReadFileContentsAsync(printfile);
byte[] bXML = new byte[stream.Length];
stream.Read(bXML, 0, (int)stream.Length);
string sXML = Encoding.UTF8.GetString(bXML, 0, bXML.Length);
myFiles = demofilesXML.XmlDeserialize(sXML);
return myFiles;
}
return new demofilesXML();
}
public async Task<Stream> ReadFileContentsAsync(StorageFile _fileName)
{
try
{
var file = await _fileName.OpenReadAsync();
Stream stream = file.AsStreamForRead();
return stream;
}
catch (Exception)
{
return null;
}
}
}
}
In the main app, better in the DemoFile
list page, these two classes are interchangeable:
static DemoFilesFileBased demoFiles = new DemoFilesFileBased();
static DemoFilesXMLBased demoFiles = new DemoFilesXMLBased();
You can use both classes interchangeably.
Bluetooth
The Bluetooth list and print code is simple:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using Windows.Networking.Proximity;
using Microsoft.Phone.Tasks;
namespace btPrint4wp
{
public partial class btdevices : PhoneApplicationPage
{
PeerInformation _peerInformation = null;
public btdevices()
{
InitializeComponent();
fillList();
}
async void fillList()
{
try
{
PeerFinder.AlternateIdentities["Bluetooth:SDP"] =
"{00001101-0000-1000-8000-00805F9B34FB}";
var peerList = await PeerFinder.FindAllPeersAsync();
if (peerList.Count > 0)
{
List<btdevice> peernames = new List<btdevice>();
foreach (PeerInformation pi in peerList)
peernames.Add(new btdevice(pi));
btlist.ItemsSource = peernames;
}
else
{
MessageBox.Show("No active peers");
showBTSettings();
}
}
catch (Exception ex)
{
if ((uint)ex.HResult == 0x8007048F)
{
MessageBox.Show("Bluetooth is turned off");
showBTSettings();
}
}
}
Now, when you got a PeerInfo
, you can use that to connect:
private void btConnect_Click(object sender, RoutedEventArgs e)
{
if (peerInformation == null)
{
addLog("Please use search first");
ShellToast toast = new ShellToast();
toast.Content = "Please use search first";
toast.Title = "Error";
toast.Show();
return;
}
if (btConn.isConnected)
{
addLog("Already connected. Disconnect first!");
return;
}
btConn.Connect(peerInformation.HostName);
return;
}
The connection is done in another class:
public async void Connect(HostName deviceHostName)
{
if(!_bIsConnected) {
Initialize();
dataReadWorker.DoWork += new DoWorkEventHandler(ReceiveMessages);
await socket.ConnectAsync(deviceHostName, "1");
dataReader = new DataReader(socket.InputStream);
dataReader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
dataReadWorker.RunWorkerAsync();
dataWriter = new DataWriter(socket.OutputStream);
MessageReceived(">>>connected to: " + deviceHostName);
ConnectDone(deviceHostName);
_bIsConnected = true;
}
}
The above code is based on code found at Nokia forum.
Finally, at some point, we do the print. We just send the file content to the printers' stream socket:
async void printFile(string _filename)
{
StorageFolder PrintfilesFolder =
await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("printfiles");
StorageFile printfile = await PrintfilesFolder.GetFileAsync(_filename);
if (printfile != null)
{
byte[] buf = await ReadFileContentsAsync(printfile);
await btConn.send(buf);
addLog("printFile done");
}
}
...
public async Task<byte[]> ReadFileContentsAsync(StorageFile _fileName)
{
try
{
var file = await _fileName.OpenReadAsync();
Stream stream = file.AsStreamForRead();
byte[] buf = new byte[stream.Length];
int iRead = stream.Read(buf, 0, buf.Length);
addLog("read file = " + iRead.ToString());
return buf;
}
catch (Exception)
{
return new byte[0];
}
}
...
public async Task<uint> send(byte[] buffer)
{
uint sentCommandSize = 0;
if (dataWriter != null)
{
dataWriter.WriteBuffer(buffer.AsBuffer());
await dataWriter.StoreAsync();
await dataWriter.FlushAsync();
sentCommandSize = (uint) buffer.Length;
}
return sentCommandSize;
}
Hope you find the one or other useful.
Source code hosted at github.
App can be installed from Windows Phone Store.
<!-- Social Bookmarks BEGIN -->
<!-- Social Bookmarks END -->