Thursday, June 25, 2009

How to create button with picture(icon) and text for Window CE/Mobile

For desktop program with full compact framework, it is very easy to create button with picture and text. Just drag the button from toolbox. Then choose the image from properties. But compact framework has no such properties for button.

What we need to do is create our own customized button control like following (button.cs):

/*

Requirements: Microsoft Development Environment 2003 Version 7.1.3088
Microsoft .NET Framework Version 1.1.4322
*/

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Windows.Forms;
using System.Drawing.Imaging;

// Assembly attribute that points to the run-time version of the assembly.
// This attribute is needed for this control to function properly during design time.
// When this control is added to the designer, the assembly indicated by
// this attribute is added to the reference collection of the project.
// The run-time assembly must be located in the reference path of the project,
// and the design-time version must appear in the "designer reference path".

//// The NETCFDESIGNTIME proprocessor variable is used to indicate whether the build is for a designer build or run-time device build.
//// This lets you add device specific routines and also desktop requirements (such as design-time attributes) without needlessly increasing your footprint.
//#if NETCFDESIGNTIME
// [assembly: System.CF.Design.
RuntimeAssemblyAttribute("xxxxxxx, Version=2.1.0.0, Culture=neutral, PublicKeyToken=null")]
//#endif

namespace DJHome.SpecialButton
{
///
/// Button class.
///

public class Button : System.Windows.Forms.Control
{
#region Component Designer generated code

///
/// Clean up any resources being used.
///

/// True to dispose of control.
protected override void Dispose( bool disposing )
{
if( disposing )
{
if( components != null )
components.Dispose();
}
base.Dispose( disposing );
}

// Required method for Designer support - do not modify
// the contents of this method with the Code Editor.
private void InitializeComponent()
{
this.Paint += new System.Windows.Forms.PaintEventHandler(this.OnPaint);
}

#endregion

#region Constructors

///
/// Constructor.
///

public Button()
{
m_penBlack = new System.Drawing.Pen(Color.Black);
m_penGray = new System.Drawing.Pen(Color.Gray);
m_penLightGray = new System.Drawing.Pen(Color.Gainsboro);
m_penWhite = new System.Drawing.Pen(Color.White);

// This call is required by the Windows.Forms Designer.
InitializeComponent();
}

#endregion

#region Enumerations

///
/// Used to set the button style.
///

public enum ButtonStyle
{
///
/// Push button style.
///

PushButton,
///
/// Toggle button style.
///

ToggleButton
}

///
/// Used to set the vertical alignment of the text on the button.
///

public enum TLocation
{
///
/// Text at bottom of button.
///

Bottom,
///
/// Text at left of button.
///

Left,
///
/// Text at middle of button.
///

Middle,
///
/// Text at right of button.
///

Right,
///
/// Text at top of button.
///

Top
}

#endregion

#region Variables

private System.ComponentModel.Container components = null;
private Pen m_penBlack;
private Pen m_penGray;
private Pen m_penLightGray;
private Pen m_penWhite;
private Bitmap m_bitBackground = null;
private object m_oControlTag = false;
private Image m_imgDisabled = null;
private Image m_imgPressed = null;
private Image m_imgUnPressed = null;
private bool m_bPressed = false;
private ButtonStyle m_ButtonStyle = ButtonStyle.PushButton;
private TLocation m_textLocation = TLocation.Middle;
private string m_sText2 = "";
private Color m_textColor;

#endregion

#region Properties

// Store a bitmap that is created when the button gets the focus.
// Used to show the dotted lines when the button has the focus.
private Bitmap BitBackground
{
get
{
return m_bitBackground;
}
set
{
m_bitBackground = value;
}
}

///
/// Get or set the tag for the control.
///

public object ControlTag
{
get
{
return m_oControlTag;
}
set
{
m_oControlTag = value;
this.Invalidate();
}
}

#if NETCFDESIGNTIME
// These design time attributes affect appearance of this property in the property grid.
[Description("The image displayed when the button is in its disabled state."), Category("Custom Settings"), DefaultValue(null, "none")]
#endif
///
/// Image to be displayed on the button when the button is disabled.
///

public Image ImageDisabled
{
get
{
return m_imgDisabled;
}
set
{
m_imgDisabled = value;
this.Invalidate();
}
}

#if NETCFDESIGNTIME
// These design time attributes affect appearance of this property in the property grid.
[Description("The image displayed when the button is in its pressed state."), Category("Custom Settings"), DefaultValue(null, "none")]
#endif
///
/// Image displayed on the button when the button is in the pressed state.
///

public Image ImagePressed
{
get
{
return m_imgPressed;
}
set
{
m_imgPressed = value;
this.Invalidate();
}
}

#if NETCFDESIGNTIME
// These design time attributes affect appearance of this property in the property grid.
[Description("The image displayed when the button is in its unpressed state."), Category("Custom Settings"), DefaultValue(null, "none")]
#endif
///
/// Image displayed on the button when the button is in the unpressed state.
///

public Image ImageUnPressed
{
get
{
return m_imgUnPressed;
}
set
{
m_imgUnPressed = value;
this.Invalidate();
}
}

#if NETCFDESIGNTIME
// These design time attributes affect appearance of this property in the property grid.
[Description("True sets the button in the pushed state, false otherwise."),Category("Custom Settings"), DefaultValue(typeof(bool), "False")]
#endif
///
/// Get or set the pressed stated of the button.
///

public bool Pressed
{
get
{
return m_bPressed;
}
set
{
m_bPressed = value;
this.Invalidate();
}
}

#if NETCFDESIGNTIME
// These design time attributes affect appearance of this property in the property grid.
[Description("Set the button style to be push or toggle button."),Category("Custom Settings"), DefaultValue(typeof(ButtonStyle), "PushButton")]
#endif
///
/// Get or Set the button style.
///

public ButtonStyle Style
{
get
{
return m_ButtonStyle;
}
set
{
m_ButtonStyle = value;
Pressed = false;
this.Invalidate();
}
}

///
/// Set the text when changed.
///

public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
this.Invalidate();
}
}

#if NETCFDESIGNTIME
// These design time attributes affect appearance of this property in the property grid.
[Description("The vertical location of the button text."),Category("Custom Settings"), DefaultValue(typeof(TLocation), "Middle")]
#endif
///
/// Location where the text is to be displayed on the button.
///

public TLocation TextLocation
{
get
{
return m_textLocation;
}
set
{
m_textLocation = value;
this.Invalidate();
}
}

#if NETCFDESIGNTIME
// These design time attributes affect appearance of this property in the property grid.
[Description("The text for the second line."),Category("Custom Settings"), DefaultValue(typeof(string), "")]
#endif
///
/// Set the text when changed.
///

public string Text2
{
get
{
return m_sText2;
}
set
{
m_sText2 = value;
this.Invalidate();
}
}

///
/// Color of the text to be displayed.
///

private Color TextColor
{
get
{
return this.m_textColor;
}
set
{
this.m_textColor = value;
}
}

#endregion

#region Functions

///
/// Create a bitmap image.
///

/// Rectangle.
/// Returns a bitmap image.
private Bitmap CreateBitmap(Rectangle r)
{
Bitmap bit = new Bitmap(this.Width, this.Height);
Graphics g = Graphics.FromImage(bit);

// Fill the bitmap with the background color.
g.FillRectangle(new SolidBrush(this.BackColor), 0, 0, bit.Width, bit.Height);

// Set the pixels for the horizontal dashed lines.
for(int i = 5; i < r.Width - 4; i++)
{
bit.SetPixel(i, 4, Color.FromArgb(64, 64, 64));
i += 1;
}
for(int i = 6; i < r.Width - 4; i++)
{
bit.SetPixel(i, r.Height - 5, Color.FromArgb(64, 64, 64));
i += 1;
}

// Set the pixels for the vertical dashed lines.
for(int i = 5; i < r.Height - 3; i++)
{
bit.SetPixel(4, i, Color.FromArgb(64, 64, 64));
i += 1;
}
for(int i = 4; i < r.Height - 5; i++)
{
bit.SetPixel(r.Width - 5, i, Color.FromArgb(64, 64, 64));
i += 1;
}

return bit;
}

///
/// Draw the text on the graphics object.
///

