Tuesday, December 07, 2004
Pioneered by Amazon (IIRC) the unobtrusive suggestions like "Other customers who bought Terminator II, have also purchased Eraser" actually make sense. That is until someone gives a whole new meaning to the word "often".
 

"Customers who bought this item often buy"... Locking Key Box (48 hooks) and ... T D INdustrial Compound Laser Miter Saw. Really???

Or this one:  ATN Night Vision spotting Scope

"Customers who bought this item often buy" Cosmopolitan Leather Tote Bag (three colors)

Well, this one actually makes sense. After all you do need something to tote your shiny new night scope around.

12/7/2004 4:43:55 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Friday, December 03, 2004

It just occured to me that the way we potty-train small children so that they stop making messes, we also should train older children to look the stuff up instead of asking endless questions. More and more I find myself answering another question like Daddy, what is Cassinian Oval, with “lookitup”. And what better place there is than Google (with all caveats related to the safe search issues).

I even went as far as coining a term - “google-trained”, although in my case I send her to Encarta first. The thing is that google has already become synonymous with search (same as Xerox meant “to copy”). Training by the way includes an ability to recognize a potentially unsafe or junk result.

Are your children google-trained?

12/3/2004 6:15:08 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Sunday, November 21, 2004

At the heart of our production application is a paradigm of a work order. As one can surmise, the real-life work order has quite a complex data structure. In our case it is a chunk of XML couple of thousands of bytes in size. When you need to edit this XML in your app, you have to keep writing long and unpleasant queries like this:

txtWONumber.Text = objWO[”WorkOrder”].GetAttribute[”Number”];
txtWOTask.Text = objWO[”WorkOrder”][”Asset”][”Task”].InnerText;

Of course in part this is caused by lack of XQuery support in CF 1.0, but still, later you have to write the update code and make sure you haven't forgotten to call it. And if you need to populate a listbox or a datagrid from a list of XML nodes, you are quite out of luck and in for some manual work.

And yet, the databinding underneath is highly automated and quite extensible, so let's see what we can do to make it understand XML data.

First of all, we will decide on what is our data unit (component). It will be an XML node. Component properties could be either attributes or subnodes, or even any valid XQuery expression - anything that returns data. A logical choice of data source is an XML node list (not an array). Such a list is easily obtained from an XML document via ChildNodes property of its document element.

I am not going to go into details of how the databinding works for there are many good sources that cover this process1. Instead I will simply say that at the root of custom databinding is an object derived from a class called PropertyDescriptor. We are going to derive our own class from it and call it XmlPropertyDescriptor.

Let's set some gorund rules. We want to be able to take an xml document like this:

xml version="1.0" encoding="utf-8" ?>
<
bookstore xmlns:bk="urn:samples">

<book genre="novel" publicationdate="1997" bk:ISBN="1-861001-57-6">

<title>Pride And Prejudicetitle>

<price>24.95price>

book>

<book genre="novel" publicationdate="1992" bk:ISBN="1-861001-45-3">

<title>The New Dawntitle>

<price>29.95price>

book>

<book genre="novel" publicationdate="1991" bk:ISBN="1-861001-57-8">

<title>Blue Smoketitle>

<price>19.95price>

book>

bookstore>

and bind to a list of “book” items using display expression like “title” or “@bk:isbn" . If we had more complex book item structure, the binding name could be a more complex path - “author/Name/@First”. What we do not want to do (since we don't really have XQuery/XPath support in CF) is to allow relative paths, functions and queries. Moreover, we will say, that in the path, every element must be a node name, except of the last one, that can be an attribute name (starting with @ ).

