Click here to Skip to main content
15,884,001 members
Articles / Programming Languages / C#
Tip/Trick

How to create an XNA Button and Labels with Mouse Events

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
2 Feb 2013CPOL2 min read 27.9K   4   3
How to create an XNA Button and Labels with mouse events.

Image 1

Introduction

Having a button in XNA is very usual and important, so let's make buttons with MouseOver and MouseClick events. I have used sound to create some action for the result of MouseOver and MouseDown.

Using the code

First we should create Structs for our picture buttons:

C#
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Media;
using System.Collections;
using Language_Learning_Application;
public struct picture
{
    public string strfilename;
    public string strfilename1;
    public string strfilename2;
    //SoundFiles
    //Mousein
    public string strfilename3;
    //MouseClick
    public string strfilename4;
    public string GiveMeTheCurrentFilename(int FormNumber,string Action)
    {
        if((Action!="LabelAnimation"))
        {
            if (FormNumber == 1) return strfilename;
            if (FormNumber == 2) return strfilename1;
            if (FormNumber == 3) return strfilename2;
        }
        return strfilename;
    }
    public string GiveMeTheCurrentFilenameSound(int SoundNumber)
    {
        if (SoundNumber == 1) return strfilename3;
        if (SoundNumber == 2) return strfilename4;
        return strfilename;
    }
    // Audio objects
    public SoundEffect soundEffect;
    public int intCurrentForm;
    public int CurrentSound;
    public Rectangle rectangleDestination;
    public bool boolisLoaded;
    public string strplaceOfUse;
    public int key;
    public bool ShouldItBeShownInThisScreen;
    public Texture2D Texture;
    public string Action;
    public string MousePictureState;
    public bool PlayingSound ;
    //Animation
    public bool HasItAnimation;
    public bool IsAnimationOn;
    public int TotalAnimationFrame;
    public int CurentAnimationFrame;
    public string OriginalFilenameWithoutIndex;
    public int FramePerSecond;
}

And then we create a struct for our MouseButton  keys and features.

C#
public struct MouseButton
{
    public ButtonState LeftClick;
    public ButtonState RightClick;
    public ButtonState MiddleClick;
    public int ScrollWheel ;
    public bool IsThisPointInThisRectangle ;
}

Now we get ContentManager form the main class:

C#
private ContentManager content;
public ContentManager PropertyContent
{
    set
    {
        content = value;
    }
}

Now we define our buttons and labels:

C#
public picture tex2dBtnAddNewWords;
public picture tex2dBtnLearn;
public picture tex2dBtnExit;
public picture tex2dBtnUpdate;
public picture lblPleaseWriteYourWordHere;
public picture txtWord;

Let's put our buttons and labels in a public array to use it everywhere in our project:

C#
public picture[] mypictures=new picture[6];

Now we initialize our buttons and labels:

C#
public void InitializeDefenitions()
{
    string BaseDirectory = ".";
    //tex2dBtnAddNewWords
    tex2dBtnAddNewWords.key = 1;
    tex2dBtnAddNewWords.strfilename = BaseDirectory + "\\StartPage\\Buttons\\jpgAddNewWordsForm1";
    tex2dBtnAddNewWords.strfilename1 = BaseDirectory + "\\StartPage\\Buttons\\jpgAddNewWordsForm2";
    tex2dBtnAddNewWords.strfilename2 = BaseDirectory + "\\StartPage\\Buttons\\jpgAddNewWordsForm3";
    tex2dBtnAddNewWords.strfilename3 = BaseDirectory + "\\StartPage\\Sounds\\ButtonMouseOverSound";
    tex2dBtnAddNewWords.strfilename4 = BaseDirectory + "\\StartPage\\Sounds\\ButtonClickSound";
    tex2dBtnAddNewWords.rectangleDestination = new Rectangle((WindowsScreenWidth - 778) / 2, 
           (WindowsScreenHeight - 124) / 2  - 124-124, 778, 124);
    tex2dBtnAddNewWords.boolisLoaded = false;
    tex2dBtnAddNewWords.strplaceOfUse = "StartPage";
    tex2dBtnAddNewWords.ShouldItBeShownInThisScreen = true;
    tex2dBtnAddNewWords.Texture = null;
    tex2dBtnAddNewWords.Action = "Button";
    tex2dBtnAddNewWords.intCurrentForm = 1;
    tex2dBtnAddNewWords.MousePictureState = "OutOfControl";
    tex2dBtnAddNewWords.CurrentSound = 1;
    //tex2dBtnLearn
    tex2dBtnLearn.key = 2;
    tex2dBtnLearn.strfilename = BaseDirectory + "\\StartPage\\Buttons\\jpgLearnForm1";
    tex2dBtnLearn.strfilename1 = BaseDirectory + "\\StartPage\\Buttons\\jpgLearnForm2";
    tex2dBtnLearn.strfilename2 = BaseDirectory + "\\StartPage\\Buttons\\jpgLearnForm3";
    tex2dBtnLearn.strfilename3 = BaseDirectory + "\\StartPage\\Sounds\\ButtonMouseOverSound";
    tex2dBtnLearn.strfilename4 = BaseDirectory + "\\StartPage\\Sounds\\ButtonClickSound";
    tex2dBtnLearn.rectangleDestination = new Rectangle(
      (WindowsScreenWidth - 778) / 2, (WindowsScreenHeight - 124) / 2 +20 , 778, 124);
    tex2dBtnLearn.boolisLoaded = false;
    tex2dBtnLearn.strplaceOfUse = "StartPage";
    tex2dBtnLearn.ShouldItBeShownInThisScreen = true;
    tex2dBtnLearn.Texture = null;
    tex2dBtnLearn.Action = "Button";
    tex2dBtnLearn.intCurrentForm = 1;
    tex2dBtnLearn.MousePictureState = "OutOfControl";
    tex2dBtnLearn.CurrentSound = 1;
    //tex2dBtnExit
    tex2dBtnExit.key = 3;
    tex2dBtnExit.strfilename = BaseDirectory + "\\StartPage\\Buttons\\jpgExitForm1";
    tex2dBtnExit.strfilename1 = BaseDirectory + "\\StartPage\\Buttons\\jpgExitForm2";
    tex2dBtnExit.strfilename2 = BaseDirectory + "\\StartPage\\Buttons\\jpgExitForm3";
    tex2dBtnExit.strfilename3 = BaseDirectory + "\\StartPage\\Sounds\\ButtonMouseOverSound";
    tex2dBtnExit.strfilename4 = BaseDirectory + "\\StartPage\\Sounds\\ButtonClickSound";
    tex2dBtnExit.rectangleDestination = new Rectangle((WindowsScreenWidth - 778) / 2, 
       (WindowsScreenHeight - 124) / 2+124  + 30, 778, 124);
    tex2dBtnExit.boolisLoaded = false;
    tex2dBtnExit.strplaceOfUse = "StartPage";
    tex2dBtnExit.ShouldItBeShownInThisScreen = true;
    tex2dBtnExit.Texture = null;
    tex2dBtnExit.Action = "Button";
    tex2dBtnExit.intCurrentForm = 1;
    tex2dBtnExit.MousePictureState = "OutOfControl";
    tex2dBtnExit.CurrentSound = 1;
    //
    //tex2dBtnUpdate
    tex2dBtnUpdate.key = 4;
    tex2dBtnUpdate.strfilename = BaseDirectory + "\\StartPage\\Buttons\\jpgUpdateForm1";
    tex2dBtnUpdate.strfilename1 = BaseDirectory + "\\StartPage\\Buttons\\jpgUpdateForm2";
    tex2dBtnUpdate.strfilename2 = BaseDirectory + "\\StartPage\\Buttons\\jpgUpdateForm3";
    tex2dBtnUpdate.strfilename3 = BaseDirectory + "\\StartPage\\Sounds\\ButtonMouseOverSound";
    tex2dBtnUpdate.strfilename4 = BaseDirectory + "\\StartPage\\Sounds\\ButtonClickSound";
    tex2dBtnUpdate.rectangleDestination = new Rectangle((WindowsScreenWidth - 778) / 2, 
      (WindowsScreenHeight - 124) / 2 + 10 - 124, 778, 124);
    tex2dBtnUpdate.boolisLoaded = false;
    tex2dBtnUpdate.strplaceOfUse = "StartPage";
    tex2dBtnUpdate.ShouldItBeShownInThisScreen = true;
    tex2dBtnUpdate.Texture = null;
    tex2dBtnUpdate.Action = "Button";
    tex2dBtnUpdate.intCurrentForm = 1;
    tex2dBtnUpdate.MousePictureState = "OutOfControl";
    tex2dBtnUpdate.CurrentSound = 1;
    //lblPleaseWriteYourWordHere
    lblPleaseWriteYourWordHere.key = 5;
    lblPleaseWriteYourWordHere.strfilename = BaseDirectory + 
      "\\AddNewWordsPage1\\Labels\\Please Write Your Word Here\\Please write your word Here1";
    lblPleaseWriteYourWordHere.OriginalFilenameWithoutIndex = BaseDirectory + 
      "\\AddNewWordsPage1\\Labels\\Please Write Your Word Here\\Please write your word Here";
    lblPleaseWriteYourWordHere.rectangleDestination = 
      new Rectangle((WindowsScreenWidth - 515) / 2, (WindowsScreenHeight - 63) / 2-63-63 , 515, 63);
    lblPleaseWriteYourWordHere.boolisLoaded = false;
    lblPleaseWriteYourWordHere.strplaceOfUse = "AddNewWords_Word";
    lblPleaseWriteYourWordHere.ShouldItBeShownInThisScreen = false;
    lblPleaseWriteYourWordHere.Texture = null;
    lblPleaseWriteYourWordHere.Action = "LabelAnimation";
    lblPleaseWriteYourWordHere.intCurrentForm = 1;
    lblPleaseWriteYourWordHere.MousePictureState = "OutOfControl";
    lblPleaseWriteYourWordHere.TotalAnimationFrame = 30;
    lblPleaseWriteYourWordHere.CurentAnimationFrame = 1;
    lblPleaseWriteYourWordHere.IsAnimationOn = true;
    lblPleaseWriteYourWordHere.HasItAnimation = true;
    lblPleaseWriteYourWordHere.FramePerSecond = 8;
    //txtWord
    txtWord.key = 6;
    txtWord.strfilename = BaseDirectory + "\\AddNewWordsPage1\\Labels\\TextBox\\TextBox";
    txtWord.strfilename3 = BaseDirectory + 
      "\\AddNewWordsPage1\\Labels\\Please Write Your Word Here\\Please write your word Here";
    txtWord.rectangleDestination = new Rectangle((WindowsScreenWidth - 1000) / 2, 
      (WindowsScreenHeight - 300) / 2 + 50, 1000, 300);
    txtWord.boolisLoaded = false;
    txtWord.strplaceOfUse = "AddNewWords_Word";
    txtWord.ShouldItBeShownInThisScreen = false;
    txtWord.Texture = null;
    txtWord.Action = "TextBox";
    txtWord.intCurrentForm = 1;
    txtWord.MousePictureState = "OutOfControl";
    txtWord.CurrentSound = 1;
    //ArrayList=>Mypictures
    mypictures[0] = tex2dBtnAddNewWords;
    mypictures[1] = tex2dBtnLearn;
    mypictures[2] = tex2dBtnExit;
    mypictures[3] = tex2dBtnUpdate;
    mypictures[4] = lblPleaseWriteYourWordHere;
    mypictures[5] = txtWord;
}

