Click here to Skip to main content
15,944,136 members
Articles / Multimedia / GDI+

Creating EAN-13 Barcodes with C#

Rate me:
Please Sign up or sign in to vote.
4.88/5 (63 votes)
11 May 200512 min read 680.7K   50.8K   219   74
Demonstrates creating EAN-13 Barcodes with C#.


This article came about as a response to a request to extend the UPC-A barcode example to include EAN-8 and EAN-13. In this article, we will look at the EAN-13 specification and examine some code that can produce EAN-13 barcodes.

EAN-13 Background

The EAN-13 barcode is composed of 13 digits, which are made up of the following sections: the first 2 or 3 digits are the country code, the next 5 to 7 digits are the manufacturer code, the next 3 to 5 digits are the product code, and the last digit is the checksum digit.

The figure below shows a typical EAN-13 barcode.

Country Code

The Country Code is a 2 or 3 digit number, which is used to identify the country that assigned the manufacturer code. All EAN-13 barcodes that begin with "0" are UPC-A barcodes. The following table contains the Country Codes:

00-13: USA & Canada20-29: In-Store Functions30-37: France
40-44: Germany45: Japan (also 49)46: Russian Federation
471: Taiwan474: Estonia475: Latvia
477: Lithuania479: Sri Lanka480: Philippines
482: Ukraine484: Moldova485: Armenia
486: Georgia487: Kazakhstan489: Hong Kong
49: Japan (JAN-13)50: United Kingdom520: Greece
528: Lebanon529: Cyprus531: Macedonia
535: Malta539: Ireland54: Belgium & Luxembourg
560: Portugal569: Iceland57: Denmark
590: Poland594: Romania599: Hungary
600 & 601: South Africa609: Mauritius611: Morocco
613: Algeria619: Tunisia622: Egypt
625: Jordan626: Iran64: Finland
690-692: China70: Norway729: Israel
73: Sweden740: Guatemala741: El Salvador
742: Honduras743: Nicaragua744: Costa Rica
746: Dominican Republic750: Mexico759: Venezuela
76: Switzerland770: Colombia773: Uruguay
775: Peru777: Bolivia779: Argentina
780: Chile784: Paraguay785: Peru
786: Ecuador789: Brazil80 - 83: Italy
84: Spain850: Cuba858: Slovakia
859: Czech Republic860: Yugloslavia869: Turkey
87: Netherlands880: South Korea885: Thailand
888: Singapore890: India893: Vietnam
899: Indonesia90 & 91: Austria93: Australia
94: New Zealand955: Malaysia977: International Standard Serial Number for Periodicals (ISSN)
978: International Standard Book Numbering (ISBN)979: International Standard Music Number (ISMN)980: Refund receipts
981 & 982: Common Currency Coupons99: Coupons

Manufacturer Code

The EAN Manufacturer Code is a variable length number. Typically, 5 digit codes are assigned to companies, however, some companies do not produce enough products to warrant a 5 digit product code, and in such cases the EAN will issue Manufacturer Codes longer than 5 digits.

Product Code

The manufacturer is free to assign its own product codes, but they must make sure that each product code is unique within their product codes. The product codes can be as few as 3 digits long, or as long as 5 digits depending on the length of the country and manufacturer codes.

Checksum Digit

The checksum digit is calculated using the country code, manufacturer's code, and the product code. The odd numbers starting with the right most digits are multiplied by 3 and added to the sum, while the even numbers are simply added to the sum. The reason for the EAN-13 check sum being calculated in reverse order (starting with the right most digit and considering it as being odd instead of even) is for compatibility with UPC-A barcodes. The modulus of 10 is then taken of the summed total. This is subtracted from 10, and the modulus of 10 is taken again.

For example: EAN-13 001234567890
Country Code : 00
Manufacturer's Code : 12345
Product Code : 67890

The last or right most digit is '0' and is considered odd, so multiply it by 3, the second right most digit '9' is even so just add it, etc...

(0 * 3) + 9 + (8 * 3) + 7 + (6 * 3) + 5 + (4 * 3) + 3 + (2 * 3) + 1 + (0 * 3) + 0 = 85

