Friday, August 21, 2009

BeginUpdate() and EndUpdate() when populating ListView/TreeView

Use of BeginUpdate() and EndUpdate() when populating ListView/TreeView

Often a listview control in C# needs to be populated with large amount of data (bulky operations, for example, adding 1000 items). For every item added in the Listview, the control will redraw itself, thus greatly reduce the overall performance.

However, using the two methods BeginUpdate() and EndUpdate() while performing these bulk operations gives significant advantage. A call to BeginUpdate() can be made before adding/deleting/clearing items. This will stop any paint messages being sent or processed. Once the operation is done, the EndUpdate() can then be called.


I have logged the time taken of the above code (i.e. adding 1000 items in a ListView for each button click) with and without the BeginUpdate/EndUpdate method calls using a test application.


The use BeginUpdate() and EndUpdate populates the control with a constant duration and much better performance. Amazing, isn't it?

SuspendLayout and ResumeLayou

SuspendLayout and ResumeLayout

The SuspendLayout and ResumeLayout are methods used in order to prevent conflict when events pertaining to placing and moving controls and setting their attributes are thrown.

You would for instance perhaps like to add controls onto a panel or a window programatically .. what you must do is suspend the control you are adding other controls to and after you are done adding ,you then can resume your layout by calling the ResumeLayout method.

say for example you have a panel and would like to add some controls to it here is how you would do it using a method :

private void AddSomeControls()
{
// Suspend the panel layout and add two buttons.
this.SuspendLayout();
TextBox txtOne = new TextBox();
txtOne.Location = new Point(10, 10);
txtOne.Size = new Size(75, 25);
txtOne.Text = "Textbox addded";

Button btnMyButton = new Button();
btnMyButton.Location = new Point(90, 10);
btnMyButton.Size = new Size(75, 25);
btnMyButton.Text = "Just been Added";

this.Controls.AddRange(new Control[]{txtOne, btnMyButton});
this.ResumeLayout();
}

Thursday, August 20, 2009

Always Ask for Reboot when installing Visual Studio .NET

http://support.microsoft.com/kb/891402.

You receive a "Setup has detected that another program requires the computer to reboot" message when you install Visual Studio 2005 or Visual Studio .NET


To resolve this issue, use one of the following methods.

Method 1: Modify the Windows Registry

  1. Click Start, click Run, type regedit, and then click OK.
  2. Locate and then click the following registry key:
    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager
  3. In the right pane, right-click PendingFileRenameOperations, and then click Delete.

    Note If the PendingFileRenameOperations value does not exist, exit Registry Editor, and then use method 2.
  4. Exit Registry Editor. Then, try to install Visual Studio 2005 or Visual Studio .NET again.

Method 2: Install the Prerequisites components from the Visual Studio 2005 or Visual Studio .NET CD

  1. Cancel the current installation.
  2. Insert the Visual Studio 2005 or Visual Studio .NET Prerequisites CD into your CD drive.
  3. Click Start, click Run, type drive:\setup.exe, and then click OK.

    In this step, drive is the letter of your CD drive.
  4. Click No when you receive the following message:
    Visual Studio .NET Prerequisites Warning - Setup on CD1 should be used to launch Visual Studio 2005 or Visual Studio .NET Prerequisites setup so that only the necessary components are installed. Click Yes to exit and change CDs. Click No to install all available components.
  5. After the installation of the Prerequisites components is complete, insert Visual Studio 2005 or Visual Studio .NET CD 1, and then install Visual Studio 2005 or Visual Studio .NET.

Wednesday, August 12, 2009

Localization of .Net applications

Localization of .Net applications


These last days, I had the change to mess up with the Localization infrastructure inside Visual Studio 2008. I must realize it´s the first time I seriously get into this issue, and I´m impressed with the work done on it.

When one needs to give an application multi-language support, the first temptation (as old-time programmers) is to build up some sort of tables with strings, each one for each language. That´s more or less what the Localization system will do, but with the following extra features:

A comfortable visual editor

We have a comfortable visual editor to manage the string tables, like this one, giving you the chance to set comments to each entry.

