Wednesday, December 26, 2007
Call to IDirectDrawSurface::Blt returns E_INVALIDARG

While possible causes for this error are numerous, I wanted to point out a specific one that is not mentioned anywhere: either source or destination rectangle is empty ( zero width or height )

12/26/2007 1:25:03 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 

It's been a while. Things were a bit hectic. Nevertheless I am back.

These days I am working on a rather interesting project involving DirectDraw on Windows Mobile 6. Without going into the project details, I wanted to share some experiences. So far, a number of time I would run into an issue, and of course not it would not be explained/documented/covered anywhere where the Google search takes you. It struck me that I must be not the first one to hit these issues, and if I list them here, chances are that Google search will be more productive for the next poor sod to plow through underdocumented (to put it mildly), convoluted (again, to put it mildly) and unforgiving API such as DirectDraw. I am going to have multiple posts with keywords facilitating search. If a month from now you will find out that I produced all of one post (this one seems to be on track so far. Chances for it not making it are slim), don't judge me too hard. I am away from home for the 3rd week in a row, having missed Christmas, but should be back for the New Year. Ok, here goes.


Call to IDirectDrawSurface::Blt returns DDERR_SURFACEBUSY

The documentation says to check for other threads accessing your surface at the same time. The group search suggests to search for mismatching Lock/Unlock calls. The actual cause turned to be a missed call to IDirectDrawSurface::ReleaseDC (after a successfull call to IDirectDrawSurface::GetDC). If you think about it, internally these calls must be using Lock/Unlock, so cudos to Group search and boo to MSDN documentation

 

12/26/2007 1:20:39 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Friday, June 01, 2007

I was quite surprised to learn that apparently I have missed an important new feature of Windows Mobile 6 (Professional)- inking and ink serialization support. Gone are the days when a developer had to tinker with oh-so-temperamental InkX control. Now everyone and his brother can take advantage not only of high-quality precision ink support with smoothing and serialization, but also of handwriting recognition built into Windows Mobile 6 Professional. To quote the documentation - "It provides a rich inking experience, through high quality curve–fitted ink with anti-aliasing, transparent ink, and highlighter ink. It provides an API for Ink collection, data management, rendering, and recognition. It also provides Ink controls to support the note–taking scenario."

Another important feature is interoperability and serialized data format compatibility with ink support on Tablet PC.

While all of this is nice, there is slight bit of bad news - using this rich set of goodies requires C++. There are 2 ways to use ink in Windows Mobile 6 - InkCanvas control and an COM automation library. InkCanvas control offers the ability to use a regular Win32 control (similar to InkX) to write, highlight, collect ink etc via a set of Windows messages. COM automation library on the other hand allows accessing the entire set of features offered by WISP. And there is no managed wrapper for the time being.

As a public service, we at OpenNETCF.com are proud to offer WISPLite managed wrapper. The wrapper offers the entire WISPLite functionality (although not every method of every interface has been tested). There is InkControl class, which wraps InkCanvas, and a OpenNETCF.WindowsMobile.Ink namespace that contains imported COM interfaces. Some of the interfaces do not wrap cleanly, so a bit of coding is needed.

Here is what the demo app looks like (warning, before running it, change line 132 in Form1.cs to be

inkControl1.SetPenStyle((float)trackBar1.Value, penColor, penType); )

Saving ink data to a file:

using (SaveFileDialog fd = new SaveFileDialog())
{
  fd.Filter =
"Ink files (*.isf)|*.isf|All files (*.*)|*.*";
 
if (fd.ShowDialog() == DialogResult.OK)
  {
   
byte[] data = (byte[])inkControl1.GetInkData(IC_INKENCODING.BINARY);
    FileStream stm = File.OpenWrite(fd.FileName);
    stm.Write(data, 0, data.Length);
    stm.Close();
  }
}

Getting ink as bitmap and retrieving recongnition result:

pbPreview.Image = inkControl1.GetInkDataAsBitmap();
lblReco.Text = inkControl1.RecognizedText;

In conclusion I'd like to ask to report problems with this wrapper to this blog's comments.

6/1/2007 2:20:22 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [3]  | 
 Tuesday, April 17, 2007