/// PaintEventArgs.param>
/// Client rectangle.
/// Image width.
private void DrawText(PaintEventArgs e, Rectangle r, int iImageWidth)
{
SizeF size = e.Graphics.MeasureString(this.Text, this.Font);
SizeF size2 = e.Graphics.MeasureString(this.Text2, this.Font);
int iGap = 2;

// Shift the text if the button is pressed.
if (Pressed)
{
if (Text2.Length == 0)
{
// Center the text inside the client area of the button.
// Set the vertical location of the text.
switch(TextLocation)
{
case TLocation.Bottom:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width + 2) / 2, (r.Height - size.Height) - 2);
break;
case TLocation.Left:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), ((r.Width - size.Width + 2) / 2) - iImageWidth, (r.Height - size.Height) / 2);
break;
case TLocation.Middle:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width + 2) / 2, (r.Height - size.Height) / 2);
break;
case TLocation.Right:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), ((r.Width - size.Width + 2) / 2) + iImageWidth, (r.Height - size.Height) / 2);
break;
case TLocation.Top:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width + 2) / 2, 5);
break;
}
}
else
{
// Center the text inside the client area of the button.
// Set the vertical location of the text.
switch(TextLocation)
{
case TLocation.Bottom:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width + 2) / 2, (r.Height - (2*size.Height)) - 2 + iGap);
e.Graphics.DrawString(this.Text2, this.Font, new SolidBrush(TextColor), (r.Width - size2.Width + 2) / 2, (r.Height - size.Height) - 2 - iGap);
break;
case TLocation.Left:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), ((r.Width - size.Width + 2) / 2) - iImageWidth, ((r.Height / 2) - size.Height + iGap));
e.Graphics.DrawString(this.Text2, this.Font, new SolidBrush(TextColor), ((r.Width - size2.Width + 2) / 2) - iImageWidth, (r.Height / 2) - iGap);
break;
case TLocation.Middle:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width + 2) / 2, ((r.Height / 2) - size.Height + iGap));
e.Graphics.DrawString(this.Text2, this.Font, new SolidBrush(TextColor), (r.Width - size2.Width + 2) / 2, (r.Height / 2) - iGap);
break;
case TLocation.Right:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), ((r.Width - size.Width + 2) / 2) + iImageWidth, ((r.Height / 2) - size.Height + iGap));
e.Graphics.DrawString(this.Text2, this.Font, new SolidBrush(TextColor), ((r.Width - size2.Width + 2) / 2) + iImageWidth, (r.Height / 2) - iGap);
break;
case TLocation.Top:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width + 2) / 2, 6 + iGap);
e.Graphics.DrawString(this.Text2, this.Font, new SolidBrush(TextColor), (r.Width - size2.Width + 2) / 2, (size2.Height + 6) - iGap);
break;
}
}
}
else
{
if (Text2.Length == 0)
{
// Center the text inside the client area of the button.
// Set the vertical location of the text.
switch(TextLocation)
{
case TLocation.Bottom:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width) / 2, (r.Height - size.Height - 1) - 2);
break;
case TLocation.Left:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), ((r.Width - size.Width) / 2) - iImageWidth, (r.Height - size.Height - 2) / 2);
break;
case TLocation.Middle:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width) / 2, (r.Height - size.Height - 2) / 2);
break;
case TLocation.Right:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), ((r.Width - size.Width) / 2) + iImageWidth, (r.Height - size.Height - 2) / 2);
break;
case TLocation.Top:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width) / 2, 4);
break;
}
}
else
{
switch(TextLocation)
{
case TLocation.Bottom:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width) / 2, (r.Height - (2*size.Height) - 1) - 2 + iGap);
e.Graphics.DrawString(this.Text2, this.Font, new SolidBrush(TextColor), (r.Width - size2.Width) / 2, (r.Height - size.Height - 1) - 2 - iGap);
break;
case TLocation.Left:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), ((r.Width - size.Width) / 2) - iImageWidth, ((r.Height / 2) - size.Height) - 1 + iGap);
e.Graphics.DrawString(this.Text2, this.Font, new SolidBrush(TextColor), ((r.Width - size2.Width) / 2) - iImageWidth, (r.Height / 2) - 1 - iGap);
break;
case TLocation.Middle:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width) / 2, ((r.Height / 2) - size.Height - 1) + iGap);
e.Graphics.DrawString(this.Text2, this.Font, new SolidBrush(TextColor), (r.Width - size2.Width) / 2, (r.Height / 2) - 1 - iGap);
break;
case TLocation.Right:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), ((r.Width - size.Width) / 2) + iImageWidth, ((r.Height / 2) - size.Height - 1 + iGap));
e.Graphics.DrawString(this.Text2, this.Font, new SolidBrush(TextColor), ((r.Width - size2.Width) / 2) + iImageWidth, (r.Height / 2) - 1 - iGap);
break;
case TLocation.Top:
e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(TextColor), (r.Width - size.Width) / 2, 5 + iGap);
e.Graphics.DrawString(this.Text2, this.Font, new SolidBrush(TextColor), (r.Width - size2.Width) / 2, (size2.Height + 5 - iGap));
break;
}
}
}
}

///
/// Get the image attributes to set the transparent color.
///

/// Image.
/// ImageAttributes of the image.
private ImageAttributes GetImageAttributes(Image img)
{
Bitmap bit = new Bitmap(img);

// Set the Attributes for the Transparent color
ImageAttributes imageAttr = new ImageAttributes();
Color colorTrans = bit.GetPixel(0, 0);
imageAttr.SetColorKey(colorTrans, colorTrans);

return imageAttr;
}

#endregion

#region Controls

///
/// Repaints the button after the button is clicked.
///

/// EventArgs.
protected override void OnClick(EventArgs e)
{
if (this.Enabled)
{
// Check if toggle is set.
if (Style == ButtonStyle.ToggleButton)
{
// Toggle the pressed state.
if (Pressed)
{
Pressed = false;
}
else
{
Pressed = true;
}
}
else
{
this.Pressed = false;
}

this.Invalidate();
base.OnClick (e);
}
}

///
/// Repaints the button in the enabled/disabled state.
///

/// EventArgs.
protected override void OnEnabledChanged(EventArgs e)
{
this.Invalidate();
base.OnEnabledChanged(e);
}

///
/// Sets the focus to the control.
///

/// EventArgs.
protected override void OnGotFocus(EventArgs e)
{
if (this.Enabled)
{
this.Focus();
this.Invalidate();
base.OnGotFocus(e);
}
}

///
/// Removes focus from the control.
///

/// EventArgs.
protected override void OnLostFocus(EventArgs e)
{
this.Invalidate();
base.OnLostFocus(e);
}

///
/// Sets the button to be pressed.
///

/// MouseEventArgs.param>
protected override void OnMouseDown (MouseEventArgs e)
{
if (this.Enabled)
{
// Check if the left mouse button was pressed.
if (e.Button == MouseButtons.Left)
{
// Check if toggle is set.
if (Style != ButtonStyle.ToggleButton)
{
this.Pressed = true;
}

this.Focus();
this.Invalidate();
}

base.OnMouseDown(e);
}
}

///
/// Repaints the button when the mouse is moved.
///

/// MouseEventArgs.param>
protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e)
{
if (this.Enabled)
{
// Check if toggle is set.
if (Style != ButtonStyle.ToggleButton)
{
// Check if the left mouse button was pressed.
if (e.Button == MouseButtons.Left)
{
// Paint the button in the unpressed state when the mouse is not over the button.
if ((Pressed) && (!this.ClientRectangle.Contains(e.X, e.Y)))
{
Pressed = false;
this.Invalidate();
}
else if ((!Pressed) && (this.ClientRectangle.Contains(e.X, e.Y)))
{
Pressed = true;
this.Invalidate();
}
}
}

base.OnMouseMove(e);
}
}

///
/// Resets the button.
///

/// MouseEventArgs.param>
protected override void OnMouseUp (MouseEventArgs e)
{
if (this.Enabled)
{
// Check if toggle is set.
if (Style != ButtonStyle.ToggleButton)
{
this.Pressed = false;
}

this.Invalidate();
base.OnMouseUp(e);
}
}

///
/// This Paint function uses routines common to both platforms.
///

private void OnPaint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Rectangle rcClient = this.ClientRectangle;
int intImageWidth = 0;

// Set the text color to be grayed or the forecolor.
if (this.Enabled == false)
{
TextColor = System.Drawing.SystemColors.GrayText;
}
else
{
TextColor = this.ForeColor;

// Draw the dotted lines that shows the button has the focus.
if (this.Focused)
{
// Create the bitmap image.
if (BitBackground == null)
{
BitBackground = CreateBitmap(rcClient);
}

// Draw the bitmap that was created and stored.
e.Graphics.DrawImage((Image)BitBackground, 0, 0);
}
}

// Draw the lines that makeup the button image.
e.Graphics.DrawLine(m_penGray, 0, 0, rcClient.Width - 1, 0);
e.Graphics.DrawLine(m_penGray, 0, 0, 0, rcClient.Height - 1);
e.Graphics.DrawLine(m_penLightGray, 1, 1, rcClient.Width - 2, 1);
e.Graphics.DrawLine(m_penLightGray, 1, 1, 1, rcClient.Height - 2);
e.Graphics.DrawLine(m_penGray, 1, rcClient.Height - 2, rcClient.Width - 2, rcClient.Height - 2);
e.Graphics.DrawLine(m_penGray, rcClient.Width - 2, 1, rcClient.Width - 2, rcClient.Height - 2);
e.Graphics.DrawLine(m_penBlack, 0, rcClient.Height - 1, rcClient.Width - 1, rcClient.Height - 1);
e.Graphics.DrawLine(m_penBlack, rcClient.Width - 1, 0, rcClient.Width - 1, rcClient.Height - 1);

// Check if the button is using an image.
if (ImageUnPressed != null)
{
intImageWidth = ImageUnPressed.Width / 2;
int intImageLeft = (rcClient.Width - ImageUnPressed.Width) / 2;
int intImageTop = (rcClient.Height - ImageUnPressed.Height) / 2;
Rectangle imgRect;

// If the text is being used, set the location of the image to be above, below, or in the middle of the text.
if (this.Text.Length > 0)
{
switch(TextLocation)
{
case TLocation.Bottom:
intImageTop = intImageTop - 5;
break;
case TLocation.Left:
intImageLeft = (rcClient.Width - ImageUnPressed.Width) - 5;
break;
case TLocation.Middle:
// Nothing to do here.
break;
case TLocation.Right:
intImageLeft = 5;
break;
case TLocation.Top:
intImageTop = intImageTop + 5;
break;
}
}

// Shift the image if the button is pressed.
if (Pressed)
{
imgRect = new Rectangle(intImageLeft + 1, intImageTop + 1, ImageUnPressed.Width, ImageUnPressed.Height);

// Draw the unpressed image if it was set.
if (ImagePressed != null)
{
// Get the Attributes for the Transparent color
ImageAttributes imageAttr = GetImageAttributes(ImagePressed);

// Draw the pressed image if set.
e.Graphics.DrawImage(ImagePressed, imgRect, 0, 0, ImagePressed.Width, ImagePressed.Height, GraphicsUnit.Pixel, imageAttr);
}
else
{
// Get the Attributes for the Transparent color
ImageAttributes imageAttr = GetImageAttributes(ImageUnPressed);

// Draw the unpressed image.
e.Graphics.DrawImage(ImageUnPressed, imgRect, 0, 0, ImageUnPressed.Width, ImageUnPressed.Height, GraphicsUnit.Pixel, imageAttr);
}
}
else
{
// Draw the unpressed image if set.
imgRect = new Rectangle(intImageLeft, intImageTop, ImageUnPressed.Width, ImageUnPressed.Height);

// Check which image to set.
if (this.Enabled == false)
{
// Set the ImageDisabled if the button is disabled.
if (ImageDisabled != null)
{
// Get the Attributes for the Transparent color
ImageAttributes imageAttr = GetImageAttributes(ImageDisabled);

e.Graphics.DrawImage(ImageDisabled, imgRect, 0, 0, ImageDisabled.Width, ImageDisabled.Height, GraphicsUnit.Pixel, imageAttr);
}
else
{
// Get the Attributes for the Transparent color
ImageAttributes imageAttr = GetImageAttributes(ImageUnPressed);

e.Graphics.DrawImage(ImageUnPressed, imgRect, 0, 0, ImageUnPressed.Width, ImageUnPressed.Height, GraphicsUnit.Pixel, imageAttr);
}
}
else
{
// Get the Attributes for the Transparent color
ImageAttributes imageAttr = GetImageAttributes(ImageUnPressed);

e.Graphics.DrawImage(ImageUnPressed, imgRect, 0, 0, ImageUnPressed.Width, ImageUnPressed.Height, GraphicsUnit.Pixel, imageAttr);
}
}
}

// Draw the text if needed.
if (this.Text.Length > 0)
{
DrawText(e, rcClient, intImageWidth);
}

// Set the button appearance when pressed.
if (Pressed)
{
// Draw the lines that makeup the button image in the pressed position.
e.Graphics.DrawLine(m_penBlack, 0, 0, rcClient.Width - 1, 0);
e.Graphics.DrawLine(m_penBlack, 0, 0, 0, rcClient.Height - 1);
e.Graphics.DrawLine(m_penBlack, 1, 1, rcClient.Width - 2, 1);
e.Graphics.DrawLine(m_penBlack, 1, 1, 1, rcClient.Height - 2);
e.Graphics.DrawLine(m_penLightGray, 1, rcClient.Height - 2, rcClient.Width - 2, rcClient.Height - 2);
e.Graphics.DrawLine(m_penLightGray, rcClient.Width - 2, 1, rcClient.Width - 2, rcClient.Height - 2);
e.Graphics.DrawLine(m_penWhite, 0, rcClient.Height - 1, rcClient.Width - 1, rcClient.Height - 1);
e.Graphics.DrawLine(m_penWhite, rcClient.Width - 1, 0, rcClient.Width - 1, rcClient.Height - 1);
}
}

///
/// Resize the control in the design view.
///

/// EventArgs.
protected override void OnResize(EventArgs e)
{
this.Refresh();
}

#endregion
}
}


You can always directly include this file to your project then use button in the code to dynamically generate a button. But if you want to use it like the button from toolbar, you have to do special thing if you are using Visual studio 2003.

If you are using Visual Studio 2005 or 2008, it is very easy.

1)Add you button.cs to you project.
2)Include its namespace.
3)From toolbar, you will find out there is one more category with "Pointer" "button" in it, which is the customized button you just created!!!

Wednesday, June 24, 2009

Understanding Garbage Collection in .NET

17 June 2009

Once you understand how .NET's garbage collector works, then the reasons for some of the more mysterious problems that can hit a .NET application become much clearer. NET may have promised the end to explicit memory management, but it is still necessary to profile the usage of memory when you're developing .NET applications if you wish to avoid memory-related errors and some performance issues.

.NET’s garbage collector has been sold to us as the end of explicit memory management, and of memory leaks, in Windows applications: the idea is that, with a garbage collector running in the background, developers no longer need to worry about the need to manage the life-cycle of the objects they create - the garbage collector will take care of them once the application has finished with them.

The reality is more complicated than this, however. The garbage collector certainly solves the most common leaks in unmanaged programs - those caused by developers forgetting to release memory when they have finished with it. It also solves the related problem of memory being released too soon, but the way in which this is solved can lead to memory leaks when the garbage collector has a different opinion to the developer about whether or not an object is still ‘live’ and able to be used. Before fixing these problems, you need some understanding of how the collector works.

How the Garbage Collector works

How, then, does the garbage collector achieve its magic? The basic idea is pretty simple: it examines how objects are laid out in memory and identifies all those objects that can be ‘reached’ by the running program by following some series of references.

When a garbage collection starts, it looks at a set of references called the ‘GC roots’. These are memory locations that are designated to be always reachable for some reason, and which contain references to objects created by the program. It marks these objects as ‘live’ and then looks at any objects that they reference; it marks these as being ‘live’ too. It continues in this manner, iterating through all of the objects it knows are ‘live’. It marks anything that they reference as also being used until it can find no further objects.

An object is identified, by the Garbage Collector, as referencing another object if it, or one of its superclasses, has a field that contains the other object.

Once all of these live objects are known, any remaining objects can be discarded and the space re-used for new objects. .NET compacts memory so that there are no gaps (effectively squashing the discarded objects out of existence) - this means that free memory is always located at the end of a heap and makes allocating new objects very fast.

GC roots are not objects in themselves but are instead references to objects. Any object referenced by a GC root will automatically survive the next garbage collection. There are four main kinds of root in .NET:

A local variable in a method that is currently running is considered to be a GC root. The objects referenced by these variables can always be accessed immediately by the method they are declared in, and so they must be kept around. The lifetime of these roots can depend on the way the program was built. In debug builds, a local variable lasts for as long as the method is on the stack. In release builds, the JIT is able to look at the program structure to work out the last point within the execution that a variable can be used by the method and will discard it when it is no longer required. This strategy isn’t always used and can be turned off, for example, by running the program in a debugger.

Static variables are also always considered GC roots. The objects they reference can be accessed at any time by the class that declared them (or the rest of the program if they are public), so .NET will always keep them around. Variables declared as ‘thread static’ will only last for as long as that thread is running.

If a managed object is passed to an unmanaged COM+ library through interop, then it will also become a GC root with a reference count. This is because COM+ doesn’t do garbage collection: It uses, instead, a reference counting system; once the COM+ library finishes with the object by setting the reference count to 0 it ceases to be a GC root and can be collected again.

If an object has a finalizer, it is not immediately removed when the garbage collector decides it is no longer ‘live’. Instead, it becomes a special kind of root until .NET has called the finalizer method. This means that these objects usually require more than one garbage collection to be removed from memory, as they will survive the first time they are found to be unused.

The Object Graph

Taken as a whole, memory in .NET forms a complicated, knotted graph of references and cross-references. This can make it difficult to determine the amount of memory used by a particular object. For instance, the memory used by a List object is quite small, as the List class only has a few fields. However, one of these is the array of objects in the list: this can be quite large if the list has many entries. This is almost always exclusively ‘owned’ by the list, so the relationship is fairly simple: The total size of the list is the size of the small initial object and the large array it references. The objects in the array could be another matter entirely, though: it’s possible that there could be some other path through memory through which they could be reached. In this case, it doesn’t make sense to count them as part of the ‘size’ of the list as they would remain even if the list ceased to exist, but neither does it make sense to count them via the alternative path - they’d remain if that was removed as well.

Things become even more confusing when circular references come into play.

When developing code, it’s more usual to think of memory as being organized into a more easily understood structure: a tree starting at individual roots:

Thinking in this way does, indeed, make it easier (or indeed possible) to think about how objects are laid out in memory. This is also how data is represented when writing the program or using a debugger, but this makes it easy to forget that an object can be attached to more than one root. This is usually where memory leaks in .NET come from: the developer forgets, or doesn’t ever realize, that an object is anchored to more than one root. Consider the case shown here: setting GC root 2 to null will not actually allow the garbage collector to remove any objects, which can be seen from looking at the complete graph but not from the tree.

A memory profiler makes it possible to view the graph from another perspective, as a tree rooted at an individual object and following the references backwards to put the GC roots at the leaves. For the ClassC object referenced by root 2, we can follow the references backwards to get the following graph:

Thinking in this way reveals that the ClassC object has two ultimate ‘owners’, both of which must relinquish it before the garbage collector can remove it. Any of the links between GC root 3 and the object can be broken in order to remove it once GC root 2 has been set to null.

This situation can arise easily in practical .NET applications. The most common one is that a data object becomes referenced by an element in the user interface, but isn’t removed when the data has been finished with. This situation isn’t quite a leak: the memory will be reclaimed when the UI control is updated with new data, but can mean that the application uses much more memory than would be expected. Event handlers are another common cause: it’s easy to forget that an object will last at least as long as the objects which it receives events from, which is forever in the case of some global event handlers such as those in the Application class.

Real applications, especially those with user interface components, have much more complicated graphs than this. Even something as simple as a label in a dialog box can be referenced from a huge number of different places…

It’s easy to see how the occasional object can become lost in this maze.

Limits of the Garbage Collector

Unused objects that are still referenced

The biggest limitation of the garbage collector in .NET is a subtle one: while it is sold as being able to detect and remove unused objects, it actually finds unreferenced objects. This is an important distinction: an object might never be referred to by a program ever again; but, while there is some path from it leading to an object that might still be used, it will never be released from memory. This leads to memory leaks; in .NET these occur when an object that will not be used again remains referenced.

The source of these leaks can be hard to detect, though the symptoms of rising memory usage are obvious. It’s necessary to determine which unused objects are remaining in memory, and then trace the references to find out why they are not being collected. A memory profiler is essential for this task: By comparing memory states while a leak is occurring, it is possible to find the troublesome unused objects, but no debugger can trace object references backwards.

The garbage collector is designed to deal with resources that are plentiful - that is, where it doesn’t matter when the object is released. On modern systems, memory falls into that category (it doesn’t matter when it’s reclaimed, just so long as it’s done in time to prevent a new allocation failing). There are still resources that don’t fall into this category: file handles need to be closed quickly to avoid causing sharing conflicts between applications, for example. These resources cannot be completely managed by the garbage collector, and so .NET provides the Dispose() method along with the using() construct for objects that manage these resources. In these cases the scarce resources used by the object are released quickly by the implementation of the Dispose method, but the much less critical memory is released later by the garbage collector.

Dispose means nothing special to .NET, so disposed objects must still be de-referenced. This makes objects that have been disposed but have not been reclaimed good candidates for the source of a memory leak.

Fragmentation of the Heap

A less widely known limitation in .NET is that of the large object heap. Objects that become part of this heap are never moved by the runtime, and this can lead to a program running out of memory prematurely. When some objects live longer than others, this causes the heap to form holes where objects used to be - this is known as fragmentation. The problem occurs when the program asks for a large block of memory but the heap has become so fragmented that there is no single region of memory big enough to accommodate it. A memory profiler can estimate the largest object that can be allocated by a program: if this is declining then this is likely to be the cause. An OutOfMemoryException caused by fragmentation will typically happen when the program apparently has a lot of free memory - on a 32-bit system, processes should be able to use at least 1.5Gb, but failures due to fragmentation will often start to occur before it is using that much memory.

Another symptom of fragmentation is that .NET will often have to keep the memory used by the empty holes allocated to the application. This causes it to apparently use much more memory than it needs when viewed in Task Manager. This effect is usually relatively harmless: Windows is quite good at realising that the memory occupied by the holes is not being used and will page it out, and if the fragmentation is not worsening then the program won’t run out of memory. It doesn’t look good to the user, though, who will probably think that the application is wasteful and ‘bloated’. This is often what is happening when a profiler shows that the objects allocated by a program are only using a small amount of memory but Task Manager shows that the process is occupying a large amount of space.

Performance of the Garbage Collector

In terms of performance, the most important characteristic of a garbage collected system is that the garbage collector can start executing at any time. This makes them unsuited for situations where timing is critical, as the timing of any operation can be thrown off by the operation of the collector.

The .NET collector has two main modes of operation: concurrent and synchronous (sometimes known as workstation and server). Concurrent garbage collection is used in desktop applications and synchronous is used in server applications such as ASP.NET by default.

In concurrent mode, .NET will try to avoid stopping the running program while a collection is in progress. This means that the total amount that the application can get done in a given period of time is less but the application won’t pause. It’s good for interactive applications where it’s important to give the impression to the user that the application is responding immediately.

In synchronous mode, .NET will suspend the running application while the garbage collector is running. This is actually more efficient overall than concurrent mode - garbage collection takes the same amount of time, but it doesn’t have to contend with the program continuing to run - but means that there can be noticeable pauses when a full collection has to be done.

The type of garbage collector can be set in the configuration file for the application if the default isn’t suitable. Picking the synchronous collector can be useful when it’s more important that an application has a high throughput instead of appearing responsive.

In large applications, the number of objects that the garbage collector needs to deal with can become very large, which means it can take a very long time to visit and rearrange all of them. To deal with this, .NET uses a ‘generational’ garbage collector, which tries to give priority to a smaller set of objects. The idea is that objects created recently are more likely to be released quickly, so a generational garbage collector prioritises them when trying to free up memory, so .NET first looks at the objects that have been allocated since the last garbage collection and only starts to consider older objects if it can’t free up enough space this way.

This system works best if .NET can choose the time of collection itself, and will be disrupted if GC.Collect() is called, as this will often cause new objects to become old prematurely, which increases the likelihood of another expensive full collection in the near future.

Classes with finalizers can also disrupt the smooth operation of the garbage collector. Objects of these classes can’t be removed immediately: they instead go to the finalizer queue and are removed from memory once the finalizer has been run. This means that any object they reference (and any object referenced by those, and so on) has to be kept in memory at least until this time as well and will require two garbage collections before the memory becomes available again. If the graph contains many objects with finalizers, this can mean that the garbage collector requires many passes to completely release all of the unreferenced objects.

There is a simple way to avoid this problem: implement IDisposable on the finalizable classes, move the actions necessary to finalize the object into the Dispose() method and call GC.SuppressFinalize() at the end. The finalizer can then be modified to call the Dispose() method instead. GC.SuppressFinalize() tells the garbage collector that the object no longer needs to be finalized and can be garbage collected immediately, which can result in memory being reclaimed much more quickly.

Conclusion

It becomes easier to understand memory and performance problems in an application if you take time to understand how the garbage collector works. It reveals that, while .NET makes the burden of memory management lighter, it does not completely eliminate the need to track and manage resources. It is, however, easier to use a memory profiler to diagnose and fix problems in .NET. Taking account of the way .NET manages memory early in development can help reduce problems, but even then such problems can still arise because of the complexity of the framework or third-party libraries.



This article has been viewed 3431 times.
Andrew Hunter

Author profile: Andrew Hunter

Andrew Hunter is a Software Engineer at Red Gate who is responsible for much of the recent rewrite of ANTS. Before that, he wrote the SQL Layout utilities for SQL Refactor/SQL Prompt. He has been described as resident master of the dark .NET arts. Dereferenced in a freak accident, he is forced to spend his days hiding from the garbage collector.

Search for other articles by Andrew Hunter

Rate this article: Avg rating: from a total of 32 votes.


Poor

OK

Good

Great

Must read
Have Your Say
Do you have an opinion on this article? Then add your comment below:


Subject: Large Object Heap
Posted by: Jon (view profile)
Posted on: Friday, June 19, 2009 at 4:20 PM
Message: If you do run into fragmentation problems with the Large Object Heap, it can sometimes be useful to break up your object into smaller objects that won't be put in the LOH. Here's an example of doing that with a MemoryStream:
http://blueonionsoftware.com/blog.aspx?p=61115cc1-7188-44d3-b8c0-8ef8a618fae6

Subject: Generational Garbage collection demonstration
Posted by: NoMoreHacks (not signed in)
Posted on: Wednesday, June 24, 2009 at 1:47 AM
Message: I created a demo to show the effects of generational garbage collection in systems that require high-throughput with low latency. The code and article are here

http://nomorehacks.wordpress.com/2008/11/27/forcing-the-garbage-collector/

Subject: .NET Memory Management
Posted by: grabnerandi (view profile)
Posted on: Wednesday, June 24, 2009 at 9:07 AM
Message: Hi Andrew
Thanks for the very detailed explaination of the .NET Gargabe Collector. Although we live in the managed world for a while now it is always good to refresh our memory about how GC works internally.
I've been doing some work regarding .NET Memory Management myself and I posted some of these findings at http://blog.dynatrace.com/tag/memory/
I also compared the the different LatencyMode's that the GC has to offer: http://blog.dynatrace.com/2009/04/13/performance-analysis-comparing-interactive-and-low-latency-gc-strategies-in-net/

Cheers
Andi

How to use C++ APIs in C# application?Presenting a C++ library (.lib) for use in C# project?

Presenting a C++ library (.lib) for use in C# project

Q:
I have a collection of C++ programs and the C++ libraries they use. The libraries currently compile to a .lib format. Changing the project property to use a .dll results in multiple reference errors.

Without having to go through the entire collection of source files, is there a way to create any kind of project in a .NET language that would provide a "wrapper" for the existing .lib files so that they could be used in that environment.

Thanks.

A:

Yes (sort of) but it's non-trivial.

Let's start with some background. Your project to compile to a .lib format is generating something known as a "static library". Static libraries are chunks of native code built to be combined with other pieces of code via the linker. You #include a header file that describes the functions/classes that you want to use and then link to the .lib file to provide the implementations of those functions/classes.

You can't use that approach with a C# client because C# generates managed code and you just can't directly link native code to managed code.