55

Culture infrastructure-ready

The system offers automatic integration with the Culture infrastructure of each application. To get more info about this, check:

  • System.Threading.Thread.CurrentThread.CurrentCulture
  • System.Threading.Thread.CurrentThread.CurrentUICulture

This gives you automatic support for different numbering and date formats, etc.

Integrated with the Visual Studio Designer

To start localizing, you just have to open the design view of a form or control and set the Localizable property of any form or control to True to generate the default resource (.resx) file. From then, each time you 51select a new language in the combo (and change any property), a newer resX file is generated to reflect the change. Of course, all this files are perfectly managed by the Solution Explorer as a part of the form or the control.

Once a form is localizable, each time we change the current language, the designer automatically updates the design view to show the appearance of the form or control in that language. ResX files are also maintained automatically.

Localization of the entire looking (not only texts)

You can make specific versions of each control or user interface for each language, including control positions, dimensions, colors, anything...

This is specially relevant because many times is not enough with just replacing texts. The translation of a text may have a remarkable different length in other languages. In this case we would have to resize the label containing the string, and maybe relocate other controls in the form, as in the following picture, where you can see two version of the same form, for two different languages.

52

Note: Text strings are saved directly in the resx files accompanying the Form1.cs class, but other properties, like dimensiones, locations, etc, are saved in other place. When you compile your solution, in the output directory you will find additional folders with culture-specific names, like “en-US”, etc. This folders will contain additional DLLs created automatically by VisualStudio, one for each localizable assembly. Inside this DLLs, you will find resources defining the appearance of the form´s controls.

Additional String Tables

We have talked about resource files handled by the Designer to reflect the appearance changes of forms for different languages but, what happens with the message shown in a MessageBox? This is not something we can manage in a design view.

A solution is to insert additional .resx files to the project. We can make them “embedded resources” and easily access them with the ResourceManager class directly, although I must tell you this is not the best solution (see next chapter). Of course, there´s no designer to handle this tables, so they will have to be maintained manually (using the visual editor).

To include a complete collection of additional string tables, you can start with the default resx file (for the default language). Give it any name you like, for example: “LocalizedStrings.resx” (just click your project with the right button, and select “Add New item”, and then “Resource File” as the item type). Afterwards, you can add any other language version for that file, using the same name with the culture-specific string representation before the extension. Some examples:

  • LocalizedStrings.en-US.resx
  • LocalizedStrings.es-ES.resx
  • LocalizedStrings.fr-FR.resx

Strongly-typed access to string tables

This is also a very important feature, because once you have your string table up and running, you need to get access to it from your code. You can do so directly through the ResourceManager.GetString() method, but this is definitely not a good idea. Mostly because you will have to give it the name of the entry you are looking for, in the form of a simple "string”.

This means that you will have no Intellisense support (you will have to look the name of the properties in the table by yourself), and if any property name is changed later, there will be no compiling warning or error. This means that if you are not extremely careful for the rest of your application´s life, you won´t notice the mistake until runtime, and this is extremely bug-prone.

The solution is to make a strongly-typed class, which includes code properties to access each entry in the table. Of course, it would be a non sense if we had to make them manually (it would be the same as accessing though the ResourceManager class). Thankfully VisualStudio includes a tool to take care of such task: the ResXFileCodeGenerator. To make VisualStudio invoke this tool, you will have to put it´s name (“ResXFileCodeGenerator”, without quotes) in the Custom Tool property of the default resx file. This is important: IN THE DEFAULT resx file. This means that, if you have LocalizedStrings.resx (with the default language, spanish for example), and LocalizedStrings.en-US.resx, you have to set the custom tool to the first one.

53

When you do so, a new “.cs” file will be generated for you (below the resx file) containing the strongly-typed class. Though the maintenance of this class is automatic, you can force a refresh anytime you want by clicking the resx file with the right button and selecting “Run custom tool”.

From now, you can access your strint table texts with Intellisensed, strongly-typed, in-code properteties like:

“text = LocalizedStrings.strWarningCaption”

APPENDIX A: Making an automatically-generated, strongly-typed class to be PUBLIC

