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:
Collapse
#ifndef __PTETABLE_H__
#define __PTETABLE_H__
#include "pteElement.h"
#include
#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__
The pteElement class:
Collapse
#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 ucancode.neted 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:
Collapse
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();
}