There are three ways (I can think of) to consume native code from a managed code client

  1. You turn the native code into a native code COM object using the static library to provide the implementation of the COM object's methods and then use COM Interop to invoke the (native) COM object from the managed code client
  2. You turn the native code into a native code DLL that exports some suitable entry points (again, using the static library to provide the actual implementation) and then call those DLL entry points from the managed code client using P/Invoke
  3. You create a managed code Class Library using C++ and use the static library to provide the underlying implementation of that managed class library

Let's say your static library's header file looks like this




namespace MathFuncs
{
class MyMathFuncs
{
public:
// Returns a + b static double Add(double a, double b);
// Returns a - b static double Subtract(double a, double b);
// Returns a * b static double Multiply(double a, double b);
// Returns a / b // Throws DivideByZeroException if b is 0 static double Divide(double a, double b);
};
}

Using approach #1, you'd create a new project using the Visual C++ | ATL | ATL Server project template. You'd add a new simple ATL object to the project that exported Add, Subtract, Multiply and Divide methods. You'd #include your static library's header file to provide the underlying implementation of Add, Subtract etc and you'd have your COM object's Add method call the underlying MathFuncs::MyMathFuncs::Add method (and return the result). Finally, you'd make sure your COM object project linked to your static library .lib file as part of the build process.

To consume the 'wrapper' from a C# program, you'd use Add | Reference to add a COM reference to your COM object and then just cal the COM object's methods as necessary.

Using approach #2, you'd create a new project using the Visual C++ | Win32 | Win32 Project template and use Application Settings to change Application Type to DLL and set Additional Options to "Export symbols". Then you would create some exported entry points (like Add, Subtract etc) to be consumed by the client. Again, you would #include your static library's header file to get function descriptions and link to the .lib file to get actual implementations. You'd code your exported DLL entry points to call the static library's 'real' implementation methods and return the result.

To consume this kind of wrapper, you would use the C# DllImportAttribute to declare the DLL entry point in C# terms and then just call the function as needed.

Finally, using appoach #3, you'd create a managed C++ project using Visual C++ | CLR | Class Library. You'd create a managed class (a 'ref' class) which would be called by your C# client. Again, you'd #include the native code header file and link to the .lib file to get the actual implementation of the methods you want the client to be able to call. The managed code wrapper for the above header file might look something like this...




#include "MathFuncsLib.h" #pragma once
using
namespace System; namespace ManagedMathFuncsLib {
public ref class Class1
{
public:
Class1 ()
{
};
// Returns a + b static double Add(double a, double b)
{
return MathFuncs::MyMathFuncs::Add (a, b);
}
// Returns a - b static double Subtract(double a, double b)
{
return MathFuncs::MyMathFuncs::Subtract (a, b);
};
// Returns a * b static double Multiply(double a, double b)
{
return MathFuncs::MyMathFuncs::Multiply (a, b);
};
// Returns a / b // Throws DivideByZeroException if b is 0 static double Divide(double a, double b)
{
return MathFuncs::MyMathFuncs::Divide (a, b);
};
};}

To consume this managed code wrapper, you'd use Add | Reference to add a .NET Reference (to your managed wrapper) to your client program and then would call the managed (ref) class's methods as necessary.

In all three cases above, I simplified things by using static methods in the original native code implementation. If your static library implements classes with non-static functions then in all three approaches above you would have to conceal a private member variable that pointed to an instance of your native class and would have to new up that instance at some suitable point (in the constructor of the wrapper for example) and would have to use that private member variable to actually invoke the actual implementation of the method you were trying to call.


Q: Could you go into a little more detail regarding the last paragraph of your post. I'm unclear as to dealing with functions that are not declared static.

Where are you creating the private member?

How do you invoke that private member, PInvoke?

Would it be possible to see another example?

Thanks

Blue


A:

There's no need to use P/Invoke because the wrapper is written in Managed C++ so it can invoke the private member's functions directly.

Let's change the example slightly and create a statistical functions library that can be used to calculate a number of statistical properties of a group of numbers. Since I'm lazy I'll just calculate average but you could imagine calculating standard deviation and median and all sorts of things. To keep the example simple, I'll not store the actual data items, I'll just incorporate them into a sum variable as I go. Again though you could imagine a much more elaborate data structure underneath this native class.

The header file for the native code static library implementation of this statistical functions library might look something like this




// StatFuncsLib.h namespace StatFuncs
{
class MyStatFuncs
{
private:
double m_Count;
double m_Sum;

public:
// Constructs an instance and initialises private members
MyStatFuncs ();

// Adds item and returns updated count
double AddItem(double item);

// Returns average
// Throws DivideByZeroException if count is 0
double Average ();

// Returns count of items
double Count ();
};
}

The AddItem function will increment m_Count by 1 and will add item to m_Sum while the Average function will return m_Sum divided by m_Count.

Because data is accumulated in the private member variables m_Count and m_Sum we can't just use static functions, we have to use individual instances of the MyStatFuncs class. That means when we create our managed C++ wrapper class it will have to create and use an instance of the MyStatFuncs class. The wrapper class might look something like this




// ManagedStatFuncsLib.h #pragma once
#include
"StatFuncsLib.h" using namespace System; namespace ManagedStatFuncs {

public ref class MyManagedStatFuncs
{

private:
StatFuncs::MyStatFuncs * m_NativeStatFuncs;

public:

// Constructs an instance of the class and an instance
// of the underlying native class MyStatFuncs
MyManagedStatFuncs ()
{
m_NativeStatFuncs =
new StatFuncs::MyStatFuncs ();
};

// Adds item and returns updated count
double AddItem(double item)
{
return m_NativeStatFuncs->AddItem (item);
};

// Returns average
// Throws DivideByZeroException if b is 0
double Average ()
{
return m_NativeStatFuncs->Average ();
};

// Returns count of items
double Count ()
{
return m_NativeStatFuncs->Count ();
};
};
}

The managed C++ wrapper will compile up into an assembly that can be referenced from a C# program (using Add Reference). Then the C# program just creates instances of the managed wrapper class and invokes the various functions as needed. For example




ManagedStatFuncs.MyManagedStatFuncs myStatOne =
new ManagedStatFuncs.MyManagedStatFuncs();
ManagedStatFuncs.MyManagedStatFuncs myStatTwo =
new ManagedStatFuncs.MyManagedStatFuncs();

myStatOne.AddItem(1.0);
myStatOne.AddItem(3.0);
myStatOne.AddItem(5.0);

myStatTwo.AddItem(2.0);
myStatTwo.AddItem(4.0);
myStatTwo.AddItem(6.0);
myStatTwo.AddItem(8.0);

Console.WriteLine(
"First group consists of {0} items with an average value of {1}",
myStatOne.Count(), myStatOne.Average());
Console.WriteLine(
"Second group consists of {0} items with an average value of {1}",
myStatTwo.Count(), myStatTwo.Average());
Q:

This is a good example! Thanks!

I am trying to get this code to work on a Pocket PC device. Is it possible to build a managed C++ assembly for the Pocket PC. So far I have only been able to build the managed C++ assembly for Win32. Visual studio will not let me do Add Reference for a Win32 assembly in C#. When I try to add reference I get an error saying that ManagedStatFuncs can not be added because it is not a device project. Maybe this is because Win32 managed code is not compatible with compact frameworks. Maybe I need a different project template. Does anyone have any thoughts on this?


A:

Brian Wall wrote:
Is it possible to build a managed C++ assembly for the Pocket PC?

In a word, no. I can't find an authoritative reference to cite but if you search the web (or these forums) for "Managed C++" and "Smart Device" you'll find several posts that say it can't be done. For example this chat transcript

Q: Will there be support for Managed C++ with .NET CF 2.0?
A: Sorry, no. Only VB and C#.

http://msdn.microsoft.com/chats/transcripts/mobileembedded/netcf_020805.aspx

C++ template and C# template

C++ template and C# template

In standard C++, template classes are resolved at compile time, basically "class generators" for the compiler. The compiler will generate a copy of the class definition for each type that is used for the template parameter. So, for "vector" and "vector", you'll end up with two seperate classes, one each for the types int and float.