By default, strongly-typed classes generated with the ResXFileCodeGenerator tool are INTERNAL. This means that they will only be accessible inside your assembly.

To make one of this classes public, you cannot just change its code as it will be re-generated by the tool on next rebuild. You have to change the custom tool, selecting the PublicResXFileCodeGenerator instead of the previous one. It will make them public for you.

You can also do this “double-clicking” your string table (to enter the visual editor view), and selecting the PUBLIC modifier in the upper part of the screen (this actually changes the custom tool as explained above).

Well, it´s been long, but hope it helps someone. However, this is my first approach to Localization and therefore, for sure there will be people with deeper knowledge on this issue. Please feel free to complete (or correct) this tutorial with comments and suggestions.

Tutorial: Visual Studio 2008 Obfuscating with Dotfuscator

Tutorial: Visual Studio 2008 Obfuscating with Dotfuscator

In this
tutorial I'll teach you what Obfuscating is, why you should use it for your .NET products and how to do it with Dotfuscator. Dotfuscator comes with Visual Studio 2008 Professional Edition, if you don't have it you can still buy it as a standalone. Lets start by looking at what obfuscating is.

What is Obfuscating?
Obfuscated code is code intentionally (mostly) created hard to read, however, poor programming skills and/or little knowledge of standards can cause a programmer to create obfuscated code without even knowing it. There are some languages more prone to obfuscated code than others such as C and C++.

Example:
Code:
double h[2]; int main(_, v) char *v; int _; { int a = 0; char f[32]; h[2%2] =

21914441197069634153456391018824026170709523170177760997320759459436800394073
07212501870429040900672146338833938303659439237740635160500855813030357492372
682887858054616489605441589829740433065995076650229152079883597110973562880.0 00000; h[4%3] =

1867980801.569119; switch (_) { case 0: break; default: main(0,(char *)h); break; } }
Why Obfuscate?
Obfuscating can make code very difficult to understand or even reverse engineer. Programs written in .NET or Java are easy to decompile to full source code as though the cracker is looking at the original code you wrote in your IDE. These reverse engineering programs are freely available on the internet making it easy for anyone to see your entire source code. While still readable, obfuscating makes the code harder to read creating some security for your applications.

Getting Started
In this tutorial we will be obfuscating a the Visual Studio 2008 C# program we created in this tutorial: http://forum.codecall.net/c-tutorial...-tutorial.html, however you can use whatever project you desire. We will use the debug build here.

Step 1
Load Visual Studio 2008 (Start/Microsoft Visual Studio 2008/Microsoft Visual Studio 2008). You don't have to select a project but VS2008 must be loaded before you can launch dotfuscator.

Step 2
Load dofuscator (Start/Microsoft Visual Studio 2008/Visual Studio Tools/Dotfuscator Community Edition). If you do not have VS2008 loaded you will see an error:



Step 3
You may be asked to Register. Click "No, I don't want to Register" or "Yes, Register Now", your option but this tutorial will not cover that. At the next screen, "Select Project Type", click "Create New Project" and press "OK".



Step 4
Click "Browse and add assembly to list" icon (below Input Assemblies: - the open folder icon). Click Browse. Navigate to your project executable file. Using the C# Hello World project you will find it located in "My Documents/Visual Studio 2008/Projects/HelloWorld/HelloWorld/bin/debug/HelloWorld.exe". Click "OK".

This image has been resized. Click this bar to view the full image. The original image is sized 600x430.


Step 5
Goto "File/Build or press Ctrl+B. You will be asked to save your project, press "Yes". Enter "HelloWorldC#" or a suitable name for your project. You should see:



Step 6
Your project has now been obfuscated. You can find the executable in "My Documents/Dotfuscated/HelloWorld.exe". This is the executable you want to package in the installation file, however, you will want to use a build release instead of a debug release. Click on "Output" tab to see what was obfuscated:

This image has been resized. Click this bar to view the full image. The original image is sized 600x430.

Quote:
Originally Posted by afkhami View Post
so how do you package your software protected with Dotfuscator?
The Enhanced Community Edition will integrate with Visual Studio, letting you use the Dotfuscated project output for your Setup project's input.

