Showing posts with label CF. Show all posts
Showing posts with label CF. Show all posts

2008-07-19

Validating NumericUpDown on compact framework

A customer requested that instead of my NumericUpDown controls silently capping the input value within the Minimum..Maximum range it instead showed an error message telling the user their input is incorrect and that they need to alter it.

I was a bit annoyed to see that NumericUpDown.Validating is never called on the compact framework, in addition there was no way to get the input value and either accept or reject it before it is applied to its data bindings.

There's an article here which shows how to implement auto-select text when the NumericUpDown receives focus and I have been using it since Feb 2006. I decided to extend upon the techniques within it to implement the Validating event. My goal was to fire the Validating event before the value is applied to all data-bindings, but also to allow the programmer to read NumericUpDown.Value in order to determine the new value. To do this I had to replace the WndProc of the control so that I could handle the
WM_UPDOWN_NOTIFYVALUECHANGED message, parse the value, validate it, and then either accept it (call the original WndProc) or restore the value to the current value.

Rather than teach how this is done I thought I would just include the source code here. One point to note though is that I had to have a "bool IsInternalCall" wrapped around my handler otherwise I would have re-entrant problems and experience a stack overflow. Here is the source, it includes the auto-select code by Mark Arteaga.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using System.ComponentModel;

namespace Mycompany.Windows.Forms
{
  public class NumericUpDownWithSelect : NumericUpDown, ISupportInitialize
  {
    #region API
    private const int GWL_WNDPROC = -4;
    private const int WM_UPDOWN_NOTIFYVALUECHANGED = 13;
    public const int WM_GETTEXTLENGTH = 0x000E;
    public const int WM_GETTEXT = 0x000D;
    private const int WM_GETSELECTION = 0x00B0;
    private const int WM_SETSELECTION = 0x00B1;

    private WndProcHandler NewWndProc = null;
    private IntPtr OldWndProc = IntPtr.Zero;

