Figure 3   String

 

mystring.h

 #ifndef MYSTRING_H__
#define MYSTRING_H__

#include "ansi.h"
#include "notify.h"

#include <iostream.h>
#include <iomanip.h>

//=====================================================================
// Microsoft's <tchar.h> file contains support for (ANSI-compatible)
// UNICODE and (Microsoft-specific) multi-byte character Strings.
// The macros map to one of two values, depending on whether _UNICODE
// or _MBCS is defined. The mappings used in the current file are:
//
//                  _UNICODE and           _UNICODE             _MBCS
//                  _MBCS not defined      defined              defined
//
// _TCHAR            char                  wchar_t              char
// _T("x")           "x"                   L"x"                 "x"
// _TEXT("x")        "x"                   L"x"                 "x"
// _tcscpy           strcmp                wcscmp               _mbscmp
// _tcslen           strlen                wcslen               _mbslen
// _tcscmp           strcpy                wcscpy               _mbscpy

#include <tchar.h>

//=====================================================================
class String : public Notifier
{
      class Buffer; // Buffer is a nested class, but I've moved the class
                    // declaration out of the class definition to reduce
                    // clutter;

      Buffer          *buf;
      Notifiable      *observer;
public:
      virtual 
      ~String( void  );
                                                           
       String(                   Notifiable *tell = &Nobody );
       String( const _TCHAR *p , Notifiable *tell = &Nobody );
       String( const String &r , Notifiable *tell = &Nobody );

      const virtual String &operator= ( const String &r      );
      const virtual String &operator= ( const _TCHAR *p      );

      bool operator< ( const String &r ) const;
      bool operator> ( const String &r ) const;
      bool operator<=( const String &r ) const;
      bool operator>=( const String &r ) const;
      bool operator==( const String &r ) const;
      bool operator!=( const String &r ) const;

      virtual int           compare ( const String &r      ) const;
      virtual ostream       &print  ( ostream &dst         ) const;
              bool          is_empty( void                 ) const;

      #ifdef MFC
            String( const CString &s, Notifiable *tell = &Nobody);
            operator CString( void );
      #endif
};

extern const String Empty_string;
//=====================================================================
// class String::Buffer
//=====================================================================
// This is actually a nested-class definition. You could remove the
// String:: and move the following class definition inside the String
// class definition without affecting anything. Note that the
// "Class View" window in the MS Developer's Studio gets confused and
// doesn't list the class name, however (there's a little icon but not
// identifying class name).

class String::Buffer
{
      int             ref_count;
      _TCHAR       *buf;
public:
      virtual
      ~Buffer      ( void                 );
       Buffer      ( const _TCHAR *p      );
       Buffer      ( const Buffer &r      );

      Buffer       *attach ( void            );
      void         release ( void            );
      int          compare ( const Buffer &r ) const;
      ostream      &print  ( ostream &dst    ) const;
      bool         is_empty( void            ) const;

