#include "wrappers.h"
#include "form.h"
//----------------------------------------------------------------------
class Employee: public Form::Element
{
Text name;
private: // Form-related functions
virtual Form::Proxy *give_me_proxy_for( const String &form_name,
const String &attribute_id );
virtual void release_proxy (Form::Proxy *p);
public:
Employee(const String &in_name): name(in_name){}
void print(void) const{ ok_box( name ); }
};
//----------------------------------------------------------------------
Form::Proxy *Employee::give_me_proxy_for( const String &form_name,
const String &attribute_id )
{
// Set things up so input flows directly from the Form into the
// Employee's Name field.
//
// Since "this" is passed as a second argument to the Proxy
// constructor, the Employee will be notified (with a release_proxy
// message) when the Form shuts down.
Form::Proxy *p = new Form::Proxy( &name, this );
assert( p );
return p;
}
//----------------------------------------------------------------------
void Employee::release_proxy(Form::Proxy *p)
{
// Sent from the form when it shuts down instead of deleting the
// proxy (because we passed our address to the Proxy's constructor).
ok_box("Form has shut down\n");
delete p; // Form does not delete the proxy if
} // we're notified. Proxy's destructor
// never deletes the associated
// User_interface object.
//----------------------------------------------------------------------
Form *make_form(void)
{
// I get an internal compiler error if I try to just initialize
// form, below, with the following new invocation. Putting the
// new in a function call and initializing form with the
// function's return value works fine, though.
Form *form = new Form( "Test Form",
new Form::Field( new Text("Name:"), Rect(0, 0, 50, 20)),
new Form::Field("Employee", "Name", Rect(50,0, 200,20)),
new Form::Field( new Form::Close_button(), Rect(80,25,120,45)),
NULL
);
form->debug_print();
return form;
}
//----------------------------------------------------------------------
Form *form = make_form();
Employee homer("Homer");
//----------------------------------------------------------------------
void CFormsView::OnTest()
{
form->put_me_on_you( "Employee", homer ); // Put the employee on
form->interact(NULL, Rect(0,0,200,200)); // the form & display it.
}
//----------------------------------------------------------------------
void CFormsView::OnReport()
{
homer.print();
}
Figure 6 Window and Button Classes
wrappers2.h
// wrappers2.h:
//
// This file is an extension of the Wrappers.h file discussed in Part
// one of the article. It is included at the end of wrappers.h and
// should not be included directly by you in your programs (include
// wrappers.h instead). Were this not a two-part article, I would have
// just put everything in the current file into wrappers.h and gotten
// it over with.
//
// The following #if makes sure that wrappers2.h is being expanded
// from within wrappers.h. The test fails if the #include occurs
// anywhere else.
#if (!defined(WRAPPERS_H__) || defined(WRAPPERS2_H_))
#error Do not include wrappers2.h directly, include wrappers.h instead.
#endif
#define WRAPPERS2_H__
//======================================================================
// Window
//======================================================================
class Window: public CWnd
, public Notifier
{
//{{AFX_VIRTUAL(Window)
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
virtual void PostNcDestroy();
//}}AFX_VIRTUAL
//{{AFX_MSG(Window)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
public:
static const String no_title;
Window( const Rect &position, const String &title = no_title,
Notifiable *notify_on_close = &Nobody );
virtual ~Window();
void show( void );
void hide( void );
void destroy( void );
private:
Notifiable *notify_on_close;
};
//------------------------------------------------------------------------
inline void Window::show( void )
{
ShowWindow( SW_SHOW );
}
//------------------------------------------------------------------------
inline void Window::hide( void )
{
ShowWindow( SW_HIDE );
}
//------------------------------------------------------------------------
inline void Window::destroy( void )
{
// A Window must be allocated from new. It is deleted automatically
// when the user shuts down the window. DO NOT PASS THE Window POINTER
// TO DELETE EVER. Instead, call p->destroy();
DestroyWindow();
}
//======================================================================
// Button
//======================================================================
class Button : public CButton
, public Notifier
{
public:
virtual ~Button(void);
Button( const String &caption, const Rect &position,
Notifiable *notify = &Nobody );
void show( Window *parent );
void hide( void );
private:
Notifiable *notify_on_press;
String caption;
Rect position;
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(Button)
//}}AFX_VIRTUAL
//{{AFX_MSG(Button)
afx_msg void OnClicked();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
wrappers2.cpp
#include "stdafx.h"
#include "wrappers.h"
#include "notify.h"
//======================================================================
// Window
//======================================================================
const String Window::no_title;
//----------------------------------------------------------------------
/*virtual*/ Window::~Window(){}
//----------------------------------------------------------------------
Window::Window( const Rect &position,
const String &title, /*= no_title */
Notifiable *notify_on_close /*= &Nobody */ )
{
this->notify_on_close = notify_on_close;
// It's convenient to have a Create() function IN ADDITION to
// PreCreateWindow(), but you must have PreCreateWindow too in case
// someone calls CreateEx() or Create() polymorphically through a
// CWnd pointer. Note that, since the current window is not a child
// window, the position is screen relative, not parent-window relative.
//
// Note that you can use CWnd::Create() to make a child window but
// not a popup [Create() requires a be a parent window]. Since the
// current example IS a popup, I must use CreateEx(), which is
// a little more awkward to use, but more functional.
#ifdef STD_DEBUG
std_debug << "Creating Window"
<< "[" << title
<< "] at " << position
<< " width=" << position.Width()
<< " height=" << position.Height()
<< "\n" ;
#endif
// Use CreateEx because this is a popup (Create() requires a parent
// window). The class name is supplied by PreCreateWindow, as are
// all attributes except the WS_CAPTION attribute. I add six units
// to the window width to compensate for the border. If the window
// has a caption bar (has_title is true), add 25 units for it and
// the border, else just add 6 units for the border..
bool has_title = &title != &no_title;
CreateEx( 0, // No extended attributes
NULL,
!has_title ? "": CString((String &)title),
!has_title ? 0 : WS_CAPTION,
position.left,
position.top,
position.Width() + 6,
position.Height() + (has_title ? 25 : 6),
NULL, // no parent
NULL // no menu
);
}
//----------------------------------------------------------------------
void Window::PostNcDestroy()
{
// Called by the framework after the windows object has been
// destroyed, frees the memory associated with the current window.
// That is, I'm assuming that the window is allocated from new and
// that it will stay around until the user opts to close it.
// There will be no delete to match the new.
notify( notify_on_close );
CWnd::PostNcDestroy();
delete this;
}
//----------------------------------------------------------------------
BOOL Window::PreCreateWindow(CREATESTRUCT& cs)
{
// Replace various arguments to Create (or CreateEx) with values
// that are required by the current window implemenation. The
// WS_VISIBLE attribute, though not strictly speaking necessary,
// lets the caller dispense with the
// win->ShowWindow( SW_SHOWNORMAL );
// win->UpdateWindow();
// calls.
static CString class_name;
if( class_name.IsEmpty() )
{
// If we haven't registered a class with Windows, yet, do it
// for now. This call usually establishes cursor, brush, and
// icon all windows of the current class. (Since the windows
// isn't going to be minimized, I've left out the icon, here).
HCURSOR cursor = AfxGetApp()->LoadStandardCursor( IDC_ARROW );
HBRUSH brush = (HBRUSH) ::GetStockObject( WHITE_BRUSH );
HICON icon = 0;
class_name = AfxRegisterWndClass(CS_VREDRAW|CS_HREDRAW,
cursor, brush,icon);
}
// Window is initially hidden, if that's not what
// you want, OR WS_VISIBLE into the file bit.
cs.style |= WS_OVERLAPPED|WS_POPUPWINDOW ;
cs.lpszClass = class_name;
return CWnd::PreCreateWindow(cs);
}
//----------------------------------------------------------------------
BEGIN_MESSAGE_MAP(Window, CWnd)
//{{AFX_MSG_MAP(Window)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
//======================================================================
// Button
//======================================================================
/*virtual*/ Button::~Button(){}
//----------------------------------------------------------------------
Button::Button( const String &in_caption,
const Rect &in_position,
Notifiable *notify /*=&Nobody*/): caption ( in_caption )
, position( in_position )
, notify_on_press(notify)
{}
void Button::show( Window *parent )
{
Create( (const CString)caption,
WS_CHILD|WS_VISIBLE,
position,
parent,
~0U ); // Button ID
}
void Button::hide( void )
{
DestroyWindow();
}
//----------------------------------------------------------------------
void Button::OnClicked(void)
{
notify( notify_on_press );
}
//----------------------------------------------------------------------
BEGIN_MESSAGE_MAP(Button, CButton)
//{{AFX_MSG_MAP(Button)
ON_CONTROL_REFLECT(BN_CLICKED, OnClicked)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
Figure 7 Final Form Classes
form.h
// form.h
#ifndef FORM_H__
#define FORM_H__
#include "mystring.h" // Discussed in Part 1.
#include "userintf.h" // Discussed in Part 1.
#include "wrappers.h" // Discussed in Part 1.
// Notes:
// (*) The Form::fields member is an array for the time
// being, it should really be variable-length collection.
//
// (*) Forms cannot be copied, so the operator= and copy ctor are
// private. There's not need to define the functions, simple
// prototypes are sufficient. (The linker kicks out an error
// if the function isn't defined and someone manages to call
// it.)
class Form : public User_interface
{
public:
class Field; // These classes are nested inside the Form
class Element; // class's name space, not external
class Proxy; // references. Definitions are below.
class Close_button;
public:
virtual
~Form( void );
Form( const String &form_name, Field *first, ... );
private:
Form ( const Form &r ); // do not define
Form &operator= ( const Form &r ); // do not define
public:
bool add_field ( Field *new_field );
void release_proxies ( void );
void put_me_on_you ( const String &class_name,
const Element &ele );
void remove_me_from_you ( const String &class_name );
public: // overrides for User_interface base class
virtual void hide ( void );
virtual bool display ( Window *win, const Rect &rect );
virtual bool interact ( Window *win, const Rect &rect );
private:
String name;
Rect size;
Form::Field **current;
enum { max_fields = 32 };
Form::Field *fields[max_fields];// fields on the form, should be a
// "collection" instead of an array.
// Create an internal class so that the Window can notify us
// when we're destroyed. Make it a friend so that it can release
// the proxies.
class Destroyer: public Notifiable
{
Form *form;
virtual void notify( class Notifier *sender );
public:
Destroyer(Form *notifiee): form(notifiee) {}
}
destroyer;
friend class Form::Destroyer;
public:
void debug_print(void);
};
#if (defined(NDEBUG) || !defined(STD_DEBUG)) // not debuggging
inline void Form::debug_print(void){}
#endif
//-------------------------------------------------------------------
// Form::Field
//
// It is a serious error for the same attribute proxy to be used in
// more than one form. This problem can be solved by reference-
// counting the proxy, but is not solved in the current implementation.
//
// A Field can't be copied.
class Form::Field: public User_interface
{
public:
enum Behavior{ input_output=0, input=0, output=1 };
virtual
~Field( void );
Field( User_interface *static_attrib,
const Rect &position );
Field( const String &class_name,
const String &attribute_id,
const Rect &position,
const Behavior behavior= input_output );
private:
Field (const Field &); // Do not define.
const Field &operator= (const Field &);
public:
const String *if_in_class_return_id (const String &cls_name)const;
void attach_proxy (Proxy *proxy );
void release_proxy (void );
public: // overrides for User_interface base class
virtual void hide ( void );
virtual bool display ( Window *win, const Rect &rect );
virtual bool interact( Window *win, const Rect &rect );
private:
Behavior mode;
String class_name;
String attribute_id;
Rect position;
Proxy *proxy; // Supplied by Form::Element if not
// static, otherwise comes from user
// via Form::Form or Form::add_field.
private: friend class Form;
const Rect &size( void );
friend void Form::debug_print(void);
};
//-------------------------------------------------------------------
class Form::Proxy : public User_interface
{
public:
virtual
~Proxy(void);
Proxy( User_interface *attribute_proxy,
Element *notify_me_rather_than_delete_proxy = NULL);
void release(void);
public: // overrides for User_interface base class
virtual void hide ( void );
virtual bool display ( Window *win, const Rect &rect );
virtual bool interact( Window *win, const Rect &rect );
protected: User_interface *ui;
private: Form::Element *creator;
friend void Form::debug_print(void);
};
//-------------------------------------------------------------------
// Elements can be copied, but there's nothing to copy at this level,
// so leave it up to the derived class. Note that, even though
// give_me_a_proxy_for(...) doesn't modify the class, it is not
// const because the returned proxy could modify the class.
class Form::Element
{
public:
virtual ~Element(void);
private: friend class Form;
virtual Proxy *give_me_proxy_for(const String &form_name,
const String &attribute_id )=0;
private: friend class Proxy;
virtual void release_proxy(Proxy *p);
friend void Form::debug_print(void);
};
//----------------------------------------------------------------------
class Form::Close_button: public User_interface
, public Notifiable
{
Button *button;
Window *parent;
public:
virtual
~Close_button(void);
Close_button(void);
virtual void hide ( void );
virtual bool display ( Window *win, const Rect &position );
virtual bool interact( Window *win, const Rect &position );
virtual void notify ( class Notifier *sender );
friend void Form::debug_print(void);
};
//----------------------------------------------------------------------
#endif FORM_H__
form.cpp
// form.cpp
#include "stdafx.h" // Needed only to keep compiler happy
#include "form.h"
#include <stdarg.h>
//===================================================================
// Form
//===================================================================
/*virtual*/ Form::~Form( void )
{
assert( this );
// Delete the fields, first releasing the proxies
for(Field **p=fields; p < &fields[max_fields] && *p ;)
{
(*p)->release_proxy();
delete( *p++ );
}
}
//-------------------------------------------------------------------
// The initializer-list element "observer(this)" causes the compiler
// to kick out a:
//
// 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 convient to pass a pointer
// to the container at construction time, so turn off the warning.
#pragma warning( disable : 4355 )
Form::Form( const String &form_name, Field *first, ... )
: current ( fields )
, name ( form_name )
, size ( 0, 0, 0, 0 )
, destroyer ( this )
{
// Initialize the Form with its name and fields. Any
// fields in excess of max_fields are silently discarded.
//
// There's nothing wrong with any function (including a constructor)
// having a variable number of arguments. The argument passed
// to va_start MAY NOT be a reference, however becuase taking
// the address of the reference gets you the referenced thing.
va_list field_list;
va_start( field_list, first );
memset( fields, 0, sizeof(fields) );
Field *field = first;
while( field && (current < &fields[max_fields]) )
{
size.expand_to_include( field->size() );
*current++ = field;
field = va_arg(field_list, Field *);
}
va_end( field_list );
}
#pragma warning( default : 4355 )
//-------------------------------------------------------------------
bool Form::add_field( Field *new_field )
{
assert( this );
assert( new_field );
if( current < &fields[max_fields] )
{
*current++ = new_field;
size.expand_to_include( new_field->size() );
return true;
}
return false;
}
//-------------------------------------------------------------------
void Form::release_proxies( void )
{
assert( this );
Field **current = fields;
while( current < &fields[max_fields] && *current )
(*current++)->release_proxy();
}
//-------------------------------------------------------------------
void Form::put_me_on_you( const String &class_name,
const Element &element )
{
assert( this );
assert( &element );
assert( &class_name );
Field **current = fields;
for(; current < &fields[max_fields] && *current; ++current )
{
const String *id = (*current)->if_in_class_return_id(class_name);
if( id )
{
Element &e = (Element &)element;
Proxy *p = e.give_me_proxy_for( name, *id );
if( p )
(*current)->attach_proxy( p );
}
}
}
//-------------------------------------------------------------------
void Form::remove_me_from_you( const String &class_name )
{
assert( this );
assert( &class_name );
// Remove all proxies on fields associated with given class_name
Field **current = fields;
for(; current < &fields[max_fields] && *current; ++current )
{
if( (*current)->if_in_class_return_id(class_name) )
(*current)->release_proxy();
}
}
//-------------------------------------------------------------------
/*virtual*/ void Form::hide( void )
{
assert( this );
Field *const *current = fields;
while( current < &fields[max_fields] )
(*current++)->hide();
}
//-------------------------------------------------------------------
/*virtual*/ bool Form::display( Window *win, const Rect &rect )
{
assert( this );
assert( win );
assert( &rect );
// Display the form, either on the indicated Window or on a
// manufactured popup window if win is NULL. The Rect argument
// is ignored. (Note that, since the Field's display and interact
// handlers also ignore the rect argument, I just pass it through
// to avoid having to create a dummy rectangle).
//
// If a popup window is created, the form name is used for the
// title and the windows size is exactly large enough to hold
// all fields.
Window *surface= win ? win
: new Window(size, name, &destroyer) ;
Field *const *current = fields;
while( current < &fields[max_fields] )
(*current++)->display(surface, rect);
return true;
}
//-------------------------------------------------------------------
/*virtual*/ bool Form::interact( Window *win, const Rect &rect )
{
assert( this );
assert( &rect );
// See comments in display() function, above.
Window *surface = win ? win : new Window(size, name, &destroyer);
surface->show();
Field **fieldv = fields;
while( *fieldv && (fieldv < &fields[max_fields]) )
(*fieldv++)->interact(surface, rect);
return true;
}
//-------------------------------------------------------------------
#if !defined(NDEBUG) && defined(STD_DEBUG)
#include "text.h"
void Form::debug_print(void)
{
std_debug << "Form @" << this
<< " name[" << name << "]"
<< " size" << size
<< " fields:\n" ;
Field **fieldv = fields;
for(; *fieldv && (fieldv < &fields[max_fields]) ; ++fieldv)
{
std_debug << " Field["<< (fieldv-fields) << "]";
std_debug << " class(" << (*fieldv)->class_name << ")";
std_debug << " attrib(" << (*fieldv)->attribute_id << ")";
std_debug << " posn" << (*fieldv)->position << ")";
std_debug << " proxy @" << (*fieldv)->proxy ;
if( (*fieldv)->proxy )
{
std_debug << " crtr @" << (*fieldv)->proxy->creator ;
Text *text = dynamic_cast<Text *>( (*fieldv)->proxy->ui );
if( text )
std_debug << " Text(\"" << *text << ")\n" ;
else if( dynamic_cast<Form::Close_button *>( (*fieldv)->proxy->ui ) )
std_debug << "(close button)\n";
else
std_debug << "***UNKNOWN PROXY***" ;
}
std_debug << "\n";
}
}
#endif
//===================================================================
// Form::Destroyer
//===================================================================
/*virtual*/ void Form::Destroyer::notify( class Notifier *sender )
{
// This function is called when the window that the form is
// using as its drawing surface is shut down by the user. It
// destroys the associated Form, which causes the proxies to
// be released.
assert( dynamic_cast<Window*>(sender) );
delete form;
}
//===================================================================
// Form::Field
//===================================================================
/*virtual*/ Form::Field::~Field( void )
{
}
//-------------------------------------------------------------------
#include "text.h"
Form::Field::Field( User_interface *static_attrib,
const Rect &position_in )
: mode ( output )
, class_name ( "" )
, attribute_id ( "" )
, position ( position_in )
, proxy ( new Proxy(static_attrib) )
{
#if (!defined(NDEBUG) && defined(STD_DEBUG))
std_debug << "Adding static_attrib @" << static_attrib << "\n";
Text *txt = dynamic_cast<Text*>(static_attrib);
if( txt )
{
std_debug << "\t(value is " << *txt << " @" << txt << ")\n" ;
return;
}
#endif
}
//-------------------------------------------------------------------
Form::Field::Field( const String &class_name_in,
const String &attribute_id_in,
const Rect &position_in,
const Behavior mode_in /*= input_output */ )
: mode ( mode_in )
, class_name ( class_name_in )
, attribute_id ( attribute_id_in )
, position ( position_in )
, proxy ( NULL )
{
}
//-------------------------------------------------------------------
const String *Form::Field::
if_in_class_return_id(const String &candidate )const
{
assert( this );
assert( &candidate );
return (class_name == candidate) ? &attribute_id : NULL;
}
//-------------------------------------------------------------------
void Form::Field::attach_proxy(Proxy *proxy)
{
assert( this );
assert( proxy );
assert( this->proxy==NULL ); // Illegal if a proxy already attached
this->proxy = proxy;
}
//-------------------------------------------------------------------
// Note for release_proxy, hide, display, interact. It's not an error
// if a field that doesn't have a proxy attached to itself is displayed,
// etc. It's just a no-op.
//
void Form::Field::release_proxy(void)
{
assert( this );
if( proxy )
proxy->release();
proxy = NULL;
}
//-------------------------------------------------------------------
/*virtual*/ void Form::Field::hide( void )
{
assert( this );
if( proxy )
proxy->hide();
}
//-------------------------------------------------------------------
/*virtual*/ bool Form::Field::display( Window *win, const Rect &rect )
{
assert( this );
assert( win );
assert( &rect );
return proxy ? proxy->display( win, rect ) : true;
}
//-------------------------------------------------------------------
/*virtual*/ bool Form::Field::interact( Window *win, const Rect &rect )
{
// Display the current field. The rect argument is ignored in
// favor of the position rectangle stored in the Field itself.
assert( this );
assert( win );
assert( &rect );
assert( proxy );
return (proxy==NULL ) ? true :
(mode == input) ? proxy->interact( win, position ) :
proxy->display ( win, position ) ;
}
//-------------------------------------------------------------------
const Rect &Form::Field::size( void )
{
// Normally, I wouldn't return a reference to a field in an object
// because the caller could modify the field. Here, it seems safe
// because the fuction is private, and called only from the Form
// class.
return position;
}
//===================================================================
// Form::Proxy
//===================================================================
/*virtual*/ Form::Proxy::~Proxy(void){}
//-------------------------------------------------------------------
Form::Proxy::Proxy( User_interface *attribute,
Form::Element *notify_me_rather_than_delete_proxy )
: ui ( attribute )
, creator ( notify_me_rather_than_delete_proxy /*=NULL*/ )
{}
//-------------------------------------------------------------------
void Form::Proxy::release( void )
{
assert( this );
if( creator )
creator->release_proxy(this);
else
{
if( ui )
delete ui;
delete this;
}
}
//-------------------------------------------------------------------
/*virtual*/ void Form::Proxy::hide( void )
{
assert( this );
assert( ui );
ui->hide();
}
//-------------------------------------------------------------------
/*virtual*/ bool Form::Proxy::display( Window *win, const Rect &rect )
{
assert( this );
assert( ui );
return ui->display( win, rect );
}
//-------------------------------------------------------------------
/*virtual*/ bool Form::Proxy::interact( Window *win, const Rect &rect )
{
assert( this );
assert( ui );
return ui->interact( win, rect );
}
//===================================================================
// Form::Element
//===================================================================
/*virtual*/ Form::Element::~Element(void){}
//-------------------------------------------------------------------
/*virtual*/ void Form::Element::release_proxy(Proxy *p)
{
// Empty on purpose. The default action is to not release
// anything. The derived class can define an override if it
// provides proxies that need to be released.
}
//===================================================================
// Form::Close_button
//===================================================================
Form::Close_button::~Close_button(){ if(button) delete button; }
Form::Close_button::Close_button() : button(NULL), parent(NULL){}
//-------------------------------------------------------------------
/*virtual*/
void Form::Close_button::hide( void )
{
assert( this );
if( button )
{
delete button;
button = NULL;
}
}
//-------------------------------------------------------------------
/*virtual*/
bool Form::Close_button::display( Window *win, const Rect &position )
{
assert(this);
assert(win );
assert(&position);
parent = win;
button = new Button( "Close", position, this );
button->show(win);
return true;
}
//-------------------------------------------------------------------
/*virtual*/
bool Form::Close_button::interact( Window *win, const Rect &position )
{
return display(win, position);
}
//-------------------------------------------------------------------
/*virtual*/
void Form::Close_button::notify( class Notifier *sender )
{
// This function is called when the window that the form is
// using as its drawing surface is shut down by the user. It
// destroys the associated Form, which causes the proxies to
// be released.
Button *b = dynamic_cast<Button*>(sender);
assert(b);
assert(b==button);
assert(parent);
parent->DestroyWindow();
}