OOFILE | Downloads | Purchasing | Press | Services | Company Information | Soapbox | References | F.A.Q. | HOME

 

Background

The PowerPlant to MFC toolkit is a collection of classes to ease the burden of coding for both frameworks. In particular, it helps users of dialogs use common (PowerPlant) code to get and set items on the screen.

The following document was written from the perspective of a specific project, the porting of Mercator Software's KIDMAP which was the largest application produced with AppMaker v2 at the time of writing, and which drove many of the features in the OOFILE tools produced during 1996-99.

KIDMAP97 had around 50 screens, many of which are multiple panel dialogs containing text and hierarchical lists with drag-and-drop etc. It uses the dBase OOFILE backend for data exchange and has around 150 reports, many of which include graphs and multiple nested levels.

That initial version of KIDMAP was also built for Windows 3.1 via the win32s API (v1.3c). The win32s API provides the same 32-bit flat memory space and many of the functions of Windows95 but has the same resource limits and screen size issues of normal 16-bit Windows 3.1 programming.

The main reason for the long gap in bringing PP2MFC to market from its initial use in KIDMAP was the strategy of hand-completing the generated code. A couple of years down the track this required considerable reverse engineering to determine what should be added to the code generator.

General Notes

We don't use '#pragma once' in our headers as it is not supported on all compilers used in Windows.

Dialog (Forms) Programming

PP2MFC uses a parallel set of controls to make both MFC and PowerPlant control interfaces available. The MFC standard is to use m_ as a prefix for controls. Typically, the PowerPlant shadows have the same names but without the m_ prefix. The shadows are initialised from their MFC equivalents and register themselves with a shadow dialog, so they are not explicitly deleted.

This is the style of code that AppMaker generates with the PowerPlant compatability generator.

See AM MFC CodeGen Details for details on how the different AppMaker languages generate Windows vs Dialogs.

String Constants