85 % 10 = 5

( ( 10 - 5 ) % 10 ) = 5

Symbol Size

The specifications for the EAN-13 barcode specify the nominal size as 37.29mm wide and 25.93mm high. Based upon this nominal size, the EAN-13 barcode can be scaled by a magnification factor of 0.8 to 2.0. Scaling the barcode will produce a barcode between the minimal allowable size of 29.83mm wide by 20.74mm high and the maximum allowable size of 74.58mm wide and 51.86mm high.

Digit Patterns

Each digit in a EAN-13 bar code is composed of a series of two spaces and two bars. Each digit is drawn within a space that is 7 modules wide. In addition to the 13 digits, which make up a EAN-13 barcode, the barcode symbol also has two quite zones, a lead block, a separator, and a trailing block. Each quite zone is 9 modules wide, the lead and trailing blocks are a series of lines and spaces in the format of bar, space, bar. The separator is signified by the sequence space / bar / space / bar / space.

Special SymbolPattern
Quite Zone000000000
Lead / Trailer101

where '0' represents space and '1' denotes a bar.

In addition to the special symbol patterns listed above, the EAN-13 barcode symbol uses three distinct digit patterns as well, the Left Digit Odd Parity pattern, the Left Digit Even Parity pattern and the Right Digit pattern. The Left Digit patterns start with spaces, and the Right Digit pattern starts with bars (see table below).

NumberLeft DigitsRight Digits
Odd ParityEven Parity

where a '0' denotes a space and '1' represents a bar.

The first digit of the Country Code is used to determine the parity of each digit of the Manufacturer's Code, see the Determining Number Parity section. The Right Digit pattern is typically used to draw the product code and the checksum digit. However, it will be used to render part of the manufacturer's code if the country code is greater than 2, or if the manufacturer's code is greater than 5.

Determining Number Parity

The first digit of the Country Code in an EAN-13 barcode is not encoded, it is used to determine the parity of the digits in the manufacturer code. The second digit of the country code is always odd and the manufacturer's code will have three left-hand numbers that use even parity and two that use odd parity, except a UPC-A compatible barcode which uses all odd parity. The table below outlines the parity for the numbers in the Manufacturer's Code.

First Country Code DigitParity
Second Country Code DigitManufacturer Code Digits
0 (UPC-A)OddOddOddOddOddOdd

For example, if the country code is 75 then based upon the 1st country code digit and the above table, the 2nd digit of the country code, 5, would be Odd. The 1st digit of the Manufacturer Code would use the Even pattern, the 2nd digit would use the Odd pattern, the 3rd digit would use the Even pattern, the 4th would be Odd, and finally the 5th digit would use the Even pattern.

Using the code

First, we will examine how to use the Ean13 class, and then we'll examine how the Ean13 class works.

Using the Ean13 Class

The code excerpt below uses the Ean13 class to draw a EAN-13 barcode in a picture box control:

private void DrawEan13( )
    System.Drawing.Graphics g = this.picBarcode.CreateGraphics( ); 

         new System.Drawing.SolidBrush(System.Drawing.SystemColors.Control), 
         new Rectangle(0, 0, picBarcode.Width, picBarcode.Height)); 
    // Create an instance of the Ean13 Class.        
    upc = new Ean13( ); 
    upc.CountryCode = "12";
    upc.ManufacturerCode = "34567"; 
    upc.ProductCode = "89012"; 
    upc.Scale = 
        (float)Convert.ToDecimal(cboScale.Items [cboScale.SelectedIndex]); 
    upc.DrawEan13Barcode( g, new System.Drawing.Point( 0, 0 ) ); 
    g.Dispose( );

The first step for the DrawEan13 function is to create an instance of the Ean13 class, and then set the country code, manufacturer code, the product code, and the scale factor properties (the check sum will be calculated by the Ean13 class). Once these properties are set, a call to the DrawEan13Barcode function is made, passing a Graphics object and a Point, which indicates the starting position to draw at, this will cause the barcode to be drawn in the picture box starting at point (0, 0).

The Ean13 Class

The most significant variables are listed below:

