Showing posts with label Visual Studio. Show all posts
Showing posts with label Visual Studio. Show all posts

Wednesday, May 02, 2007

.NET CF Custom Control: RoundedGroupBox

The .NET Compact Framework provides an excellent abstracted interface to the Graphics Device Interface (GDI) subsystem in the same manner as the full .NET Framework. However, there are still quite a few drawing methods which are not part of the CF which if included would make the design of custom controls easier (and sometimes more fun). One of the methods missing from the Compact Framework is the RoundRect GDI function. According to MSDN, "This function draws a rectangle with rounded corners. The current pen outlines the rectangle and the current brush fills it."

In this entry, the RoundRect function is used to create a new custom control called a RoundedGroupBox. The standard GroupBox control exists on the full framework and provides a panel with a rounded outline and a title. It logically groups controls into a designated area of the screen. An image of the GroupBox from the full framework is displayed in Image 1. The Compact Framework does not contain a GroupBox control, so this custom control, RoundedGroupBox, serves as a stand-in with a little more flare.

Image 1 - GroupBox from the full .NET framework


To create the RoundedGroupBox using C# and the .NET Compact Framework, aside from the RoundRect GDI function, there are three other GDI functions which need to be P/Invoked:


  • CreateSolidBrush - This function is used to create colored brushes used to fill in the round rectangles.

  • SelectObject - This function is used to select a brush to use when drawing.

  • DeleteObject - This function deletes a drawing object and frees its associated memory.



A fourth function, GetStockObject is also contained within the source code but not used within this example. GDI contains some predefined pens, brushes and fonts. This function can retrieve any of those.


One very important point when P/Invoking these functions is that they need to be accessed at both design time and runtime. On Windows Mobile, these functions are all found in the coredll native assembly. Trying to use that assembly during design time is not going to work however, because Visual Studio will try to access a coredll.dll file on the local desktop. To work in the designer on the desktop, these functions need to be accessed in the gdi32 assembly found in the \Windows\System32 folder. A simple wrapper function has been written that checks the current environment and decides which version of the function to use -- gdi32 on the desktop and coredll on Windows Mobile. The source code for accessing these functions is listed in Code Listing 1. Image 2 shows the RoundedGroupBox control displayed in the Visual Studio designer.

Code Listing 1 - NativeMethods.cs


#region Using Directives

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

#endregion

