The UI — Abstract and Platform Specific

While we have decided that out first version of Phish will run on 32-bit Windows, we want our design to maintain a level of abstraction, so that the task of moving Phish to another platform is as straightforward as possible. To this end, we abstract the display from the specific platform-dependent implementation and create PADisplay as an abstract base class. The PADisplay class provides an abstract representation of the display, and will be overridden by the framework-specific display class. The PADisplay class is shown below:

class PADisplay : public RCBody
{
public:
   int Height() const {return dHeight;};
   int Width()  const {return dWidth;};
   PADisplay();
   virtual ~PADisplay();
   virtual PADisplay *NewInstance() const = 0;
   virtual void DrawTo ( const PADevice &Device ) = 0;
   virtual void Draw 
            (const PLocation &Loc, const TImagePtr &Icon )=0;
   virtual void Draw (int x, int y, const TImagePtr &Bitmap ) = 0;
   virtual void Erase (const PLocation &Loc )=0;
   virtual void Resize (int width, int height )=0;
   virtual TImagePtr CreateBitmap 
               (const string &Name, RGBColor Color )=0;
protected:
   RGBColor dBackColor;
   int dHeight;
   int dWidth;
   typedef RCHandle<PAImage> TImagePtr;
};

In the MFC application, this class is made concrete by CDisplay. The concrete implementation of CreateBitmap() in CDisplay is framework- and operating-system dependent, as you would expect:

TImagePtr CDisplay::CreateBitmap (const string &Name, RGBColor ForeColor )
{
   if (!dMemDC) return TImagePtr();
   HWND hDesktop = GetDesktopWindow();
   CWnd Desktop;
   Desktop.Attach (hDesktop);
   CWindowDC DummyDC (&Desktop);
   Desktop.Detach();
   static int LastPos = 0;

   CBitmap bwBitmap;
   bwBitmap.LoadBitmap (Name.c_str());
   CDC bwMemDC;
   bwMemDC.CreateCompatibleDC (&dMemDC);
   bwMemDC.SelectObject (&bwBitmap);
   BITMAP TemporarySolution;
   bwBitmap.GetObject 
         (sizeof(TemporarySolution), &TemporarySolution);
   const int SrcHeight = TemporarySolution.bmHeight;
   const int SrcWidth  = TemporarySolution.bmWidth;
   DummyDC.BitBlt (LastPos, 0, 11, 11, &bwMemDC, 0, 0, SRCCOPY);
   LastPos+=10;

   CDC colMemDC;
   colMemDC.CreateCompatibleDC(&dMemDC);
   CBitmap *pcolBitmap = new CBitmap;
   CBitmap &colBitmap = *pcolBitmap;

   colBitmap.CreateCompatibleBitmap(&DummyDC, SrcHeight, SrcWidth);
   colBitmap.GetObject 
         (sizeof(TemporarySolution),&TemporarySolution);

   bwBitmap.GetObject 
         (sizeof(TemporarySolution),&TemporarySolution);
   colMemDC.SelectObject (&colBitmap);
   colMemDC.SetBkColor (ConvertColor(dBackColor));
   colMemDC.SetTextColor (ConvertColor(ForeColor));
   colMemDC.BitBlt 
         (0, 0, SrcWidth, SrcHeight, &bwMemDC, 0, 0, SRCCOPY);
   DummyDC.BitBlt(LastPos,0,10,10,&colMemDC, 0,0,SRCCOPY);
   LastPos+=10;

   colMemDC.SelectObject ((CBitmap*)0);
   return TImagePtr (new CImage(pcolBitmap));
};

All these ugly details are encapsulated by the platform-specific classes and hidden to clients of PADisplay, as they should be. The abstract display class knows that it has a height, width and background color but not how these are manipulated. The concrete display class manages device contexts, bit maps, pixels and so forth.

The PADisplay works hand in glove with the abstract base class PADevice and the concrete CDevice. Again, the specifics of the operating system devices are hidden behind the abstraction layer.

Using the PADisplay

The CPhishDlg dialog class has a member variable, dPhishWnd, which is an instance of the class CPhishWnd. This represents the window in which we'll display the Phish tank. CPhishWnd is a CStatic window and is MFC specific, as is CPhishDlg.

CPhishWnd in turn contains a data member, dTank — an instance of the PPhishTank class. A PPhishTank object contains a TDisplayPtr, which is a pointer to a PADisplay:

typedef RCHandle<PADisplay> TDisplayPtr;

This allows us to polymorphically create concrete display objects (such as CDisplay), which can be platform-specific.

class PPhishTank : public RCBody
{
protected:
   TDisplayPtr      pDisplay;

The constructor of the CPhishWnd creates the PPhishTank passing in a new CDisplay (derived from PADisplay):

CPhishWnd::CPhishWnd() : dTank(new CDisplay)
{
}

When the PPhishTank or the PSimulator object need to be drawn, the work is delegated to PPhishTank's pDisplay member, thus creating platform-specific implementations of all the drawing methods.

© 1998 by Wrox Press. All rights reserved.