An Interactive Periodic Table of the Elements






4.81/5 (21 votes)
Oct 5, 2003
2 min read

122268

2524
An article using GDI to create a scalable periodic table of the elements
Introduction
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 tablepteElement
- 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
{
public:
pteTable::pteTable();
~pteTable();
// Reads in the element data from the database
void pteTable::InitElements();
// Sets the initial 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();
protected:
// 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__</afxdb.h>
The pteElement Class
#ifndef CLASSX_H_INCLUDED
#define CLASSX_H_INCLUDED
class pteElement
{
public:
pteElement::pteElement();
~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();
protected:
// 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;
};
#endif
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:
GroupBrushes[atoi(Elements[x].GetGroupNumber())]
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++)
GroupBrushes[x].CreateSolidBrush(GetGroupBGColor(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
(&GroupBrushes[atoi(Elements[x].GetGroupNumber())]);
// if you are drawing the active group,
//outline it in red, otherwise black
if(atoi(Elements[x].GetGroupNumber()) == group)
OldPen = dc->SelectObject(&RedOutLinePen);
else
OldPen = dc->SelectObject(&BlackOutLinePen);
dc->Rectangle((
(Elements[x].GetColumn() - 1) * CellWidth),
((Elements[x].GetRow() - 1) * CellWidth),
((Elements[x].GetColumn() - 1) * CellWidth)
+ CellWidth,
((Elements[x].GetRow() - 1) * CellWidth) +
CellWidth);
dc->SelectObject(OldBrush);
dc->SelectObject(OldPen);
dc->SetBkMode(TRANSPARENT);
OldFont = dc->SelectObject(&NumberFont);
dc->TextOut((
(Elements[x].GetColumn() - 1) * CellWidth) +
AtomicNumberXOffset, ((Elements[x].GetRow() - 1)
* CellWidth) + AtomicNumberYOffset,
Elements[x].GetAtomicNumber(),
Elements[x].GetAtomicNumber().GetLength());
dc->SelectObject(&SymbolFont);
dc->TextOut(((Elements[x].GetColumn() - 1) *
CellWidth) + AtomicSymbolXOffset,
((Elements[x].GetRow() - 1) * CellWidth) +
AtomicSymbolYOffset,
Elements[x].GetElementSymbol(),
Elements[x].GetElementSymbol().GetLength());
dc->SelectObject(OldFont);
}
// clean-up fonts
SymbolFont.DeleteObject();
NumberFont.DeleteObject();
// clean-up brushes
for(x = 0; x < NUM_GROUPS; x++)
GroupBrushes[x].DeleteObject();
// clean-up pens
RedOutLinePen.DeleteObject();
BlackOutLinePen.DeleteObject();
}
History
- Version 1.0 - Submitted 3rd October, 2003
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.