// This is the nomimal size recommended by the EAN.
private float _fWidth = 37.29f;
private float _fHeight = 25.93f;
private float _fFontSize = 8.0f;
private float _fScale = 1.0f;

// Left Hand Digits.
private string [] _aOddLeft = { "0001101", "0011001", "0010011", "0111101", 
                                "0100011", "0110001", "0101111", "0111011", 
                                "0110111", "0001011" };

private string [] _aEvenLeft = { "0100111", "0110011", "0011011", "0100001", 
                                 "0011101", "0111001", "0000101", "0010001", 
                                 "0001001", "0010111" };

// Right Hand Digits.
private string [] _aRight = { "1110010", "1100110", "1101100", "1000010", 
                              "1011100", "1001110", "1010000", "1000100", 
                              "1001000", "1110100" };

private string _sQuiteZone = "000000000";

private string _sLeadTail = "101";

private string _sSeparator = "01010";

The _fWidth, _fHeight, and the _fScale variables are initialized with the nominal size. When the barcode is rendered, its actual size will be determined by the nominal size, and the scale factor, as discussed in the Symbol Size section of this article. The variables _aOddLeft, _aEvenLeft, _aRight, _sQuiteZone, _sLeadTail, and _sSeparator are all string representations of the bar/space graphics, which represent the various parts of an EAN-13 barcode. Essentially, a '1' represents a bar and a '0' represents a space, so _sSeparator would cause a space-bar-space-bar-space to be rendered. An alternate method to using a string could be to use a binary representation, where a 0 bit would be a space and a 1 bit is a bar.

There are four primary functions which provide the majority of the functionality for the Ean13 class. The workhorse of these functions is DrawEan13Barcode, which uses several functions as helper functions. The helper functions are: CalculateChecksumDigit, ConvertToDigitPatterns, ConvertLeftPattern which will be discussed first. There is also a fifth function, CreateBitmap, which provides an easy means for creating a bitmap image.

The first helper function DrawEan13Barcode calls the CalculateChecksumDigit function, which uses the country code, manufacturer code, and product code to calculate the barcode's check sum.

public void CalculateChecksumDigit( )
    string sTemp = 
      this.CountryCode + this.ManufacturerCode + this.ProductCode;
    int iSum = 0;
    int iDigit = 0;

    // Calculate the checksum digit here.
    for( int i = sTemp.Length; i >= 1; i-- )
        iDigit = Convert.ToInt32( sTemp.Substring( i - 1, 1 ) );
        // This appears to be backwards but the 
        // EAN-13 checksum must be calculated
        // this way to be compatible with UPC-A.
        if( i % 2 == 0 )
        { // odd  
            iSum += iDigit * 3;
        { // even
            iSum += iDigit * 1; 
    int iCheckSum = ( 10 - ( iSum % 10 ) )  % 10; 
    this.ChecksumDigit = iCheckSum.ToString( );

The CalculateChecksumDigit function calculates the check sum using the method discussed in the Checksum Digit section listed above.

The second helper function used is the ConvertToDigitPatterns function. This function takes the individual numbers of the manufacturer code, and the product number, and converts them to the string representation of the barcode graphics.

private string ConvertToDigitPatterns(string inputNumber, string [] patterns)
    System.Text.StringBuilder sbTemp = new StringBuilder( );
    int iIndex = 0;
    for( int i = 0; i < inputNumber.Length; i++ )
        iIndex = Convert.ToInt32( inputNumber.Substring( i, 1 ) );
        sbTemp.Append( patterns[iIndex] );
    return sbTemp.ToString( );

The ConvertToDigitPatterns function requires two parameters:

  • inputNumber
  • patterns

The inputNumber will be either the manufacturer number, or the product number, and the patterns will be the _aOddLeft, _aEvenLeft, or the _aRight array depending on whether the inputNumber is the manufacturer number or the product number.

The ConvertLeftPatterns is used to create the left hand patterns discussed in the Determining Number Parity section. The ConvertLeftPatterns determines the country code, and calls the appropriate country code converter.

private string ConvertLeftPattern( string sLeft )
    switch( sLeft.Substring( 0, 1 ) )
        case "0":
            return CountryCode0( sLeft.Substring( 1 ) );
         case "1":
            return CountryCode1( sLeft.Substring( 1 ) );

         case "2":
            return CountryCode2( sLeft.Substring( 1 ) );

         case "3":
            return CountryCode3( sLeft.Substring( 1 ) );

         case "4":
            return CountryCode4( sLeft.Substring( 1 ) );

         case "5":
            return CountryCode5( sLeft.Substring( 1 ) );

         case "6":
            return CountryCode6( sLeft.Substring( 1 ) );

         case "7":
            return CountryCode7( sLeft.Substring( 1 ) );

         case "8":
            return CountryCode8( sLeft.Substring( 1 ) );

         case "9":
            return CountryCode9( sLeft.Substring( 1 ) );

            return "";

Each country code has its own separate converter function. CountryCode0 is a UPC-A barcode so it will use only the left hand odd parity patterns. CountryCode1 through CountryCode9 each uses the appropriate parity patterns discussed in the Determining Number Parity section. See the CountryCode1 code below for an example of a country code converter function:

private string CountryCode1( string sLeft )
    // 1 Odd Odd  Even Odd  Even Even 
    System.Text.StringBuilder sReturn = new StringBuilder( );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 0, 1 ), 
                                                     this._aOddLeft ) );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 1, 1 ), 
                                                     this._aOddLeft ) );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 2, 1 ), 
                                                     this._aEvenLeft ) );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 3, 1 ), 
                                                     this._aOddLeft ) );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 4, 1 ), 
                                                     this._aEvenLeft ) );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 5, 1 ), 
                                                     this._aEvenLeft ) );
    return sReturn.ToString( );

