Facade With Right Button and Left Button Click

Introduction

In a large programming project you frequently want to provide an integerated view of a complex data base (multiple interconnected data lists for simplicity). The Facade is the pattern recommended for this purpose. Gamma et al describe the purpose this way.

Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

The implementation problem with windows is solved by having one main window (ususally called a Client that controls the windows used to display different aspects of the data set. This organization is often called a document/view, and while the Borland compiler allows the user to create a document/view project, the implementation is complicated and not as flexible as those you can implement yourself. The classic example has one window that displays a graphic image composed of lines and points and circles, while a second window displays the actual list of objects displayed on the screen. You can then edit either items in the graphic window or the list window and the results of the editing will instantly be reflected in the other window. If for example you select a line in the graphic window and delete it, the line will disappear from the list of items.

This example is simplistic since both windows are displaying exactly the same data. In a real application such as a mail order program, one window might be customer data, another inventory, another catalog and still another orders (which tie inventory to customers). Each of these displays one aspect of the document (company information). Each aspect might have its own editor and the results of an edit in one editor might change the display on other windows. If for example the catalog window is used to register orders, each order will change the inventory, order list and customer balance. One way to maintain the data base is to maintain all of the lists in the Facade and just change the displays in each window. This document describes a simple example with two different windows each sharing the same commands.

The Implementation

This is a relatively simple example with a window containing integers and another containing strings. While there is not interaction between the windows, the only addition needed to reflect changes in all of the windows is to call TransferData for each window that changes (after changing the transfer buffer for that window). We will use the main menu alter data in each window. Because the operations on each window consist of add, delete and edit we have no need for dynamic menus. If we were to have one or two items that were available on selected windows, we could handle this with command enablers. On the other hand, if the operations were completely different we would need to us dynamic menus. We will not attach menus to the windows in this case since we want operations to take place in the main window.

Create a Window

First we create the menu as we have before. Next we will create a standalone listbox window . This window contains a listbox and will be a generic listbox window/ since that is all we need for this problem. There will be two of these windows, so in addition to building the transfer buffer for the window, we will have to create a way to identify which window we have. The function whichOne will return the number of the type of window created. Notice this number is passed in by the second constructor and stored in the private variable. Also we will implement the ListBox as a RightButtonListBox, so the library must be included in the main project. Since all of the work is done outside this window, we must know which item is selected. the getSelIndex function returns the number of the item selected. The command handlers are described below.

class Generic : public TDialog {
public:
	Generic (TWindow* parent, TResId resId = IDD_GENERIC_LIST, TModule* module = 0);     
	Generic (int which,GenericXfer *tb,TWindow* parent, TResId resId = IDD_GENERIC_LIST, TModule* module = 0);
	virtual ~Generic ();
	int getSelIndex();
	int whichOne(){return _whichOne;}
//{{GenericXFER_DEF}}
protected:
	RightButtonListBox *_listIt;

//{{GenericXFER_DEF_END}}
private:
	int _whichOne;

//{{GenericRSP_TBL_BEGIN}}
protected:

Window Command Handlers and Messages

Each of these windows will be a perminent fixture, so we will override the WM_CLOSE command to disable it. We will also pass along the right button down message to the main window, so we need to handle the WM_RBUTTONDOWN message. The main window needs to know which window has the current selection, so when the selection on a window changes, it sends a message to the main window. This is a user defined message WM_MYSELECT which has a handler in Parent. The important fact about using the change in selection to triger the message is that this reacts to either right or left button down in this type of window.

protected:
    void EvRButtonDown (uint modKeys, TPoint& point);
    void EvClose ();
    void LBNSelchange ();
//{{GenericRSP_TBL_END}}
DECLARE_RESPONSE_TABLE(Generic);
};    //{{Generic}}

Outside of passing back user actions this type of window does nothing but display representations of items in a list. The three handlers are given here.

void Generic::EvRButtonDown (uint modKeys, TPoint& point)
{
    TDialog::EvRButtonDown(modKeys, point);

    // INSERT>> Your code here.
    Parent->PostMessage(WM_RBUTTONDOWN);
}

void Generic::EvClose ()
{
    ///TDialog::EvClose();  //Disable the close command for this window

      // INSERT>> Your code here.

}

void Generic::LBNSelchange ()
{
      // INSERT>> Your code here.    
    Parent->PostMessage(WM_MYSELECT); 	// This tells the main window to change 
					// the window of the selection

}

The Main Window

Child Window Creation

We have two child windows (one for strings and one for integers) and each window contains a representation of the data in one array. The arrays are hardwired here, but could have been read from a file. Since each window is an instance on the same class, and we need to use the transfer buffers many times, we create an array of transfer buffers subscripted on the enumerated type defined in the main window. We also create functions setIntBuff and setStringBuff which fill the transfer buffer every time the data in a given window changes. These functions are close to EvPaint functions except they work on the transfer buffer, so when they are used during a run (except during SetupWindow), the function calling them also tells the current window to Transfer data.

void NewmessWindow::SetupWindow ()
{
    TWindow::SetupWindow();

     // INSERT>> Your code here.
    // initialize the data in the integer array
    for (int i = 0;i<5;i++)
    	_ints.insertItem(i);
     // initialize the transfer buffer
    setIntBuff(_tb[INT_LIST]._listIt);

    Generic *wind;
    wind = new Generic (INT_LIST,&_tb[INT_LIST],this);
    wind->Create();
    // Place the first window up in the upper left corner
    // make sure to maintain the height and width.
    
    TRect rect = wind->GetWindowRect();
    int width = rect.Width();
    int height = rect.Height();
    wind->MoveWindow(25,50,width,height,true);
    // Create the string window
    // initialize the array
    _strings.insertItem("First");
    _strings.insertItem("Second");
    _strings.insertItem("Third");
    _strings.insertItem("Fourth");
    _strings.insertItem("Fifth");
    // set up the transfer buffer
    setStringBuff(_tb[STRING_LIST]._listIt);
    // create the window
    wind = new Generic(STRING_LIST,&_tb[STRING_LIST],this);
    wind->Create();
     // move to the right of the previous window
    // make sure to maintain the size
    rect = wind->GetWindowRect();
    width = rect.Width();
    height = rect.Height();
    wind->MoveWindow(375,50,width,height,true);
}

void NewmessWindow::setIntBuff(TListBoxData &lbd)
{
	lbd.Clear();
	ArrayIterator next(_ints);
  	 while (next)
	{
		char buff[255]; 
		itoa(next(),buff,10);
     		lbd.AddString(buff);
      		next++;
  	 }

}

The Right Button Menu

In this program the right-button menu is generated on the spot and each choice is tied to the same handler as we have in the edit menu for the main menu bar.

void NewmessWindow::EvRButtonDown (uint modKeys, TPoint& point)
{
    TWindow::EvRButtonDown(modKeys, point);

    // INSERT>> Your code here.
    
    _current = TYPESAFE_DOWNCAST(GetWindowPtr(GetActiveWindow()),Generic);
    GetCursorPos(point); // Put the menu at the click point
     TPopupMenu rClickMenu;
     rClickMenu.AppendMenu(MF_STRING,CM_ADD_ITEM,"&Add Item");
     rClickMenu.AppendMenu(MF_STRING,CM_DELETE_ITEM,"&Delete Item");
     rClickMenu.AppendMenu(MF_STRING,CM_EDIT_ITEM,"&Edit Item");

    rClickMenu.TrackPopupMenu( TPM_LEFTALIGN | TPM_RIGHTBUTTON,point,0,*this);


}

Menu Command Handlers

A prerequisite for any command handler is that a window be chosen. This happens every time an item is selected. Each time an item is selected, the window in which the selection is made sends the MY_SELECT message to the main window where the window sending the message is saved as _current

//Commands are enabled only when a window has been selected
void NewmessWindow::ceEditItem (TCommandEnabler &tce)
{
    // INSERT>> Your code here.
   tce.Enable(_current!=NULL);
}
// The main window responds to a selection by getting the active window and setting _current equal to it.
LRESULT NewmessWindow::EvMySelect(WPARAM,LPARAM)
{
	_current = TYPESAFE_DOWNCAST(GetWindowPtr(GetActiveWindow()),Generic);
	return 1;
}

The first thing the command handler does is determine which window is active, then it choses a programmer defined handler for the given window. The integer window handler is given below the edit handler.

void NewmessWindow::cmEditItem ()
{
    // INSERT>> Your code here.
    if (_current)  // Enabler actually takes care of this.
    {
    	int sel = _current->getSelIndex();
    	if (_current->whichOne() == STRING_LIST)
		editString(sel);
    	else
		editInt(sel);
    }

}

void NewmessWindow::editInt(int where)
{
   char buff[255];
   itoa(_ints[where],buff,10);
   TInputDialog(this,"Integer Edit","Edit this integer",buff,255).Execute();
   _ints.deleteItemAt(where);
   _ints.insertItemAt(atoi(buff),where);
    setIntBuff(_tb[INT_LIST]._listIt);
    _current->TransferData(tdSetData);
}

Notice that after the item is edited, deleted and reinserted (the only way to change an entry in an array), the appropriate transfer buffer is updated and the data is transfered to the window in the tdSetData mode.

Complete Code

For a complete copy of the code click here .