      #ifdef MFC
            operator CString( void );
      #endif
};
//---------------------------------------------------------------------
inline String::Buffer::Buffer( const _TCHAR *p )
                              : buf            ( new _TCHAR[_tcsclen(p) + 1] )
                              , ref_count      ( 1 )
{
      assert( p );
      _tcscpy( buf, p );            // generic strcmp()
}
//---------------------------------------------------------------------
// The copy constructor should never be called. Declare a private version
// to prevent it from being called externally. Deliberately do not
// implement it so that the linker will kick out an error if anyone
// (like a friend or local function) manages to call it anyway. Note
// that this strategy (of forcing a link error) won't work on all
// compilers, since some will generate a default copy constructor even
// if a prototype is present in the class declaration. If this is
// the case, uncomment the function body, below, so you'll at least
// get a runtime error.
//
// inline String::Buffer::Buffer( const Buffer &r )
// {
//    assert(0);
// }
//---------------------------------------------------------------------
inline bool String::Buffer::is_empty( void ) const
{
      return *buf == '\0';
}
//---------------------------------------------------------------------
inline String::Buffer *String::Buffer::attach( void )
{
      assert(this);
      ++ref_count;
      return this;
}
//---------------------------------------------------------------------
inline void String::Buffer::release( void )
{
      // The assert(this) assures that the function isn't called through
      // a null pointer. It probably wouldn't hurt to do it everywhere,
      // but doing it here saves me from having to do an assert(buf)
      // everywhere before release() is called in the String members.

      assert( this );
      assert( ref_count > 0 );

      if( --ref_count <= 0 )
            delete this;
}
//---------------------------------------------------------------------
inline int      String::Buffer::compare( const Buffer &r ) const
{
      assert( this );
      return _tcscmp( buf, r.buf );      // generic strcmp()
}
//---------------------------------------------------------------------
inline ostream &String::Buffer::print( ostream &dst ) const
{
      #ifndef NDEBUG
            assert( this );
            dst << "ref_count=" << dec << ref_count << ", buf=";
      #endif

      dst << buf << "\n" ;
      return dst;
}
//---------------------------------------------------------------------
#ifdef MFC
inline String::Buffer::operator CString( void )
{
      // This function is resonably efficient only because the CString
      // itself is reference counted. If it weren't, I'd have to expose
      // the _TCHAR buf to the containing string class to avoid an
      // unnecessary copy of the string's contents.

      return CString( buf );
}
#endif
//=====================================================================
// class String
//=====================================================================
inline String::String( Notifiable *tell): buf      (new Buffer(_T(""))),
                                          observer ( tell             )
{}
//---------------------------------------------------------------------
inline String::String( const _TCHAR *p, Notifiable *tell ):
                         buf      ( new Buffer(p)),
                         observer ( tell         )
{}
//---------------------------------------------------------------------
inline String::String( const String &r, Notifiable *tell ):
                         buf      ( r.buf->attach()),
                         observer ( tell           )
{}
//---------------------------------------------------------------------
#ifdef MFC
inline String::String( const CString &s, Notifiable *tell ):
                         buf        ( new Buffer( (const _TCHAR *)s )),
                         observer   ( tell                           )
{}
#endif
//---------------------------------------------------------------------
#ifdef MFC
inline String::operator CString( void )
{
      return CString( *buf );
}
#endif
//---------------------------------------------------------------------
inline bool String::operator< (const String &r)const{ return compare(r)< 0;}
inline bool String::operator> (const String &r)const{ return compare(r)> 0;}
inline bool String::operator<=(const String &r)const{ return compare(r)<=0;}
inline bool String::operator>=(const String &r)const{ return compare(r)>=0;}
inline bool String::operator==(const String &r)const{ return compare(r)==0;}
inline bool String::operator!=(const String &r)const{ return compare(r)!=0;}
inline bool String::is_empty  ( void          )const{ return buf->is_empty(); }
//---------------------------------------------------------------------
inline ostream &String::print( ostream &dst ) const
{
      #ifndef NDEBUG
            assert( this );
            dst << "buf=0x" << hex << setw(8) << buf << ", ";
      #endif

      return buf->print( dst );
}
//=====================================================================
// Global functions
//=====================================================================
inline ostream &operator<<( ostream &out, const String &s )
{
      return s.print( out );
}
//=====================================================================
#endif // MYSTRING_H__

string.cpp

 #include "stdafx.h"
#include "mystring.h"
//=====================================================================
// class String::Buffer
//=====================================================================
const String Empty_string;
//=====================================================================
/*virtual*/ String::Buffer::~Buffer( void )
{
      assert( ref_count == 0 );
      delete[] buf;
}
//=====================================================================
// class String
//=====================================================================
/*virtual*/ String::~String( void )
{
      assert(this);

      buf->release();
}
//---------------------------------------------------------------------
/*virtual*/ int String::compare( const String &r ) const
{
      return buf->compare( *r.buf );
}
//---------------------------------------------------------------------
/*virtual*/ const String &String::operator=( const String &r )
{
      assert( this );

      if( this != &r )
      {
            buf->release();
            buf = r.buf->attach();
      }

      notify(observer);
      return *this;
}
//---------------------------------------------------------------------
/*virtual*/ const String &String::operator=( const _TCHAR *p )
{
      assert( this );
      buf->release();
      buf = new Buffer( p );
      notify(observer);
      return *this;
}

Figure 4   notify.h

 #ifndef NOTIFY_H__
#define NOTIFY_H__

#include "ansi.h"

class Notifiable
{
public:
      virtual void notify( class Notifier *sender ) = 0;
};

class Notifier
{
      bool      am_silent;
public:
      virtual ~Notifier(){};

      Notifier( void ): am_silent( false ) {}

      void      notify_on      ( void ){ am_silent = false;}
      void      notify_off     ( void ){ am_silent = true; }
      
      void notify(Notifiable *observer)
      {
            if( !am_silent )
                  observer->notify(this);
      }
};


static class Notify_nobody: public Notifiable
{
public:
      virtual void notify( Notifier * ){}
}
Nobody;

#endif // NOTIFY_H__