The CountryCode1 function uses the parity patterns discussed in the Determining Number Parity section to send each number to the ConvertToDigitPatterns function with the correct left hand pattern. The sLeft parameter should contain the 2nd digit of the Country Code and the Manufacturer's Code.

Finally the workhorse; the DrawEan13Barcode handles the rendering of the barcode graphics and requires two parameters:

  • g
  • pt

This function begins by determining the width and height for the barcode by scaling the nominal width and height by the scale factor. The lineWidth is based upon the total number of modules required to render an EAN-13 barcode. The total number of modules, 113, is determined by the following: for example:

EAN-13 code - 1234567890128

Barcode SectionNumeric ValueGraphic RepresentationNumber of Modules
Quite ZoneN/A0000000009 modules
LeadN/A1013 modules
1st Digit of Country Code1 digit - "1"Used to determine the parity.
2nd Digit of Country Code1 digit - "2"00100117 modules
Manufacturer Number5 digits = "34567"011110100111010110001000010100100015 digits * 7 modules = 35 modules
SeparatorN/A010105 modules
Product Number5 digits = "89012"100100011101001110010110011011011005 digits * 7 modules = 35 modules
Check Sum1 digit = "8"10010007 modules
TrailerN/A1013 modules
Quite ZoneN/A0000000009 modules

To determine the total module width, simply add the individual parts: 9 + 3 + 7 + 35 + 5 + 35 + 7 + 3 + 9 = 113. The 2nd digit of the Country code, 2, will have an odd parity, the 1st digit of the manufacturer's code, 3, will use the Odd pattern, the 2nd digit, 4, will use the Even pattern, the 3rd digit, 5, would use the Odd pattern, the 4th and 5th digits, 6 and 7, would use the Even pattern.