When you derive from the PropertyDescriptor class there are few things to keep in mind:

  1. Override IsReadonly property. This will tell the framework whether your property supports updates
  2. Override PropertyType property to return typeof(string) - this is our property type; we don't do any data conversions here. If we want data conversion/validation, we can implement it in Format/Parse events of the binding. We could to some data validation and type conversion via reading the schema (if available), but it would be outside of the scope of this article.
  3. Override ComponentType property to return typeof(XmlNode). This is the only component type we deal with.
  4. Override GetValue method. This is the most important method. It actually goes ahead and retrieves the property value given the component. In our case it gets the XmlNode (and already has the “XPath” by way of constructor parameter). Our task is to perform the query. Very simple on the desktop. A bit more complex in CF.
  5. Override SetValue method. It receives the component and the new value and sets it using the known “Xpath”.

Now, wouldn't it be nice to make the binding use our XmlPropertyDescriptor class? Simple - we just need to create our own Data Source that would report XmlPropertyDescriptors instead of whatever is used by CurrencyManager. Whatever is usually a SimplePropertyDescriptor class, ReflectPropertyDescriptor or DataColumnPropertyDescriptor.

This brings us to building our own data source object. There is no abstract class to override. When creating it, we need to make sure we implement several interfaces:

  • ITypedList
  • IEnumerable
  • IList
  • IBindingList

ITypedList::GetItemProperties creates a bit of a problem. It is supposed to return Property descriptor collection populated with all properties the data unit exposes. Unfortunately in our case properties are valid xpath expressions, which could be quite a few, expecially on a complex XML document

The reason we need to implement ITypedList::GetItemProperties is that we need for example a data grid to be able to get populated automatically, without us specifying XPath expression for each column. The problem here is that we don't want to build a full list of all possible xpath expressions valid on our XML item, so we need to decide on some way to simplify it. Let's limit the autogenerated property list to all attributes or all 1-st level child elements. If the XML is more complex than that, well, you need to specify the grid columns explicitly.

The IEnumerable and IList are simply delegated to the underlying XmlNodeList. IBindingList implementation is somewhat simplistic as we don't really want to figure out how to track the external changes in the XmlNodeList.

We end up with a class that allows us to take the XML document above and write things like:


doc.Load("data.xml");

XmlDataSource src = new XmlDataSource(doc.DocumentElement.ChildNodes, XmlDataSourceMode.DataSourceModeAuto);

txtGenre.DataBindings.Add("Text", src, "@genre");

txtISBN.DataBindings.Add("Text", src, "@bk:ISBN");

txtPrice.DataBindings.Add("Text", src, "price");

txtPubDate.DataBindings.Add("Text", src, "@publicationdate");

txtTitle.DataBindings.Add("Text", src, "title");

 

listBox1.DisplayMember = "title";

listBox1.ValueMember = "@bk:ISBN";

listBox1.DataSource = src;

dataGrid1.DataSource = new XmlDataSource(doc.DocumentElement.ChildNodes,

new string[] { "@genre", "title/#text", "price/#text" });

This produces a screen like this one:

 

Selecting a list box item moves the current position along the node list. Data edits are also supported.

The source code for this article can be found here.

11/21/2004 8:41:11 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Sunday, November 07, 2004

I've posted an article discussing the event handler internals, tips and tricks

11/7/2004 1:19:43 AM (Pacific Daylight Time, UTC-08:00)  #    Comments [0]  | 

After working on my first speech server project for a week or so, I decided to test it on the real hardware (as in connected to the analog phone line). I already had a D/41JCT-LS board from Dialogic (er, Intel). The 180-day evaluation of the Speech Server is available from Microsoft web site, and Intel kindly offers a 60-day evaluation of their NetMerge TIM. Sounds simple. But having spent some time developing for CT Media product in the past, as well as some other telephony products (both enterprise and consumer), I knew I was bound to encounter some resistance.

Conveniently, both Speech Server and the NetMerge thing come without any sort of installation guide. Speech Server mandates 2.5+ GHz machine with 2GB RAM. I decided that a newly acquired Sempron 2400+ motherboard with 512 MB should therefore fit the bill.

The first roadblock was in the form of Speech Server refucing to install caliming it has expired. Moving the clock back to 1/1/2004 helped. Then I installed NetMerge (I will omit the gruesome description of the download process - Intel site kept resetting connection, so you would do well by getting a download manager of some sort). NetMerge installed without any complaints. NetMerge package also includes the driver for the PCI board.

