Sunday, October 28, 2007

I couldn't remember how to setup my Orange SPV e650  to enable the GPS intermediate driver.

For those of you who don't know what I'm on about.    The Microsoft GPS Intermediate driver lets multiple programs share the same physical GPS.   In addition its a doddle to set any Intermediate driver aware application, i.e. Live Search Mobile just to use the Intermediate driver rather than flaff around with COM port settings.

Microsoft provides a control panel for Windows Mobile 6 professional,  but not for Windows Mobile 6 standard.   or in English.     If you have a phone type device without a stylus you won't have the GPS control panel.   (gross generalisation, I know).

Same information is also good for Windows Mobile 5.

 

What you need is the Smartphone GPS intermediate driver setup utility.

Which is contained as part of the Windows Mobile 6, SDK and located at

<installed drive>\Program Files\Windows Mobile 6 SDK\Tools\GPS

and called 'settings.exe'

To get the Windows Mobile 6,   SDK its here

 

Copy this to your device,  then run, with file-explorer.

You need to have first paired your GPS with your device, if you're connecting up a Bluetooth device (but you knew that right).

Sunday, October 28, 2007 7:12:39 PM UTC  #    Comments [0]  | 
Saturday, October 27, 2007

image

I got thinking, Windows Mobile devices have relatively small screens.    What we need is a ticker,  so we can display more information in a small space.

So I spent a fair few hours today producing a suitable control.     implementing double buffering and trying to code something up-to the job proved to be a bit of a challenge.

I'll post my solution and a walk-through of my approach over the next couple of entries.

Saturday, October 27, 2007 10:39:37 PM UTC  #    Comments [0]  | 

A couple of weeks back I rebuilt my main laptop re-installing Visual Studio and all that good stuff.

Since then I haven't been unable to debug Compact Framework applications.    I always get symbol not loaded messages and my code refuses to stop at breakpoints.

I've just remembered their is a patch for the .Net Compact Framework 2, SP1.

Its over here if you need it -

http://www.microsoft.com/downloads/thankyou.aspx?familyId=7befd787-9b5e-40c6-8d10-d3a43e5856b2&displayLang=en

Needless to say normal debugging has resumed.

Saturday, October 27, 2007 7:24:32 PM UTC  #    Comments [0]  | 
Thursday, October 25, 2007

So this has been the focus of what I've been doing lately.   I've been looking at existing warehouse and stock mobile applications.   They seem to share one common feature in common.    That is select an operation like, stock move from a menu then scan a barcode.

So I've been thinking this is wrong.    Surely the way to build mobile applications in the 21st century is to go the other way.    Scan a barcode ; device works out what type of barcode it is,  item, rack location, pallet label etc.

Then present the user with a list of options,  I.e if I scan a product then present a list of options like stock move, dump etc..

I've started building all of my applications like this now.

Thursday, October 25, 2007 7:41:57 PM UTC  #    Comments [0]  | 
Monday, October 22, 2007
Monday, October 22, 2007 9:35:02 PM UTC  #    Comments [0]  | 
Thursday, October 18, 2007
Thursday, October 18, 2007 10:39:37 PM UTC  #    Comments [0]  | 

So today, I started to use an amazing and buried bit of technology.

I needed a way from a LOB applications to be able to programmatically print reports from SQL Reporting Services.   Now I know this is a little off topic for me.

But strike me down,   predictably, SQL reporting services can be fully driven via web-services.

Its worth looking at the Reporting Services docs on MSDN.

Bottom line, I was able to print Reporting services report to excel format; then  programmatically open up Excel.    I could then tell Excel to scale to fit the document to a page then print.   All this was triggered by an event in a LOB application.

(wicked)

Thursday, October 18, 2007 6:24:15 PM UTC  #    Comments [0]  | 
Wednesday, October 17, 2007

Just saw this -

Keep important files at your fingertips - anywhere. All file changes are automatically synchronized between linked computers, so you are always accessing the latest documents, photos, and files.

www.foldershare.com

Wednesday, October 17, 2007 6:54:59 PM UTC  #    Comments [0]  | 
Tuesday, October 16, 2007

So in the past few days  I've been trying to achieve two objectives -

  1. Improve mobile application user interfaces.
  2. Brand my company (Anglia).

 

