Click here to Skip to main content
12,895,068 members (53,411 online)
Click here to Skip to main content
Add your own
alternative version


32 bookmarked
Posted 4 Oct 2003

An Interactive Periodic Table of the Elements

, 4 Oct 2003
Rate this:
Please Sign up or sign in to vote.
An article using GDI to create a scalable periodic table of the elements.

PTE screenshot


This article presents an example of using GDI to create an interactive Periodic table of the Elements (PTE). The PTE is resizable and the controls on the dialog also resize. This article is meant to demonstrate GDI, dynamically resizing controls and simple database access.

I added the ability to select a chemical group (halogens, noble gases, etc.) and then use slider controls to set the color for that group. Double-clicking an element will open a dialog box displaying details for that element. Right clicking an element will add that element to a listbox.

This app is a good starting point for further development and touches on the following MFC capabilities:

  • Database use
  • Slider controls
  • GDI output
  • Control size scaling

Using the code

NOTE: Included with the demo project is an MS Access file - you must configure your ODBC manager to include a mapping to this MDB file - the datasource name must be PeriodicTableApp!

Two classes are presented which encapsulate the behaviors of the PTE:

  • pteTable - the Periodic table
  • pteElement - an element in the PTE

The table consists of an array of pteElement objects and members that define the size of the element cells, the offsets for the text and access methods to the element objects.

The pteTable class:

#ifndef __PTETABLE_H__
#define __PTETABLE_H__
#include "pteElement.h"
#include <AFXDB.H>

#define NUM_ELEMENTS 118
#define NUM_GROUPS   10  

class pteTable 
        // Reads in the element data from the database 

        void       pteTable::InitElements();
        // Sets the intial state of the chemical groups

        void       pteTable::InitGroups();
        // Draws the PTE

        void       pteTable::Draw(CPaintDC*, int);
        // Maps a mouse click to the element

        int        pteTable::FindElement(CPoint);
        // Returns a specified element

        pteElement pteTable::GetElement(int);
        // Returens the color for that chemical group

        COLORREF   pteTable::GetGroupBGColor(int);
        // Sets the color for a specified chemical group

        void       pteTable::SetGroupBGColor(int, COLORREF);
        // Used when drawing rectangles

        int        pteTable::GetCellWidth();
        // Used when drawing rectangles

        void       pteTable::SetCellWidth(int);
        // Sets the font to be used in the PTE

        void       pteTable::SetFontFace(CString);
        // Returns the current font used in the PTE

        CString    pteTable::GetFontFace();
        // Sets the point size for the font used in the PTE 

        void       pteTable::SetFontSize(int);
        // Returns the current font size

        int        pteTable::GetFontSize();
        // Used for positioning text during Draw

        int        pteTable::GetAtomicNumberXOffset();
        // Used for positioning text during Draw

        int        pteTable::GetAtomicNumberYOffset();
        // Used for positioning text during Draw

        int        pteTable::GetAtomicSymbolXOffset();
        // Used for positioning text during Draw

        int        pteTable::GetAtomicSymbolYOffset();
        // Get the row of an element

        int        pteTable::GetRow(int);
        // Get the column of an element

        int        pteTable::GetColumn(int);
        // Get the Atomic number of an element

        CString    pteTable::GetAtomicNumber(int);
        // Get the atomic symbol of an element

        CString    pteTable::GetAtomicSymbol(int);
        // Get the name of an element

        CString    pteTable::GetElementName(int);
        // Get the chemical group of an element

        CString    pteTable::GetGroup(int);
        // Sets the current element

        void       pteTable::SetCurrentElement(int);
        // Creates the invalidated region in prep for painting

        void       pteTable::BuildUpdateRegion(CDialog*, int, int);
        // Creates the invalidated region in prep for painting

        void       pteTable::BuildUpdateRegion(CDialog*, int);
        // Sets the pteElement CRect objects 

        void       pteTable::BuildElementRects();

        // The array of elements

        pteElement Elements[NUM_ELEMENTS]; 
        // The width of the table cells

        int        CellWidth;
        // TextOut offset

        int        AtomicNumberXOffset;
        // TextOut offset

        int        AtomicNumberYOffset;
        // TextOut offset

        int        AtomicSymbolXOffset;
        // TextOut offset

        int        AtomicSymbolYOffset;
        // The currently active pteElement

        int        CurrentElement;
        // The background colors for each chemical group

        COLORREF   GroupBGColors[NUM_GROUPS];
        // Current font size

        int        FontSize;
        // Current font face

        CString    FontFace;

#endif //__PTETABLE_H__

The pteElement class:


