Click here to Skip to main content
15,884,099 members
Articles / Desktop Programming / Windows Forms
Article

It is an example of creating custom controls for getting graphic data from database.

Rate me:
Please Sign up or sign in to vote.
2.32/5 (9 votes)
15 Apr 20073 min read 39.7K   782   23   3
This is example of creating custom controls for getting graphic data from database. I'm going to make control and then, test my control in a Windows application. I have developed some custom properties for my control.

It is an example of creating custom controls for getting graphic data from database.

Introduction

This is example of creating custom controls for getting graphic data from database. I'm going to make control and then, test my control in a Windows application. I have developed some custom properties for my control.

In standard component bar, we don't have any component for getting graphic data from database like Gallery. But we can create component themselves using some standard component.

This is a simple C# control to get images from data base.

Screenshot - demo.jpg

The first time I started to work with .NET .I saw that standard component's panel doesn't have a control which could be shown images from database as gallery and I decided to create my own control...

(Used material NET 2.0 Windows Forms and Custom Controls in C# by Matthew MacDonald)

Using the code

The project is designed as a custom control. In article "PictureBox with zooming and scrolling", there is described how to install and use such a control, or have a look at the different articles about that topic in CodeProject, for example "Writing your Custom Control: step by step" by Alexandr Khilov.

Methods / Properties

The control provides seven designer related properties:

Screenshot - property.jpg

  • DataSource: Data source of data base.

  • PicFieldName: Data field of Image.

  • PicTitleFieldName: Data field of Image's title.

  • Dimension: Space among columns.

  • IconHeight: Icon's height

  • IconWidth: Icon's width

  • Spacing: Space among rows.

Events

There is one event in the class onDoubleClick. The event is arisen if the mouse left button clicks twice and show full size image.

Helper classes

The class exPictureBox is extention of PictureBox used for saveing some propertis of image.

The class PictureSelectedEventArgs is derived from System.EventArgs. I used it for selection event of image properties. The class PictureDoubleClickEventArgs is derived from System.EventArgs used it for double click event of image properties.

The class PicFieldConverter is derived from System.ComponentModel.StringConverter. I used it to convert properties.

The class PicNameFieldConverter is derived from System.ComponentModel.StringConverter used it to convert properties.

The class NamedImage is simple class for saveing name propertis of image.

In the code you can see how to work with classes.

Building the Control

1. Open the Visual Studio and create a new project. Your project must be based on the Windows Control Library template. Call your project PicDB and click OK.

2. Once you have your project open, delete the UserControl from the project.

3. Now go to the 'Project' menu: Project->Add User Control... and select the Custom Control template there. 'Custom Control' is what we need in this case. You call it PicDB and click OK. A new Custom control has been added to your project.

4. The first thing we must do here is to change the base class of the PicDB:

Override the following line:

public partial class PicDB : UserControl

By this one:

public class PicDB : UserControl

Add attribute for our control (Before prepare icon for our control and named it _PicDB.bmp)

[ToolboxBitmap(typeof(PicDB), "_PicDB.bmp")]

5. Set necessary namespaces.

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.Threading;

6. Now create some custom properties and auxiliary classes for our control. This is done by inserting the following code inside the PicDB class:

private IContainer components;
// Each picture box will be a square of dimension X dimension pixels.
private int dimension;
// The space between the images and the top, left, and right sides.
private int border = 5;

private int indexOfImage = 0;
// The space between each image.
private int spacing;

private Panel pnlPictures;
// The images that were found in the selected table.
private ArrayList images = new ArrayList();
private ArrayList exImages = new ArrayList();

private DataSet dataset = null;
private DataColumnCollection dataColumn = null;

private BindingSource bindingSource = null;

private string imageTitle = "";
private int iconHeight;
private int iconWidth;

private int ControlColumn;
private ToolTip toolTipImage;
private Panel pnToolBar;
private ToolBar toolBar;
private ImageList imgList;
private ToolBarButton tbntNext;
private ToolBarButton tbntPrior;
private ToolBarButton tbntLast;
private ToolBarButton tbntFirst;
private OpenFileDialog openFileDialog;

private delegate void PictureSelectedDelegate(object sender, PictureSelectedEventArgs e);

private event PictureSelectedDelegate PictureSelected;

private class PictureSelectedEventArgs : EventArgs
{
    public string imgName;
    public Image Image;
    public PictureSelectedEventArgs(String imageName, Image image)
    {
        imgName = imageName;
        Image = image;
    }
}

private class exPictureBox : PictureBox
{
    private Image img = null;

    public Image ImageFullSize
    {
        get { return img; }
        set
        {
            img = value;
        }
    }

}

private class PictureDoubleClickEventArgs : EventArgs
{
    public string imgName;
    public Image Image;
    public PictureDoubleClickEventArgs(String imageName, Image image)
    {
        imgName = imageName;
        Image = image;
    }
}

private exPictureBox picSelected;

//Click on pictures
private void onPicClick(object sender, EventArgs e)
{
    // Clear the border style from the last selected picture box.
    if (sender != picSelected)
    {
        if (picSelected != null)
        {
            picSelected.BorderStyle = BorderStyle.FixedSingle;
        }

    }

    // Get the new selection.
    picSelected = (exPictureBox)sender;
    picSelected.BorderStyle = BorderStyle.Fixed3D;

    // Fire the selection event.
    PictureSelectedEventArgs args = new PictureSelectedEventArgs(picSelected.Tag.ToString(), picSelected.Image);

    indexOfImage = System.Convert.ToInt32(picSelected.Tag) + 1;

    if (PictureSelected != null)
    {
        PictureSelected(this, args);
    }

    OnClick(args);
}

//Double click on pictures
private void onDoubleClick(object sender, EventArgs e)
{
    if (picSelected.Image != null)
    {
        Form ModalForm = new Form();

        PictureBox PicImg = new PictureBox();
        //Load Image
        PicImg.Image = picSelected.ImageFullSize;
        PicImg.Dock = DockStyle.Fill;
        ModalForm.Size = new Size(picSelected.ImageFullSize.Width, picSelected.ImageFullSize.Height + 25);
        ModalForm.Controls.Add(PicImg);
        ModalForm.ShowInTaskbar = false;

        ModalForm.FormBorderStyle = FormBorderStyle.SizableToolWindow;
        ModalForm.ShowDialog();
        OnDoubleClick(e);
        ModalForm.Dispose();
    }
}

//Fields array lists
static ArrayList _PicField = new ArrayList();
static ArrayList _PicNameField = new ArrayList();

//Dimension amoung Images
[Description("Height of Images."),
 DefaultValue(120),
 Category("Images")]
public int IconHeight
{
    get
    {
        return iconHeight;
    }
    set
    {
        iconHeight = value;
    }
}

//Dimension amoung Images
[Browsable(true),
 Description("Width of Images."),
 DefaultValue(80),
 Category("Images")]
public int IconWidth
{
    get
    {
        return iconWidth;
    }
    set
    {
        iconWidth = value;
    }
}

//Dimension amoung Images
[Browsable(true)]
[DefaultValue(100)]
[Category("Images")]
[Description("Dimension amoung Images.")]
public int Dimension
{
    get
    {
        return dimension;
    }
    set
    {
        dimension = value;
    }
}

//Spacing amoung Images
[Browsable(true)]
[DefaultValue(80)]
[Category("Images")]
[Description("Spacing amoung Images.")]
public int Spacing
{
    get
    {
        return spacing;
    }
    set
    {
       spacing = value;
    }
}

private DataSet DataSet
{
    get { return dataset; }
    set
    {
        dataset = value;

        if (dataset != null)
        {
            //Separate fields name
            _PicField.Clear();
            _PicNameField.Clear();

            for (int jj = 0; jj <= dataset.Tables[0].Columns.Count - 1; jj++)
            {
                Type t = dataset.Tables[0].Columns[jj].DataType;

                if (t.FullName == "System.Byte[]")
                {
                    _PicField.Add(dataset.Tables[0].Columns[jj].ToString());
                }
                else
                {
                    _PicNameField.Add(dataset.Tables[0].Columns[jj].ToString());
                }
            }

        }
        else
        {
            _PicField.Clear();
            _PicNameField.Clear();
        }

    }
}

private DataColumnCollection DataColumn
{
    get { return dataColumn; }
    set
    {
        dataColumn = value;
    }
}

public class PicFieldConverter : StringConverter
{

    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
    {
        //true means show a combobox
        return true;
    }

    public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
    {
        //true will limit to list. false will show the list, but allow free-form entry
        return true;
    }

    public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
    {
        return new StandardValuesCollection(_PicField);
    }

}

//Property converter
public class PicNameFieldConverter : StringConverter
{

    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
    {
        //true means show a combobox
        return true;
    }

    public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
    {
        //true will limit to list. false will show the list, but allow free-form entry
        return true;
    }

    public override
        System.ComponentModel.TypeConverter.StandardValuesCollection
        GetStandardValues(ITypeDescriptorContext context)
    {
        return new StandardValuesCollection(_PicNameField);
    }

}

private string FieldName;

[Browsable(true)]
[Description("Picture field")]
[TypeConverter(typeof(PicFieldConverter))]
[Category("Data")]
//Field name property
public string PicFieldName
{
    //When first loaded set property with the first item in the rule list.
    get
    {
        string S = "";
        if (FieldName != null)
        {
            if (this.DataSet != null)
                S = FieldName;
            else
                S = "";
        }
        return S;
    }
    set { FieldName = value; }

}

private string FieldNameTitle;

[Browsable(true)]
[Description("Pictures title")]
[TypeConverter(typeof(PicNameFieldConverter))]
[Category("Data")]
//Title name property
public string PicTitleFieldName
{
    //When first loaded set property with the first item in the rule list.
    get
    {
        string S = "";
        if (FieldNameTitle != null)
        {
            if (this.DataSet != null)
                S = FieldNameTitle;
            else
                S = "";
        }
        return S;
    }
    set { FieldNameTitle = value; }
}

//Get data sourse
[Browsable(true)]
[Description("Pictures's Data Source")]
[Category("Data")]
public BindingSource DataSource
{
    get { return bindingSource; }
    set
    {
        bindingSource = value;

        if (value != null)
        {
            this.DataSet = (DataSet)bindingSource.DataSource;

            if (DataSet != null)
            {
                this.DataColumn = (DataColumnCollection) DataSet.Tables[0].Columns;
            }

        }
    }
}

private class NamedImage
{
    public Image Image;
    public string imgName;
    public NamedImage(Image image, string imageName)
    {
        Image = image;
        imgName = imageName;
    }
}

//Get all images from Data Base
private void GetImagesDB()
{
    lock (images)
    {
        int k = 0;
        images.Clear();
        int counT;

        try
        {
            counT = dataset.Tables[0].Rows.Count;
        }
        catch
        {
            counT = -1;
        }
        if (counT > 0)
            do
            {
                try
                {
                    Image img = null;
                    if (PicFieldName != "")
                    {
                        byte[] blob = (byte[])(dataset.Tables[0].Rows[k][PicFieldName]);

                        MemoryStream ms = new MemoryStream(blob, 0, blob.Length);

                        img = Image.FromStream(ms);
                        ms.Dispose();

                        if (PicTitleFieldName != "")
                        {
                            imageTitle = dataset.Tables[0].Rows[k][PicTitleFieldName].ToString();
                        }
                        else
                        {
                            imageTitle = "";
                        }

                        images.Add(new NamedImage(img, imageTitle));

                        k++;
                     }
                }
                catch
                {
                    imageTitle = "";
                    images.Add(new NamedImage(null, imageTitle));
                    k++;
                }
            }
            while (k <= counT - 1);
        exImages.Clear();
    }
    // Update the display on the UI thread.
    pnlPictures.Invoke(new MethodInvoker(this.UpdateDisplay));
}

private void GetImages()
{
    Thread getThread = new Thread(new ThreadStart(this.GetImagesDB));
    getThread.Start();
}

//Draw images
private void UpdateDisplay()
{
    // Clear the current display.
    // Suspend layout to prevent multiple window refreshes.
    if ((IconWidth != 0) && (IconHeight != 0))
    {
        pnlPictures.SuspendLayout();
        // Clear the current display.
        foreach (Control ctrl in pnlPictures.Controls)
        {
            ctrl.Dispose();
        }
        pnlPictures.Controls.Clear();
        // Row and Col will track the current position where pictures are
        // being inserted. They begin at the top-left corner.
        int row = border, col = border;
        int imgW, imgH = 0;
        double reSize, reSize1 = 0;
        //Rectangle destRect = new Rectangle();
        int i = 0;
        // Iterate through the Images collection, and create PictureBox controls.
        lock (images)
        {
            foreach (NamedImage image in images)
            {

                exPictureBox pic = new exPictureBox();

                ControlColumn = (int)(Math.Floor((double)(Width / (this.IconWidth + 10))));

                if (image.Image != null)
                {
                    reSize  = Math.Round(this.IconHeight / ((double)(image.Image.Height)), 3);
                    reSize1 = Math.Round(this.IconWidth / ((double)(image.Image.Width)), 3);

                    if (reSize >= reSize1)
                        reSize = reSize1;

                    imgW = (int)(Math.Ceiling(image.Image.Width * reSize));
                    imgH = (int)(Math.Ceiling(image.Image.Height * reSize));

                    pic.Image = new Bitmap(image.Image, new Size(imgW, imgH));
                    pic.ImageFullSize = image.Image;

                }
                else
                {
                    pic.Image = null;
                    pic.ImageFullSize = null;
                }

                this.toolTipImage.SetToolTip(pic, image.imgName);

                pic.Tag = i;
                i++;

                pic.Size = new Size(this.IconWidth, this.IconHeight);
                pic.Location = new Point(col, row);
                pic.BorderStyle = BorderStyle.FixedSingle;

                exImages.Add(pic);

                pic.DoubleClick += new EventHandler(onDoubleClick);
                pic.Click += new EventHandler(onPicClick);

                // Display the picture.
                pnlPictures.Controls.Add(pic);
                // Move to the next column.
                col += (int)Math.Ceiling((double)((dimension + IconHeight* IconWidth / IconHeight));
                // Move to next line if no more pictures will fit.
                if ((col + spacing + border + IconWidth) > Width)
                {
                    col = border;
                    row += IconHeight + spacing;
                }
            }

            if (indexOfImage > 0)
            {
                exPictureBox px = new exPictureBox();
                px = (exPictureBox)exImages[indexOfImage - 1];
                picSelected = px;
                px.BorderStyle = BorderStyle.Fixed3D;
            }
        }
        pnlPictures.ResumeLayout();
    }
}

protected override void OnSizeChanged(EventArgs e)
{
    base.OnSizeChanged(e);
    Invalidate();
}

public PicDB()
{
    InitializeComponent();
}

int pos = 0;

//Mouse scroll
private void OnMouseWheel(object sender, MouseEventArgs e)
{
    pos = pnlPictures.VerticalScroll.Value;

    if (e.Delta < 5)
    {
        if (pos < this.Height)
        {
            pos += 10;
        }

        pnlPictures.VerticalScroll.Value = pos;
    }
    else
    {
        pos -= 10;
        if (pos < 0)
        {
            pos = 0;

        }
        else
            pnlPictures.VerticalScroll.Value = pos;

    }
}

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        if (components != null)
        {
            components.Dispose();
        }
    }

    base.Dispose(disposing);
}