Sometimes when trying to connect Visual Studio t a device, especially to a custom CE platform, it is helpful to see if there are any problems reported by the CoreCon components on the device side. CoreCon components (ConmanClient, trasnport DLLs, edbgtl.dll and edm.exe) all have an integrated logging facility, which can be used by a developer for troubleshooting.

To enable Corecon debug log you can set the following under HKLM\Software\Microsoft\VSD\Logging

VSD_LogEnabled: DWORD:1,0

VSD_LogToDebugger: DWORD:1,0

VSD_LogToConsole: DWORD: 1,0

VSD_LogToFile: DWORD:1,0

VSD_LogLevel: DWORD - set to at least 4, up to 9

VSD_LogFile: REG_SZ (default VSDLogFile.txt)

Keep in mind that the ConMan log can be quite chatty, so enable it sparingly and only when needed

4/17/2007 12:12:44 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Sunday, February 25, 2007

Raffaele Rialdi pointed out that attempting to run RTF Host from Compact Framework 3.5 Power Toys on an emulator produces the following error:

This is caused by RTF getting confused because the default CoreCon transport on the emulator is DMA (DeviceDMA.dll). Here are the steps to add emulator as a manual Tcp connection. Not that many will need it, but it is useful for a demo

1. Start Emulator using Device Emulator Manager.
2. Configure Network and Storage card folder
3. Copy to storage card folder the following file: "C:\Program Files\Common
Files\microsoft shared\CoreCon\1.0\Target\wce400\armv4i\TCPConnectionA.dll"
4. In File ExplorerNavigate to \Windows\Corecon1.1. If you don't see it
there, connect to the emulator from Studio
5. Launch ClientShutdown (you should see a guid-named folder to appear)
6. Copy \Storage Card\TcpConnectionA.dll to \Windows
7. Go to \Windows and delete DeviceDMA.dll (if you can't, you forgot to
launch ClientShutdown)
8. Go back to \Windows\Corecon1.1 and launch ConmanClient.exe
9. Launch RTF Host. Enjoy

2/25/2007 6:45:36 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Monday, October 02, 2006

I have a lot of respect for MSDN technical articles. They are more often than not a source of knowledge one would be hard pressed to obtain from official documentation. Kraig Brockschmidt's treatise on OLE internals, Nancy Winnick Clutz explanation of TAPI, Icon internals by John Hornick - all of these are precious gems of knowledge. Heck, I even wrote a few myself. Over the years there were some that are brilliant and concise, other that were less interesting. Almost every article there had some code posted with it. The code samples would also vary in quality, but not so much as to raise an eyebrow.

On several occasions I had this conversation with my teenage daughter, the gist of which was that even though the Algebra lesson is not an English lesson, it does not mean she can disregard the grammar completely, while doing her math homework. Apparently it was not obvious to her, that writing properly is not something you do only when you absolutely have to. Similarly, I suppose when you write code, even throwaway code, you should still be conscious of how you do it.

When I first came across this, my first reaction was - this is good stuff. It'll teach developers not to use an old, outdated control and show, how to replace it with alternative modern controls. And then I saw the following gem:

If you must search compiled code, you can look certain patterns that represent the GUIDs. For example, the following GUID:

{ABCDEFGH-IJKL-MNOP-QRST-UVWXYZ012345}

becomes the following hexadecimal sequence in binary:

GH EF CD AB KL IJ OP MN QR ST UV WX YZ 01 23 45

 

Er, what hexadecimal sequence?
But following it was a code sample

// Compile and execute:  "FindGUIDs YourApplication.exe"
using System;
using System.Text;
using System.IO;

namespace FindGUIDs {
class Program {
  static void Main(string[] args) {
    FileStream    fs = File.OpenRead(args[0]);
    StringBuilder sb = new StringBuilder();
    do {
      Int32 b = fs.ReadByte();
      if (-1 == b) {
        break;
      }
      sb.AppendFormat("{0:X2}", b);
    } while (true);
    fs.Close();
    String s = sb.ToString();
    if (s.Contains("E0A58D4371F1D011984E0000F80270F8"))
      Console.Out.WriteLine("GUID for TriEditDocument Class detected.");
    if (s.Contains("DFA58D4371F1D011984E0000F80270F8")) {
      Console.Out.WriteLine(
        "GUID for ITriEditDocument Interface detected.");
      }
      if (s.Contains("0002362DF5FFd1118D0300A0C959BC0A")) {
        Console.Out.WriteLine("GUID for DHTMLEdit Class detected.");
      }
      if (s.Contains("91B504CE1F2Bd2118D1E00A0C959BC0A")) {
        Console.Out.WriteLine("GUID for IDHTMLEdit Interface detected.");
      }
    }
  }
}