class pteElement
        // Simple access methods - the set methods are
        // called during initialization and
        // are populated from the database
        void    pteElement::SetAtomicNumber(CString);
        CString pteElement::GetAtomicNumber();
        void    pteElement::SetAtomicWeight(CString);
        CString pteElement::GetAtomicWeight();
        void    pteElement::SetGroupNumber(CString);
        CString pteElement::GetGroupNumber();
        void    pteElement::SetGroupName(CString);
        CString pteElement::GetGroupName();
        void    pteElement::SetPeriod(CString);
        CString pteElement::GetPeriod();
        void    pteElement::SetBlock(CString);
        CString pteElement::GetBlock();
        void    pteElement::SetCASRegistryID(CString);
        CString pteElement::GetCASRegistryID();
        void    pteElement::SetElementName(CString);
        CString pteElement::GetElementName();
        void    pteElement::SetElementSymbol(CString);
        CString pteElement::GetElementSymbol();
        void    pteElement::SetDiscoveryDate(CString);
        CString pteElement::GetDiscoveryDate();
        void    pteElement::SetDiscovererName(CString);
        CString pteElement::GetDiscovererName();
        void    pteElement::SetRow(CString);
        int     pteElement::GetRow();
        void    pteElement::SetColumn(CString);
        int     pteElement::GetColumn();

        // sets the members of the CRect object
        // member variable. Passed the width of the cell.
        void    pteElement::BuildRect(int);
        // returns TRUE if the point passed
        // to it lies within its CRect
        BOOL    pteElement::CheckHit(CPoint);
        // returns the CRect ElementArea
        CRect*  pteElement::GetRect();
        // populated from database
        CString  AtomicNumber;
        CString  AtomicWeight;
        CString  GroupNumber;
        CString  GroupName;
        CString  Period;
        CString  Block;
        CString  CASRegistryID;
        CString  ElementName;
        CString  ElementSymbol;
        CString  DiscoveryDate;
        CString  DiscovererName;
        int      Row;
        int      Column;

        // the current screen coordinates
        // that this element occupies
        CRect    ElementArea;                    
        // The color for this element
        COLORREF CellColor;


Points of interest

This project started last week when a user asked about implementing a PTE and wanted to know how best to determine which element was chosen by the user. Three options were suggested:

  • Using a button for each element
  • Using a single bitmap to represent the PTE and another bitmap in a MemDC with different colors for each element
  • Using GDI

I discounted using buttons because it is very unattractive and uses a lot of resources. I discounted the second because the text would look terrible as you scaled the window and because making changes would be very time consuming. I decided on GDI. The actual code to handle the drawing is only around 14 lines and allows for highlighting all of the elements in the current chemical group:

/* ******************************** */
/* Draw the table                   */
The actual drawing of the entire table is 
accomplished in about a dozen lines.
Very simple and it allows for automatic resizing 
when the window size changes.

Because the row and column within the table 
is stored as a class member variable
in the pteElement class, computing the screen 
coordinates for that element is
very straightforward.

In drawing the elements, a few class methods are called:

COLORREF GetGroupBGColor(int);
This method takes an int representing the 
chemical group and returns 
the color for that groups background color

When selecting the brush to draw with, 
the following call is used:
Elements[] is an array of pteElement objects. 
One of pteElement's methods is:
int GetGroupNumber();
and it returns the chemical group for that element. 
The resulting number is used as an index into the GroupBrush
array of CBrushes

Determining the location for the cell is 
accomplished with the calls
GetColumn() and GetRow()

The return value from those calls is multiplied by the current 
cellwidth - and this gives you the coordinates for
drawing the rectangle. 