Now that we initialize our buttons let's loop through them to show them on screen

We create a loop engine here:

C#
public int CurrentPicture=0; 

public void fncEngineMotor()
{
    for (int i=0; i < mypictures.Length; i++)
    {
        CurrentPicture = i;
        myloadContent(ref mypictures[CurrentPicture]);
        fncCheckForMouseMoveOnButton(Mouse.GetState(), ref mypictures[CurrentPicture]);
        fncCheckForMouseEnterOnButtonSound(Mouse.GetState(), ref mypictures[CurrentPicture]);
        DoAnimationFrame(ref mypictures[CurrentPicture]);
    }
} 

Here we load and unload content of every picture:

C#
public void myloadContent(ref picture mypicture)
{
    if (mypicture.strplaceOfUse == strProgramState)
    {
        if (mypicture.ShouldItBeShownInThisScreen == true)
        {
                mypicture.Texture = content.Load<Texture2D>(
                  mypicture.GiveMeTheCurrentFilename(mypicture.intCurrentForm, mypicture.Action));
                if (mypicture.Action != "LabelAnimation" && mypicture.Action != "TextBox") 
            {
                    mypicture.soundEffect = content.Load<SoundEffect>(
                      mypicture.GiveMeTheCurrentFilenameSound(mypicture.CurrentSound));
            }
        }
        if (mypicture.ShouldItBeShownInThisScreen == false)
        {
            mypicture.Texture = null;
            mypicture.soundEffect = null;
        }
    }
}

Let's change the texture of buttons for user's attraction on mouse over and mouse click:

C#
public void fncCheckForMouseMoveOnButton(MouseState MyMousestate,ref picture myPicture)
{
    if (myPicture.ShouldItBeShownInThisScreen)
    {
        MouseButton result = fncIsThisPointInThisRectangle(MyMousestate.X, MyMousestate.Y, 
          MyMousestate.LeftButton, MyMousestate.RightButton, MyMousestate.MiddleButton, 
          MyMousestate.ScrollWheelValue, myPicture.rectangleDestination);
        if (result.IsThisPointInThisRectangle)
        {
            if (myPicture.Action == "Button")
            {
                myPicture.intCurrentForm = 2;
               
                if (result.LeftClick == ButtonState.Pressed)
                {
                    myPicture.intCurrentForm = 3;
                }

            }
        }
        if (!result.IsThisPointInThisRectangle)
        {
            if (myPicture.Action == "Button")
            {
                if (myPicture.intCurrentForm != 1)
                {
                    myPicture.intCurrentForm = 1;
                }
            }
        }
    }
}

Is mouse on The button?

To do this we need to see if mouse is on the button?

We do this here:

C#
public MouseButton fncIsThisPointInThisRectangle(int MyMousestateX, int MyMousestateY, 
  Microsoft.Xna.Framework.Input.ButtonState LeftButton, 
  Microsoft.Xna.Framework.Input.ButtonState RightButton, 
  Microsoft.Xna.Framework.Input.ButtonState MiddleButton, 
  int ScrollWheelValue, Rectangle thisRectangle)
{
     MouseButton result=new MouseButton();
    if (MyMousestateX > thisRectangle.Left && MyMousestateX < 
      thisRectangle.Right && MyMousestateY < thisRectangle.Bottom && 
      MyMousestateY > thisRectangle.Top)
    {
        result.IsThisPointInThisRectangle = true;
        result.LeftClick = LeftButton;
        result.MiddleClick = MiddleButton;
        result.RightClick = RightButton;
        result.ScrollWheel = ScrollWheelValue;
    }
    return result;
} 

Mouse Events:

Now we need some some sound to get user happy on Mouse Over and Mouse Click.

Do it like this:

