Click here to Skip to main content
Click here to Skip to main content

Collapsible ListViewGroup

, 27 May 2009
Rate this:
Please Sign up or sign in to vote.
Make ListView Groups Collapsible

NOTE: This only works from Windows 6 (Vista/2008server) and forward...

ListViewGroupColl02.jpg

ListViewGroupColl03.jpg

Introduction

This article discusses how to make a Group in a ListView collapse.

Background

You just need the control for some small project...

Using the Code

The ListView is very simple to use. It only has two methods added:

  • void SetGroupState(ListViewGroupState state)
  • void SetGroupFooter(ListViewGroup lvg, string footerText)

Here is the full source code of the ListView:

public class ListViewExtended : ListView
{
    private const int LVM_FIRST = 0x1000;                    // ListView messages
    private const int LVM_SETGROUPINFO = (LVM_FIRST + 147);  // ListView messages 
						      // Setinfo on Group
    private const int WM_LBUTTONUP = 0x0202;                 // Windows message 
						      // left button
    private delegate void CallBackSetGroupState
		(ListViewGroup lstvwgrp, ListViewGroupState state);
    private delegate void CallbackSetGroupString(ListViewGroup lstvwgrp, string value);
    /// <summary>
    /// Sends the specified message to a window or windows. 
    /// The SendMessage function calls the window procedure for the 
    /// specified window and does not return until the window procedure 
    /// has processed the message. 
    /// To send a message and return immediately, use the SendMessageCallback 
    /// or SendNotifyMessage function. To post a message to a thread's 
    /// message queue and return immediately, use the PostMessage or 
    /// PostThreadMessage function.
    /// </summary>
    /// <param name="hWnd">
    /// [in] Handle to the window whose window procedure will receive the message. 
    /// If this parameter is HWND_BROADCAST, the message is sent to all 
    /// top-level windows in the system, including disabled or 
    /// invisible unowned windows, overlapped windows, and pop-up windows; 
    /// but the message is not sent to child windows. 
    /// Microsoft Windows Vista and later. Message sending is subject to 
    /// User Interface Privilege Isolation (UIPI). The thread of a process 
    /// can send messages only to message queues of threads in processes of 
    /// lesser or equal integrity level.
    /// </param>
    /// <param name="uMsg">[in] Specifies the message to be sent.</param>
    /// <param name="wParam">[in] Specifies additional message-specific information.
    /// </param>
    /// <param name="lParam">[in] Type of LVGROUP, 
    /// Specifies additional message-specific information.</param>
    /// <returns>
    /// Microsoft Windows Vista and later. When a message is blocked by 
    /// UIPI the last error, retrieved with GetLastError, is set to 5 (access denied).
    /// Applications that need to communicate using HWND_BROADCAST 
    /// should use the RegisterWindowMessage function to obtain a unique message 
    /// for inter-application communication.
    /// The system only does marshalling for system messages 
    /// (those in the range 0 to (WM_USER-1)). To send other messages 
    /// (those >= WM_USER) to another process, you must do custom marshalling.
    /// If the specified window was created by the calling thread, 
    /// the window procedure is called immediately as a subroutine. 
    /// If the specified window was created by a different thread, 
    /// the system switches to that thread and calls the appropriate window procedure. 
    /// Messages sent between threads are processed only when the receiving 
    /// thread executes message retrieval code. The sending thread is blocked 
    /// until the receiving thread processes the message. 
    /// However, the sending thread will process incoming nonqueued messages 
    /// while waiting for its message to be processed. To prevent this, 
    /// use SendMessageTimeout with SMTO_BLOCK set. For more information on 
    /// nonqueued messages, see Nonqueued Messages.
    /// Windows 95/98/Me: SendMessageW is supported by the Microsoft 
    /// Layer for Unicode (MSLU). To use this, you must add certain files 
    /// to your application, as outlined in Microsoft Layer for Unicode on 
    /// Windows 95/98/ME Systems.
    /// </returns>
    [DllImport("User32.dll"), Description("Sends the specified message to a 
	window or windows. The SendMessage function calls the window procedure 
	for the specified window and does not return until the window procedure 
	has processed the message. To send a message and return immediately, 
	use the SendMessageCallback or SendNotifyMessage function.
	 To post a message to a thread's message queue and return immediately, 
	use the PostMessage or PostThreadMessage function.")]
    private static extern int SendMessage
	(IntPtr hWnd, int Msg, int wParam, LVGROUP lParam);
    private static int? GetGroupID(ListViewGroup lstvwgrp)
    {
        int? rtnval = null;
        Type GrpTp = lstvwgrp.GetType();
        if (GrpTp != null)
        {
            PropertyInfo pi = GrpTp.GetProperty("ID", BindingFlags.NonPublic | 
							BindingFlags.Instance);
            if (pi != null)
            {
                object tmprtnval = pi.GetValue(lstvwgrp, null);
                if (tmprtnval != null)
                {
                    rtnval = tmprtnval as int?;
                }
            }
        }
        return rtnval;
    }
    private static void setGrpState(ListViewGroup lstvwgrp, ListViewGroupState state)
    {
        if (Environment.OSVersion.Version.Major < 6)   //Only Vista and forward 
					// allows collapse of ListViewGroups
            return;
        if (lstvwgrp == null || lstvwgrp.ListView == null)
            return;
        if (lstvwgrp.ListView.InvokeRequired)
            lstvwgrp.ListView.Invoke(new CallBackSetGroupState(setGrpState), 
							lstvwgrp, state);
        else
        {
            int? GrpId = GetGroupID(lstvwgrp);
            int gIndex = lstvwgrp.ListView.Groups.IndexOf(lstvwgrp);
            LVGROUP group = new LVGROUP();
            group.CbSize = Marshal.SizeOf(group);
            group.State = state;
            group.Mask = ListViewGroupMask.State;
            if (GrpId != null)
            {
                group.IGroupId = GrpId.Value;
                SendMessage(lstvwgrp.ListView.Handle, 
				LVM_SETGROUPINFO, GrpId.Value, group);
                SendMessage(lstvwgrp.ListView.Handle, 
				LVM_SETGROUPINFO, GrpId.Value, group);
            }
            else
            {
                group.IGroupId = gIndex;
                SendMessage(lstvwgrp.ListView.Handle, LVM_SETGROUPINFO, gIndex, group);
                SendMessage(lstvwgrp.ListView.Handle, LVM_SETGROUPINFO, gIndex, group);
            }
            lstvwgrp.ListView.Refresh();
        }
    }
    private static void setGrpFooter(ListViewGroup lstvwgrp, string footer)
    {
        if (Environment.OSVersion.Version.Major < 6)   //Only Vista and forward 
						//allows footer on ListViewGroups
            return;
        if (lstvwgrp == null || lstvwgrp.ListView == null)
            return;
        if (lstvwgrp.ListView.InvokeRequired)
            lstvwgrp.ListView.Invoke(new CallbackSetGroupString(setGrpFooter), 
							lstvwgrp, footer);
        else
        {
            int? GrpId = GetGroupID(lstvwgrp);
            int gIndex = lstvwgrp.ListView.Groups.IndexOf(lstvwgrp);
            LVGROUP group = new LVGROUP();
            group.CbSize = Marshal.SizeOf(group);
            group.PszFooter = footer;
            group.Mask = ListViewGroupMask.Footer;
            if (GrpId != null)
            {
                group.IGroupId = GrpId.Value;
                SendMessage(lstvwgrp.ListView.Handle, 
				LVM_SETGROUPINFO, GrpId.Value, group);
            }
            else
            {
                group.IGroupId = gIndex;
                SendMessage(lstvwgrp.ListView.Handle, LVM_SETGROUPINFO, gIndex, group);
            }
        }
    }
    public void SetGroupState(ListViewGroupState state)
    {
        foreach (ListViewGroup lvg in this.Groups)
            setGrpState(lvg, state);
    }
    public void SetGroupFooter(ListViewGroup lvg, string footerText)
    {
            setGrpFooter(lvg, footerText);
    }
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_LBUTTONUP)
            base.DefWndProc(ref m);
        base.WndProc(ref m);
    }
}
/// <summary>
/// LVGROUP StructureUsed to set and retrieve groups.
/// </summary>
/// <example>
/// LVGROUP myLVGROUP = new LVGROUP();
/// myLVGROUP.CbSize // is of managed type uint
/// myLVGROUP.Mask // is of managed type uint
/// myLVGROUP.PszHeader // is of managed type string
/// myLVGROUP.CchHeader // is of managed type int
/// myLVGROUP.PszFooter // is of managed type string
/// myLVGROUP.CchFooter // is of managed type int
/// myLVGROUP.IGroupId // is of managed type int
/// myLVGROUP.StateMask // is of managed type uint
/// myLVGROUP.State // is of managed type uint
/// myLVGROUP.UAlign // is of managed type uint
/// myLVGROUP.PszSubtitle // is of managed type IntPtr
/// myLVGROUP.CchSubtitle // is of managed type uint
/// myLVGROUP.PszTask // is of managed type string
/// myLVGROUP.CchTask // is of managed type uint
/// myLVGROUP.PszDescriptionTop // is of managed type string
/// myLVGROUP.CchDescriptionTop // is of managed type uint
/// myLVGROUP.PszDescriptionBottom // is of managed type string
/// myLVGROUP.CchDescriptionBottom // is of managed type uint
/// myLVGROUP.ITitleImage // is of managed type int
/// myLVGROUP.IExtendedImage // is of managed type int
/// myLVGROUP.IFirstItem // is of managed type int
/// myLVGROUP.CItems // is of managed type IntPtr
/// myLVGROUP.PszSubsetTitle // is of managed type IntPtr
/// myLVGROUP.CchSubsetTitle // is of managed type IntPtr
/// </example>
/// <remarks>
/// The LVGROUP structure was created by Paw Jershauge
/// Created: Jan. 2008.
/// The LVGROUP structure code is based on information from Microsoft's MSDN2 website.
/// The structure is generated via an automated converter and is as is.
/// The structure may or may not hold errors inside the code, so use at own risk.
/// Reference url: http://msdn.microsoft.com/en-us/library/bb774769(VS.85).aspx
/// </remarks>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode), 
	Description("LVGROUP StructureUsed to set and retrieve groups.")]