#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
    this.components = new System.ComponentModel.Container();
    System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PicDB));
    this.pnlPictures = new System.Windows.Forms.Panel();
    this.pnToolBar = new System.Windows.Forms.Panel();
    this.toolBar = new System.Windows.Forms.ToolBar();
    this.tbntNext = new System.Windows.Forms.ToolBarButton();
    this.tbntPrior = new System.Windows.Forms.ToolBarButton();
    this.tbntLast = new System.Windows.Forms.ToolBarButton();
    this.tbntFirst = new System.Windows.Forms.ToolBarButton();
    this.imgList = new System.Windows.Forms.ImageList(this.components);
    this.toolTipImage = new System.Windows.Forms.ToolTip(this.components);
    this.openFileDialog = new System.Windows.Forms.OpenFileDialog();
    this.pnToolBar.SuspendLayout();
    this.SuspendLayout();
    //
    // pnlPictures
    //
    this.pnlPictures.AllowDrop = true;
    this.pnlPictures.AutoScroll = true;
    this.pnlPictures.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
    this.pnlPictures.Dock = System.Windows.Forms.DockStyle.Fill;
    this.pnlPictures.Location = new System.Drawing.Point(0, 32);
    this.pnlPictures.Name = "pnlPictures";
    this.pnlPictures.Size = new System.Drawing.Size(179, 135);
    this.pnlPictures.TabIndex = 0;
    //
    // pnToolBar
    //
    this.pnToolBar.Controls.Add(this.toolBar);
    this.pnToolBar.Dock = System.Windows.Forms.DockStyle.Top;
    this.pnToolBar.Location = new System.Drawing.Point(0, 0);
    this.pnToolBar.Name = "pnToolBar";
    this.pnToolBar.Size = new System.Drawing.Size(179, 32);
    this.pnToolBar.TabIndex = 0;
    //
    // toolBar
    //
    this.toolBar.Appearance = System.Windows.Forms.ToolBarAppearance.Flat;
    this.toolBar.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
    this.toolBar.Buttons.AddRange(new System.Windows.Forms.ToolBarButton[] {
    this.tbntNext,
    this.tbntPrior,
    this.tbntLast,
    this.tbntFirst});
    this.toolBar.ButtonSize = new System.Drawing.Size(16, 16);
    this.toolBar.Dock = System.Windows.Forms.DockStyle.Fill;
    this.toolBar.DropDownArrows = true;
    this.toolBar.ImageList = this.imgList;
    this.toolBar.Location = new System.Drawing.Point(0, 0);
    this.toolBar.Name = "toolBar";
    this.toolBar.ShowToolTips = true;
    this.toolBar.Size = new System.Drawing.Size(179, 29);
    this.toolBar.TabIndex = 0;
    this.toolBar.ButtonClick += new System.Windows.Forms.ToolBarButtonClickEventHandler(this.toolBar_ButtonClick);
    //
    // tbntNext
    //
    this.tbntNext.ImageIndex = 0;
    this.tbntNext.Name = "tbntNext";
    //
    // tbntPrior
    //
    this.tbntPrior.ImageIndex = 1;
    this.tbntPrior.Name = "tbntPrior";
    //
    // tbntLast
    //
    this.tbntLast.ImageIndex = 2;
    this.tbntLast.Name = "tbntLast";
    //
    // tbntFirst
    //
    this.tbntFirst.ImageIndex = 3;
    this.tbntFirst.Name = "tbntFirst";
    //
    // imgList
    //
    this.imgList.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("imgList.ImageStream")));
    this.imgList.TransparentColor = System.Drawing.Color.Transparent;
    this.imgList.Images.SetKeyName(0, "back.gif");
    this.imgList.Images.SetKeyName(1, "forward.gif");
    this.imgList.Images.SetKeyName(2, "first.gif");
    this.imgList.Images.SetKeyName(3, "last.gif");
    //
    // openFileDialog
    //
    this.openFileDialog.Filter = "Image Files | *.JPG;*.GIF;*.BMP";
    //
    // PicDB
    //
    this.Controls.Add(this.pnlPictures);
    this.Controls.Add(this.pnToolBar);
    this.Name = "PicDB";
    this.Size = new System.Drawing.Size(179, 167);
    this.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.OnMouseWheel);
    this.Paint += new System.Windows.Forms.PaintEventHandler(this.PicDB_Paint);
    this.pnToolBar.ResumeLayout(false);
    this.pnToolBar.PerformLayout();
    this.ResumeLayout(false);

}
#endregion