Drawing the text is accomplished the same way.
/* ******************************** */
void pteTable::Draw(CPaintDC* dc, int group)
    // declare and initialize the fonts
    CFont*  OldFont;
    CFont   NumberFont;
    CFont   SymbolFont;
    NumberFont.CreatePointFont(FontSize - 40, FontFace, NULL);
    SymbolFont.CreatePointFont(FontSize, FontFace, NULL);

    // declare and initialize the brushes
    CBrush* OldBrush;
    CBrush  GroupBrushes[NUM_GROUPS]; 
    for(int x = 0; x < NUM_GROUPS; x++)

    // declare and initialize the pens
    CPen*   OldPen;
    CPen    RedOutLinePen(PS_SOLID, 1, RGB(255, 0, 0));
    CPen    BlackOutLinePen(PS_SOLID, 1, RGB(0, 0, 0));

    // draw the elements
    for(x = 0; x < NUM_ELEMENTS; x++)
        OldBrush = dc->SelectObject

        // if you are drawing the active group, 
        //outline it in red, otherwise black
        if(atoi(Elements[x].GetGroupNumber()) == group)
            OldPen = dc->SelectObject(&RedOutLinePen);
            OldPen = dc->SelectObject(&BlackOutLinePen);

           (Elements[x].GetColumn() - 1) * CellWidth), 
           ((Elements[x].GetRow() - 1) * CellWidth), 
           ((Elements[x].GetColumn() - 1) * CellWidth) 
           + CellWidth, 
           ((Elements[x].GetRow() - 1) * CellWidth) + 
        OldFont = dc->SelectObject(&NumberFont);
          (Elements[x].GetColumn() - 1) * CellWidth) + 
          AtomicNumberXOffset, ((Elements[x].GetRow() - 1) 
          * CellWidth) + AtomicNumberYOffset, 
        dc->TextOut(((Elements[x].GetColumn() - 1) * 
          CellWidth) + AtomicSymbolXOffset, 
          ((Elements[x].GetRow()    - 1) * CellWidth) + 

    // clean-up fonts
    // clean-up brushes
    for(x = 0; x < NUM_GROUPS; x++)
    // clean-up pens


  • Version 1.0 - Submitted 3 October, 2003


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


About the Author

Terry O'Nolley
Web Developer
United States United States
I'm an ENTP personality type, Ex Army Ranger, former software design engineer for Hewlett-Packard's TV network and now self employed.

Best job: Golf caddy in high school
Worst job: Tele-marketer

Countries Lived in, or visited: USA, Mexico, South Korea, Canada, Japan, North Korea (for 10 seconds), Spain, Germany, Bahrain, Iraq, Saudi Arabia & Panama

Hobbies: Classical Guitar, Video Games, Golf, Pool, Beer, 1/48 scale WWII airplane models, Reading & B-Movies

You may also be interested in...

Comments and Discussions

GeneralNice Pin
Cloaca12-Jan-04 11:36
memberCloaca12-Jan-04 11:36 
GeneralNo executable included in demo Pin
Art Friesz8-Oct-03 14:54
memberArt Friesz8-Oct-03 14:54 
GeneralOriginality. Pin
73Zeppelin6-Oct-03 16:13
member73Zeppelin6-Oct-03 16:13 
Generalgood job Pin
Jason Henderson6-Oct-03 3:03
memberJason Henderson6-Oct-03 3:03 
GeneralRe: good job Pin
Terry O'Nolley6-Oct-03 3:48
memberTerry O'Nolley6-Oct-03 3:48 
GeneralRe: good job Pin
Jason Henderson6-Oct-03 3:55
memberJason Henderson6-Oct-03 3:55 
GeneralI like the app Pin
Chris Meech6-Oct-03 2:44
memberChris Meech6-Oct-03 2:44 
GeneralRe: I like the app Pin
Terry O'Nolley6-Oct-03 3:50
memberTerry O'Nolley6-Oct-03 3:50 
GeneralConnection in ODBC Pin
John A. Johnson5-Oct-03 23:34
memberJohn A. Johnson5-Oct-03 23:34 
GeneralRe: Connection in ODBC Pin
Terry O'Nolley6-Oct-03 3:52
memberTerry O'Nolley6-Oct-03 3:52 
GeneralRe: Connection in ODBC Pin
Kevin Sorensen7-Oct-03 5:25
memberKevin Sorensen7-Oct-03 5:25 
GeneralThank you very much for sharing Pin
John A. Johnson5-Oct-03 21:39
memberJohn A. Johnson5-Oct-03 21:39 
GeneralRe: Thank you very much for sharing Pin
Terry O'Nolley7-Oct-03 14:16
memberTerry O'Nolley7-Oct-03 14:16 
GeneralGood article Pin
Nishant S5-Oct-03 18:22
staffNishant S5-Oct-03 18:22 
GeneralRe: Good article, again Pin
conrad Braam5-Oct-03 18:54
memberconrad Braam5-Oct-03 18:54 
GeneralRe: Good article, again Pin
Terry O'Nolley6-Oct-03 3:56
memberTerry O'Nolley6-Oct-03 3:56 
GeneralRe: Good article, again Pin
Nishant S6-Oct-03 18:01
staffNishant S6-Oct-03 18:01 
GeneralRe: Good article, again Pin
Terry O'Nolley7-Oct-03 7:12
memberTerry O'Nolley7-Oct-03 7:12 

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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170424.1 | Last Updated 5 Oct 2003
Article Copyright 2003 by Terry O'Nolley
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid