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__