Friday, July 31, 2009

How to send Emails with Gmail Account with Attachment

First you need to change the settings in your Gmail account

step 1: Login to your Gmail Account with from which you will be sending e-mails.

step 2: Go to Gmail settings then click on Forwarding and POP/IMAP

step 3: In IMAP Access Check Enable IMAP

step 4: Then go to your application use below code

void AttachFile(string attachmentFile)
{
System.Net.Mail.MailAddress toAddress = new System.Net.Mail.MailAddress("your-reciving-email@gmail.com");

System.Net.Mail.MailAddress fromAddress = new System.Net.Mail.MailAddress("fromAddress@yahoo.com");
System.Net.Mail.MailMessage mm = new System.Net.Mail.MailMessage(fromAddress, toAddress);

mm.Subject = "Email Subject";
System.Net.Mail.Attachment mailAttachment = new System.Net.Mail.Attachment(printScreen);
mm.Attachments.Add(mailAttachment);
mm.IsBodyHtml = true;

mm.BodyEncoding = System.Text.Encoding.UTF8;
sendMail(mm);
}

NOTE1:
How do I specify multiple recipients? Printer Friendly
Because the
To, CC, and Bcc properties are MailAddress collections, to add
additional recipients, all we need to do is call .Add(...) on the
respective properties.


Below is an example that demonstrates adding multiple To, CC, and Bcc addresses.

