Thursday, March 04, 2004

Ilya Tumanov (MS) from CF team has just posted on newsgroups really useful and insightful information on how you'd use XML with schema or without it:

 

"...

The first problem is a shortcoming of inference process. Inference is an
attempt to 'guess' schema of the XML file using set of predefined rules.
As any 'guess', this one could be wrong and might produce some unexpected
results.

If your schema is created via inference process, columns are added in order
they've been found (might change in upcoming releases).
In the example below 'Column2' in 'Table1' will be found first, so it will
have lower ordinal than 'Column1'.
Moreover, inference process might create some hidden columns to account for
nested tables parent-child relations, so ordinals are quite unpredictable
with inference.

Let's load this sample using inference:

<?xml version="1.0" encoding="utf-8" ?>
<DS>
 <Table1>
  <Column2>Col2 Data 0 </Column2>
  <Table2>
   <Column3>Col4 Data 0 </Column3>
  </Table2>
 </Table1>
 <Table1>
  <Column1>Col1 Data 1</Column1>
  <Column2>Col2 Data 1</Column2>
  <Table2>
   <Column3>Col4 Data 1</Column3>
  </Table2>
 </Table1>
</DS>


Here's the result:

--------------------------- DataSet ----------------------
DataSet: 'DS'
    ----------------------- Tables -----------------------
    DataTable: 'Table1
        ------------------- Columns ----------------------
        Column: 'Column2'  'String'
        Column: 'Table1_Id'  'Int32' Unique Autoincrement
        Column: 'Column1'  'String'
        ----------------- Child Tables -------------------
        Relation 'Table1_Table2': 'Table2'->'Table1, nested
        ----------------- Parent Tables ------------------
        --------------------------------------------------
    DataTable: 'Table2
        ------------------- Columns ----------------------
        Column: 'Column3'  'String'
        Column: 'Table1_Id'  'Int32'
        ----------------- Child Tables -------------------
        ----------------- Parent Tables ------------------
        Relation 'Table1_Table2': 'Table2'->'Table1, nested
        --------------------------------------------------

We can see 'Column2' ordinal is zero; 'Column1' ordinal is 2!
And we have an extra column, 'Table1_Id' with ordinal 1 added to act as a
primary/foreign keys in these nested tables.

Unexpected? Sure. This is a reason why inference should _never_ be used.
Instead, you should design the schema you need so there will be no
surprises.

Let's take a look at the saved schema:

<xs:schema id="DS" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
 <xs:element name="DS" msdata:IsDataSet="true">
  <xs:complexType>
   <xs:choice maxOccurs="unbounded">
    <xs:element name="Table1">
     <xs:complexType>
      <xs:sequence>
       <xs:element name="Column2" type="xs:string" minOccurs="0" />
       <xs:element name="Column1" type="xs:string" minOccurs="0" />
       <xs:element name="Table2" minOccurs="0" maxOccurs="unbounded">
        <xs:complexType>
         <xs:sequence>
          <xs:element name="Column3" type="xs:string" minOccurs="0" />
         </xs:sequence>
        </xs:complexType>
       </xs:element>
      </xs:sequence>
     </xs:complexType>
    </xs:element>
   </xs:choice>
  </xs:complexType>
 </xs:element>
</xs:schema>


Do you see 'Table1_Id' column? Nope. It will be created for you because you
have nested tables, but it will be done after 'Column2' and 'Column1' are
created.
Thus, ordinals will change:

--------------------------- DataSet ----------------------
DataSet: 'DS'
    ----------------------- Tables -----------------------
    DataTable: 'Table1
        ------------------- Columns ----------------------
        Column: 'Column2'  'String'
        Column: 'Column1'  'String'
        Column: 'Table1_Id'  'Int32' Unique Autoincrement
        ----------------- Child Tables -------------------
        Relation 'Table1_Table2': 'Table2'->'Table1, nested
        ----------------- Parent Tables ------------------
        --------------------------------------------------
    DataTable: 'Table2
        ------------------- Columns ----------------------
        Column: 'Column3'  'String'
        Column: 'Table1_Id'  'Int32'
        ----------------- Child Tables -------------------
        ----------------- Parent Tables ------------------
        Relation 'Table1_Table2': 'Table2'->'Table1, nested
        --------------------------------------------------

Now 'Column1' ordinal is 1, and it used to be 2.

How to avoid these problems? It's easy.. Do not use inference, design
schemas yourself.

Now, to the real problem... It is a known bug introduced in SP2 with
performance optimizations. It is fixed in upcoming V2.

If you have table elements with all columns mapped as attributes (like this
one: <network linktype=0 />) and a table is not a root table, every second
row will be lost.
Possible workarounds:

