Click here to Skip to main content
15,867,453 members
Articles / Web Development / ASP.NET
Article

ASP.NET TreeView Control & the Client's Browser

Rate me:
Please Sign up or sign in to vote.
4.75/5 (346 votes)
22 Jul 200510 min read 1.9M   13.9K   288   183
Demonstrates how to use the Microsoft ASP.NET TreeView control from client-side script.

Introduction

This article demonstrates how to use the free TreeView control from Microsoft with C# and ASP.NET and how to intercept the server-side events on the client's browser to reduce the number of post-backs and increase performance of the application by handling the major events on the client.

Please provide a rating / vote before leaving this article

This is the only way authors get any type of credit for their work they freely share with everyone. It's sad to see articles that have helped over 100,000 people and fewer than 200 bother to vote or provide a rating.

Background

One common control used in web development is the TreeView. Developers can choose to ‘roll their own’ by using DHTML, purchase a third party control or use the free TreeView provided by Microsoft for use within ASP.NET. This article focuses on leveraging the free TreeView control made available by Microsoft.

One issue in using Microsoft’s free control is the lack of support and the fact that the control is designed to leverage ASP.NET’s server-side eventing architecture. Experienced web developers strive to reduce or eliminate as many server round trips as possible (commonly known as post-backs) to increase the end user’s satisfaction and response times.

This article will demonstrate how to use Microsoft’s TreeView control in such a way as to intercept the typical server-side events on the client without losing functionality. The examples in this article are basic and demonstrate how to store ‘meta-data’ with each node of the tree view that can be obtained and used on the client.

The control used in this article is a free download from Microsoft, and the control's overview is included in their online documentation.

What is the Microsoft TreeView?

The tree view provides an object oriented hierarchical or parent/child relationship view of data and meta-data. The most common example of a tree view is Windows Explorer’s directory structure where disk drives contain folders, folders contain sub-folders and folders contain files.

The MS TreeView control exposes several classes you can use to dynamically build the tree. The two main classes you will use are the TreeView and the TreeNode classes. The TreeView class provides the container that will hold 1 - N TreeNodes. The tree nodes represent individual items in the parent/child structure. Below is a short list of the two classes' characteristics:

Tree View / Node Characteristics
The TreeView control is the container for displaying the parent/child relationships.
The TreeView control contains 1 – N tree nodes.
A tree node represents an element in the parent/child relationship.
A tree node can be the parent and/or child of other tree nodes.
Tree nodes that have child nodes can be expanded to display their child nodes.
Tree nodes that have child nodes can be collapsed to hide their child nodes.
Tree nodes have a property named NodeData that can be used to store meta-data.
Tree nodes can display images (think of Windows Explorer where an image of either a closed or open folder is displayed).
Tree nodes can be hyperlinks to other web pages or sites.

What is the NodeData property?

The TreeNode class provides a property named NodeData. This property is a great place to store meta-data you may need to access either on the client or on the server.

The issue is the property’s underlying type is a string. So how do you store several pieces of meta-data in the property? Well, I’ve found the easiest way to do this is to create a delimited list of key valued pairs that can be accessed on either the client or server. I typically use a semi-colon (;) as the main delimiter and separate the key and value with an equal sign (=). For example, I would store meta-data in a tree node’s NodeData property as follows:

C#
TreeNode tn = new TreeNode();

tn.Text = "Root Parent Node";

tn.NodeData = "Id=1000;Name=Mike Elliott;Article=ASP.NET " + 
               "Tree View Control & the Client's Browser";

You can get at the NodeData property’s information on the client by accessing the element's nodeData attribute. This will allow you to obtain detail information about the tree node which the user has selected and display or submit the information as necessary. This single piece of functionality can reduce the majority of round trips or post-backs generally caused by needing meta-data associated with the node selected by the end user (this will be demonstrated later in the article).

You could choose to use XML instead of a key valued pair, but for performance reasons, I prefer to work with key valued pairs because they are stored in arrays.

Preparing to build the examples

Before jumping into examples, I want to provide a little set-up information in case you want to follow along.

The first step is to download Microsoft’s free control suite. The control suite contains more than just the TreeView; however, I am not going to cover the Tab control in this article. Once downloaded from the URL listed earlier in this article, you will have to build or run the build.bat file to produce the Microsoft.Web.UI.WebControls assembly by following the instructions in the readme file obtained in the download.

Be prepared, the .bat file distributed with the free download has a bug that may prevent the .dll from being produced (most of the time), and there are no warnings or exception messages given.

Open the build.bat file in Notepad, and find the line that reads:

csc.exe /out:build\Microsoft.Web.UI.WebControls.dll @IEWebControls.rsp

Unless you have placed the csc.exe compile utility in your main path or placed a copy of the executable in the system or system32 directory, the .bat file will not find the compile utility and fails silently.

To correct this, modify the .bat file to have the full path to the csc.exe compile utility as shown below (the location and version may be different on your machine):

C:\Windows\Microsoft.NET\Framework\v1.14322\csc.exe 
    /out:build\Microsoft.Web.UI.WebControls.dll @IEWebControls.rsp

This should correct the build issue and allow the Microsoft.Web.UI.WebControls.dll to be produced as the readme indicates in the download. Complete the installation instructions as detailed in the readme file.

Once you have properly installed the control library, create a new ASP.NET web application and add a reference to the newly created Microsoft.Web.UI.WebControls.dll. Next, add a Web Form named TVExample.aspx to the new web project. Now you will be ready to copy and paste the code as it is described in the article.

Client side events

I am going to demonstrate how to intercept the following events on the client instead of allowing server-side post-backs.

EventDescription
onSelectedIndexChangeThe event fired when the end user clicks a node in the tree view.
onExpandThe event fired when the end user expands a node.
onCollapseThe event fired when the end user collapses a node.
onDblClickThe event fired when the end user double clicks a node.
onContextMenu

The event fired when the end user right clicks a node in the tree view.

You will have to suppress the typical right-click context menu from being displayed and replace it with your functionality.

These are the most common events you will need to intercept to prevent the intrinsic server-side events or post-backs from happening. This should greatly increase your end user’s experience and increase the response time. It will also reduce the load on your web servers by pushing much of the work to the client’s machine.

The following examples are going to display the intercepted event, how to get the meta-data from the tree node's NodeData property (on the client) and how to parse the key valued pair information from the NodeData property.

Getting to the code

One point I want to make before getting into the code is ASP.NET provides access to most HTML and JavaScript attributes through the IAttributeAccessor interface. HtmlControl, ListItem, UserControl and WebControl types include intrinsic implementation for this interface. This interface allows programmatic access to any attribute defined in the tag of a server control. This is a great feature that allows you to intercept server-side events by pointing the events to your own JavaScript functions as you will see later in the article.

The HTML and the JavaScript code we will use to intercept what are typically server-side events is pretty straightforward and should be self-documenting, but here are a few high points to which you should pay attention:

  • We register the Microsoft.Web.UI.WebControls within the HTML page. This gives us declarative access to create the tree view:
    C#
    @Register TagPrefix="iewc" 
        Namespace="Microsoft.Web.UI.WebControls" 
        Assembly="Microsoft.Web.UI.WebControls"
  • There is a JavaScript function that will be called for each event we intercept on the client:
    JavaScript
    // Intercepts the index changed event on the client.
    //
    function TVIndexChanged()
    {
        ChangeText( 'node changed' );
    }
    
    // Intercepts the expand event on the client.
    //
    function TVNodeExpand()
    {
        ChangeText( 'onexpand' );
    }
    
    // Intercepts the collapse event on the client.
    //
    function TVNodeCollapse()
    {
        ChangeText( 'oncollapse' );
    }
    
    // Intercepts the double-click event on the client.
    //
    function TVDoubleClick()
    {
        ChangeText( 'dblclick' );
    }
    
    // Intercepts the right-click event on the client. The selected
    // tree node is changed to the node over which the end user has
    // right-clicked so that, that node's meta-data can be obtained.
    //
    function TVRightClick()
    {
        // Get a handle to the tree view.
        var tree = GetTreeHandle();
        var treenode;
    
        if ( null == tree || undefined == tree )
            return;
    
        // Get the selected tree node.
        treenode = tree.getTreeNode( event.treeNodeIndex );
    
        if ( null == treenode || undefined == treenode )
            return;
    
        // Cause the tree node that was right-clicked on to become the
        // selected node.
        tree.selectedNodeIndex = event.treeNodeIndex;
    
        ChangeText( 'oncontextmenu' );
    }
    
  • There is a JavaScript function that allows us to get a handle to the TreeView control itself:
    JavaScript
    // Gets a handle to the TreeView.
    //
    function GetTreeHandle()
    {
        var tree;
        var treeName = 'tvControl';
    
        // Get a handle to the TreeView.
        tree = document.getElementById( treeName );
    
        if ( null == tree || undefined == tree )
            return null;
    
        return tree;
    }
    
  • There is a JavaScript function that allows us to get a handle to the tree node object the end user has selected:
    JavaScript
    // Gets a handle to the TreeView's selected node.
    //
    function GetSelectedNode()
    {
        var tree = GetTreeHandle();
        var treeNode;
    
        if ( null == tree || undefined == tree )
            return null;
    
        treeNode = tree.getTreeNode( tree.selectedNodeIndex );
    
        if ( null == treeNode || undefined == treeNode )
            return null;
    
        return treeNode;
    }
    
  • There is a JavaScript function used to retrieve the NodeData attribute's information and return the value of the key passed in. This is one of the most important functions that allows you to retrieve the meta-data on the client and use it as necessary:
    JavaScript
    // Gets the value of the searchKey from the NodeData
    // of a TreeNode.
    //
    function GetKeyValue( searchKey )
    {
        // Get a handle to the selected TreeNode.
        var treenode = GetSelectedNode();
    
        // Validate the node handle.
        if ( null == treenode || undefined == treenode )
            return null;
    
        // Get the node's NodeData property's value.
        var nodeDataAry = treenode.getAttribute( 'nodeData' );
    
        if ( null == nodeDataAry || undefined == nodeDataAry )
            return null;
    
        nodeDataAry = nodeDataAry.split( ';' );
    
        if ( null == nodeDataAry || undefined == nodeDataAry ||
          0 >= nodeDataAry.length )
            return null;
    
        var count = 0;
        var returnValue = null;
    
        while ( count < nodeDataAry.length )
        {
            var workingItem = nodeDataAry[ count ];
    
            if ( 0 >= workingItem.length )
            {
                count++;
                continue;
            }
    
            // Split the string into its key value pairs.
            var kv = workingItem.split( '=' );
    
            if ( 1 >= kv.length )
            {
                count++;
                continue;
            }
    
            var key = kv[ 0 ];
            var kValue = kv[ 1 ];
    
            if ( key != searchKey )
            {
                count++;
                continue;
            }
    
            returnValue = kValue;
            break;
        }
    
        return returnValue;
    }
    

The entire TVExample.aspx code set

JavaScript
<%@ Page language="c#" Codebehind="TVExample.aspx.cs" 
    AutoEventWireup="false" 
    Inherits="Mike.Elliott.Articles.TreeView.TVExample" %>
<%@ Register TagPrefix="iewc" 
    Namespace="Microsoft.Web.UI.WebControls" 
    Assembly="Microsoft.Web.UI.WebControls" %>

  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 

  <html>
     <head>
       <title>Tree View Example</title>
       <meta name="GENERATOR" 
           Content="Microsoft Visual Studio .NET 7.1">
       <meta name="CODE_LANGUAGE" Content="C#">
       <meta name=vs_defaultClientScript content="JavaScript">
       <meta name=vs_targetSchema 
         content="http://schemas.microsoft.com/intellisense/ie5">
     </head>
   <body MS_POSITIONING="GridLayout">
   
   <script language="javascript">
   // Intercepts the index changed event on the client.
   //  
   function TVIndexChanged()
   {
       ChangeText( 'node changed' );
   }
   
   // Intercepts the expand event on the client.
   //    
   function TVNodeExpand()
   {
       ChangeText( 'onexpand' );
   }
   
   // Intercepts the collapse event on the client.
   //
   function TVNodeCollapse()
   {
       ChangeText( 'oncollapse' );
   }
   
   // Intercepts the double-click event on the client.
   //    
   function TVDoubleClick()
   {
       ChangeText( 'dblclick' );
   }
   
   // Intercepts the right-click event 
   // on the client. The selected 
   // tree node is changed to the node 
   // over which the end user has 
   // right-clicked so that, that node's 
   // meta-data can be obtained.
   //
   function TVRightClick()
   {
       // Get a handle to the tree view. 
       var tree = GetTreeHandle();
       var treenode;
   
       if ( null == tree || undefined == tree )
           return;
   
       // Get the selected node.
       treenode = tree.getTreeNode( event.treeNodeIndex );  
   
       if ( null == treenode || undefined == treenode )
           return;
   
       // Cause the tree node that was 
       // right-clicked on to become the 
       // selected node.  
       tree.selectedNodeIndex = event.treeNodeIndex;   
      
       ChangeText( 'oncontextmenu' );
   }
        
   // Simply changes the information 
   // in the display text boxes to 
   // demonstrate how to obtain meta-data 
   // from the selected node's 
   // NodeData property on the client.
   //
   function ChangeText( eventName )
   {
       var treeNode = GetSelectedNode();
       
       if ( null == treeNode || undefined == treeNode )
       {
           return;
       }     
       
       var nodeData = 
          treeNode.getAttribute( 'nodeData' ).split( ';' );          
           
       var id = GetKeyValue( 'SomeId' );
       var name = GetKeyValue( 'Name' );
                            
       document.getElementById( 'txtEvent' ).value = 
                                            eventName;
       document.getElementById( 'txtId' ).value = id;
       document.getElementById( 'txtName' ).value = name;
   }
   
   // Gets the value of the searchKey 
   // from the NodeData of a TreeNode.
   //
   function GetKeyValue( searchKey )
   {   
       // Get a handle to the selected TreeNode.
       var treenode = GetSelectedNode();
     
       // Validate the node handle.
       if ( null == treenode || undefined == treenode )
           return null;
   
       // Get the node's NodeData property's value.
       var nodeDataAry = treenode.getAttribute( 'nodeData' );
   
       if ( null == nodeDataAry || undefined == nodeDataAry )
           return null;
    
       nodeDataAry = nodeDataAry.split( ';' );
   
       if ( null == nodeDataAry || undefined == nodeDataAry || 
         0 >= nodeDataAry.length )
           return null;
    
       var count = 0;
       var returnValue = null;
   
       while ( count < nodeDataAry.length )
       {
           var workingItem = nodeDataAry[ count ];
    
           if ( 0 >= workingItem.length )
           {
               count++;
               continue;
           }
     
           // Split the string into its key value pairs.
           var kv = workingItem.split( '=' );
   
           if ( 1 >= kv.length )
           {
               count++;
               continue;
           }
     
           var key = kv[ 0 ];
           var kValue = kv[ 1 ];
   
           if ( key != searchKey )
           {
               count++;
               continue;
           }
   
           returnValue = kValue;
           break;
       }       
    
       return returnValue;
   }
    
   // Gets a handle to the TreeView.
   //
   function GetTreeHandle()
   {
       var tree;
       var treeName = 'tvControl';
      
       // Get a handle to the TreeView.
       tree = document.getElementById( treeName );
   
       if ( null == tree || undefined == tree )
           return null;
   
       return tree;
   }     
   
   // Gets a handle to the TreeView's selected node.
   //
   function GetSelectedNode()
   {
       var tree = GetTreeHandle();
       var treeNode;
   
       if ( null == tree || undefined == tree )
           return null;
   
       treeNode = tree.getTreeNode( tree.selectedNodeIndex );  
    
       if ( null == treeNode || undefined == treeNode )
           return null;
   
       return treeNode;
   }   
   </script>
   
   <form id="frmTVExample" method="post" runat="server">
       <table width="100%" align="center" border="0">
           <tr><td>
             <iewc:treeview id="tvControl" runat="server" 
                 SystemImagesPath=
                       "/webctrl_client/1_0/treeimages/" 
                 EnableViewState="False">
             </iewc:treeview>
           </td></tr>
           <tr>
           <td><input type="text" id="txtId" name="txtId">
           </td></tr>
           <tr>
           <td><input type="text" id="txtName" name="txtName">
           </td></tr>
           <tr>
           <td><INPUT type="text" id="txtEvent" name="txtEvent">
           </td> </tr>
       </table>
   </form>
   
   </body>
   </html>

The TVExample.aspx.cs or code behind (this section does not include the OnInit and InitializeComponent methods ASP.NET creates for you, so do not delete these methods or their content. It also does not include all the using directives, so don’t delete the using directives created for you, but you should add the using Microsoft.Web.UI.WebControls directive as indicated in the example):

C#
using Microsoft.Web.UI.WebControls;

namespace Mike.Elliott.Articles.TreeView
{
    public class TVExample : System.Web.UI.Page
    {
        protected Microsoft.Web.UI.WebControls.TreeView tvControl;

        private void Page_Load( object sender, System.EventArgs e )
        {
            // Create the root tree node.
            TreeNode root = new TreeNode();
            root.Text = "Root Parent Node";
            root.NodeData = "SomeId=1000;Name=Mike Elliott";

            // Create a child node.
            TreeNode tn = new TreeNode();
            tn.Text = "Child 1 of Root Parent";
            tn.NodeData = "SomeId=1001;Name=Play For Sport, Inc.";

            // Add the child to the root node.
            root.Nodes.Add( tn );

            // Create another child node.
            tn = new TreeNode();
            tn.Text = "Child 2 or Root Parent";
            tn.NodeData = "SomeId=1002;Name=Chip Oxendine";

            // Create a grandchild node and add it to its parent.
            TreeNode cn = new TreeNode();
            cn.Text = "Grandchild of Root Parent";
            cn.NodeData = "SomeId=1003;Name=Mike Elliott";
            tn.Nodes.Add( cn );

            // Add the child to the root node.
            root.Nodes.Add( tn );
            root.Expanded = true;

            // Add all the nodes to the tree view.
            this.tvControl.Nodes.Add( root );

            this.OverRideServerEvents();
        }

        private void OverRideServerEvents()
        {
            // Create and wire up the javascript event handlers.
            //
            string clickHandler = "TVIndexChanged();";
            this.tvControl.Attributes.Add( "onselectedindexchange",
                                                      clickHandler );

            clickHandler = "TVNodeExpand();";
            this.tvControl.Attributes.Add( "onexpand",
                                           clickHandler );

            clickHandler = "TVNodeCollapse();";
            this.tvControl.Attributes.Add( "oncollapse",
                                           clickHandler );

            clickHandler = "TVDoubleClick();";
            this.tvControl.Attributes.Add( "ondblclick",
                                           clickHandler );

            clickHandler = "TVRightClick();";
            this.tvControl.Attributes.Add( "oncontextmenu",
                                               clickHandler );
        }
    }
}

The Page_Load callback, event or method is the method called when the page loads. In this method we are simply creating the tree nodes, providing the node’s text, giving the node’s NodeData property meta-data in a key/value pair delimited string, adding the node to its parent and then finally adding the root node to the tree view.

Next, the Page_Load method delegates the work of wiring up the JavaScript intercept functions to the OverRideServerEvents method. As mentioned earlier in this article, ASP.NET provides a mechanism that allows you to add attributes to your server controls. This method simply adds attributes to the TreeView control for each event we want to intercept on the client. We add the attribute, which is the formal name of the tree view’s server-side event, and then redirect the event to our JavaScript function, and that’s all there is to it.

When you cause any of the events to fire, the events are intercepted by our JavaScript functions, the target tree node’s meta-data is parsed and displayed in the text boxes and the intercepted event’s name is also displayed so you can see exactly what's happening.

What's going on?

StepDescription
1The page is loaded.
2The tree view is created.
3A tree node is created.
4The tree node's Test and NodeData properties are set.
5The tree node is added to its parent node's Node collection.
6The root tree node is added to the tree view's Node collection.

Although I didn’t cover it in this article, you can completely manage the tree view’s state on the client by storing the state of each tree node (expanded/collapsed). This is something you will need to do to completely push the tree view’s functionality to the client. Otherwise, as soon as you cause a post-back, you will lose the state of which nodes were expanded, which nodes were collapsed and which node was selected. This is a topic that is a little more involved, and I will cover in a subsequent article.

Summary

