Tuesday, May 16, 2006

In order to minimize the size of the CAB size for the game, include the WM Smartphone 2003 as well as reduce the steps required to install the Mobile Kombat on the user’s devices, one of the main requirements was to use CF v1. We also decided not to use the SQL CE 2.0 for the same purposes. One of the data files used in the game was a set of questions and answers for the trivia on mobile programming.  The obvious solution was to use DataSet for the data files, but don’t forget that it loads the whole Xml data file into memory and we were trying really hard to reduce a memory footprint of the game as much as possible. Let’s say we have 2 tables defined: Answer and Question. Here’s how the xml data looked like:

 

<NewDataSet>

  <Question>

    <QuestionID>1</QuestionID>

    <Text>Which is a new .NET Compact Framework 2.0 namespace?</Text>

    <CorrectAnswer>2</CorrectAnswer>

  </Question>

  <Question>

    <QuestionID>2</QuestionID>

    <Text>What operating system runs on Ultra Mobile PCs?</Text>

    <CorrectAnswer>0</CorrectAnswer>

  </Question>

 

<Answer>

    <QuestionID>1QuestionID>

    <Text>Sockets</Text>

  </Answer>

  <Answer>

    <QuestionID>1QuestionID>

    <Text>Collections</Text>

  </Answer>

  <Answer>

    <QuestionID>1QuestionID>

    <Text>Cryptography</Text>

  </Answer>

 

So I came up with the solution of creating a forward only lightweight XmlDataReader that would move from one xml node to another and will create an instance of the appropriate class based on the xml data. Take a look at the class diagram:

  

Well… you could say that XmlSerializer can just do that. But remember that we had to use the CF v1, which doesn’t provide the XmlSerializer. Considering the time constraints that would be required to create a generic XmlSerializer, my solution was that all classes would have to implement the IXmlDataFormat interface:

 

interface IXmlDataFormat

{

    void ReadXml(XmlNodeList nodeList);

}

 

So the XmlDataReader when deserializing the objects will delegate the population of their properties to the object itself. Here’s how the object’s instance creation looks:

 

 

 

 

///

/// Creates an instance of the type if it implements the IXmlDataFormat interface.

///

/// The XmlNode.

///

private object GetValue(XmlNode node)

{           

     ConstructorInfo info = null;

     // Make sure that the type implements IXmlDataFormat interface.

     if (ImplementsInterface(type, typeof(IXmlDataFormat)))

     {

        info = type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[0], null);

        if (info == null)

        {

             throw new ArgumentException("No constructor");

        }

        // Create an instance

        IXmlDataFormat objValue = info.Invoke(null) as IXmlDataFormat;

        if (objValue != null)

        {

            // Call the interface method

            objValue.ReadXml(node.ChildNodes);

        }

        return objValue;

     }

     return null;

  } 

 

And here’s how an actual class would implement the IXmlDataFormat interface:

 

public void ReadXml(XmlNodeList nodeList)

{

    foreach (XmlNode node in nodeList)

    {

        XmlElement element = node as XmlElement;

        if (element != null)

        {

            switch (element.LocalName)

            {

                case "QuestionID":

                {

                    this.questionID = Convert.ToInt32(element.InnerText);

                    continue;

                }

                case "Text":

                {

                    this.text = element.InnerText;

                    continue;

                }

                case "CorrectAnswer":

                {

                    this.correctAnswer = Convert.ToInt32(element.InnerText);

                    continue;

                 }

            }

         }

      }

   }

 

The code above just simply iterates through xml nodes and populates the object’s properties with the values from xml data.

 

I’ve also created the XmlDataManager class. This class implements a few useful helper methods that wrap calls to the XmlDataReader. Here’s some sample code that will retrieve all answers from xml data file by a certain question id:

 

// Create an instance of the XmlDataReader

XmlDataReader answersReader = new XmlDataReader(this.triviaFilePath, typeof(Answer));

// Create an instance of the XmlDataManager by passing the XmlDataReader

XmlDataManager answersManager = new XmlDataManager(answersReader);

// Retreive the list of anwers

IList answersList = answersManager.GetObjectList("QuestionID", QID);

 

You can download the sample project and the source code for the XmlDataReader here:

 

 

XmlDataReaderTest.zip (48.11 KB)
5/16/2006 7:25:27 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Monday, May 15, 2006
I am back from MEDC in Vegas. As I anticipated it, the conference was an unending mini twister of different events, sessions, meeting old friends, fellow MVP’s, OpenNETCF team, Microsoft’s guys from CF and other teams and new people, sumobot competition, Mobile Kombat game we’ve developed for MEDC that Nick described in his blog. In the next few posts I’ll describe a few interesting solutions I had to come up with to make the game to have a minimal memory footprint and appropriate performance.

5/15/2006 3:41:05 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Friday, May 05, 2006