namespace Rhinomobile.ControlLibrary
{
/// <summary>
/// Platform Invocation Methods
/// </summary>
class NativeMethods
{
#region Windows CE Methods

[DllImport("coredll", EntryPoint = "RoundRect")]
private static extern bool RoundRectCE(IntPtr hdc, int nLeftRect, int nTopRect,
int nRightRect, int nBottomRect, int nWidth, int nHeight);

[DllImport("coredll", EntryPoint = "CreateSolidBrush")]
private static extern IntPtr CreateSolidBrushCE(int crColor);

[DllImport("coredll", EntryPoint = "SelectObject")]
private static extern IntPtr SelectObjectCE(IntPtr hdc, IntPtr hgdiobj);

[DllImport("coredll", EntryPoint = "DeleteObject")]
private static extern bool DeleteObjectCE(IntPtr hObject);

[DllImport("coredll", EntryPoint = "GetStockObject")]
private static extern IntPtr GetStockObjectCE(int fnObject);

#endregion

#region Windows Methods

[DllImport("gdi32", EntryPoint = "RoundRect")]
private static extern bool RoundRectWin(IntPtr hdc, int nLeftRect, int nTopRect,
int nRightRect, int nBottomRect, int nWidth, int nHeight);

[DllImport("gdi32", EntryPoint = "CreateSolidBrush")]
private static extern IntPtr CreateSolidBrushWin(int crColor);

[DllImport("gdi32", EntryPoint = "SelectObject")]
private static extern IntPtr SelectObjectWin(IntPtr hdc, IntPtr hgdiobj);

[DllImport("gdi32", EntryPoint = "DeleteObject")]
private static extern bool DeleteObjectWin(IntPtr hObject);

[DllImport("gdi32", EntryPoint = "GetStockObject")]
private static extern IntPtr GetStockObjectWin(int fnObject);

#endregion

#region Platform Agnostic Methods

/// <summary>
/// This function draws a rectangle with rounded corners. The current pen outlines the
/// rectangle and the current brush fills it.
/// </summary>
/// <param name="hdc">Handle to the device context.</param>
/// <param name="nLeftRect">Specifies the x-coordinate of the upper left corner of
/// the rectangle.</param>
/// <param name="nTopRect">Specifies the y-coordinate of the upper left corner of
/// the rectangle.</param>
/// <param name="nRightRect">Specifies the x-coordinate of the lower right corner
/// of the rectangle.</param>
/// <param name="nBottomRect">Specifies the y-coordinate of the lower right corner
/// of the rectangle.</param>
/// <param name="nWidth">Specifies the width of the ellipse used to draw the
/// rounded corners.</param>
/// <param name="nHeight">Specifies the height of the ellipese used to draw the
/// rounded corners.</param>
/// <returns>
/// Nonzero indicates success. Zero indicates failure. To get extended
/// error information, call GetLastError.
/// </returns>
internal static bool RoundRect(IntPtr hdc, int nLeftRect, int nTopRect,
int nRightRect, int nBottomRect, int nWidth, int nHeight)
{
bool result = false;
if (Environment.OSVersion.Platform == PlatformID.WinCE)
{
result = RoundRectCE(hdc, nLeftRect, nTopRect, nRightRect,
nBottomRect, nWidth, nHeight);
}
else
{
result = RoundRectWin(hdc, nLeftRect, nTopRect, nRightRect,
nBottomRect, nWidth, nHeight);
}
return result;
}

/// <summary>
/// This function creates a logical brush that has the specified solid color.
/// </summary>
/// <param name="crColor">Specifies the color of the brush.</param>
/// <returns>
/// A handle that identifies a logical brush indicates success. NULL
/// indicates failure. To get extended error information, call GetLastError.
/// </returns>
internal static IntPtr CreateSolidBrush(int crColor)
{
IntPtr result = IntPtr.Zero;
if (Environment.OSVersion.Platform == PlatformID.WinCE)
{
result = CreateSolidBrushCE(crColor);
}
else
{
result = CreateSolidBrushWin(crColor);
}
return result;
}

/// <summary>
/// This function selects an object into a specified device context. The new object
/// replaces the previous object of the same type.
/// </summary>
/// <param name="hdc">Handle to the device context.</param>
/// <param name="hgdiobj">Handle to the object to be selected.</param>
/// <returns>
/// If the selected object is not a region, the handle of the object being
/// replaced indicates success.
/// </returns>
internal static IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj)
{
IntPtr result = IntPtr.Zero;
if (Environment.OSVersion.Platform == PlatformID.WinCE)
{
result = SelectObjectCE(hdc, hgdiobj);
}
else
{
result = SelectObjectWin(hdc, hgdiobj);
}
return result;
}

/// <summary>
/// This function deletes a logical pen, brush, font, bitmap, region, or palette,
/// freeing all system resources associated with the object. After the object is
/// deleted, the specified handle is no longer valid.
/// </summary>
/// <param name="hObject">Handle to a logical pen, brush, font, bitmap, region,
/// or palette.</param>
/// <returns>Nonzero indicates success.</returns>
internal static bool DeleteObject(IntPtr hObject)
{
bool result = false;
if (Environment.OSVersion.Platform == PlatformID.WinCE)
{
result = DeleteObjectCE(hObject);
}
else
{
result = DeleteObjectWin(hObject);
}
return result;
}

/// <summary>
/// This function retrieves a handle to one of the predefined stock pens,
/// brushes or fonts.
/// </summary>
/// <param name="fnObject">Specifies the type of stock object.</param>
/// <returns>If the function succeeds, the return value identifies the
/// logical object requested.</returns>
internal static IntPtr GetStockObject(int fnObject)
{
IntPtr result = IntPtr.Zero;
if (Environment.OSVersion.Platform == PlatformID.WinCE)
{
result = GetStockObjectCE(fnObject);
}
else
{
result = GetStockObjectWin(fnObject);
}
return result;
}

#endregion
}
}



