Friday, March 19, 2004

I've finally released AtlasCE for SmartPhone 2003 which I think is one of the first commercial .NET CF programs for Smartphones. Aside from learning the intricacies of the smartphone UI, I had to create a key-press searching/filtering mechanism similar to the built-in Contacts application, a scrolling image viewer control with zooming functionality and other more interesting stuff. It was always like that for me - creating some program would lead to creation of a few “by-products” that could be utilized later on in some other applications. I'll try to show you how it's all been done in a later blog entries. Meanwhile I can tell you that I've used a lot of code from SDF: ListBoxEx, Configuraton and HTMLViewer.  

3/19/2004 8:28:03 PM (GMT Standard Time, UTC+00:00)  #     | 
 Thursday, March 18, 2004

Alright, so I've got this new and shiny blog now! Big thanks to Neil for spending his valuable time converting me to the dasBlogs. It has so much stuff that I will have to spend some time looking around.

Meanwhile, back to the subject.

In Windows programming there is a notion of Virtual ListBoxes or ListViews. It means that a list box would allow you to have unlimited amount of the displayed items. It is usually done by telling it how many items there should be and it simply delegates all drawing of items to the owner of the list box. Under Windows CE the list box can’t be made virtual, only a ListView. But it should be really easy to implement by utilizing OwnerDrawnList base control, which I described here and which has become a part of SDF.

So, here are the steps to implement the VirtualListBox:

 

  1. Derive from OwnerDrawnList to create a new class:

public class VirtualListBox : OwnerDrawnList

{

// Private members

      int count;

      private ArrayList itemList;

      // Constructor

      public VirtualListBox()

      {

            itemList = new ArrayList();

            this.ShowScrollbar= true;

      }

 

      // Property to the number of items

      public int Count

      {

            get

            {

                  return count;

            }

            set

            {

                  if (count!=value)

                  {

                        count = value;

                        itemList.Clear();

                        int[] items = new int[count];

                        itemList.AddRange(items);

                  }

            }

      }

 

      // if you don’t want to draw the item yourself just use this method

      public void DefaultDrawItem(DrawItemEventArgs e, String text)

      {

            Brush textBrush;

 

            Rectangle rcText = e.Bounds;

 

            //Add 3 pixels

            rcText.X += 3;

 

            if (e.State == DrawItemState.Selected) // selected                      {

                  e.DrawBackground();

                  textBrush = new SolidBrush(SystemColors.HighlightText);

            }

            else

            {

                  e.DrawBackground(this.BackColor);

                  textBrush = new SolidBrush(SystemColors.WindowText);

            }

 

// Draw the string

            e.Graphics.DrawString(text, this.Font, textBrush, rcText);

                 

            // Dispose the brush

            textBrush.Dispose();

 

      }

 

      // This is the required by base class override

      protected override System.Collections.IList BaseItems

      {

          get

          {

             return itemList;

          }

      }

           

       // This one required by base class too, but it will do nothing for us.

       protected override object BuildItemForRow(object row)

       {

             return null;

       }

 

   }

 

 

  1. Now we’re ready to use this control on the form. Add these lines to your form’s constructor:

 

InitializeComponent();

 

virtList = new VirtualListBox();

            virtList.Bounds = new Rectangle(1, 1, 200, 100);

            virtList.BackColor = Color.White;

            virtList.Count = 100;

            virtList.ItemHeight = 14;

           

            // Add the control to the form’s controls collection

            this.Controls.Add(virtList);

            // Let’s hook up into DrawItem event

         virtList.DrawItem+=new DrawItemEventHandler(virtList_DrawItem);

 

  1. Implement drawing event in your form:

private void virtList_DrawItem(object sender, DrawItemEventArgs e)

      {

        // You can draw the items yourself here or just delegate to the list itself.   

        virtList.DefaultDrawItem(e, "Virtual Item " + e.Index.ToString());

   }

 

 

3/18/2004 8:26:33 PM (GMT Standard Time, UTC+00:00)  #     | 
 Wednesday, March 17, 2004
Peter  has posted a new article on OpenNETCF.org about hosting Native windows controls. In order to achieve that he had to use some low level hacks by changing window style of the MessageWindow and I absolutely agree with Peter - we need WndProc and native handles in CF in the v2. And don't forget Graphics.FormHdc, Bitmap.GetHBitmap and Icon.FromHandle! Having a powerful framework is great, but it can't wrap ALL available native functionality, so Microsoft, please, give us ability to develop what's missing ourselves.
3/17/2004 4:45:37 PM (GMT Standard Time, UTC+00:00)  #     | 
 Tuesday, March 16, 2004
Sybase has announced Pocket PowerBuilder. They say that it's  "a new rapid application development tool that speeds the creation of mobile and wireless enterprise Pocket PC applications. "  So, do we have a valid competitor to VS.NET + CF on the horizon? Has anybody tried it?
3/16/2004 9:15:16 PM (GMT Standard Time, UTC+00:00)  #     | 
 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)  #     |