Figure 5   Single Function Working Overridable

 class base1
{
public: virtual void notify(void) = 0; 
        void change_base1( void ) { notify(); }
};
class base2
{
public: virtual void notify(void) = 0; 
        void change_base2( void ) { notify(); }
};
class derived: public base1, public base2
{
    virtual void notify( void )
    {
        AfxMessageBox("Some base class has changed.");
    }
};
 BOOL foo()
{
    derived d; 
    d.change_base1();
    d.change_base2();
}

Figure 6   ansi.h

 #ifndef ANSI_H__
#define ANSI_H__
 // Attempts to bring VC++ into line with the ANSI C++ DWP.
 // First, ANSI C defines NDEBUG when you're aren't debugging. MFC defines
// _DEBUG when you are. Set things up so you can use NDEBUG. Also arange
// for an MFC ASSERT macro to be mapped to an ANSI assert macro:
 #ifdef ASSERT
#   define assert(x) ASSERT(x)
#else
#   include <assert.h>
#endif

#ifndef _DEBUG
#   define NDEBUG 1
#else
#   undef NDEBUG
#endif
 // ANSI/ISO C++ is going to include a bool native type and true and fals
// keywords. These are reserved words to the VC++ 4.x compiler, but
// aren't implemented. Set up some macros so we can use them. (You
// can't use a typedef or enum because the words are reserved.)
 #define bool  unsigned
#define true  1      
#define false 0
 #endif // ANSI_H__

Figure 7   MFC Wrappers

 

wrappers.h

 #include <stddef.h>

#ifndef WRAPPERS_H__
#define WRAPPERS_H__

#include "ansi.h"
#include "mystring.h"
#include "userintf.h"

extern void ok_box( const String &s );      // generic message box

//----------------------------------------------------------------------
typedef CWnd window;

class Rect : public CRect // Can't use "Rectangle" because there's
{                         // a global function with that name.
public:
      Rect( int l, int t, int r, int b ): CRect(l,t,r,b) {}
      Rect( const Rect &source         ): CRect(source ) {}
}; 

//----------------------------------------------------------------------
// A text control handles both the display and input of text. It's
// an amalgam of a Windows Edit and Static-Text control.
//
// We must have an operator delete function because the one defined
// in CObject (which is an indirect base class of CEdit) will be
// unaccessable otherwise. We'd get:
//
//       error C2247: 'delete' not accessible because 'Text_control'
//                    uses 'private' to inherit from 'CEdit'
//
// MFC #defines new to DEBUG_NEW, so we have to undef it before
// we can declare the function.
#ifdef new
#undef new
#endif

class Text_control: private CEdit, public Notifier
{
private:
      Notifiable            *observer;
      CWnd                  *windows_obj;

      const Text_control &operator=( const Text_control &r );
      Text_control( const Text_control &r );

public:
      enum ctrl_type{ write_only, read_write };

      Text_control( ctrl_type            type,
                          const Rect     &position,
                          Window         *parent,
                          const String   &initial_value = Empty_string,
                          Notifiable     *tell          = &Nobody );

      virtual ~Text_control();

      void import ( const String &s );
      void export ( String *s       );
      void enable ( void            );
      void disable( void            );

public: // MFC Stuff required because of private inheritance

      void* PASCAL      operator new(size_t nSize);
      void* PASCAL      operator new(size_t, void* p);
      void  PASCAL      operator delete(void* p);

#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)
      void* PASCAL      operator new(size_t  nSize, 
                                     LPCSTR  lpszFileName,
                                     int     nLine);
#endif

private: // MFC Stuff

      //{{AFX_VIRTUAL(Text_control)
      //}}AFX_VIRTUAL

      //{{AFX_MSG(Text_control)
      afx_msg void OnChange();
      //}}AFX_MSG

      DECLARE_MESSAGE_MAP()
};
//----------------------------------------------------------------------
inline void Text_control::enable( void )
{
      assert( windows_obj->m_hWnd );
      windows_obj->EnableWindow( TRUE );
}
//----------------------------------------------------------------------
inline void Text_control::disable( void )
{
      assert( windows_obj->m_hWnd );
      windows_obj->EnableWindow( FALSE );
}
//----------------------------------------------------------------------
inline void* PASCAL Text_control::operator new(size_t nSize)
{
      return CEdit::operator new(nSize);
}

inline void* PASCAL Text_control::operator new(size_t size, void* p)
{
      return CEdit::operator new( size, p );
}