At this point a lesser mind would have been tempted to make a call to the new server and see whether it works, but I calmly decided to take it easy. The NetMerge program group contained two demos. I launched the first one only to see mutiple complaints about not being able to acquire speech resources. Indeed, when I tried to call in, the system responded, but would ignore me speaking into the handset. To fix this I went into DCM (Dialogic Configuration Manager) and (after stoppping the service) in the card properties made the following changes:

  • Changed the PCM Format from Automatic to mLaw
  • Instead of the defaullt firmware, selected the one from the dropdown (d41jctsp.fwl)

After restarting the service, I ran the demo again and lo and behold - the bargein worked, the demo was fully operational.

Encouraged I tried starting the Speech Server (TAS) only to find out that it still wouldn't start. The event log was complaining of the lack of voice resources. This was strange, since I was pretty sure that the recent configurastion change in the board properties should have taken care of that. Unfortunately the TAS still refused to start. It took me some time to finally find an application in the Speech Server program group called “Configure Resources”. Upon being launched it opened DOS window and happily reported having found 4 voice resources. This was it. The TAS started successfully and in a minute I was able to call in and hear “Welcome to Microsoft Speech Server”

11/7/2004 1:17:37 AM (Pacific Daylight Time, UTC-08:00)  #    Comments [0]  | 
 Thursday, October 21, 2004

I've posted an article that discusses how to create non-rectangular windows in Windows CE using Compact Framework. See it here

10/21/2004 10:27:51 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Sunday, October 03, 2004

When populating a listview it is always a good idea to call BeginUpdate before adding/removing/clearing items. Otherwise the control will attempt to repaint on every change, greatly reducing the overall performance.

A question often asked is what to do with Listbox and Combobox classes, which in Compact Framework do not expose these methods. Apparently it is quite easy to emulate missing methods.

The secret is in WM_SETREDRAW message. It tells the window to stop updating itself. The actual implementation is up to the window class, but luckily in case of Combobox and Listbox this message is handled and provides expected results. Here is what is says in remarks part of the message description:

 The window manager provides no default processing for this message. However, certain controls do support this message, list box, tree view, and combo box

Armed with this knowledge, we find it almost trivial to write the missing methods:

public class UIHelper
{

 public static IntPtr GetHandle(Control ctl)
 {
  ctl.Capture =
true;
  IntPtr ret = Win32Window.GetCapture();
  ctl.Capture =
false;
  return ret;
 }

 public static void EnableDisableUpdates(Control ctl, bool bDisable)
 {
  if ( ! (ctl is ListBox) && ! (ctl is ComboBox) )
   throw new NotSupportedException("Control must be either listbox or combobox");

  IntPtr hWnd = GetHandle(ctl);
  Win32Window.SendMessage(hWnd, WM_SETREDRAW, bDisable? 0: 1, 0);
 }

 const int WM_SETREDRAW = 0x0b;
}

Notice

This code uses Win32Window class found in OpenNETCF SDF. If you don't want to use SDF, the P/Invoke defionitions for GetCapture and SendMessage can be found here.
10/3/2004 6:28:01 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Monday, September 20, 2004

A question popped up in a public compact framework newsgroup yesterday. The OP was feeding strings coming from a multiline text box to a web service for storing in the database. To his surprise when he retrieved the strings back (via the same web service) they would display small boxes in place of line breaks.

The symptom is consistent with having just LF (0xa) as a string break instead of required CRLF (0xd0xa) and yet when the data were sent to the web service the CRLFs were there. What happened? The investigation shows that on the web service side the strings are delievered without “CRs“

The answer hides in the SOAP protocol. If you have a web service that has a method Test, taking a single string parameter, the request is passed via HTTP POST request that looks like this:

POST /FormatSvc/svc.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/Test"

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance 
xmlns:xsd=http://www.w3.org/2001/XMLSchema
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <Test xmlns="http://tempuri.org/"> <s>string</s> </Test> </soap:Body> </soap:Envelope>