Image 2 - RoundedGroupBox control displayed in the designer


By default, when a P/Invoke call is made inside a .NET CF custom control, the Visual Studio designer will not attempt to draw the control properly because it believes it cannot. The attribute which controls whether Visual Studio will attempt to draw the control or not is the DesktopCompatible attribute which is placed in the DesignTimeAttributes.xmta xml file. Code Listing 2 shows the DesignTimeAttributes.xmta file. Image 3 displays two versions of the control - one with the DesktopCompatible attribute set to true in the xmta file and the other without it.

Code Listing 2 - DesignTimeAttributes.xmta


<?xml version="1.0" encoding="utf-16"?>
<Classes xmlns="http://schemas.microsoft.com/VisualStudio/2004/03/SmartDevices/XMTA.xsd">
<Class Name="Rhinomobile.ControlLibrary.BorderPanel">
<DesktopCompatible>true</DesktopCompatible>
<Property Name="BorderColor">
<Category>Appearance</Category>
<Description>The color used to draw the border of the panel.</Description>
</Property>
<Property Name="BorderWidth">
<Category>Layout</Category>
<Description>The width of the panel border.</Description>
</Property>
</Class>
<Class Name="Rhinomobile.ControlLibrary.RoundedGroupBox">
<DesktopCompatible>true</DesktopCompatible>
<Property Name="OuterColor">
<Category>Appearance</Category>
<Description>The color user to fill the background of the title of the rounded group box.</Description>
</Property>
<Property Name="RoundedGroupBoxText">
<Category>Appearance</Category>
<Description>The text displayed in the title of the group box panel.</Description>
</Property>
</Class>
</Classes>



Image 3 - Two versions of RoundedGroupBox displayed - One with the DesktopCompatible attribute set to true in the DesignTimeAttributes.xmta file, the other without


The RoundedGroupBox control itself is comprised of two round rectangles drawn with different colored brushes. The top is slightly offset on the inner polygon which leaves an area for the title to be written. This control is derived from a standard .NET Compact Framework System.Windows.Forms.Panel, so out of the box it provides the logical grouping functionality inherent in a panel.

RoundedGroupBox provides two additional properties, OuterColor and RoundedGroupBoxText. OuterColor is the color used for the title area and the outline. The standard BackColor property is used for the area where controls are placed. RoundedGroupBoxText is a property which contains the text displayed in the title area.

RoundedGroupBox also overrides two inherited methods, OnPaintBackground and OnPaint. In OnPaintBackground, the background color of the parent control is used to fill the RoundedGroupBox control. Doing this fills in the gaps left by the rounded corners of the control so that seemingly the parent control is showing through. OnPaint is more complicated and a snippet of code is displayed inline below.


protected override void OnPaint(PaintEventArgs e)
{
int outerBrushColor = HelperMethods.ColorToWin32(m_outerColor);
int innerBrushColor = HelperMethods.ColorToWin32(this.BackColor);

IntPtr hdc = e.Graphics.GetHdc();
try
{
IntPtr hbrOuter = NativeMethods.CreateSolidBrush(outerBrushColor);
IntPtr hOldBrush = NativeMethods.SelectObject(hdc, hbrOuter);
NativeMethods.RoundRect(hdc, 0, 0, this.Width, this.Height, DIAMETER, DIAMETER);
IntPtr hbrInner = NativeMethods.CreateSolidBrush(innerBrushColor);
NativeMethods.SelectObject(hdc, hbrInner);
NativeMethods.RoundRect(hdc, 0, 18, this.Width, this.Height, DIAMETER, DIAMETER);
NativeMethods.SelectObject(hdc, hOldBrush);
NativeMethods.DeleteObject(hbrOuter);
NativeMethods.DeleteObject(hbrInner);
}
finally
{
e.Graphics.ReleaseHdc(hdc);
}

if (!string.IsNullOrEmpty(m_roundedGroupBoxText))
{
Font titleFont = new Font("Tahoma", 9.0F, FontStyle.Bold);
Brush titleBrush = new SolidBrush(this.BackColor);
try
{
e.Graphics.DrawString(m_roundedGroupBoxText, titleFont, titleBrush, 14.0F, 2.0F);
}
finally
{
titleFont.Dispose();
titleBrush.Dispose();
}
}

base.OnPaint(e);
}