Basically, what's happening here is that the application is trying to find occurences of a GUID in a binary file. I've seen many approaches to searching a binary string in a file. Some were simpler to implement, the other are more efficient, but harder to understand. This one takes the cake. In a nutshell, this code reads a binary file, byte by byte, and converts each byte into its string hexadecimal represenation. Then this string is appended to a StringBuilder. Once the entire file is loaded into StringBuilder (consuming filesize * 4 bytes of memory), the StringBuilder is used to produce a string (another memory allocation of the same size edited: no, this is actually done in place. Thanks, Dunkan!) and the string is being searched for a GUID substring.

I won't even go into the efficiency of string search as used above. My point is that you either do things right, or you sidestep the whole issue by not providing the code sample (not really needed in the context of this article) and leaving it as an excercise for the reader.

Some screens later in the article we find the following gem of regular expression (JScript, searching an HTML document for tag):

var rex = new RegExp("]*>", "i");

Er, what happened to the non-greedy qualifiers? What is that \s doing inside []? This feels like something written by a person, who does not use javascript day-to-day.

Please, please let's keep MSDN technical library standards high. After all we all benefit from better written code.

10/2/2006 1:23:53 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [3]  | 
 Thursday, September 28, 2006

Several people have hit a problem while installing the service pack on Windows Server 2003 machines. The installation (that takes obscenely long time) eventually fails with a message:

Error 1718: The file xxxxxxxx.msp was rejected by digital signature policy.

Two things with repsect to that:

1) I had this problem and was eventually able to complete installation (on the second try). What has changed is that I moved the SP file from a network location to a local drive.

2) Take a look at the setup log. It can be found under %temp%\VS80sp1-KB918525-X86-Beta-ENU (this path can be pasted into Run box to open the folder)

9/28/2006 2:08:37 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Tuesday, April 04, 2006

On April 18, 10 AM PST I am doing an MSDN webcast on IMAPI v2 and using it to burn CDs and DVDs. As you may know, I've been to some extent successful in uitilizing IMAPI v1 (found in Windows XP) in my own applications. Now with the advent of Windows Vista it is finally possible to write CDs and DVDs even from VB Script. Join me for this webcast to see how this techology can be utilized from .NET languages.

Watch this post for the web cast code samples link.

4/4/2006 3:07:24 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Friday, February 03, 2006

As promised, here are some details on the OpenNETCF.Drawing.Imaging namespace. I'm going to demonstrate how to accomplish several tasks listed in the previous post as not supported by the CF Bitmap class.

0. Preface. helper classes

In the wrapper we introduce 2 helper classes - StreamOnFile and ImageUtils. The latter is simply a collection of high-level image proverssing methods. The former is an IStream implemented over .NET Stream (including FileStream). The implementation is not complete, but sufficient for the Imaging API methods that expect an IStream parameter.

1. Thumbnails, loading parts of the large image

Loading an image in Imaging API is achieved via calls to decoders - COM objects implementing IImageDecoder interface. The basic imaging interface IImage uses decoders to load image data. Most of the decoders support loading partial image, dicarding the unnecessary data. E.g. if you need to load a 3000x2000 image into a 300x200 PictureBox control, it is obvious that you don't need all 6MP of data taking a whopping 18 MB of RAM (24bpp). Moreover, most devices will simply throw an OutOfMemoryException fi you try something like this. Decoder can be instructed to load an image of the required size so that it will skip over those pixels that don't make it (or factor them into interpolation process to scale the image more smoothly). Here is how we achieve it.


        static public IBitmapImage CreateThumbnail(Stream stream, Size size)
        {
            IBitmapImage imageBitmap;
            ImageInfo ii;
            IImage image;

            ImagingFactory factory = new ImagingFactoryClass();
            factory.CreateImageFromStream(new StreamOnFile(stream), out image);
            image.GetImageInfo(out ii);
            factory.CreateBitmapFromImage(image, (uint)size.Width, (uint)size.Height, 
          ii.PixelFormat, InterpolationHint.InterpolationHintDefault, out imageBitmap);
            return imageBitmap;
        }