I am off to Las Vegas for MEDC 2006. The last year was a blast, but this year I anticipate it be a storm . I've been doing a very cool stuff for MEDC. Hopefully it's going to become a really big hit among the attendies. So if anybody wants to meet me, you will be able to find me in the usual company of really cool people from OpenNETCF and Infusion.

5/5/2006 3:24:08 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Friday, March 17, 2006

I would like to refer you to these great reads that explain the intricacies of the persistent storage in the Windows CE 5.0 devices:

http://blogs.msdn.com/windowsmobile/archive/2006/03/16/552996.aspx

http://blogs.msdn.com/ce_base/archive/2006/03/15/IncreaseFSThroughput.aspx

The first one is from Mike Caligaro from Windows Mobile Team and the second is form Ariane Jensen who is a member of the Windows CE core team. A must read for both.

 

3/17/2006 3:00:46 PM (GMT Standard Time, UTC+00:00)  #    Comments [34]  | 
 Thursday, March 09, 2006

So, everybody have been talking about Origami lately. It's started from the first Microsoft's unobscure marketing pitch on this site and finaly culminated after offical CeBIT announcements. I would agree with many people that the UMPC devices are a hardly revolutional idea, but it has got a great potential to become a new trend in the way people would use the computer, making it more usable for the average Joe.

So what's about the developer story for these devices? Since it runs Windows XP, it is a full .NET, baby! But consider a screen size, resolution and relatively less powerful processor - what does it remind you? Right, it is a pumped up version of the Windows Mobile device. And the people who have been developing for Windows CE based devices should feel themselves right at home. Need to create a smaller screen, touch driven user interface application? No problem here. 800x480 screen size is a football field for us and we know how to utilize it without cramping up the whole screen, draining the battery and making functional and easy to use. So WM and Windows CE developers suddenly have become experts in developing for UMPC! By the way, the beta of the UMPC emulator is already available for download here. It enables you to test your application's layout and screen behavior as it appears on UMPC's. But remember it is not a hardware emulator. It just puts the UMPC skin on your screen and resizes your desktop work area to the resolution on the UMPC (800x480).

Update: Don't forget to check the Origami team blog. I am subscribed!

3/9/2006 4:10:19 PM (GMT Standard Time, UTC+00:00)  #    Comments [32]  | 
 Friday, March 03, 2006

One of my colleagues recently purchased the Treo 700w and fall in love with it. Being a developer he came up with the idea to try to use the button on the phone's headphones to initiate the Voice Commander. So he approached me with the question on how to place a hook in the WM device in order to listen for all hardware button presses. As a matter of fact, Windows CE does support a keyboard hook through the SetWindowsHookEx API. This API requires the usage of the native callback, which was not possible in the first version of the CF. The current version of .NetCF empowered us with this very useful capability. So what are hooks? Put shortly, a hook is a function that you can create as a part of your application in order to monitor on the messages inside the operating system. Hooks were provided by Microsoft primarily to help developers with the debugging their applications. The incorrect usage of them very easily could bring the system down. Therefore the hooks should be a last resort when all other a more traditional methods don't satisfy your requirents. Below you will find the class HookKeys that wraps all appropriate API calls and exposes all catched messages as a managed HookEvent:

public class HookKeys

{

        #region delegates

 

        public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

        public delegate void HookEventHandler(HookEventArgs e, KeyBoardInfo keyBoardInfo);

 

        public HookEventHandler HookEvent;

 

        #endregion

 

        #region fields

 

        private HookProc hookDeleg;

        private static int hHook = 0;

 

        #endregion

 

 

        public HookKeys()

        {

 

        }

 

        #region public methods

        ///

        /// Starts the hook

        ///

        public void Start()

        {

            if (hHook != 0)

            {

                //Unhook the previouse one

                this.Stop();

            }

 

            hookDeleg = new HookProc(HookProcedure);

            hHook = SetWindowsHookEx(WH_KEYBOARD_LL, hookDeleg, GetModuleHandle(null), 0);

 

            if (hHook == 0)

            {

                throw new SystemException("Failed acquiring of the hook.");

            }

        }

 

        ///

        /// Stops the hook

        ///

        public void Stop()

        {

            UnhookWindowsHookEx(hHook);

        }

 

        #endregion

 

        #region protected and private methods

 

        protected virtual void OnHookEvent(HookEventArgs hookArgs, KeyBoardInfo keyBoardInfo)

        {

            if (HookEvent != null)

            {

                HookEvent(hookArgs, keyBoardInfo);

            }

        }

     

 

        private int HookProcedure(int code, IntPtr wParam, IntPtr lParam)

        {

           KBDLLHOOKSTRUCT hookStruct =  (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));

 

           if (code < 0)

                return CallNextHookEx(hookDeleg, code, wParam, lParam);

 

           // Let clients determine what to do

           HookEventArgs e = new HookEventArgs();

           e.Code = code;

           e.wParam = wParam;

           e.lParam = lParam;

 

           KeyBoardInfo keyInfo = new KeyBoardInfo();

           keyInfo.vkCode = hookStruct.vkCode;