The string there is the actual string parameter passed to the method. Naturally, if it contains CRLF inside, the XML parser is going to treat it as a whitespace, which is exactly what's happening. To get around this we need to force some sort of encoding on our parameters or tell XML to preserve whitespace. While there is probably a way of doing it, there seems to be a simpler workaround. A standard, run-of-the-mill web service written in ASP.NET supports several request methods - SOAP, SOAP 1.2, POST and GET. How to select the method to be used? The answer is - you need to use wsdl.exe to generate web service proxy for you.

wsdl.exe http://yourwebserver/yourservice /Protocol:HttpGet
produces yourservice.cs - a web service proxy, that can be placed into your project instead of the web reference. Since in GET request parameters are UrlEncoded, this will preserve the special characters and CRLFs

There is one small catch. By default ASP.NET 1.1 does not accept GET requests for web services. This can be changed in C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\CONFIG\machine.config by uncommenting the line that says: <!--<add name="HttpGet"/>-->
This is not the best solution. A better one would be to use POST client. In my experiments for some reason wsdl.exe refused to produce anything when invoked with /Protocol:HttpPost. What I've done is I edited the .cs file produced for /Protocol:HttpGet replacing

System.Web.Services.Protocols.HttpGetClientProtocol

with

System.Web.Services.Protocols.HttpPostClientProtocol

in the class statement, and

[System.Web.Services.Protocols.HttpMethodAttribute(typeof(System.Web.Services.Protocols.XmlReturnReader), typeof(System.Web.Services.Protocols.UrlParameterWriter))]

with

[System.Web.Services.Protocols.HttpMethodAttribute(typeof(System.Web.Services.Protocols.XmlReturnReader), typeof(System.Web.Services.Protocols.HtmlFormParameterWriter))]

on each method.

WSDL.EXE can be found in .NET SDK usually located in <Visual Studio Directory>\Sdk\v1.1\Bin

9/20/2004 10:01:44 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Wednesday, March 17, 2004

You have probably noticed by now that Pocket PC applications built with .NET Compact Framework 1.0 are single-instance. If you try to launch it while one copy is already running, that copy will be reactivated and brought to foreground. While very convenient and compliant with PPC Design Guidelines, sometimes it is a bit too helpful.

Let's take a look at what happens when you launch your CF application. On the outside a CF application is a standard executable with PE header and entry point. The entry point is a single line of assembly code:

jmp mscoree!__CorExeMain

Upon startup CF implementation of mscoree looks for a window with a class #NETCF_AGL_PARK and title set to the current process executable full path (\Program Files\MyApp\MyApp.exe). If found, the runtime presumes that the current application is already running. In this case it will be reactivated. To do this the abovementioned window is sent a message 0x8001, and then the new instance quits . In the previously active instance upon receiving of the message 0x8001 the WindowProc of #NETCF_AGL_PARK window will perform a series of steps ending with a call to SetForegroundWindow. Notice that the call to SetForegroundWindow is passed the handle of  GetWindow(<#NETCF_AGL_PARK window>, GW_HWNDPREV).
Unfortunately there are cases when you need more than simply reactivate the previous instance. The most notable example is using a CF app as a target for CeLaunchAppAtXXX function. Problem is that the second instance quits before your managed code has a chance to execute, so there is not much you can do even if you would like to implement your own logic for detecting/activating previous instance.
The solution for disabling this "single-instance" behavior is rather obvious from the above description. All we need to do is to rename our #NETCF_AGL_PARK window. The window handle can be located using FindWindow function. The class name is #NETCF_AGL_PARK and the title is identcal to the full assembly module path:
Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedPath. Then we simply use SetWindowText to alter window title.
 
12/20/04: Update
It appears that the name of the window class has changed in SP2. The new name is #NETCF_AGL_PARK (with a trailing underscore). The rest of the informatin above stands.
3/17/2004 1:37:08 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [21]  |