public struct LVGROUP
{
    /// <summary>
    /// Size of this structure, in bytes.
    /// </summary>
    [Description("Size of this structure, in bytes.")]
    public int CbSize;
    /// <summary>
    /// Mask that specifies which members of the structure are valid input. 
    /// One or more of the following values:LVGF_NONENo other items are valid.
    /// </summary>
    [Description("Mask that specifies which members of the structure are valid input. 
	One or more of the following values:LVGF_NONE No other items are valid.")]
    public ListViewGroupMask Mask;
    /// <summary>
    /// Pointer to a null-terminated string that contains the header text 
    /// when item information is being set. If group information is being retrieved, 
    /// this member specifies the address of the buffer that receives the header text.
    /// </summary>
    [Description("Pointer to a null-terminated string that contains the header text 
	when item information is being set. If group information is being retrieved, 
	this member specifies the address of the buffer that 
	receives the header text.")]
    [MarshalAs(UnmanagedType.LPWStr)]
    public string PszHeader;
    /// <summary>
    /// Size in TCHARs of the buffer pointed to by the pszHeader member. 
    /// If the structure is not receiving information about a group, 
    /// this member is ignored.
    /// </summary>
    [Description("Size in TCHARs of the buffer pointed to by the pszHeader member. 
	If the structure is not receiving information about a group, 
	this member is ignored.")]
    public int CchHeader;
    /// <summary>
    /// Pointer to a null-terminated string that contains the footer text 
    /// when item information is being set. If group information is being retrieved, 
    /// this member specifies the address of the buffer that receives the footer text.
    /// </summary>
    [Description("Pointer to a null-terminated string that contains the 
	footer text when item information is being set. 
	If group information is being retrieved, this member specifies the 
	address of the buffer that receives the footer text.")]
    [MarshalAs(UnmanagedType.LPWStr)]
    public string PszFooter;
    /// <summary>
    /// Size in TCHARs of the buffer pointed to by the pszFooter member. 
    /// If the structure is not receiving information about a group, 
    /// this member is ignored.
    /// </summary>
    [Description("Size in TCHARs of the buffer pointed to by the pszFooter member. 
	If the structure is not receiving information about a group, 
	this member is ignored.")]
    public int CchFooter;
    /// <summary>
    /// ID of the group.
    /// </summary>
    [Description("ID of the group.")]
    public int IGroupId;
    /// <summary>
    /// Mask used with LVM_GETGROUPINFO (Microsoft Windows XP and Windows Vista) 
    /// and LVM_SETGROUPINFO (Windows Vista only) to specify which flags in the 
    /// state value are being retrieved or set.
    /// </summary>
    [Description("Mask used with LVM_GETGROUPINFO 
	(Microsoft Windows XP and Windows Vista) and LVM_SETGROUPINFO 
	(Windows Vista only) to specify which flags in the state value are 
	being retrieved or set.")]
    public int StateMask;
    /// <summary>
    /// Flag that can have one of the following values:LVGS_NORMALGroups are expanded, 
    /// the group name is displayed, and all items in the group are displayed.
    /// </summary>
    [Description("Flag that can have one of the following values:LVGS_NORMAL 
	Groups are expanded, the group name is displayed, and all items 
	in the group are displayed.")]
    public ListViewGroupState State;
    /// <summary>
    /// Indicates the alignment of the header or footer text for the group. 
    /// It can have one or more of the following values. Use one of the header flags. 
    /// Footer flags are optional. 
    /// Windows XP: Footer flags are reserved.LVGA_FOOTER_CENTERReserved.
    /// </summary>
    [Description("Indicates the alignment of the header or footer text for the group. 
	It can have one or more of the following values. Use one of the header flags. 
	Footer flags are optional. Windows XP: 
	Footer flags are reserved.LVGA_FOOTER_CENTERReserved.")]
    public uint UAlign;
    /// <summary>
    /// Windows Vista. Pointer to a null-terminated string that contains the 
    /// subtitle text when item information is being set. If group information 
    /// is being retrieved, this member specifies the address of the buffer that 
    /// receives the subtitle text. This element is drawn under the header text.
    /// </summary>
    [Description("Windows Vista. Pointer to a null-terminated string that 
    	contains the subtitle text when item information is being set. 
    	If group information is being retrieved, this member specifies the 
    	address of the buffer that receives the subtitle text. 
    	This element is drawn under the header text.")]
    public IntPtr PszSubtitle;
    /// <summary>
    /// Windows Vista. Size, in TCHARs, of the buffer pointed to by the 
    /// pszSubtitle member. If the structure is not receiving information 
    /// about a group, this member is ignored.
    /// </summary>
    [Description("Windows Vista. Size, in TCHARs, of the buffer pointed 
    	to by the pszSubtitle member. If the structure is not receiving information 
    	about a group, this member is ignored.")]
    public uint CchSubtitle;
    /// <summary>
    /// Windows Vista. Pointer to a null-terminated string that contains the text 
    /// for a task link when item information is being set. If group information 
    /// is being retrieved, this member specifies the address of the buffer 
    /// that receives the task text. This item is drawn right-aligned opposite 
    /// the header text. When clicked by the user, 
    /// the task link generates an LVN_LINKCLICK notification.
    /// </summary>
    [Description("Windows Vista. Pointer to a null-terminated string that 
    	contains the text for a task link when item information is being set. 
    	If group information is being retrieved, this member specifies the address 
    	of the buffer that receives the task text. This item is drawn right-aligned 
    	opposite the header text. When clicked by the user, the task link 
    	generates an LVN_LINKCLICK notification.")]
    [MarshalAs(UnmanagedType.LPWStr)]
    public string PszTask;
    /// <summary>
    /// Windows Vista. Size in TCHARs of the buffer pointed to by the pszTask member. 
    /// If the structure is not receiving information about a group, 
    /// this member is ignored.
    /// </summary>
    [Description("Windows Vista. Size in TCHARs of the buffer pointed to 
    	by the pszTask member. If the structure is not receiving information 
    	about a group, this member is ignored.")]
    public uint CchTask;
    /// <summary>
    /// Windows Vista. Pointer to a null-terminated string that contains the 
    /// top description text when item information is being set. 
    /// If group information is being retrieved, this member specifies the address 
    /// of the buffer that receives the top description text. 
    /// This item is drawn opposite the title image when there is a title image, 
    /// no extended image, and uAlign==LVGA_HEADER_CENTER.
    /// </summary>
    [Description("Windows Vista. Pointer to a null-terminated string that 
    	contains the top description text when item information is being set. 
    	If group information is being retrieved, this member specifies the address 
    	of the buffer that receives the top description text. 
    	This item is drawn opposite the title image when there is a title image, 
    	no extended image, and uAlign==LVGA_HEADER_CENTER.")]
    [MarshalAs(UnmanagedType.LPWStr)]
    public string PszDescriptionTop;
    /// <summary>
    /// Windows Vista. Size in TCHARs of the buffer pointed to by the 
    /// pszDescriptionTop member. If the structure is not receiving information 
    /// about a group, this member is ignored.
    /// </summary>
    [Description("Windows Vista. Size in TCHARs of the buffer pointed to 
    	by the pszDescriptionTop member. If the structure is not receiving 
    	information about a group, this member is ignored.")]
    public uint CchDescriptionTop;
    /// <summary>
    /// Windows Vista. Pointer to a null-terminated string that contains the 
    /// bottom description text when item information is being set. 
    /// If group information is being retrieved, this member specifies the address 
    /// of the buffer that receives the bottom description text. 
    /// This item is drawn under the top description text when there is a title image, 
    /// no extended image, and uAlign==LVGA_HEADER_CENTER.
    /// </summary>
    [Description("Windows Vista. Pointer to a null-terminated string that 
    	contains the bottom description text when item information is being set. 
    	If group information is being retrieved, this member specifies the address 
    	of the buffer that receives the bottom description text. 
    	This item is drawn under the top description text when there is a title image, 
    	no extended image, and uAlign==LVGA_HEADER_CENTER.")]
    [MarshalAs(UnmanagedType.LPWStr)]
    public string PszDescriptionBottom;
    /// <summary>
    /// Windows Vista. Size in TCHARs of the buffer pointed to by the 
    /// pszDescriptionBottom member. If the structure is not receiving 
    /// information about a group, this member is ignored.
    /// </summary>
    [Description("Windows Vista. Size in TCHARs of the buffer pointed 
    	to by the pszDescriptionBottom member. If the structure is not receiving 
    	information about a group, this member is ignored.")]
    public uint CchDescriptionBottom;
    /// <summary>
    /// Windows Vista. Index of the title image in the control imagelist.
    /// </summary>
    [Description("Windows Vista. Index of the title image in the control imagelist.")]
    public int ITitleImage;
    /// <summary>
    /// Windows Vista. Index of the extended image in the control imagelist.
    /// </summary>
    [Description("Windows Vista. Index of the extended image in the control imagelist.")]
    public int IExtendedImage;
    /// <summary>
    /// Windows Vista. Read-only.
    /// </summary>
    [Description("Windows Vista. Read-only.")]
    public int IFirstItem;
    /// <summary>
    /// Windows Vista. Read-only in non-owner data mode.
    /// </summary>
    [Description("Windows Vista. Read-only in non-owner data mode.")]
    public IntPtr CItems;
    /// <summary>
    /// Windows Vista. NULL if group is not a subset. 
    /// Pointer to a null-terminated string 
    /// that contains the subset title text when item information is being set. 
    /// If group information is being retrieved, this member specifies the address 
    /// of the buffer that receives the subset title text.
    /// </summary>
    [Description("Windows Vista. NULL if group is not a subset. 
    	Pointer to a null-terminated string that contains the subset title text 
    	when item information is being set. If group information is being retrieved, 
    	this member specifies the address of the buffer that 
	receives the subset title text.")]
    public IntPtr PszSubsetTitle;
    /// <summary>
    /// Windows Vista. Size in TCHARs of the buffer pointed to by the 
    /// pszSubsetTitle member. 
    /// If the structure is not receiving information about a group, 
    /// this member is ignored.
    /// </summary>
    [Description("Windows Vista. Size in TCHARs of the buffer pointed to 
    	by the pszSubsetTitle member. If the structure is not receiving 
    	information about a group, this member is ignored.")]
    public IntPtr CchSubsetTitle;
}
public enum ListViewGroupMask
{
    None = 0x00000,
    Header = 0x00001,
    Footer = 0x00002,
    State = 0x00004,
    Align = 0x00008,
    GroupId = 0x00010,
    SubTitle = 0x00100,
    Task = 0x00200,
    DescriptionTop = 0x00400,
    DescriptionBottom = 0x00800,
    TitleImage = 0x01000,
    ExtendedImage = 0x02000,
    Items = 0x04000,
    Subset = 0x08000,
    SubsetItems = 0x10000
}
public enum ListViewGroupState
{
    /// <summary>
    /// Groups are expanded, the group name is displayed, 
    /// and all items in the group are displayed.
    /// </summary>
    Normal = 0,
    /// <summary>
    /// The group is collapsed.
    /// </summary>
    Collapsed = 1,
    /// <summary>
    /// The group is hidden.
    /// </summary>
    Hidden = 2,
    /// <summary>
    /// Version 6.00 and Windows Vista. The group does not display a header.
    /// </summary>
    NoHeader = 4,
    /// <summary>
    /// Version 6.00 and Windows Vista. The group can be collapsed.
    /// </summary>
    Collapsible = 8,
    /// <summary>
    /// Version 6.00 and Windows Vista. The group has keyboard focus.
    /// </summary>
    Focused = 16,
    /// <summary>
    /// Version 6.00 and Windows Vista. The group is selected.
    /// </summary>
    Selected = 32,
    /// <summary>
    /// Version 6.00 and Windows Vista. The group displays only a portion of its items.
    /// </summary>
    SubSeted = 64,
    /// <summary>
    /// Version 6.00 and Windows Vista. The subset link of the group has keyboard focus.
    /// </summary>
    SubSetLinkFocused = 128,
}

Hope you can use it...

History

  • 27th May, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author


Comments and Discussions

 
BugIt throws an exception about not match SendMessage between x86 and x64 PinmemberMarksion4-Jun-14 17:37 
GeneralRe: It throws an exception about not match SendMessage between x86 and x64 PinmemberMarksion4-Jun-14 17:43 
GeneralRe: It throws an exception about not match SendMessage between x86 and x64 PinpremiumPaw Jershauge5-Jun-14 10:45 
Questionhow to remove the group lines PinmemberMember 1029340813-Apr-14 21:07 
QuestionModifying the Group Footer PinmemberRip_Diggler10-Feb-14 12:48 
AnswerRe: Modifying the Group Footer PinprofessionalPaw Jershauge10-Feb-14 22:48 
GeneralRe: Modifying the Group Footer PinmemberRip_Diggler10-Feb-14 23:45 
GeneralRe: Modifying the Group Footer PinprofessionalPaw Jershauge11-Feb-14 20:45 
QuestionCollapse Group when Expand a Group PinmemberMember 1039632124-Jan-14 10:22 
QuestionState changed event? PinmemberEvan W. Mulawski29-Oct-13 9:17 
AnswerRe: State changed event? PinprofessionalPaw Jershauge30-Oct-13 22:41 
GeneralRe: State changed event? PinmemberMember 97233525-Nov-13 5:55 
GeneralRe: State changed event? PinprofessionalPaw Jershauge5-Nov-13 10:41 
QuestionMultiple ListViews PinmemberEvan W. Mulawski29-Oct-13 3:24 
AnswerRe: Multiple ListViews PinprofessionalPaw Jershauge29-Oct-13 3:34 
GeneralRe: Multiple ListViews PinmemberEvan W. Mulawski29-Oct-13 4:50 
QuestionAttempted to read or write protected memory [modified] PinmemberChristopher Finch25-Sep-13 5:29 
AnswerRe: Attempted to read or write protected memory PinprofessionalPaw Jershauge26-Sep-13 2:34 
QuestionHow to add Collapsible groups in the collapsed state Pinmemberwcb2@cornell.edu19-Jun-13 9:51 
AnswerRe: How to add Collapsible groups in the collapsed state PinmemberPaw Jershauge19-Jun-13 20:37 
QuestionWorks great after modification for C# Pinmemberheavylifting18-May-12 9:21 
AnswerRe: Works great after modification for C# Pinmemberjeffvb7366418-Dec-12 4:26 
GeneralRe: Works great after modification for C# PinmemberMember 1020774215-Aug-13 5:21 
QuestionIs it stable ? PinmemberMazen el Senih3-Apr-12 4:07 
AnswerRe: Is it stable ? PinmemberPaw Jershauge3-Apr-12 4:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 27 May 2009
Article Copyright 2009 by Paw Jershauge
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid