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 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
{
public:
pteTable::pteTable();
~pteTable();
void pteTable::InitElements();
void pteTable::InitGroups();
void pteTable::Draw(CPaintDC*, int);
int pteTable::FindElement(CPoint);
pteElement pteTable::GetElement(int);
COLORREF pteTable::GetGroupBGColor(int);
void pteTable::SetGroupBGColor(int, COLORREF);
int pteTable::GetCellWidth();
void pteTable::SetCellWidth(int);
void pteTable::SetFontFace(CString);
CString pteTable::GetFontFace();
void pteTable::SetFontSize(int);
int pteTable::GetFontSize();
int pteTable::GetAtomicNumberXOffset();
int pteTable::GetAtomicNumberYOffset();
int pteTable::GetAtomicSymbolXOffset();
int pteTable::GetAtomicSymbolYOffset();
int pteTable::GetRow(int);
int pteTable::GetColumn(int);
CString pteTable::GetAtomicNumber(int);
CString pteTable::GetAtomicSymbol(int);
CString pteTable::GetElementName(int);
CString pteTable::GetGroup(int);
void pteTable::SetCurrentElement(int);
void pteTable::BuildUpdateRegion(CDialog*, int, int);
void pteTable::BuildUpdateRegion(CDialog*, int);
void pteTable::BuildElementRects();
protected:
pteElement Elements[NUM_ELEMENTS];
int CellWidth;
int AtomicNumberXOffset;
int AtomicNumberYOffset;
int AtomicSymbolXOffset;
int AtomicSymbolYOffset;
int CurrentElement;
COLORREF GroupBGColors[NUM_GROUPS];
int FontSize;
CString FontFace;
};
#endif //__PTETABLE_H__</afxdb.h>
The pteElement Class
#ifndef CLASSX_H_INCLUDED
#define CLASSX_H_INCLUDED
class pteElement
{
public:
pteElement::pteElement();
~pteElement();
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();
void pteElement::BuildRect(int);
BOOL pteElement::CheckHit(CPoint);
CRect* pteElement::GetRect();
protected:
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;
CRect ElementArea;
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:
void pteTable::Draw(CPaintDC* dc, int group)
{
CFont* OldFont;
CFont NumberFont;
CFont SymbolFont;
NumberFont.CreatePointFont(FontSize - 40, FontFace, NULL);
SymbolFont.CreatePointFont(FontSize, FontFace, NULL);
CBrush* OldBrush;
CBrush GroupBrushes[NUM_GROUPS];
for(int x = 0; x < NUM_GROUPS; x++)
GroupBrushes[x].CreateSolidBrush(GetGroupBGColor(x));
CPen* OldPen;
CPen RedOutLinePen(PS_SOLID, 1, RGB(255, 0, 0));
CPen BlackOutLinePen(PS_SOLID, 1, RGB(0, 0, 0));
for(x = 0; x < NUM_ELEMENTS; x++)
{
OldBrush = dc->SelectObject
(&GroupBrushes[atoi(Elements[x].GetGroupNumber())]);
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);
}
SymbolFont.DeleteObject();
NumberFont.DeleteObject();
for(x = 0; x < NUM_GROUPS; x++)
GroupBrushes[x].DeleteObject();
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.