[ C# ]

static void MultipleRecipients()
{
//create the mail message
MailMessage mail = new MailMessage();


//set the addresses
//to specify a friendly 'from' name, we use a different ctor
mail.From = new MailAddress("me@mycompany.com"
, "Steve James");

//since the To,Cc, and Bcc accept addresses, we can use the same technique as the From address
//since the To, Cc, and Bcc properties are collections, to add multiple addreses, we simply call .Add(...) multple times
mail.To.Add("you@yourcompany.com");
mail.To.Add("you2@yourcompany.com");
mail.CC.Add("cc1@yourcompany.com");
mail.CC.Add("cc2@yourcompany.com");
mail.Bcc.Add("blindcc1@yourcompany.com");
mail.Bcc.Add("blindcc2@yourcompany.com");

//set the content
mail.Subject = "This is an email";
mail.Body = "this is the body content of the email.";

//send the message
SmtpClient smtp = new SmtpClient("127.0.0.1");
smtp.Send(mail);

string sendMail(System.Net.Mail.MailMessage mm)
{
try
{
string smtpHost = "smtp.gmail.com";

string userName = "your-email-address@gmail.com";//sending Id
string password = "your-password";
System.Net.Mail.SmtpClient mClient = new System.Net.Mail.SmtpClient();

mClient.Port = 587;
mClient.EnableSsl = true;
mClient.UseDefaultCredentials = false;
mClient.Credentials = new NetworkCredential(userName, password);

mClient.Host = smtpHost;
mClient.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network;
mClient.Send(mm);
}
catch (Exception ex)

{
MessageBox.Show(ex.Message);
}
}
NOTE2

Have to replace \r\n with html line break "br"
in order to show paragraph and line breaks.
string sNewBody = textBoxBody.Text.Replace("\r\
n", "
");
//mm.Body = textBoxBody.Text;
mm.Body = sNewBody;

Monday, July 27, 2009

Showing a Separate Tooltip for Each Item in a ListBox

http://www.codeproject.com/KB/combobox/ToolTipForListBox.aspx

Introduction

ListBox is a very common control used in Windows Forms. Most often we would need to show some information on each item in the ListBox when the user places the mouse pointer above that item. Sadly there is no straight forward way to do that. The reasons are:

  1. There is no separate mouse hover event for individual items in the list.
  2. As each item in the list is not a control (of course!!), we cannot set a tool tip for items of the list separately. A tooltip can be set only for the ListBox as such.

But don't give up!! If there is a will, there is a way…

Using the Code

The steps required are explained below:

  1. Drag and drop a ToolTip control into the form having the ListBox. I am calling it ListToolTip.

  2. We have to know the ToolTip text for each item in the ListBox. I have an array of strings as items in the ListBox. So I maintain the ToolTip text for each item in another string array. So if I know the index of the Item in the array of items, I can get the corresponding ToolTip text from the ToolTip array using the same index.

  3. Create a handler for the Mouse Move event of the ListBox. Calculate the index of the item over which the mouse pointer is placed now. (This is the tricky part!!)

    1. Calculate the Offset of the item from the top of the list box. This can be obtained by dividing the Y coordinate of the house position by the height of each item. Height of each item is a property of the list box:

      Collapse
      itemIndex = e.Y / objListBox.ItemHeight; 
    2. But the topmost item will not always be the first item in the list. If there is a scrollbar and user scrolls down, then the topmost item visible will change. This means that the index obtained above is not the index of the required item in the list of items. Fortunately ListBox has another property called TopIndex. If we add the TopIndex with the index obtained in the above step, then we will get the correct index.

      Collapse
      itemIndex += objListBox.TopIndex;
  4. Now we know the index of the item over which the mouse pointer is placed. Just take the Tooltip of that item from the Tooltip array and associate it with the ToolTip control.

    Collapse
     if ((itemIndex >= 0) && (itemIndex < m_arrItemToolTips.Length))
    {
    ListToolTip.SetToolTip(objListBox, m_arrItemToolTips[itemIndex]);

    }
  5. If the calculated index is out of the range of valid indexes, hide the ToolTip.

    Collapse
      else
    {
    ListToolTip.Hide(objListBox);
    }

The entire code for the mouse move event handler is given below:

Collapse
private void ItemsListBox_MouseMove(object sender, MouseEventArgs e)

{
try
{
ListBox objListBox = (ListBox)sender;
int itemIndex = -1;
if (m_arrItemToolTips != null)

{
if (objListBox.ItemHeight != 0)
{
itemIndex = e.Y / objListBox.ItemHeight;

itemIndex += objListBox.TopIndex;
}
if ((itemIndex >= 0) && (itemIndex < m_arrItemToolTips.Length))

{
ListToolTip.SetToolTip(objListBox, m_arrItemToolTips[itemIndex]);
}
else
{
ListToolTip.Hide(objListBox);

}
}
}
catch (Exception ex)
{
}
}

Thursday, July 16, 2009

C#: Resize An Image While Maintaining Aspect Ratio and Maximum Height


Function works, but need 2 changes:

1. Set image properties, otherwise resized pics lose image information like date, camera type etc.
2. Need to specify image type when saving
// This allows us to resize the image. It prevents skewed images and
// also vertically long images caused by trying to maintain the aspect
// ratio on images who's height is larger than their width

public void ResizeImage(string OriginalFile, string NewFile, int NewWidth, int MaxHeight, bool OnlyResizeIfWider)
{
System.Drawing.Image FullsizeImage = System.Drawing.Image.FromFile(
OriginalFile);


// Prevent using images internal thumbnail

FullsizeImage.RotateFlip(
System.Drawing.RotateFlipType.Rotate180FlipNone);

FullsizeImage.RotateFlip(
System.Drawing.RotateFlipType.Rotate180FlipNone);


if (OnlyResizeIfWider)
{
if (FullsizeImage.Width <= NewWidth) { NewWidth = FullsizeImage.Width; } } int NewHeight = FullsizeImage.Height * NewWidth / FullsizeImage.Width; if (NewHeight > MaxHeight)
{

// Resize with height instead

NewWidth = FullsizeImage.Width * MaxHeight / FullsizeImage.Height;

NewHeight = MaxHeight;
}


System.Drawing.Image NewImage = FullsizeImage.GetThumbnailImage(NewWidth, NewHeight, null, IntPtr.Zero);

//save all the photo properties
foreach (PropertyItem e in FullsizeImage.PropertyItems)
{
NewImage.SetPropertyItem(e);
}

// Save resized picture
NewImage.Save(NewFile, ImageFormat.Jpeg);
// Clear handle to original file so that we can overwrite it if necessary

FullsizeImage.Dispose();


}

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