The first two function calls are to the ColorToWin32 method. This method converts a System.Drawing.Color value into an integer used with Windows 32 functions. It is contained in the HelperMethods class. This class can be found in Code Listing 3.

Following the color conversions, a handle to the underlying GDI device context is retrieved using the GetHdc method. A solid brush of the outer color is then created using CreateSolidBrush. This brush is then selected using the SelectObject function. This is analagous to selecting a type of brush within a graphics program when drawing shapes. A handle to the previously selected brush is saved so that it can be the selected brush again when the control is finished drawing. The outer round rectangle is drawn with a call to the RoundRect function. The same steps are repeated for the inner round rectangle: creation, selection, and drawing. Both the outer and inner round rectangles are drawn using a constant value for nWidth and nHeight giving the control a circular corner. When both round rectangles have been drawn, the previously selected brush is set as the selected brush again. The two created brushes are then deleted. Finally, the handle to the device context is released. The remaining part of the OnPaint method is responsible for writing the title text in the space between the two round rectangles. The source code for RoundedGroupBox.cs is found in Code Listing 4. Image 4 shows a screenshot of a sample application which uses the RoundedGroupBox control to ask a question where a singular answer can be chosen.

Code Listing 3 - HelperMethods.cs


#region Using Directives

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;

#endregion

namespace Rhinomobile.ControlLibrary
{
/// <summary>
/// Useful methods
/// </summary>
static class HelperMethods
{
/// <summary>
/// Converts a System.Drawing.Color value into a Windows 32 color value.
/// </summary>
/// <param name="color">System.Drawing.Color value</param>
/// <returns>Windows 32 integer representation of a color</returns>
public static int ColorToWin32(Color color)
{
return ((color.R | (color.G << 8)) | (color.B << 16));
}
}
}



Code Listing 4 - RoundedGroupBox.cs


#region Using Directives

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Text;

#endregion