It is very much possible to move the heavy server-side events to the client to streamline your ASP.NET applications that make use of the free MS TreeView control. It is very straightforward, and the concepts can be carried over to most of the intrinsic server controls included with ASP.NET.

History

  • Version 1.0.0.0

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
Chief Technology Officer
United States United States
Michael has spent almost 2 decades in the Enterprise IT industry. Michael continues to be hands-on and involved in low-level details of technologies, and he loves to guide and help his clients achieve success.

Comments and Discussions

 
GeneralRe: How to use CheckBoxes in TreeView ? Pin
Chinmay_V18-Jul-06 9:50
Chinmay_V18-Jul-06 9:50 
GeneralRe: How to use CheckBoxes in TreeView ? Pin
Chinmay_V18-Jul-06 9:43
Chinmay_V18-Jul-06 9:43 
AnswerRe: How to use CheckBoxes in TreeView ? Pin
ByteGuru1-Nov-05 3:45
ByteGuru1-Nov-05 3:45 
GeneralRe: How to use CheckBoxes in TreeView ? Pin
ByteGuru29-Dec-05 9:31
ByteGuru29-Dec-05 9:31 
GeneralOnly 3 text boxes get displayed. Pin
3-Aug-05 10:15
suss3-Aug-05 10:15 
GeneralRe: Only 3 text boxes get displayed. Pin
MichaelElliott3-Aug-05 14:10
MichaelElliott3-Aug-05 14:10 
GeneralNo images and all nodes on the same line Pin
DoomKickYourAss3-Aug-05 5:45
DoomKickYourAss3-Aug-05 5:45 
GeneralRe: No images and all nodes on the same line Pin
DoomKickYourAss3-Aug-05 5:57
DoomKickYourAss3-Aug-05 5:57 
Ok i find the solution... sorry i didn't read the other post before to send the mine..

The \webctrl_client\1_0\treeimages must be in the wwwroot folder, not in the application folder. !!

Hmmm | :|

Thanks bye !
GeneralRe: No images and all nodes on the same line Pin
MichaelElliott3-Aug-05 14:06
MichaelElliott3-Aug-05 14:06 
GeneralCan't get it to work... Pin
Seuss2-Aug-05 23:41
Seuss2-Aug-05 23:41 
GeneralRe: Can't get it to work... Pin
MichaelElliott3-Aug-05 14:15
MichaelElliott3-Aug-05 14:15 
GeneralRe: Can't get it to work... Pin
Seuss3-Aug-05 21:38
Seuss3-Aug-05 21:38 
GeneralBuild.bat file Pin
1-Aug-05 8:32
suss1-Aug-05 8:32 
GeneralRe: Build.bat file Pin
MichaelElliott1-Aug-05 13:53
MichaelElliott1-Aug-05 13:53 
GeneralRe: Build.bat file Pin
MichaelElliott1-Aug-05 15:02
MichaelElliott1-Aug-05 15:02 
Generalthis is great Pin
Mohamed Hussein Ragab30-Jul-05 23:44
Mohamed Hussein Ragab30-Jul-05 23:44 
GeneralRe: this is great Pin
MichaelElliott31-Jul-05 5:17
MichaelElliott31-Jul-05 5:17 
GeneralBroken links Pin
HB HB24-Jul-05 20:43
HB HB24-Jul-05 20:43 
GeneralRe: Broken links Pin
MichaelElliott25-Jul-05 9:08
MichaelElliott25-Jul-05 9:08 
GeneralTHANK YOU! Pin
DaveBrown24-Jul-05 10:23
DaveBrown24-Jul-05 10:23 
GeneralRe: THANK YOU! Pin
MichaelElliott25-Jul-05 9:09
MichaelElliott25-Jul-05 9:09 
GeneralTreeView appear flat in the browser Pin
jandf13-Jul-05 15:15
jandf13-Jul-05 15:15 
GeneralRe: TreeView appear flat in the browser Pin
MichaelElliott13-Jul-05 15:47
MichaelElliott13-Jul-05 15:47 
GeneralRe: TreeView appear flat in the browser Pin
jandf13-Jul-05 19:09
jandf13-Jul-05 19:09 
GeneralRe: TreeView appear flat in the browser Pin
MichaelElliott14-Jul-05 3:24
MichaelElliott14-Jul-05 3:24 

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.