1. Map at least one column with not null data as element. Primary/Foreign
key is the best candidate.
2. Do not use nested tables, use related tables. It might also save you
some space in XML file, improve loading performance.

Here's a sample schema/data with related tables:

<xs:schema id="DS" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="DS" msdata:IsDataSet="true">
   <xs:complexType>
    <xs:choice maxOccurs="unbounded">
     <xs:element name="Table1">
      <xs:complexType>
       <xs:sequence></xs:sequence>
       <xs:attribute name="Column1" type="xs:string" />
       <xs:attribute name="Column2" type="xs:string" />
       <xs:attribute name="PrimaryKey" type="xs:int" />
      </xs:complexType>
     </xs:element>
     <xs:element name="Table2">
      <xs:complexType>
       <xs:sequence></xs:sequence>
       <xs:attribute name="Colum3" type="xs:string" />
       <xs:attribute name="ForeignKey" type="xs:int" />
      </xs:complexType>
     </xs:element>
    </xs:choice>
   </xs:complexType>
   <xs:key name="DSKey1" msdata:PrimaryKey="true">
    <xs:selector xpath=".//Table1" />
    <xs:field xpath="@PrimaryKey" />
   </xs:key>
   <xs:keyref name="Table1Table2" refer="DSKey1">
    <xs:selector xpath=".//Table2" />
    <xs:field xpath="@ForeignKey" />
   </xs:keyref>
  </xs:element>
 </xs:schema>
 <Table1 Column1="Data11" Column2="Data12" PrimaryKey="1" />
 <Table1 Column1="Data21" Column2="Data22" PrimaryKey="2" />
 <Table2 Colum3="Data31" ForeignKey="1" />
 <Table2 Colum3="Data32" ForeignKey="2" />
</DS>

...

"

 

XML
3/4/2004 9:06:20 PM (GMT Standard Time, UTC+00:00)  #     | 
 Friday, February 27, 2004

If you think that this is a screenshot of the Today screen then you're mistaken:

In fact, it's the owner-drawn ListBoxEx control from our SDF at OpenNETCF. There was only one line of code had been writen to make it look like Today screen:

listBoxEx1.BackgroundImage = new Bitmap(@"\windows\stwater.gif");

All other settings have been done in the VS IDE designer.

2/27/2004 9:17:16 PM (GMT Standard Time, UTC+00:00)  #     | 
 Tuesday, February 17, 2004

I know that I promissed to show the code that will draw a gradient background similar to this in the managed control. The key method here is filling a rectangle with a gradient colors. A while back I posted a managed implementation of that which has evolved into something more usefull:

public static void DrawGradient(Graphics g, Color color1, Color color2, Rectangle rect)

{

      Pen pen = null;

      //draw the lines

      for(int i=0;i<rect.Width;i++)

      {

            Color currColor = InterpolateLinear(color1, color2, (float)i, (float)0,  (float)rect.Width);

            pen = new Pen(currColor);

            g.DrawLine(pen, rect.X + i, rect.Top, rect.X + i, rect.Bottom );

            pen.Dispose();

      }

}

And here's the method that would return a required color with a simple interpolation:

public static Color InterpolateLinear(Color first, Color second, float position, float start, float end)

{

      float R = ((second.R)*(position - start) + (first.R)*(end-position))/(end-start);

      float G = ((second.G)*(position - start) + (first.G)*(end-position))/(end-start);

      float B = ((second.B)*(position - start) + (first.B)*(end-position))/(end-start);

 

      return Color.FromArgb((int)Math.Round((double)R), (int)Math.Round((double)G), (int)Math.Round((double)B));

 

}

 

And at last the method that will create a bitmap with gradient stripes:

 

public static Bitmap CreateGradBackground(Rectangle rc, Color colStart, Color colEnd)

{

      //Initialize gradient line + white space

      Bitmap bmpLine = new Bitmap(rc.Width, 4);

      Graphics gxLine = Graphics.FromImage(bmpLine);

      gxLine.Clear(Color.White);

      //Initialize Backgound bitmap

      Bitmap bmpBack = new Bitmap(rc.Width, rc.Height);

      Graphics gxBack = Graphics.FromImage(bmpBack);

      gxBack.Clear(Color.White);

                 

      //gradient line rectangle

      Rectangle rcLine = new Rectangle(0, 0, rc.Width, 2);

      //draw gradient

      ControlPaint.DrawGradient(gxLine, colStart, colEnd, rcLine);

      //Calculate half color

      Color halfColor = Color.FromArgb((colStart.R + colEnd.R) / 2, (colStart.G + colEnd.G) / 2, (colStart.B + colEnd.B) / 2);

      //Draw that next to line

      ControlPaint.DrawGradient(gxLine, colStart, halfColor, new Rectangle(0, 2, rc.Width, 2));

 

      //Fill the whole backround with prepared lines

      for(int i=0;i<rc.Height;i++)

      {

            gxBack.DrawImage(bmpLine, 0, i);

            i+=3;

      }

      gxLine.Dispose();

      gxBack.Dispose();

      return bmpBack;

}

 

The next question comes to mind is how can we identify the gradient colors that're currently set in the system? The answer is located in the themes xml configuration files that are available on PPC2003 and Smartphone devices. The settings for COLOR_GRADLEFT and COLOR_GRADRIGHT attributes should give you the missing values for colStart and colEnd parameters. I'll show you how to retrieve these values from xml file and convert them to a managed Color in my next blog post.

Anyway you can use this technique for creating gradient backgrounds for your custom owner-drawn controls or if you'd like, in the ListBoxEx control that comes with SDF.

 

 

 
2/17/2004 5:09:21 PM (GMT Standard Time, UTC+00:00)  #     | 

The OpenNETCF Smart Device Framework is officially released! Grab your copy and enjoy a free ride. :) 

SDF
2/17/2004 1:31:35 PM (GMT Standard Time, UTC+00:00)  #     | 
 Monday, February 09, 2004
As Neil rightfully noticed the System.Drawing assembly had been re-written from the ground up for .NetCF to squize up the best performance and compactness. But this "re-write" came with the price of a lot of functionality missing in CF (Pen size and type, access to the bitmap bits and ability to get a native handles etc...).  Therefore creation a better version of the Graphics and Bitmap classes has been on a radar at OpenNETCF.org for a long time. So a few days ago I decided to give it shot, starting from the "proof-of-the-concept" prototype and moving along with functionality that worked. Although it did involved implementing of my own Pen, Font, Graphics classes. I concentrated on the stuff that you can not currently do in .NetCF: drawing dashed lines and rectangles, rounded rectangle, drawing text in ClearType and different angles. Please, let us know if you are interested in the stuff like that and also we are looking for your feedback on the libraries and code already available from OpenNETCF. We would like hear from you on how and where the code being used. What do you like or dislike about OpenNETCF.org etc...
2/9/2004 3:21:46 PM (GMT Standard Time, UTC+00:00)  #     | 
 Friday, January 30, 2004

One of my wild google searches today have returned this jem: Compact Framework Architecture. This is a presentation by Frank Perchel-Galee which reviews a CF architecture. The most interesting slides for me are the ones that show the memory pools in the CLR. There 3 memory pools allocated: Metadata Cache, JIT Cashe and GC Pools. Memory reclaiming by GC is happenning in 3 phases:
Phase1: Mark and sweep
Phase2: Compact whatever is left
Phase3: Flush JIT Cache

That could mean that if your program requires quite a lot of memory to run, the once JIT'ed code could be flushed and would force JITer to kick in again for the code that's already been JIT'ed. One more interesting bit from one of the slides says that the compile time of JIT compiler is LINEAR to length of the method. This leads to immediate conclusions that if you want to get a good performance from your CF app, try to brake your long winded methods to a few shorter ones. It could save you some time during execution...

 

Oh and don' forget this MSDN article Writing High Performance Managed Applications. Most of the stuff mentioned there is applicable to CF too.

1/30/2004 2:02:55 PM (GMT Standard Time, UTC+00:00)  #     | 
 Tuesday, January 27, 2004

As you know, the program memory on Pocket PC and Smartphone devices supposed to be handled by the OS itself. Meaning that shell would start sending WM_CLOSE messages if there is less than 128 KB of free memory. In some cases when running you application before allocation of some big chunk of memory you'd need to make sure that there's enough memory available for that operation. There is the SHCloseApps API function in the aygshell library that will do just what you need. We could also utilize the GlobalMemoryStatus to verify if such a drastic measures are required:

public bool FreeProgramMemory(int memSought)

{                

      bool result = true;

      MEMORYSTATUS memStatus = new MEMORYSTATUS();

      GlobalMemoryStatus(ref memStatus);

      //check if we're out of memory first

      if( memStatus.dwAvailPhys < memSought)

      {

            if (SHCloseApps(memSought)!= 0)

                  result = false;

      }

      return result;

}

//Required P/Invoke declarations

[DllImport("aygshell.dll")]

public static extern int SHCloseApps(int  dwMemSought);

 

[DllImport("coredll.dll")]

public static extern  void GlobalMemoryStatus(

            ref MEMORYSTATUS lpBuffer );

 

public struct MEMORYSTATUS

{

      public int dwLength;

      public int dwMemoryLoad;

      public int dwTotalPhys;

      public int dwAvailPhys;

      public int dwTotalPageFile;

      public int dwAvailPageFile;