So with this in mind, I realised that the first candidate and the second could be the same thing.   Often in mobile applications, we need to display message boxes.   Ah ha, I thought.   perfect case for a bit of branding.

So instead of doing -

MessageBox.Show("Hello World");

 

I added my own class to provide a branded message box,  like

MessageBoxAnglia.Show("Hello World");

Here's the result -

image

Of course just like the usual MessageBox we can do all the normal questions and stuff,  like.

MessageBoxAnglia.Show("Is This Any Good?","Question",MessageBoxButtons.YesNo,MessageBoxIcon.Question,MessageBoxDefaultButton.Button1);

 

image

 

So how's this done.   Well quite simply I have just build a custom form, with our branded messagebox.

The code is fairly simple.     I'll give you the code behind the form so that you can design your own.  Before I do,  I think I have achieved both objectives,  branding and a slightly improved user interface.

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace AngliaTemplate
{
    public partial class MessageBoxAnglia : Form
    {
        public static DialogResult Show(string text)
        {
            return Show(text, "", MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
        }
        public static DialogResult Show(string text, string caption)
        {
            return Show(text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);

        }

        public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultbutton)
        {
            bool switchedoffwait = false;

            if (Cursor.Current == Cursors.WaitCursor)
            {
                switchedoffwait = true;
                Cursor.Current = Cursors.Default;
                Application.DoEvents();
            }
            MessageBoxAnglia mb = new MessageBoxAnglia();
            mb.Text = caption;
            mb.Message = text;
            mb.Buttons = buttons;
            mb.FormIcon = icon;
            mb.DefaultButton = defaultbutton;
            DialogResult dr = mb.ShowDialog();
            Application.DoEvents();
            mb.Dispose();

            if (switchedoffwait)
            {
                Cursor.Current = Cursors.WaitCursor;
                Application.DoEvents();
            }
            return dr;

        }

        private string message = "";
        private MessageBoxButtons buttons = MessageBoxButtons.OK;
        private MessageBoxIcon formicon = MessageBoxIcon.None;
        private MessageBoxDefaultButton defaultbutton = MessageBoxDefaultButton.Button1;

        public string Message
        {
            set
            {
                message = value;
            }

        }

        public MessageBoxButtons Buttons
        {
            set
            {
                buttons = value;
            }
        }

        public MessageBoxIcon FormIcon
        {
            set
            {
                formicon = value;
            }
        }

        public MessageBoxDefaultButton DefaultButton
        {
            set
            {
                defaultbutton = value;
            }
        }

        public MessageBoxAnglia()
        {
            InitializeComponent();
        }

        private void btok_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.OK;
        }

        private void MessageBoxAnglia_Load(object sender, EventArgs e)
        {
            if (this.Text == "")
            {
                this.FormBorderStyle = FormBorderStyle.None;
                this.WindowState = FormWindowState.Maximized;
            }
            this.label1.Text = message;
            if (message.Length > 100)
            {
                Font f = new Font(FontFamily.GenericSansSerif, 12, FontStyle.Regular);

                this.label1.Font = f;
            }

            this.btyes_from_yes_no_cancel.Visible = false;
            this.btno_from_yes_no_cancel.Visible = false;
            this.btcancel_from_yes_no_cancel.Visible = false;
            switch (buttons)
            {
                case MessageBoxButtons.OK:
                    this.mnucancel.Text = "";
                    break;
                case MessageBoxButtons.OKCancel:
                case MessageBoxButtons.RetryCancel:
                case MessageBoxButtons.YesNo:

                    if (buttons == MessageBoxButtons.RetryCancel)
                    {
                        this.mnuok.Text = "Retry";
                    }

                    if (buttons == MessageBoxButtons.YesNo)
                    {
                        this.mnuok.Text = "Yes";
                        this.mnucancel.Text = "No";
                    }
                    break;
                case MessageBoxButtons.AbortRetryIgnore:
                case MessageBoxButtons.YesNoCancel:
                    this.mainMenu1.MenuItems.Clear();

                    this.btyes_from_yes_no_cancel.Visible = true;
                    this.btno_from_yes_no_cancel.Visible = true;
                    this.btcancel_from_yes_no_cancel.Visible = true;

                    if (buttons == MessageBoxButtons.AbortRetryIgnore)
                    {
                        this.btyes_from_yes_no_cancel.Text = "Abort";
                        this.btno_from_yes_no_cancel.Text = "Retry";
                        this.btcancel_from_yes_no_cancel.Text = "Ignore";
                    }
                    break;

            }

            // icon

            switch (formicon)
            {

                case MessageBoxIcon.Asterisk:
                    this.pbicon.Image = loadimage("Asterisk.png");
                    break;
                case MessageBoxIcon.Exclamation:
                    this.pbicon.Image = loadimage("Exclamation.png");

                    break;
                case MessageBoxIcon.Hand:
                    this.pbicon.Image = loadimage("Hand.png");

                    break;
                case MessageBoxIcon.Question:
                    this.pbicon.Image = loadimage("Question.png");

                    break;
                default:
                    this.pbicon.Visible = false;
                    break;

            }

            // default button

            switch (buttons)
            {
                case MessageBoxButtons.OKCancel:
                case MessageBoxButtons.RetryCancel:
                case MessageBoxButtons.YesNo:
                    if (defaultbutton == MessageBoxDefaultButton.Button2)
                    {
                        //this.mnucancel.Focus();
                    }
                    else
                    {
                        //this.mnuok.Focus();

                    }
                    break;
                case MessageBoxButtons.AbortRetryIgnore:
                case MessageBoxButtons.YesNoCancel:
                    switch (defaultbutton)
                    {
                        case MessageBoxDefaultButton.Button1:
                            this.btyes_from_yes_no_cancel.Focus();
                            break;
                        case MessageBoxDefaultButton.Button2:
                            this.btno_from_yes_no_cancel.Focus();
                            break;
                        case MessageBoxDefaultButton.Button3:
                            this.btcancel_from_yes_no_cancel.Focus();
                            break;
                    }
                    break;
            }

        }

        private Bitmap loadimage(string imageName)
        {
            return new Bitmap(System.Reflection.Assembly.GetExecutingAssembly().
                GetManifestResourceStream("AngliaTemplate.Images." + imageName));
        }
        private void mnuok_Click(object sender, EventArgs e)
        {
            DialogResult dr = DialogResult.None;
            switch (buttons)
            {
                case MessageBoxButtons.RetryCancel:

                    dr = DialogResult.Retry;
                    break;
                case MessageBoxButtons.YesNo:
                    dr = DialogResult.Yes;
                    break;
                default:
                    dr = DialogResult.OK;
                    break;
            }
            this.DialogResult = dr;
        }

        private void mnucancel_Click(object sender, EventArgs e)
        {
            DialogResult dr = DialogResult.None;
            switch (buttons)
            {
                case MessageBoxButtons.YesNo:
                    dr = DialogResult.No;
                    break;
                case MessageBoxButtons.OK:
                    return;
                default:
                    dr = DialogResult.Cancel;
                    break;
            }
            this.DialogResult = dr;
        }

        private void btyes_from_yes_no_cancel_Click(object sender, EventArgs e)
        {
            DialogResult dr = DialogResult.Yes;
            if (buttons == MessageBoxButtons.AbortRetryIgnore)
            {
                dr = DialogResult.Abort;
            }

            this.DialogResult = dr;
        }

        private void btno_from_yes_no_cancel_Click(object sender, EventArgs e)
        {
            DialogResult dr = DialogResult.No;
            if (buttons == MessageBoxButtons.AbortRetryIgnore)
            {
                dr = DialogResult.Retry;
            }

            this.DialogResult = dr;
        }

        private void btcancel_from_yes_no_cancel_Click(object sender, EventArgs e)
        {
            DialogResult dr = DialogResult.Cancel;
            if (buttons == MessageBoxButtons.AbortRetryIgnore)
            {
                dr = DialogResult.Ignore;
            }

            this.DialogResult = dr;
        }

        private void MessageBoxAnglia_Click(object sender, EventArgs e)
        {
            if (buttons == MessageBoxButtons.OK)
            {
                this.DialogResult = DialogResult.OK;
            }
        }
    }
}