After we got IBitmapImage object, we can convert it to the .NET Bitmap:

             Bitmap bm = ImageUtils.IBitmapImageToBitmap(imageBitmap);

2. Image transformation (flip, rotate, gamma/brightness/contrast controls)

Imaging library offers a limited set of the image operations exposed via interface IBasicBitmapOps. These are also wrapped in the ImageUtils class so that you get the following methods:

public Bitmap RotateFlip(Bitmap bitmap, RotateFlipType type)
public Bitmap Rotate(Bitmap bitmap, float angle)
public Bitmap Flip(Bitmap bitmap, bool flipX, bool flipY)

Of course you are welcome to use the IBasicBitmapOps directly.

3. Image tags

TBD

4. Transparency and alpha blending

If you have a PNG image with alpha channel information and you load it into a Bitmap object, the transparency is immediately lost. Not so, if using IImage class.

ImagingFactory factory = new ImagingFactoryClass();
IImage
img;
factory.CreateImageFromFile(
"rgba8.png", out img);

Bitmap imageBackground = new Bitmap(“MyImage.bmp“);
Graphics
g = Graphics.FromImage(imageBackground);

IntPtr hDC = g.GetHdc();

RECT rc = RECT.FromXYWH(200, 200, width, h
eight);
img.Draw(hDC, rc,
null
);
g.ReleaseHdc(hDC);

The above code will transparently draw rgba8.png over the specified bitmap.

2/3/2006 5:13:30 PM (Pacific Standard Time, UTC-08:00)  #    Comments [7]  | 
 Saturday, January 28, 2006

I've been encouraged to blog about a new feature available as a part of newly released beta 1 of OpenNETCF SDF - the Imaging API wrapper.

The Imaging API is an attempt to bring Image codec support from the desktop GDI+ to mobile devices. It is implemented as a set of COM interfaces available to C/C++ applications. There is a cocreatable class ImagingFactory and a bunch of interfaces that allow loading, saving and manipulating the images. Effective 1.0 SP2 Compact Framework uses this API internally to load (and in 2.0 to save) images instead of imgdecmp.dll used in CF1 SP1 and before.

In CF2 it became possible to wrap COM interfaces for use in the managed applications. The Bitmap class is also significantly richer than before. Given this, one would ask what would be the reason to try using Imaging API directly. Here is a brief list of things that are not part of the CF2 Bitmap class:

  • Access to image tags (EXIF header etc)
  • Image transformation (flip, rotate, gamma/brightness/contrast controls)
  • Thumbnails, loading parts of the large image

The Imaging wrapper is located inside OpenNetCF.Drawing components as OpenNetCF.Drawing.Imaging namespace. On top of the basic interfaces it offers a small utility layer presented as ImageUtils class. ImageUtils has the following high-level methods:

  • Bitmap RotateFlip(Bitmap bitmap, RotateFlipType type)
  • Bitmap Flip(Bitmap bitmap, bool flipX, bool flipY)
  • Bitmap IBitmapImageToBitmap(IBitmapImage imageBitmap)
  • IBitmapImage BitmapToIImageBitmap(Bitmap bitmap)
  • IBitmapImage CreateThumbnail(Stream stream, Size size)

The last method - CreateThumbnail - allows loading a small thumbnail instead of a large image. Most Pocket PC devices won't be able to load and display a 5-6 megapixel image produced by most digital cameras. At the same time one can easily load 640x480 thumbnail that will let one implement basic zoomable view.

In the next couple of days I'm going to provide a few code samples. Stay tuned.

1/28/2006 1:53:31 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Monday, September 12, 2005

Update: I'm getting reports that this build of ISO Recorder crashes on the recent Vista builds. I pulled the download for now. Expect a new drop for the Vista Beta 2 timeframe

With certain amount of help from other Vista beta testers I have released an early build of ISO Recorder for Windows Vista.

I want to thank David Smith and Jonathan Rosenberg for their assistance with testing and willingnes to help prove that 3rd time is indeed a charm. Apparently working with beta OS (Vista) and beta compiler (VS2005) can be ... tricky sometimes

9/12/2005 12:56:36 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Tuesday, August 02, 2005