private void PicDB_Paint(object sender, PaintEventArgs e)
{
    GetImages();
    this.MouseWheel -= new System.Windows.Forms.MouseEventHandler(this.OnMouseWheel);
    this.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.OnMouseWheel);
}

//Processing toolbar button
private void toolBar_ButtonClick(object sender, ToolBarButtonClickEventArgs e)
{

    exPictureBox px = new exPictureBox();
    //next action
    if (DataSet != null)
    {
        if (e.Button == toolBar.Buttons[1])
        {
            if (indexOfImage >= 0 && indexOfImage < exImages.Count)
            {
                px = (exPictureBox)exImages[indexOfImage];
                picSelected = px;
                px.BorderStyle = BorderStyle.Fixed3D;

            }

            if (indexOfImage > 0 && indexOfImage < exImages.Count)
            {
                px = (exPictureBox)exImages[indexOfImage - 1];
                px.BorderStyle = BorderStyle.FixedSingle;
            }
            if (indexOfImage < exImages.Count)
                indexOfImage++;

        }
        //prior action
        if (e.Button == toolBar.Buttons[0])
        {
            if (indexOfImage <= exImages.Count && indexOfImage > 0)
            {
                if (indexOfImage > 1)
                    indexOfImage--;
                px = (exPictureBox)exImages[indexOfImage - 1];
                picSelected = px;
                px.BorderStyle = BorderStyle.Fixed3D;
                px = (exPictureBox)exImages[indexOfImage];
                px.BorderStyle = BorderStyle.FixedSingle;
            }

        }
        //last position
        if (e.Button == toolBar.Buttons[3])
        {
            if (indexOfImage != (exImages.Count))
            {
                px = (exPictureBox)exImages[exImages.Count - 1];
                px.BorderStyle = BorderStyle.Fixed3D;


                if (indexOfImage >= 1)
                {
                    px = (exPictureBox)exImages[indexOfImage - 1];
                    picSelected = px;
                    px.BorderStyle = BorderStyle.FixedSingle;
                }
                indexOfImage = exImages.Count;
            }
        }
        //first position
        if (e.Button == toolBar.Buttons[2])
        {
            px = (exPictureBox)exImages[0];
            px.BorderStyle = BorderStyle.Fixed3D;

            if (indexOfImage > 1)
            {
                px = (exPictureBox)exImages[indexOfImage - 1];
                picSelected = px;
                px.BorderStyle = BorderStyle.FixedSingle;
            }
            indexOfImage = 1;
        }
    }
}

