Click here to Skip to main content
15,884,388 members
Articles / Web Development / ASP.NET

Moving Sorting Searching and Filtering Items between ListBoxes User Control

Rate me:
Please Sign up or sign in to vote.
4.44/5 (6 votes)
18 Sep 2014CPOL4 min read 22.1K   319   4   1
How to move, sort, filter and search items between Listboxes and make it as reusable user control using ASP.NET (C#)

Introduction

In this example, I will explain how to do searching, filtering, sorting and moving the items between two ListBoxes and make it a reusable user control using Generic Collection IList in ASP.NET (C#).

Generic collection is used to bind the ListBoxes with any Generic type IList and also explain how to bind both ListBoxes and remove all the existing records from the Source Listbox which already exists in Destination Listbox if in case you need to update the existing records which are already saved in database.

Background

Almost all web applications have security module to gives grants and privileges to the users. In this scenario, developers require two Listboxes for available permissions and granting permission. They need lots of effort to develop the functionality every time in their application.

Source Code

In the first part, explain how to develop the user control using two ListBoxes and add the functionality of these two ListBoxes. After that, we will add the user control in a page and call it .

User Control Code

  • Create a new website in .NET 2008 and add the user control from add new item from the solution. Name it ucListBox.

ASCX Code

HTML
<%@ Control Language="C#" AutoEventWireup="false" 
CodeBehind="ucListBox.ascx.cs" Inherits="ListBox_Demo.ucListBox" %>

<script type="text/javascript" language="javascript">
    var ddlText, ddlValue, ddl, lblMesg;
    function CacheItems() {

        ddlText = new Array();
        ddlValue = new Array();
        ddl = document.getElementById("<%=lstAvailable.ClientID %>");

        for (var i = 0; i < ddl.options.length; i++) {
            ddlText[ddlText.length] = ddl.options[i].text;
            ddlValue[ddlValue.length] = ddl.options[i].value;
        }
    }

    window.onload = CacheItems;

    function FilterItems(value) {

        ddl.options.length = 0;
        for (var i = 0; i < ddlText.length; i++) {
            if (ddlText[i].toLowerCase().indexOf(value) != -1) {
                AddItem(ddlText[i], ddlValue[i]);
            }
        }

        if (ddl.options.length == 0) {
            AddItem("", "");
        }
    }

    function AddItem(text, value) {

        var opt = document.createElement("option");
        opt.text = text;
        opt.value = value;
        ddl = document.getElementById("<%=lstAvailable.ClientID %>");
        ddl.options.add(opt);
    }
 
</script>

<table width="100%" border="0" id="tblSearch" runat="server" visible="true">
    <tr>
        <td style="width: 19%; height: 24px;" align="left" runat="server" id="tdd">
            <span style="font-size: 8pt"><strong>
                <asp:Label runat="server" Text="Search" ID="lblSearch"></asp:Label>
            </strong></span>
        </td>
        <td>
            <asp:TextBox ID="txtSearch" runat="server" 
            onkeyup="FilterItems(this.value)"></asp:TextBox><br />
        </td>
    </tr>
</table>
<table width="80%" border="0">
    <tr align="left">
        <td align="left">
            <!-- Start  list box-->
            <table width="100%">
                <tr>
                    <td valign="top">
                        <table border="0" width="100%">
                            <tr>
                                <td>
                                    <span style="font-size: 8pt"><strong>
                                        <asp:Label runat="server" 
                                        ID="lblAvailable" Text="Available"></asp:Label>
                                    </strong></span>
                                </td>
                            </tr>
                            <tr>
                                <td style="height: 200px">
                                    <asp:ListBox ID="lstAvailable" 
                                    runat="server" Height="100%" SelectionMode="Multiple"
                                        Width="300px" Font-Size="10pt"></asp:ListBox>
                                </td>
                            </tr>
                        </table>
                    </td>
                    <td align="center" valign="top">
                        <table border="0" cellpadding="0" cellspacing="0">
                            <tr>
                                <td>
                                    <br />
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    <br />
                                </td>
                            </tr>
                            <tr>
                                <td>
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    <asp:ImageButton ID="btnAddAll" 
                                    ImageUrl="~/Images/GrantAll.png" 
                                    runat="server" CausesValidation="False" />
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    <asp:ImageButton ID="btnAdd" 
                                    ImageUrl="~/Images/Grant.png" 
                                    runat="server" CausesValidation="False" />
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    <asp:ImageButton ID="btnRemove" 
                                    ImageUrl="~/Images/Revoke.png" 
                                    runat="server" CausesValidation="False" />
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    <asp:ImageButton ID="btnRemoveAll" 
                                    ImageUrl="~/Images/RevokeAll.png" runat="server"
                                        CausesValidation="False" />
                                </td>
                            </tr>
                        </table>
                    </td>
                    <td valign="top">
                        <table width="100%">
                            <tr>
                                <td>
                                    <span style="font-size: 8pt"><strong>
                                        <asp:Label runat="server" 
                                        ID="lblAdded" Text="Added"></asp:Label>
                                    </strong></span>
                                </td>
                            </tr>
                            <tr>
                                <td style="height: 200px">
                                    <asp:ListBox ID="lstAdded" 
                                    runat="server" Font-Size="10pt" 
                                    Height="100%" SelectionMode="Multiple"
                                        Width="300px"></asp:ListBox>
                                </td>
                            </tr>
                            <tr>
                                <td>
                                </td>
                            </tr>
                        </table>
                    </td>
                    <td>
                        <table width="100%">
                            <tr>
                                <td>
                                    <asp:ImageButton ID="btnMoveUp" 
                                    runat="server" ImageUrl="~/Images/MoveUp.png" 
                                    CausesValidation="False" />
                                </td>
                            </tr>
                            <tr>
                                <td>
                                    <asp:ImageButton ID="btnMoveDown" 
                                    runat="server" CausesValidation="False"
                                        ImageUrl="~/Images/MoveDown.png" />
                                </td>
                            </tr>
                        </table>
                    </td>
                </tr>
            </table>
            <!-- end  list box-->
        </td>
    </tr>
</table>

In the above code, I added one label and text box for searching and filtering the records in the available ListBox called lstAvailable.

I have used two listboxes - lstAvailable and lstAdded, Move, Next, Pervious, First, Up and Down buttons for moving and sorting the items between the ListBoxes.

The above three JavaScript methods take care of the Filtering and Searching process. The details of these methods is described below:

CacheItems

This method is called on the window onload event. The job of this method is to populate text and value arrays that will be used to cache the ListBox items of Available List.

FilterItems

This method is called when keyup event fires in the Search TextBox. This method searches for the string segment and filters the ListBox items of Available List.

AddItem

This method is used to add a new item in the ListBox.

C# Code

Add the System.Collections in code behind and add the following code:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections;

namespace ListBox_Demo
{
    public partial class ucListBox : System.Web.UI.UserControl
    {
        #region Property
        private IList _DataSourceAvailable = null;
        private IList _DataSourceAdded = null;
        private string _dataTextFieldAvailable = string.Empty;
        private string _dataValueFieldAvailable = string.Empty;
        private string _dataTextFieldAdded = string.Empty;
        private string _dataValueFieldAdded = string.Empty;
        private string _availableItemText = "Available Roles"; //Resources.Common.AvailableItemText;
        private string _addedItemText = "Assgin Roles";// Resources.Common.AddedItemText;
        protected ArrayList arlList = new ArrayList();

        /// <summary>
        /// Gets or sets the available item text.
        /// </summary>
        /// <value>The available item text.</value>
        public string AvailableItemHeaderText
        {
            get { return _availableItemText; }
             set { _availableItemText = value; }
        }

        /// <summary>
        /// Gets or sets the added items text.
        /// </summary>
        /// <value>The added items text.</value>
        public string AddedItemsHeaderText
        {
            get { return _addedItemText; }
              set { _addedItemText = value; }
        }

        /// <summary>
        /// Gets the available items.
        /// </summary>
        /// <value>The available items.</value>
        public ListItemCollection AvailableItems
        {
            get { return lstAvailable.Items; }
        }

        /// <summary>
        /// Gets the added items.
        /// </summary>
        /// <value>The added items.</value>
        public ListItemCollection AddedItems
        {
            get { return lstAdded.Items; }
        }

        /// <summary>
        /// Gets or sets the data source for the available items listbox.
        /// </summary>
        /// <value>The data source for available items.</value>
        public IList DataSourceAvailable
        {
            get { return _DataSourceAvailable; }
            set { _DataSourceAvailable = value; }
        }

        /// <summary>
        /// Gets or sets the data source for the added items listbox.
        /// </summary>
        /// <value>The data source for added items.</value>
        public IList DataSourceAdded
        {
            get { return _DataSourceAdded; }
            set { _DataSourceAdded = value; }
        }

        /// <summary>
        /// Gets or sets the data text field available.
        /// </summary>
        /// <value>The data text field available.</value>
        public string DataTextFieldAvailable
        {
            get { return _dataTextFieldAvailable; }
            set { _dataTextFieldAvailable = value; }
        }

        /// <summary>
        /// Gets or sets the data value field available.
        /// </summary>
        /// <value>The data value field available.</value>
        public string DataValueFieldAvailable
        {
            get { return _dataValueFieldAvailable; }
            set { _dataValueFieldAvailable = value; }
        }

        /// <summary>
        /// Gets or sets the data text field added.
        /// </summary>
        /// <value>The data text field added.</value>
        public string DataTextFieldAdded
        {
            get { return _dataTextFieldAdded; }
            set { _dataTextFieldAdded = value; }
        }

        /// <summary>
        /// Gets or sets the data value field added.
        /// </summary>
        /// <value>The data value field added.</value>
        public string DataValueFieldAdded
        {
            get { return _dataValueFieldAdded; }
            set { _dataValueFieldAdded = value; }
        }
        #endregion

        #region Method
        protected void Page_Load(object sender, EventArgs e)
        {

        }
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            Initializer();

        }
        private void Initializer()
        {
            btnAdd.Click += new ImageClickEventHandler(btnAdd_Click);
            btnAddAll.Click += new ImageClickEventHandler(btnAddAll_Click);
            btnRemove.Click += new ImageClickEventHandler(btnRemove_Click);
            btnRemoveAll.Click += new ImageClickEventHandler(btnRemoveAll_Click);
            btnMoveUp.Click += new ImageClickEventHandler(btnMoveUp_Click);
            btnMoveDown.Click += new ImageClickEventHandler(btnMoveDown_Click);
            //lblAdded.Text = this.AddedItemsHeaderText;
           //lblAvailable.Text = this.AvailableItemHeaderText;
        }

        public void BindAvailableList()
        {
            //Set the Header Text of the Available and Added Items
            lblAdded.Text = this.AddedItemsHeaderText;
            lblAvailable.Text = this.AvailableItemHeaderText;

            //Bind the Added List Control
            lstAvailable.DataSource = this.DataSourceAvailable;
            lstAvailable.DataTextField = this.DataTextFieldAvailable;
            lstAvailable.DataValueField = this.DataValueFieldAvailable;
            lstAvailable.DataBind();
        }
        public void BindAddedList()
        {
            //Set the Header Text of the Available and Added Items
            lblAdded.Text = this.AddedItemsHeaderText;
            lblAvailable.Text = this.AvailableItemHeaderText;

            //Bind the Available List Control
            lstAdded.DataSource = this.DataSourceAdded;
            lstAdded.DataTextField = this.DataTextFieldAdded;
            lstAdded.DataValueField = this.DataValueFieldAdded;
            lstAdded.DataBind();
        }

        public void BindList()
        {
            //Set the Header Text of the Available and Added Items
            lblAdded.Text = this.AddedItemsHeaderText;
            lblAvailable.Text = this.AvailableItemHeaderText;

            //Bind the Available and Added List Controls
            lstAdded.DataSource = this.DataSourceAdded;
            lstAdded.DataTextField = this.DataTextFieldAdded;
            lstAdded.DataValueField = this.DataValueFieldAdded;
            lstAdded.DataBind();

            lstAvailable.DataSource = this.DataSourceAvailable;
            lstAvailable.DataTextField = this.DataTextFieldAvailable;
            lstAvailable.DataValueField = this.DataValueFieldAvailable;
            lstAvailable.DataBind();

            #region Remove intersection Record
            ArrayList ary = new ArrayList();

            for (int i = 0; i < lstAvailable.Items.Count; i++)
            {
                for (int j = 0; j < lstAdded.Items.Count; j++)
                {

                    if (lstAvailable.Items[i].Value == lstAdded.Items[j].Value)
                    {
                        // lstAvailable.RemoveAt(i);
                        if (!ary.Contains(lstAvailable.Items[i]))
                        {
                            ary.Add(lstAvailable.Items[i]);
                            j = -1;
                        }
                        break;
                    }
                }
            }
            for (int i = 0; i < ary.Count; i++)
            {
                lstAvailable.Items.Remove(((ListItem)ary[i]));
            }

            #endregion

            ScriptManager.RegisterStartupScript(this.Page, typeof(Page), "myscript", "CacheItems();", true);
            //ScriptManager.RegisterStartupScript(Page, typeof(Page), "myscript", "UploadFileToParent();", true);
        }

        #endregion

        #region Navigation

        /// <summary>
        /// Add all the selected items from the Available Items to the Added Items
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> 
        /// instance containing the event data.</param>
        protected void btnAdd_Click(object sender, EventArgs e)
        {
            if (lstAvailable.SelectedIndex >= 0)
            {
                for (int i = 0; i < lstAvailable.Items.Count; i++)
                {
                    if (lstAvailable.Items[i].Selected)
                    {
                        if (!arlList.Contains(lstAvailable.Items[i]))
                            arlList.Add(lstAvailable.Items[i]);
                    }
                }
                for (int i = 0; i < arlList.Count; i++)
                {
                    if (!lstAdded.Items.Contains((ListItem)arlList[i]))
                        lstAdded.Items.Add((ListItem)arlList[i]);
                    lstAvailable.Items.Remove((ListItem)arlList[i]);
                }
            }
        }

        /// <summary>
        /// Add all the items from the Available items to the Added Items
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="
        /// System.EventArgs"/> instance containing the event data.</param>
        protected void btnAddAll_Click(object sender, EventArgs e)
        {
            foreach (ListItem list in lstAvailable.Items)
            {
                lstAdded.Items.Add(list);
            }
            lstAvailable.Items.Clear();
        }

        /// <summary>
        /// Moves the Selected items from the Added items to the Available items
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="
        /// System.EventArgs"/> instance containing the event data.</param>
        protected void btnRemove_Click(object sender, EventArgs e)
        {
            if (lstAdded.SelectedIndex >= 0)
            {
                for (int i = 0; i < lstAdded.Items.Count; i++)
                {
                    if (lstAdded.Items[i].Selected)
                    {
                        if (!arlList.Contains(lstAdded.Items[i]))
                            arlList.Add(lstAdded.Items[i]);
                    }
                }
                for (int i = 0; i < arlList.Count; i++)
                {
                    if (!lstAvailable.Items.Contains((ListItem)arlList[i]))
                        lstAvailable.Items.Add((ListItem)arlList[i]);
                    lstAdded.Items.Remove((ListItem)arlList[i]);
                }
            }
        }

        /// <summary>
        /// Moves all the items from the Added items to the Available items
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">
        /// The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void btnRemoveAll_Click(object sender, EventArgs e)
        {
            foreach (ListItem list in lstAdded.Items)
            {
                lstAvailable.Items.Add(list);
            }
            lstAdded.Items.Clear();
        }

        /// <summary>
        /// Move item to upwards
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void btnMoveDown_Click(object sender, ImageClickEventArgs e)
        {
            try
            {
                int startindex = lstAdded.Items.Count - 1;
                for (int i = startindex; i > -1; i--)
                {
                    if (lstAdded.Items[i].Selected)//identify the selected item
                    {
                        //swap with the lower item(move down)
                        if (i < startindex && !lstAdded.Items[i + 1].Selected)
                        {
                            ListItem bottom = lstAdded.Items[i];
                            lstAdded.Items.Remove(bottom);
                            lstAdded.Items.Insert(i + 1, bottom);
                            lstAdded.Items[i + 1].Selected = true;
                        }
                    }
                }
            }
            catch (Exception ex)
            {

            }
        }

        /// <summary>
        /// Move Item To down Words
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void btnMoveUp_Click(object sender, ImageClickEventArgs e)
        {
            try
            {
                for (int i = 0; i < lstAdded.Items.Count; i++)
                {
                    if (lstAdded.Items[i].Selected)//identify the selected item
                    {
                        //swap with the top item(move up)
                        if (i > 0 && !lstAdded.Items[i - 1].Selected)
                        {
                            ListItem bottom = lstAdded.Items[i];
                            lstAdded.Items.Remove(bottom);
                            lstAdded.Items.Insert(i - 1, bottom);
                            lstAdded.Items[i - 1].Selected = true;
                        }
                    }
                }
            }
            catch (Exception ex)
            {

            }
        }

        #endregion
    }
}

The above mentioned three binds functions are used to bind ListBox and each one has a different functionality whose description is given below.

BindAvailableList()

If you want to bind the Available ListBox, then call this method form the aspx page and its required following properties should be assigned from the aspx page:

  • DataSourceAvailable: Pass the IList Collection
  • DataTextFieldAvailable: Its required Property Name which shows in the ListBox
  • DataValueFieldAvailable: Its required Property ID which should be unique in the ListBox
BindAddedList()

If you want to bind the Added ListBox, then call this method form the aspx page and its required following properties should be assign from the aspx page.

  • DataSourceAdded: Pass the IList Collection
  • DataTextFieldAdded: Its required Property Name which shows in the ListBox
  • DataValueFieldAdded: Its required Property ID which should be unique in the ListBox
BindList()

If you want to bind both ListBoxes Available and Added and need to remove all the items which already exist in the Added ListBox, then call this function from the aspx page and its required following properties should be assigned from the aspx page.

  • DataSourceAvailable: Pass the IList Collection
  • DataTextFieldAvailable: Its required Property Name which shows in the ListBox
  • DataValueFieldAvailable: Its required Property ID which should be unique in the ListBox
  • DataSourceAdded: Pass the IList Collection
  • DataTextFieldAdded: Its required Property Name which shows in the ListBox
  • DataValueFieldAdded: Its required Property ID which should be unique in the ListBox

Note

AvailableItemHeaderText and AddedItemsHeaderText are optional, It will be required if you want to change the heading of Listboxes from aspx page.

GUI Page Code

Aspx Code

Add the ucListBox user control in default.aspx page which is automatically added in the website when you created it in .NET 2008.

ASP.NET
<%@ Register src="ucListBox.ascx" tagname="ucListBox" tagprefix="uc1" %>

To show the complete working of user control, I have created three buttons in a page and call user control all three binds methods.

HTML
<%@ Page Language="C#" AutoEventWireup="false" CodeBehind="Default.aspx.cs" Inherits="ListBox_Demo._Default" %>

<%@ Register src="ucListBox.ascx" tagname="ucListBox" tagprefix="uc1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="btnAvailable" runat="server" Text="Bind Available List" 
            onclick="btnAvailable_Click" />
        <asp:Button ID="btnAdded" runat="server" Text="Bind Added List" 
            onclick="btnAdded_Click"  />
        <asp:Button ID="btnBoth" runat="server" Text="Bind Both" 
            onclick="btnBoth_Click"  />
        <uc1:ucListBox ID="ucListBox1" runat="server" />
    
    </div>
    </form>
</body>
</html>

C# Code

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections;

namespace ListBox_Demo
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {           
        }
        
        void AvailableItems()
        {
             List<Roles> lstRole = new List<Roles>();
            Roles objRoles = new Roles();
            objRoles.ID = 1;
            objRoles.Name = "Admin";
            lstRole.Add(objRoles);

            objRoles = new Roles();
            objRoles.ID = 2;
            objRoles.Name = "Manager";
            lstRole.Add(objRoles);

            objRoles = new Roles();
            objRoles.ID = 3;
            objRoles.Name = "Supervisor";
            lstRole.Add(objRoles);

            objRoles = new Roles();
            objRoles.ID = 4;
            objRoles.Name = "Employee";
            lstRole.Add(objRoles);

            objRoles = new Roles();
            objRoles.ID = 5;
            objRoles.Name = "User";
            lstRole.Add(objRoles);

            objRoles = new Roles();
            objRoles.ID = 6;
            objRoles.Name = "Client";
            lstRole.Add(objRoles);

            ucListBox1.DataTextFieldAvailable = "Name";
            ucListBox1.DataValueFieldAvailable = "ID";
            ucListBox1.DataSourceAvailable = lstRole;
            ucListBox1.AvailableItemHeaderText = "Available Permissions";
            ucListBox1.AddedItemsHeaderText = "Added Permissions";     
        }

        void SelectedItems()
        {
            List<Roles> lstRole = new List<Roles>();
            Roles objRoles = new Roles();
            objRoles.ID = 1;
            objRoles.Name = "Admin";
            lstRole.Add(objRoles);

            objRoles = new Roles();
            objRoles.ID = 2;
            objRoles.Name = "Manager";
            lstRole.Add(objRoles);
            
            objRoles = new Roles();
            objRoles.ID = 4;
            objRoles.Name = "Employee";
            lstRole.Add(objRoles);

            objRoles = new Roles();
            objRoles.ID = 5;
            objRoles.Name = "User";
            lstRole.Add(objRoles);

            ucListBox1.DataTextFieldAdded = "Name";
            ucListBox1.DataValueFieldAdded = "ID";
            ucListBox1.DataSourceAdded = lstRole;
            ucListBox1.AvailableItemHeaderText = "Available Permissions";
            ucListBox1.AddedItemsHeaderText = "Added Permissions";
        }

        protected void btnAvailable_Click(object sender, EventArgs e)
        {
            AvailableItems();
            ucListBox1.BindAvailableList();
        }

        protected void btnAdded_Click(object sender, EventArgs e)
        {
            SelectedItems();
            ucListBox1.BindAddedList();
        }

        protected void btnBoth_Click(object sender, EventArgs e)
        {
            AvailableItems();
            SelectedItems();
            ucListBox1.BindList();
        }
    }

    public class Roles
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }
}

I have created two functions, SelectedItems() and AvailableItems() to fill the hard code List and call it on three different button clicks.

Image 1

Points of Interest

It was annoying writing these examples since they are too big and took up a lot of my time.

License

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


Written By
Web Developer
Pakistan Pakistan
I have worked on number of technologies including C# .Net, VB .Net, ASP.Net, LINQ, WCF, X++, and SharePoint,Oracle,Crystal Reports.

http://dotnetfarrukhabbas.blogspot.com

Comments and Discussions

 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun18-Sep-14 2:58
Humayun Kabir Mamun18-Sep-14 2:58 

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.