전산쟁이의 카피질

뒤로 검색

Windows Mobile Development

2009/01/15 00:08

http://www.christec.co.nz/blog/archives/category/user-interface/




Archive for the ‘User Interface’ Category

Creating custom animated wait cursors

Friday, April 4th, 2008

Most .NET Compact Framework developers will be familiar with theCursor.Current property and how to display the standard Wait Cursor, but did you know that you could easily display your own custom cursor? This blog entry discusses how you can replace the standard wait cursor with your own application specific cursor. This is an ideal way to brand kiosk style applications for example.

Creating a custom cursor

Recent versions of the Windows CE operating system (and hence Windows Mobile) support an OS API called LoadAnimatedCursor. This API enables you to specify a sequence of individual bitmap frames and will convert them into an animated cursor. For example an animated cursor of a rotating monkey could be made up of the following 4 bitmaps.

101 102 103 104

The more frames the cursor consists of the smoother the animation will be. Individual frames within the animation should be 48×48 pixel bitmap resources within a *.dll or *.exe file. The bitmap resources are identified by a numeric ID and must be in sequential order (such as the values 101, 102, 103 and 104 used in the example above).

The id of the first bitmap, the total number of frames and the period of time to delay between frames is then passed into the LoadAnimatedCursor API which will return a handle to the newly created cursor (an HCURSOR). Passing this handle to the SetCursor API will then make the cursor visible on the screen.

Unfortunately the LoadAnimatedCursor function is not as easy to use from managed code as it should be. The API expects the bitmap images to be native bitmap resources meaning you can not store them within a *.resx resource file within your .NET Compact Framework application. The easiest way to store the bitmaps in the correct format is to create a native C++ DLL project. You can then remove all the C++ source files, leaving a sole Win32 *.rc resource file to which you can add the bitmaps to (as will be demonstrated later).

Sample Application

[Download animatedcursortest.zip - 37KB]

The sample application available for download consists of two projects. The first (called AnimatedCursors) demonstrates how to create a resource only DLL that contains the bitmap images required for the two custom cursors shown above.

The second project is a C# example demonstrating how to use Platform Invoke to access theLoadAnimatedCursor and SetCursor APIs to display the custom cursors. This second project loads the custom cursors from the AnimatedCursors.dll file built by the first project.

The C# sample wraps up the required Platform Invoke code within a class called AnimatedWaitCursor. This class implements the IDisposable interface so that the following syntax can be used to display a custom cursor. This code structure should be familiar to anyone who has used MFC’s CWaitCursor class.

// Use the animated cursor that has 4 frames starting with
// bitmap id 101, delaying 125 milliseconds between each frame.
string dll = @"\path\to\some.dll";
using (AnimatedWaitCursor cursor = new AnimatedWaitCursor(dll, 101, 4, 125))
{
  // do some long running task
}

How to make your ListView columns reorderable

Tuesday, March 18th, 2008

Another finishing touch that I like to see in applications that use ListViews is the ability for the end user to re-order the columns to suit their own preferences. This blog entry discusses one approach for adding this functionality to the ListView control present within the .NET Compact Framework. Although it is difficult to convey in a static screenshot, the screenshot above shows a user dragging the stylus over the header of the listview control to move the position of the “First Name” column.

Obtaining Draggable Columns

The System.Windows.Forms.ListView control is a wrapper over top of the native ListView control. The native ListView control supports the notion of extended styles, which allow various optional features to be enabled or disabled as desired. One of the extended styles is calledLVS_EX_HEADERDRAGDROP. If this extended style is enabled the user can re-order the columns by dragging and dropping the headers shown at the top of the listview while it is in report mode.

Although the .NET Compact Framework ListView control does not expose a mechanism to enable extended styles, we can use a technique discussed in a previous blog entry of mine to add or remove the LVS_EX_HEADERDRAGDROP extended style as desired.

private const int LVM_SETEXTENDEDLISTVIEWSTYLE = 0x1000 + 54;
private const int LVS_EX_HEADERDRAGDROP = 0x00000010;
 
public static void SetAllowDraggableColumns(this ListView lv, bool enabled)
{
  // Add or remove the LVS_EX_HEADERDRAGDROP extended
  // style based upon the state of the enabled parameter.
  Message msg = new Message();
  msg.HWnd = lv.Handle;
  msg.Msg = LVM_SETEXTENDEDLISTVIEWSTYLE;
  msg.WParam = (IntPtr)LVS_EX_HEADERDRAGDROP;
  msg.LParam = enabled ? (IntPtr)LVS_EX_HEADERDRAGDROP : IntPtr.Zero;
 
  // Send the message to the listview control
  MessageWindow.SendMessage(ref msg);
}

This method allows the drag feature to be turned on and off for a given ListView control. Notice that this method makes use of a C# 3.0 feature called Extension Methods. The “this” keyword in front of the first parameter means that this method can be called as if it was part of the standard ListView control, meaning the following code snippet will work (assuming listView1 is an instance of the System.Windows.Forms.ListView control).

listView1.SetAllowDraggableColumns(true);

This is pure syntactic sugar, behind the scenes the C# compiler is simply passing in listView1 as the first parameter to the SetAllowDraggableColumns method.

Persisting Column Order Preferences

Once you have reorder-able columns it can be desirable to persist the user’s preferred layout across multiple executions of your application. It would be a pain if the columns always defaulted back to a standard order everytime the form was displayed.

The native ListView control provides two window messages, LVM_GETCOLUMNORDERARRAYand LVM_SETCOLUMNORDERARRAY that can be used to implement this feature. The code sample available for download wraps up these two window messages to allow you to query the current order of the columns by using a statement such as the following:

int[] columnOrder = listView1.GetColumnOrder();
// TODO: save 'columnOrder' to the registry
// or another persistent store

When columns are added to a ListView they are given an index. The first column is column 0 while the second is column 1 and so on. When columns are re-ordered they keep their index value but their position on screen changes. The array returned by the GetColumnOrder function contains the index for each column in the order that they are visible on screen. For example if the array contains the values 2, 0, and 1 it means that the last column (column 2) has been dragged from the right hand side of the listview to become the left most column.

Once we have obtained the order of the columns we can store the data in any persistent storage mechanism such as a file, a database table, or registry key. When the form is reloaded we can initialise the default order of the columns by calling the equivalent SetColumnOrder method with the value we previously saved:

// TODO: should read 'columnOrder' from the registry
// or other persistent store
int[] columnOrder = new int[]{2, 0, 1};
 
listView1.SetColumnOrder(columnOrder);

Sample Application

[Download ListViewExtenderTest.zip - 11 KB]

The sample application displays a list of three columns. While running on a Windows Mobile Professional device you should be able to re-order the columns by dragging and dropping the column headers with your stylus. If you exit and restart the application you should see that your custom column ordering is persisted. Via the left soft key menu item you can select an option that will disable the user from re-ordering the columns.

Most of the magic occurs within a file you can reuse in your own applications called ListViewExtender.cs. The sample application targets .NET CF 3.5 and hence requires Visual Studio 2008. With minor tweaks the source code would also be usable within .NET CF 2.0 projects. I would be keen to hear what your thoughts are about this. Is it time to shift code samples to .NET CF 3.5/Visual Studio 2008 or are you still wanting .NET CF 2.0 and Visual Studio 2005 compatible samples?

Improvements to the OpenFileDialog class

Saturday, March 8th, 2008

José Gallardo Salazar has blogged about an enhanced OpenFileDialog implementation that fixes a number of common complaints with the standard .NET Compact Framework version. The most common complaints being not able to browse outside the “My Documents” folder and the lack of support for Smartphone (Windows Mobile Standard) devices.

As indicated in his blog entry on adding Smartphone support the OpenFileDialogEx class is still a work in progress with many possible areas of improvement. I have taken José’s source code and made a number of changes.

My improvements include:

  • Shell Icon List support - The dialog now uses the same icons as shown in File Explorer. Directories for example may have a custom icon (for example the Games folder shown in the screenshot above).
  • DPI / Resolution awareness - icons change size to match current system settings (i.e. larger icons on higher resolution devices).
  • Support for both Pocket PC and Smartphone - the platform is detected at runtime so a single assembly can alter it’s UI behaviour to run on both platforms. Some UI elements are hidden while running on a Smartphone for example.
  • Back Key support - The Back key on Smartphone Devices now moves up one directory when pressed (just like the builtin File Explorer application on WM6 devices).
  • Sorted Directory Listings - Directory entries are now sorted into alphabetical order. There is provision for the sort order to be easily modified (although this is not yet publicly exposed).
  • InitialDirectory property - This property enables the dialog to default to displaying a directory other than the root of the filesystem.

Sample Application

[Download iconviewer.zip - 22KB]

The sample application available for download demonstrates how to use the OpenFileDialogEx class and demonstrates its cross platform capability. Once you have built the application you should be able to run it on practically any Windows Mobile device and see a file dialog that is tailored to the capabilities of the current device.

One small disadvantage of my changes is the fact that the control is now less “pure”. It relies upon a number of Platform Invoke calls to access some of the system functionality not exposed by the .NET Compact Framework. An interesting note is that .NET Compact Framework version 3.5 removes the need for most of the Platform Invoke code due to new functionality or bug fixes. At this point in time however supporting .NET CF 2.0 is still important to me.

Improving Comboboxes on Smartphone devices

Monday, February 11th, 2008

Screen real-estate is at a premium on smaller Windows Mobile smartphone devices, so it is important to maximise the use of every available pixel in conveying useful information to the user. This blog entry demonstrates a technique to maximise the usability of combo boxes within .NET Compact Framework applications by reusing some of the existing screen real-estate.

Defining the problem

When you place a Combobox control on a form within a Smartphone application you get a control which shows a single item with left/right arrows that allow you to scroll through the list of options.

As an alternative if you press the Action key (middle of the D-PAD) on the combobox the full list is displayed in a fullscreen window. This window however is always labelled “Select an Item”. Because the window is fullscreen it is possible for the user to loose context (they can’t see any labels etc you have placed on your form), and forget what they are meant to be selecting. What we would like to do is to replace the “Select an Item” title with something more appropriate for the current field.

What we get by default What we desire

Developing a solution

This is where having knowledge of the underlying native (C/C++) APIs that implement Windows Mobile is useful. When targeting a smartphone device a .NET Compact Framework Combobox control is actually implemented via two separate controls as far as the operating system is concerned, a 1 item high listbox coupled to an up/down control. The native Win32 smartphone documentation calls this type of configuration a Spinner Control.

By using a utility included with Visual Studio called Remote Spy++ we can see this collection of controls. In the screenshot to the left you can see that one of the combo boxes in the sample application is selected, and underneath it you can clearly see the listbox and up/down (msctls_updown32) controls it is made up of.

In order to change the title of the popup window associated with a combo box we need to:

  1. Find the native window handle for the up/down control
  2. Change it’s window title to the desired prompt text

The ComboBox class has a Handle property that returns a native window handle (HWND) that is associated with the managed control. For a ComboBox the Handle property actually returns the handle of the listbox control and not the parent “NETCFITEMPICKERCLASS_A” control as may be expected. This was probably done for native code interop compatibility reasons. So to find the native window handle of the up/down control we simply need to find the handle for the window immediately after the window returned by the ComboBox.Handle property.

Once we have found the window handle for the up/down control we are finally ready to replace the popup window title. According to the Spin Box Control documentation, the popup window title comes from the title of the up/down control, and it defaults to the “Select an Item” prompt if a title isn’t specified. We can change the title of a native window by calling the SetWindowText API.

All these individual steps can be wrapped up into an easy to call method as follows:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
 
public static class ComboboxExtender
{
  public static void SetPromptText(ComboBox combo, String text)
  {
    // Obtain the native window handle of the up/down spinner control
    IntPtr hWndListBox = combo.Handle;
    IntPtr hWndSpinner = GetWindow(hWndListBox, GW_HWNDNEXT);
 
    // Set the title of the spinner
    SetWindowText(hWndSpinner, text);
  }
 
  [DllImport("coredll.dll")]
  private static extern bool SetWindowText(IntPtr hWnd, String lpString);
 
  [DllImport("coredll.dll")]
  private static extern IntPtr GetWindow(IntPtr hWnd, UInt32 uCmd);
 
  private const UInt32 GW_HWNDNEXT = 2;
}

Sample Application

[Download ComboBoxPromptExample.zip - 16KB]

The sample application available for download demonstrates using the ComboBoxExtender class developed above. The interface consists of two combo boxes which have been configured identically. The first combo box shows the default prompt text, while the second has had its prompt text replaced via a call to ComboBoxExtender.SetPromptText within the Form’s Load event handler as shown below:

ComboBoxExtender.SetPromptText(comboBox2, "Select Car Colour");

I am a stickler for improving the quality and polish of Windows Mobile applications. This tip is a very minimal code change that can be implemented quickly, yet can have a profound impact on the usability of your application if it is full of combo boxes. A similar trick can also be implemented for Expandable Edit controls.

If you are developing .NET Compact Framework applications that target the Windows Mobile Smartphone (Standard) platform, I seriously encourage you to consider making this usability change to your applications.

Aspect Ratio Picture Box control

Tuesday, January 29th, 2008

One feature missing from the PictureBox control within the .NET Compact Framework is the Zoom SizeMode. This mode makes the image contained within the PictureBox as large as possible while still maintaining its original aspect ratio. Luckily this feature is easy to implement as a custom control.

Scaling an Image

The ratio of a picture’s width to height is known as its aspect ratio. An image with an aspect ratio greater than 1 means that it is wider than it is high (landscape image). While an image with an aspect ratio of less than one is taller than it is wide (portrait image). If the aspect ratio is exactly 1 the image is square.

By comparing the aspect ratio of an image against the aspect ratio of our control we can determine if the best axis to stretch the image (in order to maximise its size) is vertically or horizontally. Once we have determined the maximum width or height of our scaled image we can then utilise the aspect ratio to calculate the other dimension in order to keep the same aspect ratio.

Creating a custom control

There are two main types of custom controls within a System.Windows.Forms based application, those that derive from the Control base class and those that derive from the UserControl base class. For this kind of custom control, deriving from System.Windows.Forms.Control is the best approach since we are after a fairly light weight control.

To make a custom control we simply need to declare a class which derives from Control:

using System.Windows.Forms;
 
public class AspectRatioPictureBox : Control
{
}

The code sample above is a complete custom control. Once it is compiled you will be able to place an AspectRatioPictureBox control onto a form, in the same way you would any other control such as a Button or Label.

The control however is quite boring, being a completely white rectangle making it indistinguishable from the form it is placed upon. Before we cover how to paint more attractive contents, we need a way for the user to specify the image they would like to display. The easiest way to do this is to implement a public property by adding the following to our class definition:

private Bitmap photo;
 
public Bitmap Image
{
   get { return photo; }
   set
   {
     photo = value;
 
     // Redraw the contents of the control
     Invalidate();
   }
}

Any public property exposed by our control will be accessible via the Properties window within the Forms Designer when the user utilises our control.

Notice the call to the Control.Invalidate method within the Image property setter? This is important, as this is the call which notifies the operating system that our control has changed state and needs to be redrawn. If the control is not invalidated, the OS will not attempt to redraw it and the user would not be able to see the new image that has just been provided. In general any property which affects the onscreen presentation of a custom control should invalidate it.

Now that we have a way to specify the bitmap we want to display, we can finally implement the code that will paint the contents of the control. We do this by overriding the OnPaint method as follows:

protected override void OnPaint(PaintEventArgs e)
{
  if (photo != null)
  {
    float controlAspect = (float)ClientSize.Width / (float)ClientSize.Height;
    float photoAspect = (float)photo.Width / (float)photo.Height;
 
    if (photoAspect < controlAspect)
    {
      // Stretch the height of the photo to match
      // the height of the control
      ....
    }
    else
    {
      // Stretch the width of the photo to match
      // the width of the control
      ....
    }
  }
}

The framework will call the OnPaint method each time the OS needs the control’s contents to be repainted onto the screen. For a complete implementation of this method see the sample application downloadable below.

Using a custom control

Using custom controls within your projects is reasonably straight forward. Once you have created your new custom control class you simply need to recompile your project in order for the control to be picked up by Visual Studio and displayed in the Toolbox as a custom control. This is shown in the above screenshot. Once an instance of the custom control has been placed onto a form its properties can be configured via the standard Property window.

Unlike the Desktop development environment, Smart Device custom controls do not directly utilise attributes such as [Description()] to configure their appeance and behaviour within the Visual Studio IDE. Instead Smart Device custom controls utilise what is called an XMTA file. Customising the design time experience of your custom controls will be discussed in a future blog post.

Sample Application

[Download aspectratiopictureboxtest.zip - 25.7KB]

A small sample application is available for download. It demonstrates the use of the AspectRatioPictureBox custom control developed within this blog entry in a number of different scenarios.

Feel free to utilise the included AspectRatioPictureBox.cs file within your own projects. As an added feature an additional property called “Restricted” has been added. When the Restricted property of the control is set to true the image will not be stretched any larger than it’s original size. When “unrestricted” the image will be stretched to take the full width or height of the control, even if that means stretching the image beyond it’s original size. The restricted mode is useful for image viewing applications where you don’t want to stretch small images and degrade their quality, while still ensuring large images are scaled down to fit the screen.

Determining the depth of a node within a TreeView control

Sunday, January 13th, 2008

Given a TreeNode somewhere within a TreeView control, a desktop System.Windows.Forms developer would query the TreeNode.Level property to determine the depth of the node within the tree as demonstrated below:

MessageBox.Show(
  String.Format("Depth of node: {0}",
    treeView1.SelectedNode.Level));

However as stated by the MSDN Documentation this property is not available on the .NET Compact Framework. This is because one of the techniques Microsoft has used to reduce the size of the .NET Compact Framework is to remove methods and properties from the Base Class Library when the same behaviour can be implemented with a couple of lines of code written by the user.

So although the .NET Compact Framework does not provide a TreeNode.Level property we can implement our own, but first we must come up with a suitable algorithm.

General algorithm
Given a node within the tree (represented by the TreeNode class) we can determine the node’s Parent by querying the Parent property. If we repeat this process we will eventually get to a node which has null for it’s parent. This node is called the root (or top) of the tree. If we count the number of nodes we walked over to reach this node we have calculated the depth (or level) of the node we started at. This algorithm is implementable within C#.

Pre C# v3 Implementation
The best we can do with C# version 1 or 2 is to replace calls to missing methods with calls to static helper methods we manually write. For example we could write the following helper class to replace the TreeNode.Level property.

namespace MyProject
{
  public static class Utils
  {
    public static int Level(TreeNode node)
    {
      int i = -1;
 
      // Walk up the tree until we find the
      // root of the tree, keeping count of
      // how many nodes we walk over in
      // the process
      while (node != null)
      {
        i++;
        node = node.Parent;
      }
 
      return i;
    }
  }
}

and make use of it as follows

MessageBox.Show(
  String.Format("Depth of node: {0}",
    Utils.Level(treeView1.SelectedNode)));

C# v3 Implementation
C# version 3 (first available with the .NET 3.5 framework release) introduces a language feature called extension methods. This is basically a compiler trick which allows us to call helper methods such as the one created above with a slightly different syntax, that makes it appear as if the new methods we define are part of the existing objects. Behind the scenes the compiler is simply rewriting our statements to use the old syntax.

To convert our helper method into an extension method we only need to convert the method prototype from

public static int Level(TreeNode node) { ... }

to

public static int Level(this TreeNode node) { ... }

The magical “this” keyword is enough to get the following syntax to work

MessageBox.Show(
  String.Format("Depth of node: {0}",
    treeView1.SelectedNode.Level()));

Notice how our new Level() method can be called as it it was a member of the TreeNode class. This is very close to the original syntax that a desktop developer would utilise. In this example ideally we would create an “extension property”, which would mean we could utilise identical syntax on both platforms, however C# v3 does not support extension properties, only extension methods.

Sample Application

[Download TreeNodeLevelExample.zip - 19KB]

A sample application is available for download. The application shows a simple tree view and allows the user to select different nodes within it. As different nodes are selected a label is updated to show the depth of the currently selected node.

There are two versions of the sample application. The first is designed for .NET CF 2.0 and uses the first technique demonstrated, while the second is for .NET CF 3.5 and demonstrates the cleaner extension method based syntax.

Daniel Moth among others have discussed various techniques to support cross compiling code across both the .NET Compact and Full desktop frameworks. Extension methods are another tool to add to the toolbox, allowing you to provide your own implementations for methods missing from the .NET Compact Framework without requiring code changes to your main application source code.

Is anyone aware of any discussions available online about why it was decided not to support extension properties (or events)? Is this something we can expect to see in C# version 4?

Capture and respond to user input to a notification bubble

Tuesday, October 30th, 2007

In my previous blog entry about notifications I mentioned that we had not covered the ResponseSubmitted event. Subscribing to this event allows you to respond to user input entered within a notification bubble. This blog entry discusses how you can capture and process user input within notifications.