There is no easy way to cope with the Pascal string literals of the Mac (eg: with "\pThis is a Pascal String") as this is a compiler-level extension of the C standard, for the Mac. (In case you're wondering, yes it can be turned off.)

We use only plain C strings throughout on MFC, and the Mac types that referred to unsigned chars have been appropriately redefined. A portable-conversion macro has been defined that can be applied to your Mac code, rewriting

           ::AppendMenu(focusH, "\pdummy");
as
            ::AppendMenu(focusH, MacLiteral("dummy"));
 
The GREP replace strings for this conversion are:
FIND            "\\p(.*)"
REPLACE WITH    MacLiteral\("\1"\)

If you are writing portable code using QuickTime examples you will have run into their use of unsigned chars in Mac toolbox code and the "\p" behaviour to generate such string literals. This was available in some versions of Visual C++ because of their Mac cross-compiler but removed in VC6 onwards.

Lists

The LTextTableView class used extensively throughout KIDMAP is an "official" PowerPlant class now but was mainly written for this project, and to give AppMaker a robust list class in PowerPlant. As a descendant of LTableView it has the heavily factored model for selection, storage & geometry being managed by families of abstract helpers (Strategy pattern).

One of the most significant behavioural differences between the PP table classes and the MFC model is the storage model. By default, the PP classes use a virtual storage model where the Windows model is preloaded and you have to go to a lot of effort to implement virtual classes. The OOFILE MFC integration has done so but still has the compromise of preloading a set of row entries to be later populated with the actual data from the database.

For non-OOFILE use, we will make no attempt to make the lists look like the PP lists in these behaviours. They will be preloaded more like PP's LListbox.

Tree Controls (Hierarchical Tables)

The tree control being used in PP is a subclass of LOutlineTable. It provides behaviour very similar to LTextTableView, being essentially a text list with selection behaviour (shift & command-clicks) similar to straight text lists. This was based on user tests - a large number of complaints were received when the class was first implemented, with the default behaviour from LOutlineTable being that of "Finder-style" selection. It seems the key determinant in Finder-style selection is that the list be iconic or closely associated with an iconic view, and not intermingled closely with traditional text lists.

The vast difference in models between the LTableView family in PowerPlant, and the windows CTreeCtrl makes it too hard to currently generate plain MFC code for loading tree controls. We don't do it for Mac anyway.

With the COOFTreeCtrl class, the same hierBrowser model is implemented as on the Mac and so coding differences are minimal.

Tab Controls

Like the hierarchical list controls we are again concerned with the question of to what should we be compatible. The Mac controls used are those that shipped with AppMaker (CTabPanel) in early '96, being a lightweight wrapper around the tab control in the freeware collection Jim's CDEF's. (In the near future these will be upgraded to include the new AM compatible tabs.)

The behaviour this implements is the standard Get/SetValue which is the 1-based integer of the pane number selected, and a message broadcast (standard PP message msg_ControlClicked) on change of panel.

Command Handling

Complete mimicry of the PP ListenToMessage and ObeyCommand methods is provided. There are a number of mechanisms used for this:

Button forwarding

MFC lacks the concept of attaching a command to a button. Message maps are used to handle the button-clicked event generated.

Message commands are passed through to ObeyCommand and then later up into the MFC command dispatching hierarchy (if not handled).

SetValueMessage

In PowerPlant, SetValueMessage is used to make runtime changes to the command sent by a button, checkbox etc.

Our LCommander class provides a redirection table to map any original MFC messages to the values changed by SetValueMessage. These redirected values are then passed into ListenToMessage.

See the discussion of Synthetic Menus below for more information on dynamic menus and how commands are generated and trapped.

Value Messages and View Hierarchy

see also AM MFC CodeGen Details: "Responding to Control Changes to Update DataDefs - Comparison with PP"

All the child controls in a dialog descend from CWnd but they don't receive messages.

The standard controls (CButton, CList and CEdit) send messages that we intercept in LWindow::OnCommandRedirector (packaged as WM_COMMAND) messages.

We call ListenToMessage with these values.

Common controls (eg: CSliderCtrl) package their notifications in WM_NOTIFY messages.

Menu Construction & Manipulation

There are MFC analogues for almost all the commands in the Mac toolbox and PP used in appending and changing items on menus, so writing interfaces for these is very straightforward. Our classes usually effectively translate the call into an MFC call.

LStdPopupMenu::GetMacMenuH() is used frequently to get a MenuHandle. This is a forwarding class that knows if it is talking to a ComboBox (as in this case) or a normal menu. Windows doesn't use menus for ComboBoxes - they are actually lists and so we internally will get/set/append/delete items differently.

Our LStdPopupMenu and LMenu therefore have methods corresponding to the common Mac toolbox calls. These can be safely called directly.

Hierarchical Mac menus have submenu ID's inserted in them already, so the dynamic calls are FetchMenu to get the submenu (the MenuBar doesn't distinguish) followed by ::AppendMenu on the contained MenuHandle. The PP classes don't provide features to manipulate submenus, other than treating them like any other menu once installed. However, in InstallMenu, the Mac menu is scanned recursively for submenus so an LMenu can be added to the (linear) list in LMenuBar.

Hierarchical (cascading) menus is where the big difference between the OS-level handling of menus becomes a nuisance. On the Mac, the menu ID of the resource is used to continue to manage sub-menus, as a way of linking an entry in the parent menu to the sub-menu. In Windows, cascading menus are lexically embedded in a resource definition, and even when added programmatically there is no parent ID referring to a separate menu.

This means we can't write logic that retrieves submenus by an ID - there never was one to trace in the first place.

The solution to this is declarative - you call SetNthMenuID on either an LMenuBar or an LMenu (for its submenus), to specify what the PowerPlant ID's are going to be. This forces some awareness of the positions of menus in the menu bar, and submenus in their parent, but is localised to OnInitDialog.

Thus, PowerPlant code that later uses FetchMenu(ResIDT) will still work. If you add a menu to the menubar with InstallMenu this implies a SetNthMenuID.

Synthetic Menu ID's

Synthetic command handling is somewhat more complex. MFC doesn't allow us to append menu items without an ID.