Yesterday Peter Foot has released his suite of Windows Mobile APIs for .NETCF (http://www.inthehand.com/1stAugust2005.aspx). This is an evolution of the PocketOutlook library which now incorporates Email and SMS functionality. Along with this are a selection of other assemblies for Configuration, System Status and Telephony. Very cool!

8/2/2005 9:07:56 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Wednesday, April 20, 2005

I've been asked for assistance with the following issue today. A developer has a CAB that he wants to install on the device without using CeAppMgr, from his own setup application. In addition he wanted to force the installation to the storage card if present.

This task involves several steps:

  1. Copy CAB to the device
  2. Detect storage card presence
  3. Launch wceload.exe to install the cab

1. Copying CAB to the device.

I would use cecopy (from Windows Mobile Developer Power Toys) or RAPI (CeCreateFile, CeWriteFile). If working with managed code, I suggest OpenNETCF.Communications library, or RapiDeploy tool

2. Detecting storage card presence.

While on the device side the preferred method is to use FindFirstFlashCard/FindNextFlashCard, these functions do not have Rapi equivalent. From the desktop side use CeFindAllFiles/FAF_FOLDERS_ONLY (or CeFindFirstFile/CeFindNextFile) to search the root directory and enumerate all files for having FILE_ATTRIBUTE_DIRECTORY and FILE_ATTRIBUTE_TEMPORARY combination of attributes (0x110).

3. Launch wceload.exe to install the cab

This is the interesting part. Here is a list of command-line switches that wceload.exe supports:

  • /delete - if value = 0 do not delete cab after install
  • /noui - perform a silent operation. Do not ask if it is ok to overwrite the exiting files
  • /nouninstall - do not create a .uninstall file. The applciation entry will not appear in Remove Programs list
  • /askdest - will force wceload to display a dialog that allows user to select installaltion location and some other things
  • /noaskdest - install specified applications to specified locations (see below)

 


Dialog displayed by wceload when /askdest is specified

The /noaskdest switch is the most inetersting of them all. When you specify it, wceload ignores the rest of the command line. Instead it checks the following registry location - [HKEY_LOCAL_MACHINE\SOFTWARE\Apps\Microsoft Application Installer\Install]

The key contents are key/value pairs:
[CAB file path] = [CAB destination directory]
e.g. \Storage Card\MyApp.CAB = \Storage Card\Program Files\My App

wceload will try to install the cab specified in the value name to the location specified in the value value. Below is the sample registry content:

To reiterate: in order to install a cab to the memory card, one needs to:

  1. Copy CAB to the device (e.g. to the memory card)
  2. Create a registry value on the device specifying the cab location and cab destination
  3. Launch wceload.exe with /noaskdest parameter
4/20/2005 5:52:05 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [5]  | 
 Thursday, February 24, 2005

The Toolbar control provided as a part of Compact Framework v1.0 does not support tooltips (they are shown when you tap and hold toolbar button). Here is how to add this missing functionality.

First of all, let's see how the tooltips are added. The proper way to do it is to create a tooltip control and pass its handle to the TB_SETTOOLTIPS message as wParam. This sounds pretty painful if we were to do this by means of CF. Fortunately there is an easier way listed in the documentation as legacy but supported all the way through CE 5.0. You can send TB_SETTOOLTIPS passing an array of tooltip strings as lParam and string count as wParam and the toolbar control will create a tooltip control for you. Finally one has to remember to modify toolbar style to include TBS_TOOLTIP and do all of the above before adding the buttons to the toolbar.

Armed with this knowledge we start with defining a few P/Invoke functions:


[DllImport("coredll")]

extern static IntPtr GetCapture();

[DllImport("coredll")]

extern static IntPtr LocalAlloc(int flags, int size);

[DllImport("coredll")]

extern static IntPtr LocalFree(IntPtr p);

[DllImport("aygshell")]

extern static IntPtr SHFindMenuBar(IntPtr hwnd);

[DllImport("coredll")]

extern static int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);

[DllImport("coredll")]

extern static int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);

const int TB_SETTOOLTIPS = (WM_USER + 81);

const int TB_SETSTYLE = (WM_USER + 56);

const int TB_GETSTYLE = (WM_USER + 57);

const int TBSTYLE_TOOLTIPS = 0x0100;

const int WM_USER = 0x0400;