namespace Rhinomobile.ControlLibrary
{
/// <summary>
/// A group box panel with rounded corners and a title
/// </summary>
public class RoundedGroupBox : Panel
{
#region Constants

private const int DIAMETER = 32;

#endregion

#region Fields

private Color m_outerColor = SystemColors.ActiveCaption;
private string m_roundedGroupBoxText = string.Empty;

#endregion

#region Protected Methods

protected override void OnPaintBackground(PaintEventArgs e)
{
if (this.Parent != null)
{
SolidBrush backBrush = new SolidBrush(this.Parent.BackColor);
try
{
e.Graphics.FillRectangle(backBrush, 0, 0, this.Width, this.Height);
}
finally
{
backBrush.Dispose();
}
}
}

protected override void OnPaint(PaintEventArgs e)
{
int outerBrushColor = HelperMethods.ColorToWin32(m_outerColor);
int innerBrushColor = HelperMethods.ColorToWin32(this.BackColor);

IntPtr hdc = e.Graphics.GetHdc();
try
{
IntPtr hbrOuter = NativeMethods.CreateSolidBrush(outerBrushColor);
IntPtr hOldBrush = NativeMethods.SelectObject(hdc, hbrOuter);
NativeMethods.RoundRect(hdc, 0, 0, this.Width, this.Height, DIAMETER, DIAMETER);
IntPtr hbrInner = NativeMethods.CreateSolidBrush(innerBrushColor);
NativeMethods.SelectObject(hdc, hbrInner);
NativeMethods.RoundRect(hdc, 0, 18, this.Width, this.Height, DIAMETER, DIAMETER);
NativeMethods.SelectObject(hdc, hOldBrush);
NativeMethods.DeleteObject(hbrOuter);
NativeMethods.DeleteObject(hbrInner);
}
finally
{
e.Graphics.ReleaseHdc(hdc);
}

if (!string.IsNullOrEmpty(m_roundedGroupBoxText))
{
Font titleFont = new Font("Tahoma", 9.0F, FontStyle.Bold);
Brush titleBrush = new SolidBrush(this.BackColor);
try
{
e.Graphics.DrawString(m_roundedGroupBoxText, titleFont, titleBrush, 14.0F, 2.0F);
}
finally
{
titleFont.Dispose();
titleBrush.Dispose();
}
}

base.OnPaint(e);
}

#endregion

#region Properties

/// <summary>
/// The color user to fill the background of the title of the rounded group box.
/// </summary>
public Color OuterColor
{
get
{
return m_outerColor;
}
set
{
m_outerColor = value;
this.Invalidate();
}
}

/// <summary>
/// The text displayed in the title of the group box panel.
/// </summary>
public string RoundedGroupBoxText
{
get
{
return this.m_roundedGroupBoxText;
}
set
{
m_roundedGroupBoxText = value;
this.Invalidate();
}
}

#endregion
}
}



Image 4 - RoundedGroupBox example application

Friday, April 20, 2007

.NET CF Custom Control: BorderPanel

Creating custom controls using the .NET Compact Framework 2.0 and Visual Studio 2005 can be a tough chore at times, but sometimes it can also be very rewarding and time saving. This entry details how to create a .NET CF BorderPanel custom control. A BorderPanel is a type of Panel which contains a border of a specified color and width. This control is used in the Run Application at Time (and Time Change) post to separate different sections of the demonstration application into logical groupings.

Inside the Rhinomobile.ControlLibrary sample project, which is of type ControlLibrary, there are three files to note: BorderPanel.bmp, BorderPanel.cs, and DesignTimeAttributes.xmta. BorderPanel.bmp is the graphic which represents the BorderPanel when it is placed in the Toolbox. BorderPanel.cs is the source code for the BorderPanel custom control. DesignTimeAttributes.xmta is an xml file which contains information used by Visual Studio when using the control through the designer. The project layout including these three files is displayed in Image 1.

Image 1 - Solution Explorer Showing BorderPanel.bmp, BorderPanel.cs, and DesignTimeAttributes.xmta Files


BorderPanel.bmp is a 16 x 16 bitmap added to the project with the same base name as the custom control. The Build Action for this file should be set to "Embedded Resource". The BorderPanel.bmp file used in this project is displayed in Image 2.

Image 2 - BorderPanel.bmp Graphic


BorderPanel is a .NET Compact Framework 2.0 Custom Control which behaves exactly like a Panel yet has a border of a specified color and width. BorderPanel inherits from the standard System.Windows.Forms.Panel class. By doing so, BorderPanel automatically acts in the same fashion as a Panel. The only difference with a BorderPanel is how it is drawn.

Two fields are added to the BorderPanel class, one containing information about the border color and the other about the border width. Corresponding public properties are also added to the class. These are named appropriately enough BorderColor and BorderWidth. A call to Invalidate() is also added to the mutator (set) of the public properties. The Invalidate method with no parameters causes the entire control to be redrawn on the next painting. Changing the border color or changing the border width would both cause the visual look of the control to change and is why Invalidate is called.

The OnPaint method inherited from the Panel class is overridden to draw the custom control. A new Pen object is instantiated, used to draw the border, and then disposed. It is of extreme importance to dispose of GDI objects as soon as they are finished being used on a mobile device as they are a major source of memory issues. The width of the Pen is set to the BorderWidth property and the color is set to BorderColor.