#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)
inline void* PASCAL Text_control::operator new
                                  (size_t nSize, LPCSTR lpszFileName, int nLine)
{
      return CEdit::operator new(nSize, lpszFileName, nLine);
}
#endif

inline void Text_control::operator delete(void *p)
{
      CEdit::operator delete(p);
}

#ifdef _DEBUG      // put back standard mfc macro
#define new DEBUG_NEW
#endif

#endif // WRAPPERS_H__

wrappers.cpp

 #include "stdafx.h"
#include "wrappers.h"

//======================================================================
// Global Functions
//======================================================================
void ok_box( const String &s )
{
      CString cs = (CString)s;
      AfxMessageBox( (const _TCHAR *)cs );
}

//======================================================================
// Text_control
//======================================================================
Text_control::Text_control( ctrl_type        type,
                            const Rect       &position,
                            Window           *parent,
                            const String     &initial_value,
                            Notifiable       *tell): observer ( tell )
{
      bool success = false;

      // Casts in the conditional, below, are required because the ?
      // and : phrases must evaluate to objects of the same type.

      windows_obj = (type==write_only) ? (CWnd*)(new CStatic())
                                                       : (CWnd*)(CEdit*)this ;
      if(windows_obj == this)
      {
            success = CEdit::Create(
                                    WS_CHILD | WS_VISIBLE | WS_TABSTOP,
                                    position,
                                    parent,
                                    ~0U );      // ID is immaterial
      }
      else
      {
            success = ((CStatic *)windows_obj)->
                             Create("",
                                    WS_CHILD | WS_VISIBLE | WS_TABSTOP,
                                    position,
                                    parent,
                                    ~0U );      // ID is immaterial
      }

      assert(success);  // Maybe throw an exception here instead of assert.

      // The SetWindowText sends an EN_CHANGE, which calls OnChange, which
      // notifies the Text_control, which updates the String, which notifies
      // the Text_control, which updates the window text, and around and
      // and around. Solve the problem in the OnChange() function, but set
      // "initializing" true to tell OnChange what's going on. Turn off
      // notification to prevent this.
      //
      // I'm assuming synchronous behavior; I don't expect SentWindowText
      // to return until AFTER the EnChange() handler has been executed.
      // If this turns out not to be the case in the future, you'll have
      // to modify this code: add an "initializing" field to the class,
      // set it true instead of calling notify_off, set it false instead
      // of calling notify_on, and modify OnChange to not notify anybody
      // if "initializing" is true.


      if( !initial_value.is_empty() )
      {
            notify_off();
            windows_obj->SetWindowText
                           ( (const _TCHAR *)(CString)initial_value );
            notify_on();
      }
}
//----------------------------------------------------------------------
/*virtual*/ Text_control::~Text_control( void )
{
      if( windows_obj != this )      // it came from new
            delete windows_obj;
}
//----------------------------------------------------------------------
void Text_control::import( const String &s )
{
      assert( windows_obj->m_hWnd ); // Make sure there's a window
      windows_obj->SetWindowText( (const _TCHAR *)(CString)s );
      notify(observer);
}
//----------------------------------------------------------------------
void Text_control::export( String *s )
{
      _TCHAR buf[255];

      // Get the text from Windows. Note that the buffer size has to be
      // computed with sizeof(buf)/sizeof(*buf) because a _TCHAR can
      // evaluate to a wchar_t, which will be two bytes. A simple
      // sizeof(buf) without the subsequent divide could yield a number
      // twice the actual buffer size.

      assert( windows_obj->m_hWnd ); // Make sure there's a window
      windows_obj->GetWindowText( buf, sizeof(buf)/sizeof(*buf) );
      
      *s = buf;
}

//----------------------------------------------------------------------
BEGIN_MESSAGE_MAP(Text_control, CEdit)
      //{{AFX_MSG_MAP(Text_control)
      ON_CONTROL_REFLECT(EN_CHANGE, OnChange)
      //}}AFX_MSG_MAP
END_MESSAGE_MAP()
//---------------------
void Text_control::OnChange() 
{
      notify( observer );
}

Figure 8   Using a Text Control Directly

 class Flintstone: public Notifiable
{
public:
    virtual void notify( Notifier *sender )
    {
        Text_control *p = dynamic_cast<Text_control *>(sender);
        if( !p )
            cerr << "Something's wrong\n" ;
        else
        {
            String s;
            p->export( &s );
            cout << "The window changed to: " << s << "\n";
        }
    }
}
fred;
 Text_control *The_control;
void CFormsView::OnTest()
{
    The_control = new Text_control( Text_control::read_write,
                                    Rect(0,0,100,20),
                                    this,
                                    "Initial value",
                                    &fred
                                  );
}

Figure 9   Text

 
text.h
#ifndef TEXT_H__
#define TEXT_H__

#include "ansi.h"
#include "mystring.h"
#include "userintf.h"
#include "wrappers.h"
#include "notify.h"

class Text : public String, public User_interface
{
      class Observer: public Notifiable
      {
            Text *the_text_obj;
      public:
            Observer   ( Text *container  ): the_text_obj(container){}
            void notify( Notifier *sender );
      }
      watcher;

      friend void Observer::notify( Notifier *sender );

      Text_control      *display_mechanism;

public: // overrides of User_interface functions
      Text( void            );
      Text( const String &r );

      virtual      ~Text( void );

      virtual      const String &operator=( const String &r );
      virtual      const String &operator=( const _TCHAR *p );
      virtual      const Text   &operator=( const Text   &r );

      virtual bool display ( Window *win, const Rect &rect );
      virtual bool interact( Window *win, const Rect &rect );
      virtual void hide    ( void                          );
};
//---------------------------------------------------------
// The following code kicks out:
//
// warning C4355: 'this' : used in base member initializer list.
//
// Normally, it would be dangerous for the contained-object
// constructor to access the container class because the contained
// objects are initialized before the container is initialized.
// We don't do that here, and it's convenient to pass a pointer
// to the container at construction time, so turn off the warning.

#pragma warning( disable : 4355 )

inline Text::Text( void ): watcher           ( this         ),
                           display_mechanism ( NULL         ),
                           String            ( "", &watcher )
{}

inline Text::Text( const String &r ): watcher                 ( this        ),
                                      display_mechanism       ( NULL        ),
                                      String                  ( r, &watcher )
{}

#pragma warning( default : 4355 )
#endif // TEXT_H__

text.cpp
#include "stdafx.h"
#include "text.h"

/*virtual*/ Text::~Text( void )
{
      delete display_mechanism;
}

/*virtual*/      const String &Text::operator=( const String &r )
{
      return String::operator=(r);
}

/*virtual*/      const String &Text::operator=( const _TCHAR *r )
{
      return String::operator=(r);
}

/*virtual*/      const Text &Text::operator=( const Text &r )
{
      *(String *)this = r;
      return *this;
}

/*virtual*/ void Text::Observer::notify( Notifier *sender )
{
      // This function is a friend of class Text, a pointer to which is
      // in the owner field; It's called when the String base class changes.
      // Note that we have to turn notify off in whichever object is being
      // updated. Otherwise, we'll just end up back here.

      Text *p = dynamic_cast<Text *>(sender);
      if( p )
      {
            // String base class was changed, transfer the new value
            // to the display.

            if( p->display_mechanism )
            {
                  p->display_mechanism->notify_off();
                  p->display_mechanism->import( *the_text_obj );
                  p->display_mechanism->notify_on();
            }
      }
      else // User changed the Text_control, copy new value to
      {    // the string base class of the associated text object.

            Text_control *display = dynamic_cast<Text_control*>(sender);
            assert(display);
            
            display->notify_off ();
            display->export     ( the_text_obj );
            display->notify_on  ();
      }
}

/*virtual*/ bool Text::display(Window *win, const Rect &rect)
{
      // Create a static-text object displaying the text in the
      // String base class.

      display_mechanism =      new Text_control( Text_control::write_only,
                                                 rect,
                                                 win,
                                                 *(String *)this,
                                                 &watcher
                                               );
      return true;
}

/*virtual*/ void Text::hide( void )
{
      delete display_mechanism;
      display_mechanism = NULL;
}

/*virtual*/ bool Text::interact(Window *win, const Rect &rect)
{
      // Create an editble representation of the String base
      // class. Any changes made by the user will transfer
      // automatically to the string.

      display_mechanism =      new Text_control( Text_control::read_write,
                                                 rect,
                                                 win,
                                                 *(String *)this,
                                                 &watcher
                                               );
      return true;
}

Figure 10   userintf.h

 #ifndef USERINTF_H__
#define USERINTF_H__

typedef CWnd Window; // Forward references to classes in wrappers.h,
class Rect;          // avoids circular #include dependency;

class User_interface
{
public:
      virtual void hide    ( void                          ) = 0;
      virtual bool display ( Window *win, const Rect &rect ) = 0;
      virtual bool interact( Window *win, const Rect &rect ) = 0;
};

#endif // USERINTF_H__