Now, that we are almost ready to set tooltips, there is one final step left. The control expects to receive an array of string pointers. The Compact Framework marshaller is not capable of creating one. Instead we write a special piece of code that would marshal an array of strings into a block of unmanaged memory.

To add tootlips we create a string array, convert it into unmanaged array and then use it as the LPARAM when sending a TB_SETTOOLTIPS message. Keep in mind that the tooltip control will expect this memory to be preserved throught the application lifetime. Release it when the form is closed (or the tooltips are changed).

private IntPtr m_pLabels;

private string[] m_labels = new string[]
{ "Button1", "Button2", "Another button" };

SendMessage(hWndToolbar, TB_SETSTYLE, 0, SendMessage(hWndToolbar, TB_GETSTYLE, 0, 0) | TBSTYLE_TOOLTIPS);

m_pLabels = AllocateStringArray(m_labels);

SendMessage(hWndToolbar, TB_SETTOOLTIPS, m_labels.Length, m_pLabels);

The end result looks like this:

The sample code can be found here.

2/24/2005 7:14:38 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Wednesday, February 09, 2005

There is a common technique of obtaining the hWnd of various controls in Compact Framework applications to use with native Win32 APIs. It involves setting a Capture property to true and then using GetCapture() to get the handle:

txtUser.Capture = true;
IntPtr hWndUser = GetCapture();
txtUser.Capture = false;

Suprisingly this does not work on Smartphone. It gets you a handle alright, but the handle seems to be wrong. It is wrong indeed. The reason is that on the Smartphone the native Edit control (wrapped by the TextBox) is hosted inside another child window. This has to do with the Smartphone navigation. When you set TextBox.Capture to true, the outer control gets Captrure and as a result, it's the outer control, of which you get the handle. Since we know that the outer control has just one child, we can see our way from this quandary.

txtUser.Capture = true;
IntPtr hWndUser = GetCapture();
hWndUser = GetWindow(hWndUser, GW_CHILD);
txtUser.Capture = false;

//GW_CHILD = 5;

The required PInvoke definitions are parts of Win32Window class in OpenNETCF SDF

Note: This is applicable to CF v1. I have a reason to believe that in v2 things are done differently

2/9/2005 12:24:05 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 

A question popped up. Let's say we have an application \Program Files\MyApp\MyApp.exe that references a class library \Program Files\MyApp\Framework\MyLib.dll. How can we avoid a TypeLoadException in this scenario?

To answer this let's take a look at how the type resolution works in CF. When an application code attempts to load type T, it first checks if the assembly that contains the type (the one referenced in the Type's full name) is already loaded in the current AppDomain. Obviously, if the appdomain already has the assembly, there is no reason to perform a costly file operation lookihng it up and loading it again. This suggests an easy way to “help” the loader to resolve a type. All you need to do is preload the assembly before the code attempts to use the type from that assembly. In our scenario the following code need to be made:

System.Reflection.Assembly.LoadFrom(@”\Program Files\MyApp\Framework\MyLib.dll”);

This will ensure that the types that belong to this class library are successfully resolved.

The next question is - when to load the assembly. The easy answer is - to play it safe, load all such assemblies in the Main, before the Application.Run. This approach has a disadvantage - a noticeable performance hit because a number of modules are being loaded in the memory before the UI started painting. Besides some of them might be never used. Because of this I would advise staggered load. The trick is to make sure the appropriate assembly is loaded before code execution has entered a block that defines/instantiates a variable of a type defined in that assembly. For example if you have a function:

void DoSomething()
{
MyType myVar = new MyType();
}

where MyType is defined in a dynamically-loaded assembly, and your code never ever call this function, there is no reason at all to load the assembly (provided the no type from that assembly is ever used outside that function).

By cleverly structuring your code you can avoid performance hit even if you have a large amount of dynamically loaded assemblies

2/9/2005 12:15:44 PM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Tuesday, January 18, 2005

Today in the Compact Framework public newsgroup I spotted a code snippet (seemed like a piece of a newsreader application) where a variable was called neueNachricht. Try doing something like that in English. I don't think so.

Code | Computers | Life
1/18/2005 11:00:24 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Sunday, December 19, 2004

Q: When I append text to the end of the current content of a multiline listbox, it is always scrolled back to the first line. Even if I later use ScrollToCaret to return to the end of the text, the whole control briefly flickers. Is there a way to avoid it?