In .NET 2.0+ (not just C#), generic classes are resolved at runtime. They are special constructs that maintain their type information. There is only ever one copy of the class definition, and that class definition keeps track of the type of objects being used with it. So, "List" and "List" use the same class definition.

Because of this, you can created a templated class of managed types because the runtime type information is unimportant to the template class, but you cannot create a generic class of unmanaged types, as the type information for unmanaged types isn't availabe at runtime.

C# for MFC programmers Quick Equivalents Map

C# for MFC programmers Quick Equivalents Map


This list is by no means intended to be comprehensive. I am learning C#, and as I've had to look something up, I record it here. I figure if I had to look it up, you will, too. If you have entries to contribute based on your own experience, or if you discover an error caused by my own naiveté in C#, feel free to send them. I won't attribute each individual contributions, but I will add you to a "contributions by..." section at the end. If you want your email or your Web site included in the attributions list, please say so explicitly; otherwise I will just put your name there. Or, if you want to remain anonymous, tell me and I will hog all the credit myself.

Useful Web Sites

I've discovered a number of useful Web sites for beginning C# programmers. Included in no particular order,

My FAQ

A

B

C

D

E

F

G

H

I

J

K

L

M

N

O

P

Q

R

S

T

U

V

W

X

Y

Z

C/MFC concept C#

A

abs
Math.Abs

int n = ...;

int a = abs(n);

int n = ...;

int a = Math.Abs(n);
int n = -2147483648; // largest negative #

UINT u = abs(n); // n = 2147483648
int n = -2147483648;

uint u = Math.Abs(n); // exception!

but the following works!

uint u = unchecked(n <> 
.AddString(...) (CListBox)
.Items.Add(...) (CListBox)
AfxMessageBox
MessageBox.Show

UINT n =

AfxMessageBox(body, flags);

DialogResult n =

MessageBox.Show(body, caption,
flags, icon);
See MessageBox
atoi
Parse

CString s;

int n = atoi(s);

String s

int n = int.Parse(s)
atol
Parse

CString s;

long n = atol(s);

String s

long n = long.Parse(s)


B

BOOL
Boolean

bool
bool
Boolean

bool


BS_PUSHLIKE

Drag a check box onto the form. Select its Appearance property as Button









BYTE
byte

C

CBitmap
Bitmap (actually, a GDI+ bitmap)
CBrush
Brush
CDC
Graphics
CClientDC
Graphics

CClientDC dc(&c_Ctl)

Graphics g =

c_Ctl.CreateGraphics();
ceil
Math.Ceiling
CenterWindow (CWnd)
.StartPosition = FormStartPosition.CenterParent 

(can be set at design time)

CFileDialog
Create an object (usually in the forms design template) of type SaveFileDialog or OpenFileDialog. Use the ShowDialog method to invoke it.
CFont
Font
char (as a character type)
char



Note however that char in C# means Unicode character and is not the same as byte or the concept in C/C++ known as char. Signedness is not a characteristic of the C# char type. It is conceptually closer to the C/C++ WCHAR type.
char (as a signed 8-bit integer)
sbyte
CListBox::AddString(...) (CListBox)
CListBox.Items.Add(...) (CListBox)
CListBox.GetLBText(n) (CListBox)
CListBox.GetItemText(n)
COLOR_ constants
See GetSysColor
COLORREF
Color
COLORREF r =

RGB(255,128,0);

Color c =

Color.FromArgb(255,128,0);
This actually oversimplifies, or over complicates, the problem. C# contains a number of "predefined color names", many with really obscure names and no good graphical representation in the Help system of what they look like. However, RGB(255,0,0) is the name .Red. The names appear to conform to the HTML naming conventions. You can also name system colors. See GetSysColor. Argb stands for the 4-tuple, Alpha, Red, Green, Blue, and the overload with three arguments assumes Alpha (transparency) is 255 (completely opaque).
COLORREF r = RGB(255,0,0)
Color c = Color.Red;
CPaintDC
The .Graphics member of the System.Windows.Forms.PaintEventArgs argument to the OnPaint event
CPen
Pen


CSize
Size (integer values)

SizeF (floating point values)

CSize sz = ...;

int w = sz.cx;
int h = sz.cy;

SizeF sz = ...;

int w = sz.Width;
int h = sz.Height;
This is an oversimplification because the fields of a SizeF are floats and not integers!
CSpinCtrl (with CEdit buddy control)
NumericUpDown
CString
String
CString::Find(CString s)
String.IndexOf(String s)
CString::Format
Format

CString s;

s.Format(fmt, val...)

String s;

s = Format(fmt, val...)

Formatting specifications in C# are much more flexible than in traditional C. For example, you can associate several format requests with the same argument, and the formatting options are richer, including localized date, time, and currency representations.

Instead of %, the format specifiers in C# are enclosed in {}, and are of the form {n:Fp} where n specifies the argument in the formatting string (the n: can be omitted in the ToString method call), F is a format specifier, and p is the precision.

%d
{n:d} or {n:D}
%02d
{n:d2} or {n:D2}
%o
octal is not supported
%ld
{n:d} No special handling is required for int values
%I64d
{n:d} No special handling is required for 64-bit int values
%x
{n:x}
%X
{n:X}
CWnd::GetClientRect()
ClientRectangle (property)
CWnd::Invalidate()
.Invalidate()
CWnd::InvalidateRect(&r)
.Invalidate(...);
CWnd::ShowWindow(SW_SHOW)
.Visible = true;
CWnd::ShowWindow(SW_HIDE);
.Visible = false;


CString.GetLength();
String.Length;

D

DeleteDC
No need to; implicitly handled by the Graphics destructor during garbage collection.
DestroyWindow
Close
DoModal
.ShowDialog
double
double
DrawItem (CListBox)
DrawItem event

See http://www.syncfusion.com/faq/winforms/627.asp

DWORD
uint

E

.Ellipse
.DrawEllipse

CClientDC dc(&wnd);

CRect r;
CPen pen(PS_SOLID, n, RGB(0,0,0));
dc.SelectObject(&pen);
dc.Ellipse(&r);

Graphics g = wnd.CreateGraphics();

Rectangle r;
Pen pen = new Pen(Color.Black, n);
g.DrawEllipse(pen, r);
EndDialog(IDCancel)
Close
EndDialog(IDOK)
Close
exp
Math.Exp

F



.Find (CString)
.IndexOf (String)


float
float
.Format
see CString::Format

G

GetBValue
.B

COLORREF c = ...;

int b = GetBValue(c);

Color c = ...;

int b = c.B;
.GetClientRect()
.ClientRectangle (property)

CRect r;

wnd.GetClientRect(&r);

Rectangle r =

wnd.ClientRectangle;
GetCurSel (CComboBox)
... = ComboBox.SelectedIndex;
GetDC
Graphics

CDC * dc = wnd.GetDC();

Graphics g =

wnd.CreateGraphics();
GetFont
.Font

CFont * f = wnd.GetFont();

Font f = wnd.Font;
GetGValue
.G

COLORREF c = ...;

int g = GetGValue(c);

Color c = ...;

int g = c.G;
.GetLBText(n) (CListBox)
.GetItemText(n)
.GetLBText(n) (CComboBox)
.GetSelectedItem.ToString();
.GetLength() (CString)
s.Length
.GetMiterLimit (CDC)
... = Pen.MiterLimit;
GetModuleFileName
System.Windows.Forms.Application.ExecutablePath

TCHAR name[MAX_PATH];

GetModuleFileName(NULL, name, MAX_PATH);

String name = System.Windows.Forms.

Application.ExecutablePath;
GetPos (CSpinCtrl)
NumericUpDown.Value

Note that the values of .Minimum, .Maximum, and .Value are of type decimal. This means that to assign a value which is a fractional value, you have to use a decimal constant. To use the values, you may have to cast them from decimal to int or float.

GetRange (CSpinCtrl)
NumericUpDown.Maximum and

NumericUpDown.Minimum

Note that the values of .Minimum, .Maximum, and .Value are of type decimal. This means that to assign a value which is a fractional value, you have to use a decimal constant. To use the values, you may have to cast them from decimal to int or float.

GetRValue
.R

COLORREF c = ...;

int r = GetRValue(c);

Color c = ...;

int r = c.R;
.GetSize() (CArray)
s.Length
GetSysColor
Color.FromKnownColor(KnownColor.colorname)
COLORREF c = ::GetSysColor(COLOR_WINDOW)

Color.KnownColor.Window
COLOR_3DDKSHADOW
Color.KnownColor.ControlDarkDark (?)
COLOR_3DFACE
Color.KnownColor.Control (?)
COLOR_3DHIGHLIGHT
Color.KnownColor.ControlLight (?)
COLOR_3DHILIGHT
Color.KnownColor.ControlLight (?)
COLOR_3DLIGHT
Color.KnownColor.?
COLOR_ACTIVEBORDER
Color.KnownColor.ActiveBorder
COLOR_ACTIVECAPTION
Color.KnownColor.ActiveCaption
COLOR_APPWORKSPACE
Color.KnownColor.AppWorkspace
COLOR_BACKGROUND
Color.KnownColor.Desktop
COLOR_BTNFACE
Color.KnownColor.Control (?)
COLOR_BTNHIGHLIGHT
Color.KnownColor.ControlLight (?)
COLOR_BTNHILIGHT
Color.KnownColor.ControlLight (?)
COLOR_BTNSHADOW
Color.KnownColor.ControlDark (?)
COLOR_BTNTEXT
Color.KnownColor.ControlText
COLOR_CAPTIONTEXT

COLOR_DESKTOP
Color.KnownColor.Desktop
COLOR_GRADIENTACTIVECAPTION

COLOR_GRADIENTINACTIVECAPTION

COLOR_GRAYTEXT
Color.KnownColor.GrayText
COLOR_HIGHLIGHT
Color.KnownColor.Highlight
COLOR_HIGHLIGHTTEXT
Color.KnownColor.HighlightText
COLOR_HOTLIGHT
Color.KnownColor.HotTrack (?)
COLOR_INACTIVEBORDER
Color.KnownColor.InactiveBorder
COLOR_INACTIVECAPTION
Color.KnownColor.InactiveCaption
COLOR_INACTIVECAPTIONTEXT
Color.KnownColor.InactiveCaptionText
COLOR_INFOBK

COLOR_INFOTEXT
Color.KnownColor.InfoText
COLOR_MENU
Color.KnownColor.Menu
COLOR_MENUTEXT
Color.KnownColor.MenuText
COLOR_SCROLLBAR
Color.KnownColor.ScrollBar
COLOR_WINDOW
Color.KnownColor.Window
COLOR_WINDOWFRAME
Color.KnownColor.WindowFrame
COLOR_WINDOWTEXT
Color.KnownColor.WindowText
GetSystemMetrics
System.Windows.Forms.SystemInformation
GetTextExtent
MeasureString

CClientDC dc(&wnd);

CSize sz;
CString s = ...;
sz = dc.GetTextExtent(s);

Graphics g = wnd.CreateGraphics();

SizeF sz;
String s = ...;
sz = g.MeasureString(wnd.Font, s);
GetTickCount DateTime.Now.Ticks
GetUserName
System.Environment.UserName
GWL_DLGRESULT
Closing event, DialogResult property

void CMyDialog::OK()

{
DWORD result = ...;
SetWindowLong((HWND)this,
GWL_DLGRESULT,
result);

CDialog::OnOK();
}

select the Closing event for the form and type a name of the function you want, such as OnClosing

private void OnClosing(

Object sender,
System.ComponentModel.CancelEventArgs e)
{
Object result = ...;
e.DialogResult = result;
}

H

HBITMAP
Bitmap (actually, a GDI+ bitmap)
HBRUSH
Brush
HDC
Graphics (see CDC)
HPEN
Pen

I

_I64_MAX Int64.MaxValue
_I64_MIN Int64.MinValue
int
int
__int64
long
INT_MAX Int32.MaxValue
INT_MIN Int32.MinValue
.Invalidate()
.Invalidate();
.InvalidateRect
.Invalidate();

CRect r;

wnd.InvalidateRect(&r)

Rectangle r;

wnd.Invalidate(r);
itoa
ToString
%d
3

int v;

String s;
s = i.ToString();
%3d
··3 (leading spaces) int v;
String s;
s = String.Format("{0,3:d}", v);
%03d
003
int v = 3;

String s;
s = i.ToString("D3");
%03d
003
int v = 3;

String s;
s = i.ToString("000");

K

KillTimer
place a timer object on the form and give it a name
timer.Stop();

L



LineTo
DrawLine
CClientDC dc(&wnd);

dc.SelectObject(&pen);
dc.MoveTo( x0, y0);
dc.LineTo( x1, y1);

Graphics g = wnd.CreateGraphics();

g.DrawLine(pen, x0, y0, x1, y1);
CClientDC dc(&wnd);

dc.SelectObject(&pen);
CPoint p0(x0, y0);
CPoint p1(x1, y1);
dc.MoveTo( p0 );
dc.LineTo( p1 );
Graphics g = wnd.CreateGraphics();

Point p0 = new Point(x0, y0);
Point p1 = new Point(x1, y1);
g.DrawLine(pen, p0, p1);
::LoadCursor(NULL, cursorid)

LoadStandardCursor(cursorid) (CWinApp)
No equivalent. See OnSetCursor
long
int
LONG
int
LONGLONG
long

M

MessageBox
MessageBox.Show

UINT n =

MessageBox(body, caption, flags);

DialogResult n =

MessageBox.Show(body);
DialogResult n =
MessageBox.Show(body, caption);
DialogResult n =
MessageBox.Show(body, caption,
buttons);
DialogResult n =

MessageBox.Show(body, caption,
buttons, icon);
DialogResult n =
MessageBox.Show(body, caption,
buttons, icon,
defaultbutton);

buttons can be any of the following:
MB_ABORTRETRYIGNORE

MessageBoxButtons.AbortRetryIgnore
MB_HELP

no equivalent

MB_OK

MessageBoxButtons.OK
MB_OKCANCEL

MessageBoxButtons.OKCancel
MB_OKRETRY

MessageBoxButtons.OKRetry
MB_YESNO

MessageBoxButtons.YesNo
MB_YESNOCANCEL

MessageBoxButtons.YesNoCancel

icon is optional and can be any of
MB_ASTERISK
MessageBoxIcon.Asterisk

Note: this code should be considered obsolete; .Information should be used instead

MB_ERROR
MessageBoxIcon.Error
MB_EXCLAMATION
MessageBoxIcon.Exclamation

Note: this code should be considered obsolete; .Warning should be used instead

MB_HAND
MessageBoxIcon.Hand

Note: this code should be considered obsolete; .Error should be used instead

MB_INFORMATION
MessageBoxIcon.Information


MessageBoxIcon.None
MB_ICONQUESTION
MessageBoxIcon.Question
MB_ICONSTOP
MessageBoxIcon.Stop

Note: this code should be considered obsolete; .Error should be used instead

MB_ICONWARNING
MessageBoxIcon.Warning
MB_USERICON

no equivalent


The default button can be one of the following

MB_DEFBUTTON1

MessageBoxDefaultButton.Button1
MB_DEFBUTTON2
MessageBoxDefaultButton.Button2
MB_DEFBUTTON3
MessageBoxDefaultButton.Button3
MB_DEFBUTTON4

no equivalent

The return value can be one of the following

IDABORT

DialogResult.Abort
IDCANCEL
DialogResult.Cancel
IDIGNORE
DialogResult.Ignore
IDNO
DialogResult.No
There is no equivalent to this value in the standard API
DialogResult.None
IDOK
DialogResult.OK
IDRETRY
DialogResult.Retry
IDYES
DialogResult.Yes
MoveTo
DrawLine
CClientDC dc(&wnd);

dc.SelectObject(&pen);
dc.MoveTo( x0, y0);
dc.LineTo( x1, y1);

Graphics g = wnd.CreateGraphics();

g.DrawLine(pen, x0, y0, x1, y1);
CClientDC dc(&wnd);

CPoint p0(x0, y0);
CPoint p1(x1, y1);
dc.SelectObject(&pen);
dc.MoveTo( p0 );
dc.LineTo( p1 );
Graphics g = wnd.CreateGraphics();

Point p0 = new Point(x0, y0);
Point p1 = new Point(x1, y1);
g.DrawLine(pen, p0, p1);
MoveWindow
.Location (changes only the top-left origin)

// Move window to X, Y retaining size

CRect r;
ctl.GetWindowRect(&r);
CSize sz(r.Width(), r.Height());
ctl.MoveWindow(X, Y, sz.cx, sz.cy);

ctl.Location = new Point(X, Y);

Note that accessing the .Location member returns a copy of the Point value, so changing the values in this will not change the size in the control itself, e.g.,

ctl.Location.x = X; // does not work

changes only the value in the copy. You must assign a complete Point object to the .Location member.

MoveWindow
.Size (changes only the size)

.Width (changes only the width)
.Height (changes only the height)

// Change window to size W,H in same place

CRect r;
ctl.GetWindowRect(&r);
ScreenToClient(&r);
ctl.MoveWindow(r.left, r.top, W, H);

ctl.Size = new Size(X, Y);

or you can change each dimension independently

ctl.Width = W;

ctl.Height = H;

Note that accessing the .Size member returns a copy of the Size value, so changing the values in this will not change the size in the control itself, e.g.,

ctl.Size.Width = W; // does not work

changes only the value in the copy. You must assign a complete Size object to the .Size member, or assign independently to the .Width or .Height members

N

NULL
null

O



OnClose
Closing event

void CMyWnd::OnClose()

{
if(MyQueryClose())
return;
CWnd::OnClose();
}

select the Closing event for the form and type a name of the function you want, such as OnClosing

private void OnClosing(

Object sender,
System.ComponentModel.CancelEventArgs e)
{
e.Cancel = MyQueryClose();
}
OnDestroy
Closed event

void CMyWnd::OnDestroy() { ... }

select the Closing event for the form and type a name of the function you want, such as OnClosed

private void OnClosing(Object sender,

System.ComponentModel.EventArgs e)
{
...
}
OnVScroll(...)
Create a ScrollEvent handler

switch(nSBCode)

switch(e.Type)
SB_PAGEUP
ScrollEventType.LargeDecrement
SB_PAGEDOWN
ScrollEventType.LargeIncrement
SB_LINEUP
ScrollEventType.SmallDecrement
SB_LINEDOWN
ScrollEventType.SmallIncrement
SB_TOP
ScrollEventType.First
SB_BOTTOM
ScrollEventType.Last
SB_THUMBPOSITION
ScrollEventType.ThumbPosition
SB_THUMBTRACK
ScrollEventType.ThumbTrack
SB_ENDSCROLL
ScrollEventType.EndPos


OnInitDialog
Load event
OnLButtonDown
MouseDown event
OnLButtonUp
MouseUp event
OnMouseMove
MouseMove event
OnMove
LocationChanged event
OnPaint
OnPaint event
OnSetCursor
No equivalent; cursor is set using the Cursor property of the window whose cursor is to be set. See SetCursor
OnSize
SizeChanged event

P

PostMessage
BeginInvoke

ON_MESSAGE(UWM_MYMESSAGE, OnMyMessage)
LRESULT CMyClass::OnMyMessage(

WPARAM, LPARAM)
{
... do something
return 0;
}
void CMyClass::DoSomething()

{
CString * s =
new CString(_T("Message"));
PostMessage(UWM_MYMESSAGE,
(WPARAM)s);

public delegate

void MessageHandler(String s);
class CMyClass {

public void MyHandler(String s) { ... }
}
 public void DoSomething()

{
String s = "Message";
MessageHandler handler =
new MessageHandler(MyHandler);
handler.BeginInvoke(handler);



As with many C# equivalences, this is an oversimplification of a far more sophisticated concept. For example, in C# you can actually receive a callback when the message is handled, or wait for the receiving thread to process it.
PostQuitMessage(0)
Close()
pow
Math.Pow
printf
System.Console.Write

System.Console.WriteLine

printf("%s", string);

CString string;

System.Console.Write(string);
printf("%s\n", string);

CString string;

System.Console.Write(string +"\n");
CString string;

System.Console.WriteLine(string);
puts
System.Console.Write

System.Console.WriteLine

CString string;

puts(string);

CString string;

System.Console.Write(string);

R

.Rectangle
.DrawRectangle

CClientDC dc(&wnd);

CRect r;
...assign to r
dc.SelectObject(&pen);
dc.SelectStockObject(HOLLOW_BRUSH);
dc.Rectangle(&r);

Graphics g = wnd.CreateGraphics();

Rectangle r;
...assign to r
g.DrawRectangle(pen, r);
ReleaseDC
No need to; implicitly handled by the Graphics destructor during garbage collection.
.ResetContent
listbox.Items.Clear()
.RestoreDC (CDC)
.Restore (graphics)

CClientDC dc(&wnd);

int n = dc.SaveDC();
...
dc.RestoreDC(n);

Graphics g = wnd.CreateGraphics();

GraphicsState gs = g.Save();
...
g.Restore(gs);
.ReverseFind (CString)
.LastIndexOf
RGB
.FromArgb
COLORREF r =

RGB(255,128,0);

Color c =

Color.FromArgb(255,128,0);
This actually oversimplifies, or over complicates, the problem. C# contains a number of "predefined color names" with really obscure names and no good graphical representation in the Help system of what they look like. The names correspond to the standard names used in HTML. However, as an example, RGB(255,0,0) is the name .Red Argb stands for the 4-tuple, Alpha, Red, Green, Blue, and the overload with three arguments assumes Alpha (transparency) is 255 (completely opaque).
COLORREF r = RGB(255,0,0)
Color c = Color.Red;

or
Color c = Color.FromArgb(255,0,0);

S

.SaveDC (CDC)
.Save (graphics)

CClientDC dc(&wnd);

int n = dc.SaveDC();
...
dc.RestoreDC(n);

Graphics g = wnd.CreateGraphics();

GraphicsState gs = g.Save();
...
g.Restore(gs);
SetCurSel (CComboBox)
ComboBox.SelectedIndex = ...;
::SetCursor(AfxGetApp()->LoadStandardCursor(id));
.Cursor = Cursors.id 
IDC_APPSTARTING
Cursors.AppStarting
IDC_ARROW
Cursors.Arrow
IDC_CROSS
Cursors.Cross

no equivalent


Cursors.Default
IDC_HAND
Cursors.Hand
IDC_HELP
Cursors.Help
no equivalent
Cursors.HSplit
IDC_IBEAM
Cursors.IBeam
IDC_NO
Cursors.No
no equivalent
Cursors.NoMove2D
no equivalent
Cursors.NoMoveHoriz
no equivalent
Cursors.NoMoveVert
no equivalent
Cursors.PanEast
no equivalent
Cursors.PanNE
no equivalent
Cursors.PanNorth
no equivalent
Cursors.PanNW
no equivalent
Cursors.PanSE
no equivalent
Cursors.PanSouth
no equivalent
Cursors.PanSW
no equivalent
Cursors.PanWest
IDC_SIZEALL
Cursors.SizeAll
IDC_SIZENESW
Cursors.SizeNESW
IDC_SIZENS
Cursors.SizeNS
IDC_SIZESWNE
Cursors.SizeSWNE
IDC_SIZEWE
Cursors.SizeWE
IDC_UPARROW
Cursors.UpArrow
no equivalent
Cursors.VSplit
IDC_WAIT
Cursors.WaitCursor
.SetMapMode (CDC)
.PageScale (Graphics)
MM_TEXT

GraphicsUnit.Pixel
MM_LOENGLISH

MM_HIENGLISH
GraphicsUnit.Inch
MM_LOMETRIC

MM_HIMETRIC
GraphicsUnit.Millimeter
MM_TWIPS (1/1440 inch)
GraphicsUnit.Document (1/300 inch)
no equivalent
GraphicsUnit.Display (1/72 inch)
MM_ISOTROPIC

MM_ANISOTROPIC
GraphicsUnit.World (closest approximation)

no equivalent

GraphicsUnit.Display (1/75 inch)
.SetMiterLimit (CDC)
Pen.MiterLimit = ...;
.SetPos (CSpinCtrl)
NumericUpDown.Value = ...

Note that the values of .Minimum, .Maximum, and .Value are of type decimal. This means that to assign a value which is a fractional value, you have to use a decimal constant. To use the values, you may have to cast them from decimal to int or float.

.SetRange (CSpinCtrl)
NumericUpDown.Maximum = ...

NumericUpDown.Minimum = ...;

Note that the values of .Minimum, .Maximum, and .Value are of type decimal. This means that to assign a value which is a fractional value, you have to use a decimal constant. To use the values, you may have to cast them from decimal to int or float.

SetTimer

place a timer object on the form and give it a name

timer.Interval = ...; // can be set

// at design time
timer.Start();
.SetViewPortExt/

.SetWindowExt (CDC)
.PageScale (Graphics)
SetWindowPos
.Location (changes only the top-left origin)

.Size (changes width and height but not origin)
.Width (changes only width)
.Height (changes only height)

// Move window to X, Y retaining size

ctl.SetWindowPos(NULL, X, Y, 0, 0,
SWP_NOSIZE | SWP_NOZORDER);

ctl.Location = new Point(X, Y);

Note that accessing the .Location member returns a copy of the Point value, so changing the values in this will not change the size in the control itself, e.g.,

ctl.Location.x = X; // does not work

changes only the value in the copy. You must assign a complete Point object to the .Location member.


// Change size to W, H and don't move

ctl.SetWindowPos(NULL, 0, 0, W, H,
SWP_NOMOVE | SWP_NOZORDER);

ctl.Size = new Size(W, H);

or the values can be set independently

ctl.Width = W;

ctl.Height = H;

Note that accessing the .Size member returns a copy of the Size value, so changing the values in this will not change the size in the control itself, e.g.,

ctl.Size.Width = W; // does not work

changes only the value in the copy. You must assign a complete Size object to the .Size member, or assign independently to the .Width or .Height members

ShBrowseForFolder
No good equivalent. KB article 306285 shows how to call the underlying ShBrowseForFolder call in .NET 1.0; in .NET 1.1 there is a FolderBrowserDialog class, but it is not supported in the Common Framework, meaning you can't use it if you expect to run on a Pocket PC. Probably your best bet is to look at the Web site

http://www.wintoolzone.com/dotnetcom.aspx

where there is an implementation that does not involve the shell API and does not require Common Framework (CF) support.

ShellExecute
System.Diagnostics.Process.Start
SHRT_MAX Int16.MaxValue
SHRT_MIN Int16.MinValue
short
short
.ShowWindow(SW_SHOW);
.Visible = true;
.ShowWindow(SW_HIDE);
.Visible = false;
sprintf
ToString
%d
3

int v;

String s;
s = i.ToString();
%3d
··3 (leading spaces) No equivalent in C#
%03d
003
int v = 3;

String s;
s = i.ToString("D3");
%03d
003
int v = 3;

String s;
s = i.ToString("000");
strcat
+ (applied to String objects)
strcmp .Equals String s1;
String s2;
if(s1.Equals(s))
note that the comparison operators work also; so you can write
if(s1 <> or if(s1 == s2) or if(s1 > s2) as well as !=, <= and >=.
strrchr
LastIndexOf
strstr .Contains String s;
s = "Permissions";
if(s.Contains("miss"))
... contains the substring
strtol(LPCTSTR, NULL, 16)
int.Parse(String, "x") // throws exception


strtol(LPCTSTR, NULL, 10)
int.Parse(String, "d") // throws exception
strtol(LPCTSTR, NULL, 2)

No equivalent

strtol(LPCTSTR, NULL, 8)

No equivalent



T

_T(...)
Not needed as all strings and characters are implicitly Unicode strings and characters.

U



UINT
uint
ULONG
uint
ULONGLONG
ulong
unsigned
uint
unsigned int
uint
unsigned __int64
ulong

W

WM_CLOSE
Close

PostMessage(WM_CLOSE)

Close()
WM_DESTROY
Closed event

void CMyWnd::OnDestroy() { ... }

select the Closing event for the form and type a name of the function you want, such as OnClosed

private void OnClosed(Object sender,

System.ComponentModel.EventArgs e)
{
...
}
WM_INITDIALOG
Load event
WM_MOVE
LocationChanged event
WM_SIZE
SizeChanged event
WORD
ushort

Acknowledgements

I didn't discover all of this myself. I asked a lot of questions. Thanks to many contributors, especially those in the microsoft.public.dotnet.languages.csharp and the microsoft.public.dotnet.framework.windowsforms newsgroups. Particular contributors whose information I have used in this table, in alphabetical order, include

  • Mike Burton
  • Yan-Hong Huang
  • Daniel Jebaraj
  • Gaurav Khanna
  • Mattias Sjögren
  • Geert Verkade