      public int dwTotalVirtual;

      public int dwAvailVirtual;

}

 

1/27/2004 2:20:36 PM (GMT Standard Time, UTC+00:00)  #     | 
 Thursday, January 22, 2004

I don't know how about you, but for me visiting The Code Projects at least once a day have become a pleasant and necessary habit. It's really a valuable web site with a wealth of useful samples and projects for .NET. So, the other day I've stumbled upon this article on the web site. It explains how to create thumbnail images from a directory of Adobe Acrobat PDF documents.  The author gives details on a technique of using a template image for a thumbnail, making it transparent and overlaying it on a top of resized original image.  The code in the article is using available in .NET GDI+ methods like Image.GetThumbnailImage, Bitmap.MakeTransparent, etc… So I asked myself a question: “Is it still possible to implement a similar technique in Compact Framework?” Aside from saving the resulting image to a file I couldn’t see any problems in doing that and in 15 minutes I had this code…

 

The simplified implementation of the GetThumbnailImage goes first:

 

public static Bitmap GetThumbnailImage(Bitmap image, int height, int width)

{

    Bitmap bmp = new Bitmap(height, width);

    //create temp Graphics

    Graphics gx = Graphics.FromImage(bmp);

    //Resize the image

    gx.DrawImage(image, new Rectangle(0, 0, height, width), new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);

    //don't forget the dispose

    gx.Dispose();

    return bmp;

}

 

Now the CF version of the code from the article:

 

int thumbnailWidth = 38;

int thumbnailHeight = 52;

 

//Load original bitmap first

Bitmap orig = new Bitmap(@"\Program Files\ThumbnailProj\today.PNG");

//Create a thumbnailed version

Bitmap thumbOrig = GetThumbnailImage(orig, thumbnailWidth, thumbnailHeight);

//Load a template image

Bitmap thumbTemp = new Bitmap(@"\Program iles\ThumbnailProj\template_portrait.gif");

//Create new blank bitmap

Bitmap thumbnailBitmap = new Bitmap(thumbnailWidth + 7, thumbnailHeight + 7);

                 

Graphics gx = Graphics.FromImage(thumbnailBitmap);

gx.Clear(Color.White);

//Draw a thumbnailed image version  first

gx.DrawImage(thumbOrig, 2, 2);

           

//Draw a template image on the top with transparent attributes.

//This is what we do instead of Bitmap.MakeTransparent()

ImageAttributes attr = new ImageAttributes();

attr.SetColorKey(thumbTemp.GetPixel(5, 5), thumbTemp.GetPixel(5, 5));

gx.DrawImage(thumbTemp, new Rectangle(0, 0, thumbTemp.Width, thumbTemp.Height), 0, 0, thumbTemp.Width, thumbTemp.Height, GraphicsUnit.Pixel, attr);

 

//Draw the final image on the Form/Control

Graphics gxScr = this.CreateGraphics();

gxScr.DrawImage(thumbnailBitmap, 10, 10);



You can download the whole demo project here.
1/22/2004 2:31:49 PM (GMT Standard Time, UTC+00:00)  #     | 
 Tuesday, January 20, 2004

You probably already know about a brilliant ApplicationEx class created by Chris for OpenNETCF.  It allows getting every single Windows message that your app receives.  I 've put together a IMessageFilter implementation that would filter out the messages that are send to a particular control on your Form or to the Form itself if you wish... Here is the code that for IMessageFilter implementation:

//Delegate to raise a event to a client

public delegate void WinProc(ref Message m);

 

class WinProcFilter : IMessageFilter

{

      public WinProc WndProc;

      private IntPtr handle;

 

      public WinProcFilter(Control ctl)

      {

            //Get control's window handle           

            ctl.Capture = true;

            this.handle = GetCapture();

            ctl.Capture = false;

      }

      #region IMessageFilter Members

      public bool PreFilterMessage(ref Message m)

      {

            bool handled = false;

            if (WndProc!=null)

            {

                  if (m.HWnd == handle) //Check window handle event

                  {

                      if (WndProc!=null) 

                       WndProc(ref m); //Raise the event

                  }

            }

            return handled;

      }

      #endregion

 

      [DllImport("coredll.dll")]

      private static extern IntPtr GetCapture();

}

On the client form you would need to declare and instanciate WinProcFilter class:

//Let's get the winproc for some TextBox

WinProcFilter procFilter = new WinProcFilter(textBox1);

//Hook up to WinProc event

procFilter.WndProc+=new WinProc(textBox1_WinProc);

.....

 

private void textBox1_WinProc(ref Message m)

{

    //Do whatever you like with the messages.

}

 

1/20/2004 5:02:04 PM (GMT Standard Time, UTC+00:00)  #     |