Calculating where to draw the border though is an interesting issue. The drawing is covered in these two lines:


int halfWidth = m_borderWidth / 2;
e.Graphics.DrawRectangle(borderPen, halfWidth, halfWidth,
this.Width - m_borderWidth, this.Height - m_borderWidth);


The halfWidth variable is used due to the alignment of the Pen object. Pen.Alignment is not a property that is supported on the .NET Compact Framework. The alignment of the Pen is therefore set to center, meaning that if the width of the pen is 10 pixels, 5 are going to be drawn on one side of the pen and 5 on the other. For this reason, half the width (halfWidth) of the Pen is calculated and used as the starting x,y coordinate for the drawing of the border. This ensures that the entire border is drawn on the viewable area. The third and fourth parameters to the DrawRectangle method are a width and height of the rectangle which is the reason the entire width and entire height of the border are subtracted from these values respectively.

The final item in OnPaint is a call to base.OnPaint(e) to ensure that other items are properly drawn. The source code for the BorderPanel is shown in Code Listing 1.

Code Listing 1 - BorderPanel.cs


#region Using Directives

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

#endregion

namespace Rhinomobile.ControlLibrary
{
/// <summary>
/// A Panel with a colored sizable border
/// </summary>
public class BorderPanel : Panel
{
#region Fields

private Color m_borderColor = SystemColors.Control;
private int m_borderWidth = 4;

#endregion

#region Constructor

/// <summary>
/// BorderPanel Constructor
/// </summary>
public BorderPanel() : base()
{
}

#endregion

#region Protected Methods

protected override void OnPaint(PaintEventArgs e)
{
if ((m_borderWidth > 0) && (m_borderWidth < (this.Width / 2)))
{
Pen borderPen = new Pen(m_borderColor, (float)m_borderWidth);
try
{
int halfWidth = m_borderWidth / 2;
e.Graphics.DrawRectangle(borderPen, halfWidth, halfWidth,
this.Width - m_borderWidth, this.Height - m_borderWidth);
}
finally
{
borderPen.Dispose();
}
}
base.OnPaint(e);
}

#endregion

#region Properties

/// <summary>
/// Color used for drawing border on panel
/// </summary>
public Color BorderColor
{
get
{
return m_borderColor;
}
set
{
m_borderColor = value;
this.Invalidate();
}
}

/// <summary>
/// Width of border on panel
/// </summary>
public int BorderWidth
{
get
{
return m_borderWidth;
}
set
{
m_borderWidth = value;
this.Invalidate();
}
}

#endregion
}
}



DesignTimeAttributes.xmta is an xml file which contains information about classes, properties, methods, and events used by Visual Studio at design time for controls. The xmta file for the BorderPanel custom control, demonstrates how to add descriptions for added properties as well as placing the properties within the proper categories in the Properties Window.

Working with the DesignTimeAttributes.xmta file can be one of the flakiest and time consuming tasks when working with the .NET Compact Framework. The one for this control has been simplified to only show the simplest two elements. The DesignTimeAttributes.xmta file can be found in Code Listing 2.

Code Listing 2 - DesignTimeAttributes.xmta


<?xml version="1.0" encoding="utf-16"?>
<Classes xmlns="http://schemas.microsoft.com/VisualStudio/2004/03/SmartDevices/XMTA.xsd">
<Class Name="Rhinomobile.ControlLibrary.BorderPanel">
<Property Name="BorderColor">
<Category>Appearance</Category>
<Description>The color used to draw the border of the panel.</Description>
</Property>
<Property Name="BorderWidth">
<Category>Layout</Category>
<Description>The width of the panel border.</Description>
</Property>
</Class>
</Classes>



At this point the project can be compiled and the control library built. The following images indicate how to go about adding the BorderPanel to the Toolbox within Visual Studio.

Image 3 - Toolbox Before Adding BorderPanel


Right click on the Toolbox and select "Choose Items..."

Image 4 - Choose Toolbox Items Dialog Before Adding BorderPanel