C#
public int fncCheckForMouseEnterOnButtonSound(MouseState MyMousestate,ref picture myPicture)
{
    int myreturn = 0;
    if (myPicture.ShouldItBeShownInThisScreen)
    {
        MouseButton result = fncIsThisPointInThisRectangle(MyMousestate.X, MyMousestate.Y, 
           MyMousestate.LeftButton, MyMousestate.RightButton, MyMousestate.MiddleButton, 
           MyMousestate.ScrollWheelValue, myPicture.rectangleDestination);
        if (result.IsThisPointInThisRectangle)
        {
            if (myPicture.Action == "Button")
            {
                if (result.LeftClick ==Microsoft.Xna.Framework.Input.ButtonState.Pressed)
                {
                    if (myPicture.MousePictureState == "ControlPressed")
                    {
                        myPicture.CurrentSound = 1;
                        myPicture.MousePictureState = "ControlPressedRepetition";
                        myPicture.PlayingSound = false;
                    }
                    else if (myPicture.MousePictureState == "InControlRepetition")
                    {
                        myPicture.MousePictureState = "ControlPressed";
                    }
                }
                else if (myPicture.MousePictureState == "OutOfControl")
                {
                    myPicture.CurrentSound = 1;
                    myPicture.MousePictureState = "InControlForTheFirstTime";
                    myPicture.PlayingSound = true;
                    fncPutAWavSoundOnBuffer(1);

                }
                else if (myPicture.MousePictureState == "InControlForTheFirstTime")
                {
                    myPicture.MousePictureState = "InControlRepetition";
                    myPicture.PlayingSound = false;
                }
                //
                else if (result.LeftClick == Microsoft.Xna.Framework.Input.ButtonState.Released)
                {
                    if (myPicture.MousePictureState == "ControlPressedRepetition")
                    {
                        myPicture.MousePictureState = "InControlRepetition";
                        myPicture.CurrentSound = 2;
                        myPicture.PlayingSound = true;
                        fncPutAWavSoundOnBuffer(2);
                        OnPressButton(myPicture.key);
                        if (isarrlistMypicturesUnderConstruction)
                        {
                            isarrlistMypicturesUnderConstruction = false;
                            return 1000;
                        }
                    }
                }
            }
        }
        if (!result.IsThisPointInThisRectangle)
        {
            if (myPicture.Action == "Button")
            {
                myPicture.MousePictureState = "OutOfControl";
                myPicture.PlayingSound = false;
            }
        }
    }
    return myreturn;
}

Clicked?

C#
public void OnPressButton(int ButtonKey)
{
    switch (ButtonKey)
    {
        case 0:
            {
                Dosth();
                break;
            }
        case 1:
            {
                break;
            }
        case 2:
            {
                
                break;
            }
        case 3:
            {
                break;
            }
        case 4:
           {
               break;
           }
        case 5:
           {
               break;
           }
        case 6:
           {
               break;
           }
        case 7:
           {
              Dosth();
               break;
           }
        case 8:
           {
               break;
           }
        default:
           {
               break;
           }
    }
}

Sound Buffer:

Now how to handle sounds, we create a buffer to handle simple sounds, every sound will be load and on it's turn will be played:

C#
public bool songbuffer = false;
public bool soundbuffer = false;
public Song CurrentSong;
public SoundEffect CurrentSound; 
public void fncPutAMP3SoundOnBuffer(int code)
{
    CurrentSong = content.Load<Song>(GivemeSoundFileNameByCode(code));
    songbuffer = true;
}
public void fncPutAWavSoundOnBuffer(int code)
{
    CurrentSound = content.Load<SoundEffect>(GivemeSoundFileNameByCode(code));
    soundbuffer = true;
}

Let's find if the sound file name is here:

C#
public string GivemeSoundFileNameByCode(int code)
{
    string BaseDirectory = ".";
    switch (code)
    {
        case 1:
            {
                return BaseDirectory + "\\StartPage\\Sounds\\ButtonMouseOverSound";
            }
        case 2:
            {
                return BaseDirectory + "\\StartPage\\Sounds\\ButtonClickSound";
            }
        case 3:
            {
                return BaseDirectory + "\\AddNewWordsPage1\\Sounds\\mp3PleaseWiteYourWordHere";
            }
        default:
            {
                break;
            }
    }
    return "";
}

Our main game update will be something like this:

C#
protected override void Update(GameTime gameTime)
{
    // Allows the game to exit
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == 
             Microsoft.Xna.Framework.Input.ButtonState.Pressed)
        this.Exit();
    clsMyDefenition.fncEngineMotor();
    clsMyDefenition.fncGenerateTime();
    PlaySound();
    base.Update(gameTime);
}

To play sounds here:

C#
/// <summary>
/// ////////////
/// </summary>
public void PlaySound()
{
    if (clsMyDefenition.soundbuffer)
    {
        clsMyDefenition.CurrentSound.Play();
        clsMyDefenition.soundbuffer = false;
    }  
    if (clsMyDefenition.songbuffer)
    {
        MediaPlayer.Play(clsMyDefenition.CurrentSong);
        clsMyDefenition.songbuffer = false;
    }            
}