Testing the Control

1. Open a new instance of the VS .NET. Create a new project choosing the Windows Application template.

2. From a new Windows Forms project, we can add the compiled custom control to the toolbox. You can choose menu Tools then Choose Toolbox Items.., and from the .NET Framework Components tab, clicking Browse and locating the Control Library DLL # (in our case path is C:\Documents and Settings\GalkinA\My Documents\Visual Studio 2005\Projects\PicDB\PicDB\bin\Debug). The component PicDB will then appear in the Toolbox.

3. For creation table with image field you can use article "Storing and Retrieving Images from SQL Server using Microsoft .NET" or "Load/Unload images into/from DB table"

4. After setting BindingSource and configuration add PicDB component. You can play a bit changing its properties (IconHeight, IconWidth, Dimension, Spacing, PicTitleFieldName).

Good Luck in building own controls.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Team Leader Octopus
Kyrgyzstan Kyrgyzstan
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionAll's well that compiles in VS2010 ... Pin
RedDk30-Sep-11 8:18
RedDk30-Sep-11 8:18 
Except when it doesn't. There's a missing class definition; from the looks of it the dataset table adapter. Without it, the demo using the src .dll never makes it to the finish line.

Looks good otherwise.
GeneralFix your demo zip file Pin
fwsouthern24-Mar-07 18:17
fwsouthern24-Mar-07 18:17 
GeneralRe: Fix your demo zip file Pin
AndyKG25-Mar-07 19:46
AndyKG25-Mar-07 19:46 

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

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