public void DrawEan13Barcode(System.Drawing.Graphics g, System.Drawing.Point pt)
    float width = this.Width * this.Scale;
    float height = this.Height * this.Scale;

    //    EAN13 Barcode should be a total of 113 modules wide.
    float lineWidth = width / 113f;

    // Save the GraphicsState.
    System.Drawing.Drawing2D.GraphicsState gs = g.Save( );

    // Set the PageUnit to Inch because all of 
    // our measurements are in inches.
    g.PageUnit = System.Drawing.GraphicsUnit.Millimeter;

    // Set the PageScale to 1, so a millimeter 
    // will represent a true millimeter.
    g.PageScale = 1;

    System.Drawing.SolidBrush brush = 
        new System.Drawing.SolidBrush(System.Drawing.Color.Black);

    float xPosition = 0;

    System.Text.StringBuilder strbEAN13 = new System.Text.StringBuilder( );
    System.Text.StringBuilder sbTemp = new System.Text.StringBuilder( );

    float xStart = pt.X;
    float yStart = pt.Y;
    float xEnd = 0;

    System.Drawing.Font font = 
         new System.Drawing.Font("Arial", this._fFontSize * this.Scale);

    // Calculate the Check Digit.
    this.CalculateChecksumDigit( );

    sbTemp.AppendFormat( "{0}{1}{2}{3}", 
                        this.ChecksumDigit );

    string sTemp = sbTemp.ToString( );

    string sLeftPattern = "";

    // Convert the left hand numbers.
    sLeftPattern = ConvertLeftPattern(sTemp.Substring( 0, 7 ));

    // Build the UPC Code.
    strbEAN13.AppendFormat( "{0}{1}{2}{3}{4}{1}{0}",
               this._sQuiteZone, this._sLeadTail,
               sLeftPattern, this._sSeparator,
               ConvertToDigitPatterns(sTemp.Substring( 7 ), this._aRight));

    string sTempUPC = strbEAN13.ToString( );

    float fTextHeight = g.MeasureString( sTempUPC, font ).Height;

    // Draw the barcode lines.
    for( int i = 0; i < strbEAN13.Length; i++ )
        if( sTempUPC.Substring( i, 1 ) == "1" )
            if( xStart == pt.X )
                xStart = xPosition;

            // Save room for the UPC number below the bar code.
            if( ( i > 12 && i < 55 ) || ( i > 57 && i < 101 ) )
                // Draw space for the number
                g.FillRectangle( brush, xPosition, yStart, 
                                       lineWidth, height - fTextHeight );
                // Draw a full line.
                g.FillRectangle( brush, xPosition, yStart, lineWidth, height );

        xPosition += lineWidth;
        xEnd = xPosition;

    // Draw the upc numbers below the line.
    xPosition = 
      xStart - g.MeasureString(this.CountryCode.Substring( 0, 1 ), font).Width;
    float yPosition = yStart + ( height - fTextHeight );

    // Draw 1st digit of the country code.
    g.DrawString( sTemp.Substring( 0, 1 ), font, brush, 
         new System.Drawing.PointF( xPosition, yPosition ) );

    xPosition += 
      (g.MeasureString(sTemp.Substring( 0, 1 ), font).Width + 43 * lineWidth) -
                 (g.MeasureString( sTemp.Substring( 1, 6 ), font ).Width);

    // Draw MFG Number.
    g.DrawString( sTemp.Substring( 1, 6 ), font, brush, 
         new System.Drawing.PointF( xPosition, yPosition ) );

    xPosition += 
      g.MeasureString(sTemp.Substring( 1, 6 ), font).Width + (11 * lineWidth);

    // Draw Product ID.
    g.DrawString( sTemp.Substring( 7 ), font, brush, 
         new System.Drawing.PointF( xPosition, yPosition ) );

    // Restore the GraphicsState.
    g.Restore( gs );

The function uses the CalculateChecksumDigit function to calculate the correct check sum digit, next the call to ConvertLeftPatterns is made, and then the ConvertToDigitPatterns function is used to convert the product code and check sum number of the EAN-13 barcode number to a string representation. Once the number has been converted over to a string representation, the code uses the string representation to render the barcode, 1 will cause a rectangle to be drawn, and 0 will cause the code to skip drawing a rectangle. If the code draws a rectangle, it also takes into consideration whether it needs to shorten the rectangle to allow space for the manufacturer's number and the product number. Once the barcode is completely rendered, the code then determines the position, and draws the country code, the manufacturer's number, the product number, and the check sum digit.

The CreateBitmap function simply creates a Bitmap object, and uses the DrawEan13Barcode function to render the barcode to the Bitmap object, and then it returns the Bitmap.