Click the "Browse" button on the Choose Toolbox Items Dialog.

Image 5 - Choose Toolbox Items Browse Dialog


Navigate to the location of the control library assembly and select it.

Image 6 - Choose Toolbox Items After Adding BorderPanel


Notice the controls in the assembly have been added to the list of components and checked. Click the "Ok" button on the dialog and the controls should be added to the Toolbox.

Image 7 - Toolbox After Adding BorderPanel


Image 8 - Form1.cs Design After Dragging Over a BorderPanel from the Toolbox


Image 9 - Properties Window Showing the BorderColor and BorderWidth Properties

Saturday, March 17, 2007

What's the Deal: Smart Device Project Ordering in Visual Studio 2005

While creating an application earlier this afternoon for testing a proposed change, I went to create a new Windows Mobile 5.0 Pocket PC application using Visual Studio 2005's New Project dialog. I was curious as to why the entries underneath Visual C#\Smart Device seemed to be in alphabetical order except for Windows Mobile 6 Professional.

The entries were in the following order under Smart Device:


  • Pocket PC 2003

  • Windows Mobile 6 Professional

  • Smartphone 2003

  • Windows CE 5.0

  • Windows Mobile 5.0 Pocket PC

  • Windows Mobile 5.0 Smartphone

  • Windows Mobile 6 Standard



Image 1 - New Project dialog out of order


After a little bit of digging around in the C:\Program Files\Microsoft Visual Studio 8\ directory, I found a folder structure with the hierarchy of "Common 7\IDE\ProjectTemplates\CSharp\SmartDevice". Inside the SmartDevice folder was a file named TemplateIndex.vstdir and 7 folders:


  • PocketPC2003

  • ppc

  • Smartphone2003

  • sp

  • Windows Mobile 6 Professional

  • Windows Mobile 6 Standard

  • Windows CE



Image 2 - SmartDevice folder structure


I soon noticed that each of the 7 folders had their own TemplateIndex.vstdir files. I did a search on that file and discovered an excellent blog entry entitled "New Project Generation: Under the Hood" by the Visual Studio Documentation Team. It confirmed my suspicions that the SortIndex was what controlled the ordering and also brought to my attention that I needed to start visual Studio using the /installvstemplates command line parameter in order for the changes to take effect.


Entry Old Sort Index New Sort Index
Pocket PC 2003 1100 1100
Smartphone 2003 1110 1110
Windows CE 5.0 1120 1120
Windows Mobile 5.0 Pocket PC 1150 1130
Windows Mobile 5.0 Smartphone 1160 1140
Windows Mobile 6 Professional 1100 1150
Windows Mobile 6 Standard 1100 1160





After making the changes to the associated TemplateIndex.vstdir files, I ran Visual Studio with the /installvstemplates command line parameter.

Image 3 - Visual Studio started via command line


When Visual Studio finished processing the template files and opened, going to the New Project dialog had things ordered alphabetically underneath SmartDevice:


  • Pocket PC 2003

  • Smartphone 2003

  • Windows CE 5.0

  • Windows Mobile 5.0 Pocket PC

  • Windows Mobile 5.0 Smartphone

  • Windows Mobile 6 Professional

  • Windows Mobile 6 Standard



Image 4 - New Project dialog in order

Saturday, December 02, 2006

Quick Hitter: Blank Form

What a difference a function call makes sometimes, right?

Ever start a program, go to a specific form and all the controls that were placed on the form at design time are missing? Well, some way some how, the InitializeComponent method has probably been removed from the form's constructor. The InitializeComponent method is a private void method which is placed inside the "Windows Form Designer generated code" region by Visual Studio. The attached comment even states that it is a "Required method for Designer support - do not modify the contents of this method with the code editor." This method sets all the properties of the form, initializes every control and subsequently adds each one to its parent control. Sometimes people think this is just magic that the controls get added to the form, but it's the InitializeComponent method which is doing all of the heavy lifting. So, if the controls are missing, check to verify that InitializeComponent is being called in the form's constructor.