Creating an HTML based form
The contents of a notification bubble is formatted using HTML. To request user input within a notification bubble we can utilise a HTML based form.

A simple form specified within HTML may look like the following example:

<form>
  Name: <input name="name" /><br />
  <input type="submit" />
</form>

Similar to a System.Windows.Forms base application there are a range of controls which can be used within an HTML based form. These controls are typically specified via an <input /> element, although there are some exceptions as shown in the following table:

Name HTML Syntax
Hidden Field
<input type="hidden" name="fieldname" value="default" />
Textbox (single line)
<input type="text" name="fieldname" value="default" />
Textbox (single line password)
<input type="password" name="fieldname" value="default" />
Textbox (multi line)
<textarea name="fieldname" cols="40" rows="5">
    default value
</textarea>
Radio buttons
<input type="radio" name="fieldname" value="1" />
<input type="radio" name="fieldname" value="2" />
<input type="radio" name="fieldname" value="3" />
Checkbox
<input type="check" name="fieldname" checked="true" />
Combobox
<select name="fieldname">
    <option value="1">option 1</option>
    <option value="2">option 2</option>
    <option value="3">option 3</option>
</select>

A sample form with two textbox controls could be specified within C# source code as follows:

Notification notif = new Notification();
notif.Text = @"<form>
    Field 1: <input type=""text"" name=""fieldname1"" /><br />
    Field 2: <input type=""text"" name=""fieldname2"" />
  </form>";

Buttons
Using the controls specified above allows a notification to accept input from the user. However it does not provide the user with a mechanism to submit a completed form to the application for further processing.

A form is typically submitted when the user presses a submit button. A submit button can be specified within HTML via the use of an <input type=”submit” /> element. Whenever the user presses the submit button the ResponseSubmitted event will be triggered, allowing the form to be processed by the application.

Buttons can also be utilised within a notification to temporarily hide or permanently close a notification without submitting a form (useful for cancel or postpone style buttons etc). These actions can be specified via the use of button elements within the HTML as demonstrated below:

<!-- This button will minimise the notification -->
<input type="button" name="cmd:2" value="Hide" />
 
<!-- This button will permanently close the notification -->
<input type="button" name="something" value="Close" />

The value attribute contains the text displayed on the button, while the name (i.e. “cmd:2″) controls the action which occurs when the button is pressed. The name “cmd:2″ is a special value indicating to the operating system that the notification should be minimised and displayed as an icon that can be clicked to redisplay the notification. Having a button with any other name will cause the notification to permiantly be dismissed without the ResponseSubmitted event firing. All other “cmd:n” style button names are reserved by Microsoft for future use.

Hyperlinks
A HTML form can also contain traditional hyperlinks such as the following example:

<a href="help">Display further help</a>

Whenever such a link is pressed within the notification, the ResponseSubmitted event will trigger and the response string will be the string specified as the href attribute (”help” in this example).

Many of the built in operating system notifications utilise hyperlinks to provide access to settings or customisation dialogs.

Processing the response
When a HTML form within a notification is submitted the ResponseSubmitted event will trigger and this is the ideal opportunity for an application to process the contents of a form. TheResponseEventArgs parameter passed to this event handler contains a Response property that includes the current values of all fields within the form encoded in a format known as application/x-www-form-urlencoded.

Section 17.13.4 of the HTML 4.01 standard discusses application/x-www-form-urlencoded form data with the following description of the encoding process.