public System.Drawing.Bitmap CreateBitmap( )
    float tempWidth = ( this.Width * this.Scale ) * 100 ;
    float tempHeight = ( this.Height * this.Scale ) * 100;

    System.Drawing.Bitmap bmp = 
        new System.Drawing.Bitmap( (int)tempWidth, (int)tempHeight );

    System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp);
    this.DrawEan13Barcode( g, new System.Drawing.Point( 0, 0 ) );
    g.Dispose( );
    return bmp;

Special Thanks

I would like to thank m@u for pointing out the flaw in my original article and source code. I would also like to thank MArmbruckner for testing version 2.0 and making sure the barcodes would scan properly.


  • Version 1.0 - Initial application.
  • Version 2.0 - Revised application to use the correct barcode patterns based on the 1st digit of the country code.


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
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

PraiseОтличная работа! Pin
Aleshin Slawa4-Jun-20 20:44
Aleshin Slawa4-Jun-20 20:44 
QuestionHow to do the same in WPF in image control? Pin
Member 108281324-May-18 6:59
Member 108281324-May-18 6:59 
QuestionEAN 13 for Web apllication Pin
shah.nilang1-Apr-18 8:50
shah.nilang1-Apr-18 8:50 
QuestionThere are an error Pin
Member 1027957918-Jul-17 12:12
Member 1027957918-Jul-17 12:12 
QuestionCreateBitmap( ) and PrintDocument barcode not same Pin
arshadch1233-Mar-17 10:51
arshadch1233-Mar-17 10:51 
QuestionNot scanning fix, exporting to image black screen fix. Pin
Member 1288836110-Jan-17 5:39
Member 1288836110-Jan-17 5:39 
QuestionCant save the barcode bitmap to image file Pin
Member 1089550718-Mar-15 21:28
Member 1089550718-Mar-15 21:28 
GeneralMy vote of 4 Pin
Araujocf18-Feb-15 8:25
Araujocf18-Feb-15 8:25 
QuestionHeader & Footer Pin
ittechnosolutindo10-Feb-14 21:33
ittechnosolutindo10-Feb-14 21:33 
Questionthanks Pin
Member 1039991425-Nov-13 1:07
Member 1039991425-Nov-13 1:07 
QuestionRotate Pin
Subash Nair27-Sep-13 4:50
Subash Nair27-Sep-13 4:50 
GeneralMy vote of 5 Pin
marwan201120-May-13 1:15
marwan201120-May-13 1:15 
QuestionThanks! Pin
ChowMatch29-Aug-12 19:50
ChowMatch29-Aug-12 19:50 
Questiondo not recognize barcode when print from image Pin
Nguyen Van Rang26-Jul-12 20:03
Nguyen Van Rang26-Jul-12 20:03 
GeneralMy vote of 5 Pin
Wong Pak Kei4-Jul-12 15:56
Wong Pak Kei4-Jul-12 15:56 
Questionreading and adding the barcodes to the crystal reports Pin
karunakar pal1-Jul-12 0:08
karunakar pal1-Jul-12 0:08 
QuestionThankful Pin
starnger_p_a9-Feb-12 4:40
starnger_p_a9-Feb-12 4:40 
GeneralMy vote of 5 Pin
Teddy Segoro4-Dec-11 16:20
Teddy Segoro4-Dec-11 16:20 
GeneralMy vote of 5 Pin
yoave2325-Oct-11 20:20
yoave2325-Oct-11 20:20 
QuestionCreateBitmap() and returning bitmap Width Pin
Nikolay Volf22-Aug-11 6:39
Nikolay Volf22-Aug-11 6:39 
GeneralThnx Pin
RobbKirk1-May-11 6:18
RobbKirk1-May-11 6:18 
GeneralGreat !!! Pin
pongsth28-Feb-11 15:40
pongsth28-Feb-11 15:40 
GeneralMy vote of 5 Pin
ultimateremedy6-Nov-10 0:35
ultimateremedy6-Nov-10 0:35 
GeneralSave as WMF file is better Pin
markW34527-Oct-10 5:19
markW34527-Oct-10 5:19 
GeneralSmall bug with horizontal position Pin
Jos Branders17-Jan-10 2:11
Jos Branders17-Jan-10 2:11 

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.