           keyInfo.scanCode = hookStruct.scanCode;

           OnHookEvent(e, keyInfo);

 

           // Yield to the next hook in the chain

           return CallNextHookEx(hookDeleg, code, wParam, lParam);

 

       }

 

        #endregion

 

       #region P/Invoke declarations

 

       [DllImport("coredll.dll")]

       private static extern int SetWindowsHookEx(int type, HookProc hookProc, IntPtr hInstance, int m);

 

       [DllImport("coredll.dll")]

       private static extern IntPtr GetModuleHandle(string mod);

 

       [DllImport("coredll.dll")]

       private static extern int CallNextHookEx(

               HookProc hhk,

               int nCode,

               IntPtr wParam,

               IntPtr lParam

               );

 

       [DllImport("coredll.dll")]

       private static extern int GetCurrentThreadId();

 

       [DllImport("coredll.dll", SetLastError = true)]

       private static extern int UnhookWindowsHookEx(int idHook);

 

       private struct KBDLLHOOKSTRUCT

       {

           public int vkCode;

           public int scanCode;

           public int flags;

           public int time;

           public IntPtr dwExtraInfo;

       }

 

       const int WH_KEYBOARD_LL = 20;

 

       #endregion

   }

 

        #region event arguments

  

    public class HookEventArgs : EventArgs

    {

        public int Code;    // Hook code

        public IntPtr wParam;   // WPARAM argument

        public IntPtr lParam;   // LPARAM argument

    }

 

    public class KeyBoardInfo

    {

        public int vkCode;

        public int scanCode;

        public int flags;

        public int time;

    }

 

        #endregion

 

 

In order to use this class in your program, just declare the varialble and hook up into HookEvent:

 

HookKeys hook = new HookKeys();

hook.HookEvent += new HookKeys.HookEventHandler(HookEvent);  

The HookKeys class should work properly, but there is something missing from this class to be complete. I am not going to tell you what it is. This is going to be a self learning quiz for my readers. You can post the anwers in the comments. I'll anounce the winners in the next post.

3/3/2006 10:32:08 PM (GMT Standard Time, UTC+00:00)  #     | 
 Friday, February 24, 2006

Ever wanted to display images in the header of a ListView control in the .NET CF application? Sure we can do that. The Windows CE SDK documentation reveals that practically all messages that are available on a desktop are supported by the ListView. In particular we should be interested in HDM_SETITEM message which supposed to be send to the ListView header with the pointer to the HDITEM structure.  Fifteen minutes later I had this class ready:

public class ListViewHeaderIcon

{

        IntPtr hWndHeader;

        ListView listView;

 

        public ListViewHeaderIcon(ListView listView)   

        {

            this.listView = listView;

            //Get HWND of the header

            hWndHeader = SendMessage(listView.Handle, LVM_GETHEADER,

             IntPtr.Zero, IntPtr.Zero);

        }

        public void SetHeaderImage(int columnIndex, int imageIndex, bool placeOnRight)

        {

            //Make sure that we have handle of the header

            if (hWndHeader == IntPtr.Zero)

                throw new Exception("Handle of header does not exist.");

 

            //Create HDITEM and populate with values

            HDITEM hdItem = new HDITEM();

 

            hdItem.mask = HDI_TEXT | HDI_IMAGE | HDI_FORMAT;

            hdItem.fmt = HDF_STRING | HDF_IMAGE | (placeOnRight ? HDF_BITMAP_ON_RIGHT : 0);

            hdItem.iImage = imageIndex;           

            hdItem.pszText = Marshal.StringToBSTR(listView.Columns[columnIndex].Text);

                     

            //Sending HDM_SETITEM message

            SendMessage(hWndHeader, HDM_SETITEM, (IntPtr)columnIndex, ref hdItem);

 

        }

 

        #region P/Invokes

 

         const uint HDM_FIRST = 0x1200;

         const uint HDM_SETITEM = HDM_FIRST + 12;

         const uint HDI_FORMAT = 0x0004;

         const uint HDI_TEXT = 0x0002;

         const uint HDI_BITMAP = 0x0010;

         const uint HDI_IMAGE = 0x0020;

         const int HDF_STRING = 0x4000;

         const int HDF_BITMAP = 0x2000;

         const int HDF_IMAGE = 0x0800;

         const int HDF_BITMAP_ON_RIGHT = 0x1000;

         const uint LVM_FIRST = 0x1000;

         const uint LVM_GETHEADER = LVM_FIRST + 31;       

 

        [DllImport("coredll.dll")]

         static extern IntPtr SendMessage(IntPtr hWnd,

         uint uMsg, IntPtr wParam, ref HDITEM lParam);

 

        [DllImport("coredll.dll")]

        static extern IntPtr SendMessage(IntPtr hWnd,

        uint uMsg, IntPtr wParam, IntPtr lParam);

       

        struct HDITEM

        {

            public uint mask;

            public int cxy;

            public IntPtr pszText;

            public IntPtr hbm;