Tuesday, October 16, 2007 7:47:54 PM UTC  #    Comments [0]  | 
Friday, October 12, 2007

A client phoned me yesterday, wanting some enhancements to one of their mobile applications.   Simple I thought, have a quick check of feasibility by looking at the copy of the test database I have running on my laptop.   I then remembered that I've recently rebuilt my laptop and hadn't the customers database attached to my copy of SQL 2005.

This is where it gets interesting,   the solution replicates part of a SQL database over to a bunch of mobile business applications built on SQL Compact.     I attached the database and it all went pear-shaped.   I was unable to publish any tables.

After much hair pulling I got myself back to a working state by going to the master database and running the following SQL command -

sp_removedbreplication @dbname ='NAMEOFTHEDATABASE'

 

This took me about 3 hours to arrive at this conclusion.    Moral of the story, use SQL backups rather than trying to just re-attach to existing databases.

Friday, October 12, 2007 6:38:47 PM UTC  #    Comments [0]  | 
Tuesday, October 09, 2007

So some of the software I write has bugs in it.   Yes I admit it.   Little small niggles that over time need to be ironed out.   Furthermore those pesky users keep wanting new and exciting things added to their mobile applications.

To keep things moving along about a year ago at Anglia we added an automatic update facility that compares the version of the client application against either a web-service call to return a version number, or looking in a replicated SQL Compact table for a current version number.