This will be our main Drawing

C#
protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.Black);
    // TODO: Add your drawing code here
    spriteBatch.Begin();
    clsMyDefenition.DrawPictures(spriteBatch);
    spriteBatch.End();
    // TODO: Add your drawing code here
    base.Draw(gameTime);
} 

Base of our program:

C#
public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    graphics.PreferredBackBufferHeight = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height - 
      System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height/10;
    graphics.PreferredBackBufferWidth = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width - 
      System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width/10;
    graphics.ApplyChanges();
    clsMyDefenition.PropertyWindowsScreenHeight = this.Window.ClientBounds.Height;
    clsMyDefenition.PropertyWindowsScreenWidth = this.Window.ClientBounds.Width;
    //It makes the content Default path
    Content.RootDirectory = "Content";
    clsMyDefenition.PropertyContent = Content;
}

And our main initialization here:

C#
protected override void Initialize()
{
    // TODO: Add your initialization logic here
    
    clsMyDefenition.InitializeDefenitions();
    //To make mouse visible
    this.IsMouseVisible = true;

    base.Initialize();
}

Now our Drawing picture Function: 

C#
public void DrawPictures(SpriteBatch mybatch)
{
    for (int i = 0; i < mypictures.Length; i++)
    {
        if (mypictures[i].ShouldItBeShownInThisScreen)
        {
            mybatch.Draw(mypictures[i].Texture, mypictures[i].rectangleDestination, 
              Microsoft.Xna.Framework.Color.White);
        }
    }
}

Our Animation engine for label:

C#
public void DoAnimationFrame(ref picture mypicture)
{
    if (mypicture.ShouldItBeShownInThisScreen)
    {
        if (mypicture.HasItAnimation)
        {
            if (mypicture.IsAnimationOn)
            {
                if (second % mypicture.FramePerSecond == 0)
                {
                    mypicture.strfilename = createFilename(mypicture.OriginalFilenameWithoutIndex, 
                      ref  mypicture, mypicture.TotalAnimationFrame);
                    mypicture.CurentAnimationFrame++;
                }
            }
        }
    }
}

public string createFilename(string OriginalFileName,ref picture mypicture,int TotalAnimationFrame)
{
    mypicture.CurentAnimationFrame++;
    string tempStr = "";
    if (mypicture.CurentAnimationFrame > TotalAnimationFrame) mypicture.CurentAnimationFrame = 1;
    tempStr = OriginalFileName + mypicture.CurentAnimationFrame.ToString();
    return tempStr;
}

To have the trace of time:

C#
public int second=0;
public int minute = 0;
public int hour=0;
public int day = 0; 

public void fncGenerateTime()
{
    second++;
    if (second == 60)
    {
        second = 0;
        minute++;
    }
    if (minute == 60)
    {
        minute = 0;
        hour++;
    }
    if (hour == 24)
    {
        day++;
    }
}

Points of Interest

So it was complete, any shortage in the article would be updated if mentioned.

We saw how to draw buttons, creating mouse events for buttons, how to create some animations for our labels and we also beeped. MP3 and wav file type were covered.

Wasn't it fun? Comment!

History  

Started it 1/23/2013.

Updated Mouse events on1/29/2013

License

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


Written By
Engineer
Italy Italy
Education:

NODET 2003-2007
B.Sc. Electrical Power Engineering 2007–2012

Work:

T.G.D. Co.(Power,Software) July 2010 -February 2012
Artnous Co.(Power) July 2010 -February 2012
Savay Co.(Software) September 2011- July 2012

Comments and Discussions

 
QuestionNeed to download Pin
sherryatef7-Jul-13 8:54
sherryatef7-Jul-13 8:54 
GeneralMy vote of 5 Pin
Michael Haephrati24-Jan-13 8:51
professionalMichael Haephrati24-Jan-13 8:51 
General.Re: My vote of 5 Pin
F.moghaddampoor29-Jan-13 4:36
F.moghaddampoor29-Jan-13 4:36 

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.