- Windows CE Programming - http://www.hjgode.de/wp -

Windows Phone 8: print demo for bluetooth label/receipt printers

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.

btprint_main_start_bt_and_file_selected [1] btprint_demolist [2]

Similar to my BTprint4/Android you will get a list of connected printers and a list of demo files.

btprint_btdevicelist_selected [3]

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.

btprint_demolist [2]

Coding was harder than for Android, I 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 helpfull 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.

vs_printfileslist_empty [4]

I created another dummy page (dialog) to be able to get a bit visual design feedback.

vs_printfileslist_dummy [5]

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.

<?xml version="1.0" encoding="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 List<DemoFile> demofiles { get; private set; }

        public DemoFilesFileBased()
        {
            demofiles = new List<DemoFile>();
            //read files
            readList();
        }

        public async void readList()
        {
            StorageFolder InstallationFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;//.GetFolderAsync("/printfiles");
            StorageFolder PrintfilesFolder = await InstallationFolder.GetFolderAsync("printfiles");

            IReadOnlyList<StorageFile> filelist = await PrintfilesFolder.GetFilesAsync();// .GetItemsAsync();

            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)
        {
            //get folder
            StorageFolder PrintfilesFolder = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("printfiles");
            //get file
            StorageFile printfile = await PrintfilesFolder.GetFileAsync(_filename);
            if (printfile != null)
            {
                //read file
                Stream stream = await ReadFileContentsAsync(printfile);
                //read byte stream
                byte[] bXML = new byte[stream.Length];
                stream.Read(bXML, 0, (int)stream.Length);
                //convert to string
                string sXML = Encoding.UTF8.GetString(bXML, 0, bXML.Length);
                //deserialize string
                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 to get same result on page back and for navigation, otherwise listbox is empty on subsequent navigateTo
        //either use file based list
        static DemoFilesFileBased demoFiles = new DemoFilesFileBased();
        // or use xml based file list
        static DemoFilesXMLBased demoFiles = new DemoFilesXMLBased();

You can use both classes interchangeable.

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:Paired"] = "";
                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:

        /// <summary>
        /// Connect to the given host device.
        /// </summary>
        /// <param name="deviceHostName">The host device name.</param>
        public async void Connect(HostName deviceHostName)
        {
            if(!_bIsConnected)// if (socket != null)
            {
                Initialize();
                dataReadWorker.DoWork += new DoWorkEventHandler(ReceiveMessages);
                await socket.ConnectAsync(deviceHostName, "1"); //can we use the SPP UUID too? {00001101-0000-1000-8000-00805F9B34FB} 
                
                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 [6].

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)
            {
                //uint commandSize = dataWriter.MeasureString(command);
                //dataWriter.WriteByte((byte)commandSize);
                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 [7]

App can be installed from Windows Phone Store [8]