However the biggest issue ws faced, was ensuring that the install of the new application is pain free to the end users (and a good way of disguising my behind the scenes fixes).   Windows Mobile has long had a great command line tool for installing CAB files.   WCELOAD.EXE   up until day told the world with a series of dialogs that I was fixing their software.     But today,  I found out how to silence these onscreen admissions of buggy software/user requests (maybe not in that order).

So when I new version to be installed,   I quit my running application,  launch a small program that pulls down over the network the new cab file.    Then from code  I issue the magic command line -

wceload "CABFILE.CAB" /silent

 

And the world keeps turning.....

Tuesday, October 09, 2007 7:53:04 PM UTC  #    Comments [0]  | 
Monday, October 08, 2007

So, just when thought it was safe to go back in the water.   Having posted about auto detecting iPhones, hitting your mobile web-pages,   I needed to also allow for the iPod Touch.

So this is my updated default web-page that will automatically redirect to appropriate web pages for desktop browser.  iPod, iPhone and any Windows Mobile device.   The source for my default.aspx page is as follows -

<%@ Page Language="C#" Trace="false" %>

<SCRIPT LANGUAGE="C#" RUNAT=SERVER>

    protected void Page_Load(Object sender, EventArgs e)
    {
        System.Web.Mobile.MobileCapabilities cur = (System.Web.Mobile.MobileCapabilities) Request.Browser;

        // this block of code is to detect if users are running Pocket PC 2003/CE or iPhone
        bool isce=false;
        bool isiphone=false;
        bool isipod=false;

        try    {
            isce = (Request.UserAgent.IndexOf("Windows CE")!=-1);
            isiphone = (Request.UserAgent.IndexOf("iPhone;")!=-1);
            isipod = (Request.UserAgent.IndexOf("iPod;")!=-1);
        }
        catch
        {

        }
        if (cur.IsMobileDevice||isce||isiphone||isipod)
        {               
            Response.Redirect("mobile");
        }
        else
        {
            Response.Redirect("main");
        }
    }

</SCRIPT>

 

 

Technorati Tags: , ,
Monday, October 08, 2007 7:32:56 PM UTC  #    Comments [0]  | 

Just to let you all know. I am working with the OpenNETCF folks to add my Windows Live Barcode/QR Tag generator to their impressive library.

Monday, October 08, 2007 4:20:03 PM UTC  #    Comments [0]  | 
Thursday, October 04, 2007

Today, I've been pre-selling again.   Its always a challenge trying to convince a great bunch of warehouse staff with next to no interest in anything computer related that a mobile solution will somehow enhance their working life.  So without getting too philosophical I find the best way of winning hearts and minds is to stoaryboard the solution showing screen-by-screen ideas for the eventual system.   I then pass to all interested parties a bunch of printouts of blank windows mobile screen to let the people 'who know how it all works' contribute and come up with their own designs.

It has to be a team effort to get these project off the ground.   I find it a real challenge to strike the balance  and not force my views onto people who have lived and breathed complex warehousing for more years than I have lived.   However,   mobile has its place,  done right it can boost, motivate and just make people feel worthwhile in what they do.   Juggling the sensitivies is the key.  Progress without the push is always at the back of my mind.    Mobile LOB, is cool technically challenging and cutting edge; however I believe the key to understanding  is that  real people doing real jobs have a great idea of what they want.   Recognising this  makes the difference between winning the deal and just being another salesperson at the door.

