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)  #     | 
 Tuesday, January 13, 2004

You've probably noticed that the "Home" key press is not possible to catch in either of the Key event handles of the Form or Control. The solution to that will be to use the RegisterHotKey API in conjunction with MessageWindow:

using System;

using Microsoft.WindowsCE.Forms;

using System.Windows.Forms;

using System.Runtime.InteropServices;

public class MessageWin : MessageWindow

{

    //event for client

    public System.EventHandler HomeKeyPress;

 

    public MessageWin()

    {

       //register to listen for home key

       RegisterHotKey(this.Hwnd, VK_LWIN, 8, VK_LWIN);

    }

  

    ~MessageWin()

    {

       Unregister();

    }

 

    protected override void WndProc(ref Message m)

    {

       if (m.Msg == WM_HOTKEY)

       {

          if ((int)m.WParam == VK_LWIN)

             //Raise event

             if (HomeKeyPress!=null)

                   HomeKeyPress(this, null);

       }

       base.WndProc (ref m);

    }

 

  #region P/Invokes

    private const int VK_LWIN    =    0x5B;

    private const int WM_HOTKEY   =      0x0312;

    [DllImport("coredll.dll")]

    public static extern bool RegisterHotKey(

         IntPtr hWnd, // handle to window

         int id, // hot key identifier

         int Modifiers, // key-modifier options

         int key //virtual-key code);

    [DllImport("coredll.dll")]

    public static extern bool UnregisterHotKey(

         IntPtr      hWnd,

         int         id);

   #endregion

 }

The usage of this class should be pretty obviouse:

MessageWin  msgWin = new MessageWin();

msgWin.HomeKeyPress+=new EventHandler(msgWin_HomeKeyPress);

 

private void msgWin_HomeKeyPress(object sender, System.EventArgs e)

{

      MessageBox.Show("Home Key Pressed.");

}

 

1/13/2004 9:27:59 PM (GMT Standard Time, UTC+00:00)  #     | 
 Monday, January 12, 2004

To draw a rectangle with rounded corners is not a trivial task in Compact Framework due to lack of ability to draw arcs (Graphics.DrawArc, GraphicPaths in the big .NET, etc..). OK, so why don't we just use Graphics.DrawEllipse to draw a rounded corners instead? Definitelly we can do that, but we would have to somehow wipe out the internal arcs:

So, my solutuion to that problem is to create a poligon shape that would fill the rectangle with the required back color and would wipe out unneeded arcs:

public static void DrawRoundedRectangle(Graphics g, Pen p, Color backColor, Rectangle rc, Size size)
{
   Point[] points = new Point[8];
   
   //prepare points for poligon
   points[0].X = rc.Left + size.Width / 2;
   points[0].Y = rc.Top + 1;
   points[1].X = rc.Right - size.Width / 2;
   points[1].Y = rc.Top + 1;

   points[2].X =  rc.Right;
   points[2].Y = rc.Top + size.Height / 2;
   points[3].X =  rc.Right;
   points[3].Y = rc.Bottom - size.Height / 2;

   points[4].X =  rc.Right - size.Width / 2;
   points[4].Y = rc.Bottom;
   points[5].X =  rc.Left  + size.Width / 2;
   points[5].Y = rc.Bottom;

   points[6].X =  rc.Left + 1;
   points[6].Y = rc.Bottom - size.Height / 2;
   points[7].X =  rc.Left + 1;
   points[7].Y = rc.Top + size.Height / 2;

   //prepare brush for background
   Brush fillBrush = new SolidBrush(backColor);

   //draw side lines and circles in the corners
   g.DrawLine(p, rc.Left  + size.Width / 2, rc.Top,
    rc.Right - size.Width / 2, rc.Top);
   g.FillEllipse(fillBrush, rc.Right - size.Width, rc.Top,
    size.Width, size.Height);
   g.DrawEllipse(p, rc.Right - size.Width, rc.Top,
    size.Width, size.Height);

   g.DrawLine(p, rc.Right, rc.Top + size.Height / 2,
    rc.Right, rc.Bottom - size.Height / 2);
   g.FillEllipse(fillBrush, rc.Right - size.Width, rc.Bottom - size.Height,
    size.Width, size.Height);
   g.DrawEllipse(p, rc.Right - size.Width, rc.Bottom - size.Height,
    size.Width, size.Height);
   
   g.DrawLine(p, rc.Right - size.Width / 2, rc.Bottom,
    rc.Left  + size.Width / 2, rc.Bottom);
   g.FillEllipse(fillBrush, rc.Left, rc.Bottom - size.Height,
    size.Width, size.Height);
   g.DrawEllipse(p, rc.Left, rc.Bottom - size.Height,
    size.Width, size.Height);
   
   g.DrawLine(p, rc.Left, rc.Bottom - size.Height / 2,
    rc.Left, rc.Top + size.Height / 2);
   g.FillEllipse(fillBrush, rc.Left, rc.Top,
    size.Width, size.Height);
   g.DrawEllipse(p, rc.Left, rc.Top,
    size.Width, size.Height);
   
   //fill the background and remove the internal arcs  
      g.FillPolygon(fillBrush, points);
   //dispose the brush
   fillBrush.Dispose();
  }

And this is how you'd use this method:

Rectangle rc =  new Rectangle(10, 10, 100, 30);

DrawRoundedRectangle(e.Graphics,

new Pen(Color.Black), Color.CadetBlue, rc, new Size(8, 8));

 
1/12/2004 11:52:30 PM (GMT Standard Time, UTC+00:00)  #     |