A: Indeed there seems to be an issue in Compact Framework where replacing selection in an edit control. Apparently someone wanted to be clever and even though there is a dedicated control message EM_REPLACESEL, he implemented set_SelectedText in the following way:

string SelectedText
{
   
get
   
{
       
if
( SelectionLength == 0 )
           
return
"";
       
return new string
(Text.ToCharArray(), SelectionStart, SelectionLength);
    }
   
set
   
{
       
this.Text = this.Text.Remove(SelectionStart, SelectionLength).Insert(SelectionStart, value
);
        SelectionStart =
this
.Text.Length;
        SelectionLength = 0;
    }
}

 

Why is it done this way, I don't know. There are perfectly good tools at the edit control message level to work with selection. What's important here is that set_SelectedText internally calls Control.set_Text which translates into WM_SETTEXT message. In case of an edit control it has a side effect of moving caret to the beginning and scrolling the text to the same place, thus creating an unpleasant flicker.

To remedy this problem we simply replace set_SelectedText with our own implementation:

Instead of
textBox.SelectedText = "A quick brown fox jumped over lazy dogs. ";
use
SendMessage(hWnd, EM_REPLACESEL, false, "A quick brown fox jumped over lazy dogs. ");

You will also need the following PInvoke definitions:

 

const uint EM_REPLACESEL = 0xc2;

[DllImport("coredll")]
extern static IntPtr GetCapture();
[DllImport("coredll")]
extern static int SendMessage(IntPtr hWnd, uint Msg, bool WParam, string LParam);

 

To get HWND of your textbox use something like this:

 

private IntPtr GetHWND(Control ctl)
{
    ctl.Capture =
true
;
    IntPtr hWnd = GetCapture();
    ctl.Capture =
false
;
   
return
hWnd;
}

 

12/19/2004 10:12:42 PM (Pacific Standard Time, UTC-08:00)  #    Comments [2]  | 
 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]  | 
 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]  | 
 Friday, May 23, 2003

OpenNETCF.Callbacks library is a rather powerful tool that can be used to significantly enhance the functionality of CF application. Its primary use is to allow using APIs that require the developer to provide a callback function, such as EnumWindows etc. An unexpected bonus is an ability to subclass an arbitrary window, including those behind CF intrinsic controls (Form, Textbox, Label etc). For example the only way to process Help menu item in Window CE is for the application window to handle WM_HELP message. The Form object does not provide such facility, but by subclassing the main form it is possible to intercept this message and process it, presenting user with the help page.

Download the sample here: http://www.alexfeinman.com/download.asp?doc=TabTest.zip

5/23/2003 4:36:36 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [11]  | 
 Tuesday, May 20, 2003
I just wanted to clarify an issue that comes up once in a while. It is rather poorly documented and can make one spend some time figuring it out.
Many times a developer writing CF code would do something like this:
    
    
MyForm form = new MyForm(); 
form.ShowDialog(); 
... rest of the function 
    
What do you expect to happen to the form when the variable goes out of scope? It is supposed to become a victim of the garbage collector, right? Wrong. To see what really happens let's consider even simpler case:
Control c = new Control();
c = null;
You would think simply creating the control will have it unattached to anything (like a parent control) and thus subject to garbage collection. Apparently not so. If we look inside the Control object constructor, we will see that upon creation the Control is added to an internally maintained list of all controls in the application:
Control()
{
   ... create new control...
   Control.m_rlctlMaster.Add(this);
   ... do more housekeeping
}
m_rlctlMaster is an ArrayList - an internal static member of Control class. The repercussions of the above action are rather obvious - the control is always referenced by the control list. It refrence count never becomes zero unless you destroy it explicitly.
When control is destroyed, it destroys all its child controls. This means that you do not have to explicitly kill every label, textbox and panel - it is enough to delete the form. If however the control was dynamically removed from the parent form:
myPanel.Parent = null;
or
myForm.Controls.Remove(myPanel);
it becomes orphaned and needs to be destroyed explicitly by calling Control.Dispose()
Same thing is true for forms. What the code in the beginning of this post is missing is form.Dispose(). Without it the form and all its controls will stay in memory consuming resources and eventually will bog down the whole application
5/20/2003 12:34:39 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [1]  |