We generate our own synthetic command ID's within LMenu as we append menu items. Note: the range for these needs to be set per application to avoid conflict - we can pick a good default and maybe rely on AppMaker to provide a hint.

The important assertion to remember is that the only way we have Synthetic ID's is because the menu item has gone through FetchMenu()->GetMacMenu()->AppendMenu() chain. We don't generate them for the MFC menus loaded from resources, and don't have the data structures there to support a lookup.

Note: Synthetic Menu commands are used in PowerPlant for dynamic menus like Font menus where you want a range of numbers to be generated from a menu.

Notes on CMenu

CMenu is the MFC class for managing menus, and like their controls you can have menus loaded from a resource file without needing any MFC objects.

The most common way to use CMenu is to get a CMenu* from CWnd::GetMenu() or GetSubMenu(), to point to an existing class.

You can use CMenu::LoadMenu (Prosis p190) to create a menu from a resource but would normally either attach it as the topmost menu (via SetMenu) or append to an existing menu (top-level, or as hierarchic submenu) using AppendMenu.

After attaching a menu in some way, CMenu::Detach must be called - the menu has been handed over to the OS and an error will result if the CMenu dtor is allowed to delete the menu.


PLAUSIBLE BUT NOT CONFIRMED COMMENTS ON MFC IN GENERAL

From a comparison of OWL vs MFC in March 96 by Steve Loughran " Too many of the MFC classes assume that there is a frame window, a view and a document in the app. ... Specifically, the command routing and enabling functions work best if there is a CFrameWnd further up the heirarchy with views and documents somewhere managed by it. Many "utility" classes -CToolBar, for example, do not work properly without a framewindow as a parent, as neither the enabler functions or ToolTips work.

Another example of where the enabler functions fail is in apps built from a dialog rather than a main window: here you have to steal somne code from Framewnd.cpp to get popup menus to work. Given that MFC 4 added extra code to handle OCXs in modal dialogs, you'd think that the enablers could have been supported too."

The implications of this comment for PP2MFC use in writing applications are minimal but it's instructive if we attempt to make more of the MFC classes usable in less traditional ways. PowerPlant is better factored.

COMPILER NOTES

get a lot of spurious warnings from CodeWarrior compiling MFC if turn on "hides inherited" warning. These are NOT our problem. eg: Warning : 'CDialog::Create(const char *, CWnd *)' hides inherited virtual function 'CWnd::Create(const char *, const char *, unsigned long, const tagRECT&, CWnd *, unsigned int, CCreateContext *)' AFXWIN.H line 2643 };

need to #define NOMINMAX in your prefs file and in the precompiled headers (had trouble with precompiled headers in CodeWarrior)

DEALING WITH COMPILER ERRORS

The following loose notes should help you if things go wrong - always try building with a copy of our standard projects first before making your own projects from scratch - there may be a significant but minor setting difference.

-----

Since CW Pro5 (or maybe 5.3) linking OOFILE may cause errors like:

Link Error   : Undefined symbol: ??0?$ctype@D@std@@QAE@PBW4mask@ctype_base@1@_NI@Z  (std::ctype<char>::ctype<char>(enum std::ctype_base::mask const *, bool, unsigned int)) in
oofarray.cpp

Make sure you either 1) link in the SIOUX libs, or 2) define OOF_NO_STDLIB in your project prefix

-----

Calling Enable3D() or Enable3DStatic() without #include <afxcmn.h> in your stdafx.h will give errors like:

Link Error : Undefined symbol: _Ctl3dRegister@4 in APP3DS.obj

-----

You may see resource errors like:

Error : the file 'res\sarrows.cur' cannot be opened afxres.rc line 169 30980

This is due to relative paths and having only a {compiler} access path. I think that project conversion between versions of CW may cause this scenario as I've seen it even with standard examples.

Make sure your system paths include {Compiler}Win32-x86 Support/Headers/MFC/ and (on the Mac) "Interpret DOS and Unix Paths" is checked.


back to PP2MFC main page