This is the default content type. Forms submitted with this content type must be encoded as follows:

  • Control names and values are escaped. Space characters are replaced by `+’, and then reserved characters are escaped as described in [RFC1738], section 2.2: Non-alphanumeric characters are replaced by `%HH’, a percent sign and two hexadecimal digits representing the ASCII code of the character. Line breaks are represented as “CR LF” pairs (i.e., `%0D%0A’).
  • The control names/values are listed in the order they appear in the document. The name is separated from the value by `=’ and name/value pairs are separated from each other by `&’.

As an example, if the form specified above contained the strings “Hello World” and “Good Bye” it would be encoded and appear within the Response property as follows:

?fieldname1=Hello+World&fieldname2=Good+Bye

The Microsoft documentation for the Response property contains an example of how to parse such response strings. It does this via some rather brittle string search and replace style operations. The sample code is not very generic, as it will break if you change the structure of your form even a little (such as renaming a field) and it does not deal with decoding hex escaped characters.

Within the sample application mentioned below I have implemented a function calledParseQueryString which performs the application/x-www-form-urlencoded decode process and returns a more easily used Dictionary of field control names to value mappings. This allows you to write a ResponseSubmitted event handler which looks something like the following:

private void notification1_ResponseSubmitted(object sender,
    ResponseSubmittedEventArgs e) 
{ 
  // This dictionary contains a mapping between 
  // field names and field values. 
  Dictionary<string, string> controls = ParseQueryString(e.Response); 
 
  // Make use of the field values, in this case pulling the
  // values out of the textboxes and displaying a message
  // box.
  MessageBox.Show(String.Format("first field = {0}, second field = {1}", 
	controls["fieldname1"], controls["fieldname2"]); 
}

This should make the intention of the ResponseSubmitted event handler easier to determine, and makes for more easily maintained code. The “hard” part of the response parsing logic is now hidden away within the ParseQueryString function, leaving you with an easy to use collection of field values to play with.

Including images within a notification
Sometimes it is helpful to include a small image within a popup notification. This is possible, but as Keni Barwick found out, the syntax of your HTML has to be fairly precise for the notification balloon to locate the image (a similar situation to images within HTML help files).

You should have good success if you use the file:// protocol, and include a full path to your image using backward slashes to separate directories, i.e. use the format:

<img src="file:///path/to/image.bmp" />

For example to include the image \windows\alarm.bmp you would use the following HTML:

<img src="file:///windows/alarm.bmp" />

You could hard-code the path to your image file but this could cause problems if the user decides to install your application in a different location (on an SD card for example). If your images are stored in the same folder as your main executable you can determine the path to your images at runtime by using a function similar to the following code snippet:

using System.Reflection; 
using System.IO;
 
public string GetPathToImage(string fileName)
{
  // Determine the exe filename and path
  Assembly assembly = Assembly.GetExecutingAssembly();
  string executablePath = assembly.GetName().CodeBase;
 
  // Trim off the exe filename from the path
  executablePath = Path.GetDirectoryName(executablePath);
 
  // Add the specified filename to the path
  return Path.Combine(executablePath, fileName);
}
 
string imageURL = GetPathToImage("foo.bmp");

Sample Application

[Download notificationuserresponsesample.zip - 16KB]

A sample application is available for download. The sample demonstrates using a notification to specify the details of a pizza order. It also demonstrates the inclusion of an image within a notification.

When targeting a Windows Mobile 5.0 or above device, you could extend the sample by using thecustom soft keys feature mentioned in a previous blog entry to submit the notification by pressing a subkey instead of pressing a button within the body of the notification.

Please feel free to make use of the ParseQueryString method and any associated functionality within your own applications.

Custom soft keys for Notifications

Thursday, October 4th, 2007

Since my previous posting about Notifications within the .NET Compact Framework I have seen the question of how to provide custom menu items at the bottom of a notification come up a number of times. Today I am going to provide you with one solution to this challenge.

The short answer is that the Microsoft.WindowsCE.Forms.Notification class provided by the .NET Compact Framework does not expose the required functionality. Although the native notification API allows custom menus to be associated with a notification, for some reason this hasn’t be exposed to managed developers. With a little bit of work however (i.e. some PInvoking) you can produce your own managed wrapper around the Notification APIs to expose the required functionality.

I have done this and produced a class called NotificationWithSoftKeys. You can download a copy of this class, along with a sample application which demonstrates it’s various features below.

[Download notificationsoftkeys.zip 32KB]

The rest of this blog entry will demonstrate how you can utilise the class within your own applications. It is designed to be a stand in replacement for the existing class, but adds some additional properties and events.

Adding Spinners
Spinners are the first additional functionality theNotificationWithSoftKeys control exposes. A notification spinner is a little bit of text on the right hand side of a notification which sits in-between a pair of < and > buttons. The user can tap on these buttons to “spin” through a series of related notifications.

Here is a brief example of how to enable this feature within your notification.

private NotificationWithSoftKeys notification;
 
private void btnCreateNotification_Click(object sender, EventArgs e)
{
  notification = new NotificationWithSoftKeys();
  notification.Icon = Properties.Resources.Icon;
  notification.Caption = "This is my notification";
  notification.Text = "Spinner Test\t1 of 5";
  notification.Spinners = true;
  notification.SpinnerClicked +=
      new SpinnerClickEventHandler(notification_SpinnerClicked);
  notification.Visible = true;
}

The main thing you need to do is set the Spinners property to True in order to enable the Spinner functionality. After this any text within the Caption property after the first tab (\t) character will be displayed on the right hand side of the notification’s caption.

When the user presses the < or > buttons the SpinnerClicked event will fire. Within your event handler you should update your Notification’s Caption and Text properties to give the illusion that another notification has been displayed.

Display Current Time on Caption
Displaying the current system time on the caption is very straight forward. All you need to do is set the TitleTime property to true. 

Custom Buttons
This is the feature which will probably generate the most interest. The standard notification has a “Hide” button on the left soft key. Some built in applications however display notifications with alternative softkeys and perform different actions depending upon which softkey was selected. Using the NotificationWithSoftKey class we can achieve the same effect as demonstrated with the following example.

private NotificationWithSoftKeys notification;
 
private void btnCreateNotification_Click(object sender, EventArgs e)
{
  notification = new NotificationWithSoftKeys();
  notification.Icon = Properties.Resources.Icon;
  notification.Caption = "This is my notification";
  notification.Text = "A soft key test";
  notification.LeftSoftKey =
      new NotificationSoftKey(SoftKeyType.Dismiss, "Close");
  notification.RightSoftKey =
      new NotificationSoftKey(SoftKeyType.StayOpen, "View");
  notification.RightSoftKeyClick +=
      new EventHandler(notification_rightSoftKeyClick);
  }
 
  notification.Visible = true;
}

This example will display a notification with two soft keys. The left soft key will be labeled “Close” and will remove the notification, while the right soft key will be labeled “View” and trigger the RightSoftKeyClick event when selected.

Within this event handler you could open up a second form, delete something from a database or perform any other task you would like to in response to the user pressing the soft key.

There are a number of soft key types to choose from as shown below:

Type Description
Dismiss Remove the notification when the soft key is pressed
Hide Hide the notification when the softkey is pressed (but do not dismiss)
StayOpen Do not dismiss or hide the notification when the softkey is pressed.
Submit Submit the HTML form in the notification instead of calling the click event handler
Disabled The softkey is disabled (grayed out)

Backwards Compatibility
The additional features discussed today are only available on devices running Windows Mobile 5.0 or higher. If your application is targeting multiple device types, and you need to run on Pocket PC 2003 devices the new functionality will not work.

The NotificationWithSoftKeys class utilises techniques outlined in my blog posting on device and platform detection to fail gracefully on older devices. When the class detects an older device type, it will simply ignore all the soft key related properties and revert back to the old notification style.

Sometimes you may want to check if this fallback has occurred. For instance you may want to utilise the soft key buttons for user input. In this case you would need to use an alternative approach (such as HTML based buttons) when running on Pocket PC 2003 or older devices which don’t have softkeys. You can do this by checking thePlatformSupportsCustomSoftKeyButtons property and altering the other properties of theNotificationWithSoftKeys class as required.

Summary
This is still only scratching the surface of what is possible with the native notification APIs. There are a number of notification features which are not exposed by this wrapper. For example there are the following flags which could easily be exposed as properties.

  • SHNF_DISPLAYON - the display is forced to turn on for the notification
  • SHNF_SILENT - the notification is forced to be silent and not vibrate, regardless of system settings

Please feel free to download the sample project and have a play around with it. I would be keen to hear from anyone who attempts to use it or modifies it to expose additional functionality etc.

Add Cut/Copy/Paste functionality to a Textbox

Tuesday, October 2nd, 2007

Earlier I discussed how you could manually get the software keyboard (SIP) to display whenever a TextBox control gained focus. There was potentially a lot of event handlers to write, two for every control on a form. Today I will show you an alternative approach that utilises less code but also has some additional benefits.

A common thing I do while testing new Windows Mobile applications, is to tap-and-hold on a text field. Very well behaved applications should popup a context sensitive menu containing cut/copy/paste style options for the current control. Surprisingly, very few applications actually pass this test, even though it is a feature that has been built into the operating system for a while.

Within Windows CE the SIPPREF control can be used to automatically implement default input panel behavior for a dialog. It provides the following features:

  • The Input Panel is automatically shown/hidden as controls gain and loose focus.
  • Edit controls have an automatic context menu with Cut, Copy, Paste type options.
  • The SIP state is remembered if the user switches to another application and later returns to this form.

In my mind this has three advantages over the process I previously discussed.

  1. You add the SIPPREF control once and it automatically hooks up event handlers for each of your controls. With the manual event handler approach it’s easy to add a new control and forget to hook up the events required to handle the SIP.
  2. You get free localisation. Although you could create a custom context menu for cut/copy/paste, you would need to localise the text into multiple languages yourself (if you are concerned with true internalisation that is) and it’s another thing thing to hook up for each control.
  3. You get standardised behavior. By using functionality provided by the operating system you are ensuring that your application has a natural and expected behavior to it. If the platform ever changes the conventions of SIP usage, your application will automatically be updated.

For the rest of this blog entry I will discuss how to go about utilising the SIPPREF control within your application. I have split my discussion into two sections. The first section will be of interest to native developers developing in C or C++, while the second section is intended for .NET Compact Framework developers.

Each section contains a small example application which demonstrates the behaviour of the SIPPREF control within the respective environment. When you run the sample applications you will notice that the SIP does not popup up when you click within a text box. And a tap-and-hold operation yields nothing. This behaviour changes when a SIPPREF control is added to the dialog, which can be achieved by clicking the sole button.

Native Developers

[Download sipprefcontrolnativeexample.zip 16KB]

In order to use the SIPPREF control we must first request the operating system to register the SIPPREF window class. We do this by calling the SHInitExtraControls function. This step only needs to be done once, so is typically done during your application’s start up code. It is very easy to call, as the following example demonstrates:

#include <aygshell.h>
 
SHInitExtraControls();

Since SHInitExtraControls lives within aygshell.dll, we also need to modify our project settings to link with aygshell.lib, otherwise the linker will complain that it can not find the SHInitExtraControls function.

Once we have registered the SIPPREF window class, we simply create a SIPPREF control as a child of our dialog. When the SIPPREF control is created it will enumerate all sibling controls andsubclass them in order to provide the default SIP handling behaviour. The SIPPREF control must be the last control added to your dialog, as any controls added after the SIPPREF control will not be present when the SIPPREF control enumerates its siblings, and hence will not be subclassed to provide the proper SIP handling.

If dynamically creating the SIPPREF control, a good place to do this is within the WM_CREATE or WM_INITDIALOG message handler, as the following code sample demonstrates:

case WM_INITDIALOG:
  // Create a SIPPREF control to handle the SIP. This
  // assumes 'hDlg' is the HWND of the dialog.
  CreateWindow(WC_SIPPREF, L"", WS_CHILD,
       0,  0, 0, 0, hDlg, NULL, NULL, NULL);

As an alternative to dynamically creating the SIPPREF control, we can place the control within our dialog resource by adding the following control definition to the end of a dialog within the project’s *.rc file.

CONTROL  "",-1,WC_SIPPREF, NOT WS_VISIBLE,-10,-10,5,5

Depending upon your developer environment you may even be able to do this entirely from within the Resource Editor GUI. For example within Visual Studio 2005 you could drag the “State of Input Panel Control” from the Toolbox onto your form to cause a SIPPREF control to be added.

.NET Compact Framework Developers

[Download sipprefcontrolexample.zip 16KB]

The process of using the SIPPREF control for a .NET Compact Framework application is fairly similar to that of a Native application. Since the .NET Compact Framework does not natively support the use of dialog templates, we must use the CreateWindow approach to create a SIPPREF control dynamically at runtime.

The first step is to declare a number of PInvoke method declarations for the various operating system APIs we need to call.

using System.Runtime.InteropServices;
 
[DllImport("aygshell.dll")]
private static extern int SHInitExtraControls();
 
[DllImport("coredll.dll")]
private static extern IntPtr CreateWindowEx(
  uint dwExStyle,
  string lpClassName,
  string lpWindowName,
  uint dwStyle,
  int x,
  int y,
  int nWidth,
  int nHeight,
  IntPtr hWndParent,
  IntPtr hMenu,
  IntPtr hInstance,
  IntPtr lpParam);
 
  private static readonly string WC_SIPPREF = "SIPPREF";
  private static readonly uint WS_CHILD = 0x40000000;

One interesting fact is that we PInvoke a function called CreateWindowEx, while the native example above called CreateWindow. If you dig deeper into the Window Header files you will notice that CreateWindow is actually a macro which expands into a call to CreateWindowEx. At the operating system level the CreateWindow function doesn’t actually exist.

With this boiler plate code out of the way, the solution is very similar to the native one…

protected override void OnLoad()
{
     // Initialise the extra controls library
     SHInitExtraControls();
 
     // Create our SIPPREF control which will enumerate all existing
     // controls created by the InitializeControl() call.
     IntPtr hWnd = CreateWindowEx(0, WC_SIPPREF, "", WS_CHILD,
          0, 0, 0, 0, this.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
}

In the above example we simply create the SIPPREF control within the OnLoad method of our form. Within the downloadable sample project I have wrapped up this code into a static method called SIPPref.Enable(Form f) to enable to it easily be reused between forms.

Hopefully today I have shown you that having knowledge of the underlying operating system is still a useful skill to have for .NET Compact Framework developers. Knowing the features provided by the operating system can allow you to add some neat functionality to your applications with little additional effort on your behave.

Creating non full screen forms and custom MessageBoxes

Monday, October 1st, 2007

Every now and then someone asks how to create a .NET Compact Framework form which does not cover the fullscreen. Here is my solution to this problem.

This solution is built upon details discussed by other bloggers, including:

The trick is to set the form’s FormBorderStyle property to None. This disables the .NET Compact Framework’s built in functionality which forces a form to become fullscreen (on Windows Mobile devices), but also has the side effect of removing the border around the edge of the form. We can add the border (and a caption) back via the use of some PInvoke calls into the native operating system.

Points of interest

I decided to create my solution as two classes which are designed to be reusable within your own applications. One class enables the non full screen functionality, while the other provides an implementation for a common reason why non full screen forms are requested. These classes are as follows:

  1. NonFullScreenForm - a base class which you can use instead of Form. This base class allows you to have a form which does not cover the entire screen, and will optionally automatically center the form in the middle of the screen.
  2. MessageBoxForm - this is a subclass of NonFullScreenForm which provides a static method called Show. This method behaves in a similiar way to the standardMessageBox.Show API, with a few extensions such as the ability to use custom buttons.

Example Application

[Download nonfullscreendemo.ZIP 32Kb]

The example application available for download demonstrates the use of the NonFullScreenForm and MessageBoxForm classes via a series of buttons.

The first two buttons compare the behaviour of the standard MessageBox.Show API against our custom MessageBoxForm implementation for a number of message box styles. You will notice that the look and feel of our custom message boxes are similiar, but often have a slightly different layout.

The button labeled “Auto Centered Form” demonstrates the effect of setting NonFullScreenForm’s CenterFormOnScreen property to True. Whenever you tap the “Expand” or “Collapse” buttons the form changes size, and the NonFullScreenForm base class automatically changes the location of the form to ensure it is centered at all times. It will even re-center the form when the device switches between landscape and portrait modes.

How to use the NonFullScreenForm class

To use the NonFullScreenForm class within your own applications follow these steps:

  1. Add NonFullScreenForm.cs to your project.
  2. Set the FormBorderStyle property of your form to None.
  3. Set the Size and Location properties of your form to the desired size and position on the screen.
  4. View your form’s source code and change the form to derive from NonFullScreenForm instead of Form.
  5. If you want your form automatically centered on the screen set the CenterFormOnScreen property to true.
  6. If you don’t want an [OK] button in the caption, set the ControlBox property to false.

There are a number of areas where these custom controls could be improved. MessageBoxForm’s compatability could be improved. It does not support displaying an icon within the MessageBox at present for instance. There is also a slight flickering on the navbar (the bar at the top of the device which contains the start menu etc) when a non full screen dialog is created which would be nice to eventually remove. This is caused by the .NET Compact Framework dispaying the form before we have had a chance to change it’s window style to include a caption.

These controls were quickly put together to demonstrate what could be possible with a little bit of work. If anyone is interested in collaborating to improve these controls, I would be keen to hear from them.


Close
  • Social Web
  • E-mail
<FORM action="http://www.christec.co.nz/blog/index.php" method="post"> </FORM>
이 페이지는 Textcube 1.10.0 : beta 1 로 구동됩니다 데스크탑 화면