    public delegate IntPtr WndProcHandler(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    [DllImport("coredll.dll", SetLastError = true)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
    [DllImport("coredll.dll", SetLastError = true)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, StringBuilder buffer);
    [DllImport("coredll.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, WndProcHandler wndproc);
    [DllImport("coredll.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
    [DllImport("coredll.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
    [DllImport("coredll.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr CallWindowProc(IntPtr wndProc, IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    #endregion

    private bool ControlDisposed = false;
    private bool IsValidating = false;
    private decimal ValueToValidate;

    public NumericUpDownWithSelect()
    {
    }

    public new event CancelEventHandler Validating;
    protected virtual void OnValidating(out bool cancel, decimal newValue)
    {
      cancel = false;
      CancelEventHandler validating = Validating;
      if (validating == null)
        return;

      cancel = false;
      CancelEventArgs args = new CancelEventArgs(false);
      IsValidating = true;
      try
      {
        ValueToValidate = newValue;
        Validating(this, args);
      }
      finally
      {
        IsValidating = false;
      }
      cancel = args.Cancel;
    }

    private decimal currentValue = 0;
    public new decimal Value
    {
      get
      {
        if (IsValidating)
          return ValueToValidate;
        return base.Value;
      }
      set
      {
        bool cancel;
        OnValidating(out cancel, value);
        if (!cancel)
        {
          base.Value = value;
          currentValue = value;
        }
      }
    }

    #region Validation
    protected override void OnHandleCreated(EventArgs e)
    {
      base.OnHandleCreated(e);
      if (this.Site == null)
      {
        NewWndProc = new WndProcHandler(ReplacementWndProcImpl);
        OldWndProc = SetWindowLong(this.Handle, GWL_WNDPROC, NewWndProc);
      }
    }

    private static bool IsInternalCall = false;
    private IntPtr ReplacementWndProcImpl(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam)
    {
      bool cancelled = false;
      if (msg == WM_UPDOWN_NOTIFYVALUECHANGED && !IsInternalCall)
      {
        IsInternalCall = true;
        try
        {
          int length = CallWindowProc(OldWndProc, this.Handle, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero).ToInt32();
          StringBuilder buffer = new StringBuilder(length + 1);
          SendMessage(this.Handle, WM_GETTEXT, length + 1, buffer);
          try
          {
            decimal newValue = decimal.Parse(buffer.ToString());
            OnValidating(out cancelled, newValue);
            if (cancelled)
            {
              Value = currentValue;
            }
          }
          catch (FormatException)
          {
            cancelled = true;
          }
        }
        finally
        {
          IsInternalCall = false;
          if (cancelled)
          {
            Focus();
            SelectAll();
          }
        }
      }
      return CallWindowProc(OldWndProc, hWnd, msg, wParam, lParam);
    }
    #endregion

    #region AutoSelect

    private delegate void SelectAllInvoke();

    private bool suppressOnGotFocus = false;

    protected override void OnGotFocus(EventArgs e)
    {
      base.OnGotFocus(e);
      if (!this.suppressOnGotFocus)
        SelectAll();
    }

    public void SelectAll()
    {
      this.SelectInternal(0, this.Value.ToString().Length);
    }

    public void Select(int start, int length)
    {
      this.SelectInternal(start, length);
    }

    private void SelectInternal(int start, int length)
    {
      if (!ControlDisposed)
      {
        this.suppressOnGotFocus = true;
        if (!this.Focused)
          this.Focus();
        IntPtr ret = SendMessage(this.Handle, WM_SETSELECTION, start, length);
        this.suppressOnGotFocus = false;
      }
    }
    #endregion

    protected override void Dispose(bool disposing)
    {
      ControlDisposed = true;
      base.Dispose(disposing);
    }

    #region ISupportInitialize Members
    //This region is here simply because the WinForm designer insists on casting this control
    //to ISupportInitialize
    void ISupportInitialize.BeginInit()
    {
    }

    void ISupportInitialize.EndInit()
    {
    }

    #endregion
  }

}


And how might you use it?

private void numericUpDownWithSelect1_Validating_1(object sender, CancelEventArgs e)
{
  if (numericUpDownWithSelect1.Value < numericUpDownWithSelect1.Minimum
    || numericUpDownWithSelect1.Value > numericUpDownWithSelect1.Maximum)
  {
    //No need to cancel, the new value will be rejected
    MessageBox.Show("Warning, value is about to be capped");
  }
  if (numericUpDownWithSelect1.Value > 5)
  {
    MessageBoxIcon icon = new MessageBoxIcon();
    e.Cancel =
      MessageBox.Show(
    "Is it really greater than 5?",
        "Are you sure?",
        MessageBoxButtons.YesNo,
        icon,
        MessageBoxDefaultButton.Button1) != DialogResult.Yes;
  }
}

2008-02-07

Your bug is my bug

I recently released an update to some software and a bug slipped through the net. It introduced some odd behaviour with a control named SmartGrid. After some testing I was able to determine that it wasn't my fault and that I could reproduce a bug in SmartGrid. I hate bugs in other people's source code, I can't fix it, I am at their complete mercy.

Thankfully the Resco support was amazing! I posted on their forums and immediately someone sent me instructions on where to send my project. The next morning I was disappointed to see an email saying that the project worked fine. I posted again and almost immediately someone had offered to chat on skype.

We did that for a while, both confused by the problem. We then went on to use Remote Assistance so that he could observe my bug which he wasn't experiencing.

In the end the problem was very confusing. I had Version A of the DLL in which the error occurred. I upgraded to the latest version (B) and it still occurred. The guy at Resco sent me a DLL with debug strings being sent to the IDE (C.DEBUG) and everything worked. I reverted to Version B and now it worked whereas before it didn't. Don't you just hate phantom bugs that "fix" themselves?

In the end the Resco guy sent me a full build of the very latest code base (Version C) and all seems fine.

Both of us were at a complete loss, but thanks to their excellent support the problem with my application was kept to as short a time as possible!

2008-01-17

No symbols loaded

This has been driving me mad for hours now! Whenever I run my PocketPC compact framework app I cannot debug it! None of the breakpoints will stop, each breakpoint just shows as an empty circle instead of a solid one.

So, what was the solution? I tried deleting all PDB files on my hard disk but that didn't do it. In the end manually deleting all of the files previously deployed to my PPC did the trick. Maybe VS couldn't overwrite them or something? No idea, but at least it works now :-)

2008-01-09

PPC

First a tip: If you spend ages wondering why you cannot connect your Pocket PC to your computer user ActiveSync etc here's something to check. Is the PPC plugged directly into your computer or via a USB hub? If it's via a hub then take it out and slap it into the computer itself.

Next a moan: Why oh why oh why..... Does the Compact Framework's NumericUpDown control have Value, Minimum, and Maximum defined as Decimal, but when you put a Maximum value in that is greater than 32767 it goes screwy when the user tries to put in a number larger than 32767? Sigh!

2007-08-17

Printing bitmaps using CPCL

I've had no end of grief trying to print a PCX to a Zebra Printer using the CPCL printer language. Silly me, didn't notice the EG command (expanded graphics) so there was no need to convert my BMP to a PCX and then struggle with binary data.

I still had a bit of grief working out how to print using the EG command because the documentation is quite frankly crap. The expected command format is

EG {WidthInBytes} {HeightInPixels} {XPos} {YPos} {Data}\r\n


The printer expects a 1 bit pixel matrix. So if pixel(0, 0) is set you will set "80" in the data. If pixel(0, 0) is set and pixel (7, 0) is also set you would sent "81". Basically what you need to do is to read each set of 8 horizontal pixels and then use bit operations to create a byte value 0..255, and then output this as hex 00..FF.

Here's the routine :-)

  public void DrawBitmap(Bitmap bmp, int xPosition, int yPosition)
{
if (bmp == null)
throw new ArgumentNullException("bmp");

//Make sure the width is divisible by 8
int loopWidth = 8 - (bmp.Width % 8);
if (loopWidth == 8)
loopWidth = bmp.Width;
else
loopWidth += bmp.Width;

DataString.Append(string.Format("EG {0} {1} {2} {3} ", loopWidth / 8, bmp.Height, xPosition, yPosition));

for (int y = 0; y < bmp.Height; y++)
{
int bit = 128;
int currentValue = 0;
for (int x = 0; x < loopWidth; x++)
{
int intensity;

if (x < bmp.Width)
{
Color color = bmp.GetPixel(x, y);
intensity = 255 - ((color.R + color.G + color.B) / 3);
}
else
intensity = 0;

if (intensity >= 128)
currentValue |= bit;
bit = bit >> 1;
if (bit == 0)
{
DataString.Append(currentValue.ToString("X2"));
bit = 128;
currentValue = 0;
}
}//x
}//y
DataString.Append("\r\n");
}

2007-02-16

Disabling BlueTooth on a Pocket PC

We use wireless printing through a COM port over BlueTooth. Having BlueTooth on all of the time can contribute towards energy consumption and cause the battery life on the Pocket PC to deplete faster. Now I disable BT when the application starts, and then re-enable it to print and disable it immediately afters. This adds about 1 second to each print job but it should save the battery power.


[DllImport("BthUtil.dll")]
private static extern int BthGetMode(out BlueToothRadioMode dwMode);

[DllImport("BthUtil.dll")]
private static extern int BthSetMode(BlueToothRadioMode dwMode);

public static BlueToothRadioMode BlueToothRadioMode
{
get
{
BlueToothRadioMode result;
BthGetMode(out result);
return result;
}
set
{
if (value != BlueToothRadioMode)
BthSetMode(value);
}
}

2007-02-14

Keeping a Pocket PC awake

My compact framework application imports XML into a local database. As there is so much data to import this can take up to an hour. During development there were no problems with this, but of course during development the Pocket PC is docked in its cradle which provides it with power.

When a Pocket PC is removed from its cradle it manages power differently, just like an unplugged laptop. So every five minutes the Pocket PC would hibernate and the user would have to turn it back on in order for the import to continue. During an hour the user would have to do this approximately twelve times. How annoying, and dangerous too if the employee is driving to their first job.

Anyway, I found the following very useful code on the web and thought I'd point it out as it was so useful!


public class Device
{
#region Device sleep support
[DllImport("CoreDll.dll")]
public static extern void SystemIdleTimerReset();

private static int DisableSleepCallsCount = 0;
private static System.Threading.Timer PreventSleepTimer = null;
#endregion

public static void EnableDeviceSleep()
{
DisableSleepCallsCount--;
if (DisableSleepCallsCount == 0)
{
if (PreventSleepTimer != null)
{
PreventSleepTimer.Dispose();
PreventSleepTimer = null;
}
}
}

public static void DisableDeviceSleep()
{
DisableSleepCallsCount++;
if (DisableSleepCallsCount == 1)
{
TimerCallback keepAlive = new System.Threading.TimerCallback(
delegate(object applicationData)
{
try
{
SystemIdleTimerReset();
}
catch (Exception)
{
}
}
);
PreventSleepTimer = new System.Threading.Timer(keepAlive, null, 0, 30 * 1000);
}
}
}


Now I can do this


Device.DisableDeviceSleep();
try
{
//Do stuff
}
finally
{
Device.EnableDeviceSleep();
}

2007-02-01

string.GetHashCode

I recently developed a simple support application that allows users on a PC to provide a one-off security number to a Pocket PC user and when entered the PPC will perform a specific support function.

I used the same class to generate these security codes in both the desktop and compact framework applications, tested it quite thoroughly and it all seemed to work fine. However once deployed it became evident that the codes provided by our support department were being rejected by the PPC as invalid. So what went wrong?

Seeing as the same class was used for both applications I thought it would be okay to test the encoding/decoding of command numbers on only a single platform, and this was my mistake! The routines use string.GetHashCode() to add a checksum to the end of the security codes just to prevent the user from performing actions without authorisation. For reasons I cannot imagine the implementation of string.GetHashCode() is different in the compact framework from the one in the full .NET framework. It was due to the result of this method being different on the two platforms that the codes were being rejected.

So, I have disassembled the compact framework version and used that in my desktop application instead, and now it works just fine. Here is the code....

public int MyHashCode(string value)
{
int charIndex = 0;
int num1;
int num2 = 0x1505;
int num3 = num2;
char currentChar;
while (charIndex < value.Length)
{
currentChar = value[charIndex];
num1 = (int)currentChar;
num2 = ((num2 << 5) + num2) ^ num1;

if (charIndex == value.Length - 1)
break;

num1 = value[charIndex + 1];
if (num1 == 0)
{
break;
}
num3 = ((num3 << 5) + num3) ^ num1;
charIndex += 2;
}
return (num2 + (num3 * 0x5d588b65));
}

2006-12-02

OutOfMemory, or maybe not?

I've been writing an app for the compact framework for some months now. It's quite a complicated app that includes an object persistence framework, a task oriented application layer and a loosely coupled GUI which is generated through factories (the app only has 1 form, but lots of user controls + factories).

The app has been experiencing apparently random OutOfMemoryExceptions, no matter how hard I have tried I have found it impossible to reproduce one of these errors. I have spent quite some time really optimising the memory useage of my OPF so that it works on the bare minimum of memory yet still operates quickly enough (and I'm very pleased with its performance too). However, the OOM exceptions persisted!

I wrote a logging tool which records the last X actions the user performs, when an unexpected exception occurs this log is written to disk along with a stack trace of the exception. I noticed that the top of the stack trace always read...

at Microsoft.AGL.Common.MISC.HandleAr()
at System.Windows.Forms.Control._InitInstance()
at System.Windows.Forms.Control..ctor()
So it seems that my app failed each time I tried to create a user control. Considering my GUI gets updated by disposing of the current control and then replacing it with a control created by a factory this happens quite a lot, but nowhere more frequently than when I am importing data (updated every 500ms). So now I know a good candidate for finding the bug but no way to actually reproduce it in my app. Strangely enough it wasn't what the user was doing in my application that mattered but what they were doing in another one.

It would seem that if the PPC application is doing something that takes a few seconds (on a 350Mhz CPU that isn't rare) the user decides to pop off to another part of Windows and check the battery level, check the memory usage or just generally "muck about". Finally I had the last piece of the jigsaw! I ran my data import and then started to select/deselect files in the Program Manager. OutOfMemoryException!

The strange thing was that whenever this exception occured I would have at least 5MB of RAM free. It would seem that if a CF WinForms app tries to call Microsoft.AGL.Common.MISC.HandleAr() at the same time some other app is changing its GUI the call will fail with an OutOfMemoryException. So I decided to put the control create in a loop like this
int triesLeft = 20;
while (true)
{
try
{
return {Something confidential that creates the control}
}
catch (OutOfMemoryException outOfMemoryException)
{
triesLeft--;
if (triesLeft == 0)
throw outOfMemoryException;
Thread.Sleep(500);
}
}
The hope was that if I tried up to 20 times, half a second apart, the chances of another app updating GUI at the same time would be very low. Unfortunately even if I closed all other apps during this loop the creation of the control would still fail. It seems that once it fails there is no hope, all is lost, it just packs up and refuses to work at all!

Instead of updating the GUI straight away I started a timer "GuiTimer" with an interval of 100ms. The Tick event of this timer first disables the timer and then creates the control needed. Maybe the Timer messages sent be WinCE are all done from a single thread or something, I don't know because I haven't looked into it. This doesn't fix my problem, but it does make it occur less often.

Despite my attempts to create a small app to reproduce this problem I have not been successful, yet I can reproduce it easily in my main application.

The above attempt however made no difference, .NET will perform a garbage collection before throwing an OutOfMemoryException so it was really quite pointless. A much better solution to my problem has been to create my user controls only once and then to keep hold of them, reusing an existing instance seems to almost completely eradicate my bug.