Thursday, October 04, 2007 8:50:01 PM UTC  #    Comments [0]  | 
Wednesday, October 03, 2007

I'm on a mission building a large scale solution for a client where mobile devices are communicating with a bunch of web-services.   Here are my top 3 NOW obvious tips.

1) Keep round trips to the web-service down to an absolute minimum.  Its better to cache on the device to store web-service results.   I've been using Dictionary objects to achieve this.   I use the key-field of the dictionary to list out the parameters I would pass to the web-service.   Before calling the web-service I check the dictionary.   This is a massive time saver.    You have to be a little careful with a cache renewal policy, i.e you don't want the last version of an order if things have changed on the server.    In addition, watch the memory.   A poorly thought out cache  can eat up space fast.

2) The XML payload is very verbose.    If you keep your parameter variable names and response structure variable names nice and short,  like 'IT' for Item,  rather than 'ITEM_NUMBER'.    Over slow connections (GPRS)   you get a significant performance improvement, i.e if you look at the SOAP data <ITEM_NUMBER>123</ITEM_NUMBER>  is sent.   so better to use <IT>123</IT>

3) Think messaging.     If you look up and order, in the web-service go pull out all relevant data (and only relevant data).    Don't necessarily treat web-services as just a way of issuing single SQL selects.    Decode foreign keys etc.  to save processing on the client,   i.e  if you look at Product_Type  to be ProductType1 its better to send back to the client  'Some Product Type'.   Obviously rule 2 applies.

More tips, will inevitably follow as more trip ups occur.

Wednesday, October 03, 2007 10:36:06 AM UTC  #    Comments [0]  | 
Tuesday, October 02, 2007

I'm pleased to announce that Microsoft have awarded me an MVP award.

MVP

I'm representing the Device Dev. group now,  so I'd better really check my facts before posting :-)

Thank you, to all of you who read my blog.   In addition its great to get feedback from so many people working on projects similar to the ones I bang on about.     I will continue to share my experiences good and bad about all things Mobile Line Of Business. 

Best regards

 

Richard

 

Technorati Tags:
Tuesday, October 02, 2007 9:27:56 AM UTC  #    Comments [0]  | 
Monday, October 01, 2007

Here's the second part of what I was banging on about yesterday.  To automatically decide which type of barcode reader is attached to the mobile device we need to know what device we are on.

 

I used a Pinvoke for SystemparetersInfo to return the name of  the manufacturer (and sometimes model) of the device.    An MSDN sample did the hard work for me.    Here's the bit of code you need -

private static class NativeMethods
     {
         [DllImport("coredll.dll")]
         private static extern int SystemParametersInfo(uint uiAction, uint uiParam, StringBuilder pvParam, uint fWiniIni);

         private const uint SPI_GETPLATFORMTYPE = 257;
         private const uint SPI_GETOEMINFO = 258;

         private static string GetSystemParameter(uint uiParam)
         {
             StringBuilder sb = new StringBuilder(128);
             if (SystemParametersInfo(uiParam, (uint)sb.Capacity, sb, 0) == 0)
                 throw new ApplicationException("Failed to get system parameter");
             return sb.ToString();
         }

         public static string GetOEMInfo()
         {
             return GetSystemParameter(SPI_GETOEMINFO);
         }


     }

 

The final bit is to work out which barcode reader is attached is -

public static bool IsEmulator
        {
            get { return (NativeMethods.GetOEMInfo() == "Microsoft DeviceEmulator"); }
        }

public static Scanner ScannerSelect()
    {
        if (Core.IsEmulator)
        {
            return new ScanningDummy();
        }

        string x = NativeMethods.GetOEMInfo();
        if (x.StartsWith("SYMBOL"))
        {
            return new ScanningSymbol();
        }

        if (x.StartsWith("Intermec"))
        {
            return new ScanningIntermec();
        }

        if (x.StartsWith("DataLogic"))
        {
            return new ScanningDataLogic();
        }
        MessageBox.Show("Unknown Scanner Type - " + x);
        return new ScanningDummy();

    }

}

 

There you have it.    One code base supporting multiple different types of scanner

Monday, October 01, 2007 8:17:53 PM UTC  #    Comments [0]  | 

Theme design by Jelle Druyts

Pick a theme: