WINDOWS95 STYLE PROPERTY SHEETS (TAB DIALOGS) IN OWL 2.0
========================================================

Introduction
------------

With the complexity of modern applications, it is sometimes necessary to
group dialog boxes together.  Traditionally this was done by having
dialog boxes with buttons that brought up yet more dialog boxes, but this
approach is extremely cumbersome and not intuitive for users.

More recently, applications like Quattro Pro, and subsequently Microsoft's
Office applications (Word, Excel etc.) have used "property dialogs" - a
dialog box which contains a series of "tabs" that allow the user to
quickly switch from one screen to another like flipping through pages in a
notebook.

Such is the usefullness of the "property dialog" approach, Windows95 (or
Chicago as it was formerly known) will make widespread use of them.  Now
you don't have to wait, as I present here a set of C++ classes that allow
you to implement Windows95 style property dialogs today!


How to use property dialogs
---------------------------

My property dialog classes are built around two simple classes, both of
which derive from the OWL TDialog class:

  TPropertyDialog       a holder for the pages
  TPropertyPage         an individual page

Generally you will not need to override TPropertyDialog, as all of the
functionality of your application will be built around the actual pages.

To create a property dialog you need to follow these steps:

1. Create an instance of TPropertyDialog (or your derived class)
2. Call the Add function in TPropertyDialog to add each of the pages
   (which derive from TPropertyPage)
3. Execute the dialog

The following code fragment (taken from the supplied test program)
shows these three steps being performed.

  void TMainWindow::TestDialog ()
  {
    TPropertyDialog Property (this, IDD_TEST, Tab::DoubleHeight);

    Property.Add ("First page",     new TMyPropertyPage (CHILD_1));
    Property.Add ("Second page",    new TMyPropertyPage (CHILD_2), FALSE);
    Property.Add ("Third page",     new TMyPropertyPage (CHILD_3));
    Property.Add ("Fourth page",    new TMyPropertyPage (CHILD_4));

    Property.Execute ();
  }

The property dialog will handle all of the drawing of the tabs and
switching between pages.

The property pages themselves are implemented as child dialogs (ie. they
have the WS_CHILD style).  When TPropertyDialog is executed is creates
all of the property page dialog and shows the first of them.  To prevent
undue flicker you should make sure you page dialogs do NOT have the
WS_VISIBLE style, as it is up to TPropertyDialog to show them at the
right time.


So what do I need to do?
------------------------

As stated earlier, you will want to override the TPropertyPage class to
add your own functionality.  Your derived class will generally need to
override the following:

	void    SetupWindow ()  - to set up the fields on the page
	void    SaveData ()     - to save away the data before closing

The SaveData takes the place of the CmOk function that you would generally
override.  Although you will need to put Ok (and Cancel if necessary) on
your page, these are handled by TPropertyPage and should NOT be overriden,
as they need to tell the TPropertyDialog to close (not the page).

Note: The default SaveData function calls the TransferData function to
take advantage of OWL's transfer mechanism (which can include my own
"transfer arrays" found in tfarry.cpp)


Things to try
-------------

You might like to play around with the following:

- various styles of tab style are available to the programmer.  The are
  specified via the third parameter to TPropertyDialog, eg.

    TPropertyDialog Property (this, IDD_TEST, Tab::DoubleHeight);

  The following styles are available (all must be prefixed with "Tab::")

  - Tab::SingleHeight       - single height (one row of text) tabs
  - Tab::DoubleHeight       - double height (two rows of text) tabs
  - Tab::VariableWidth      - variable width tabs
  - Tab::FixedWidth         - fixed width tabs
  - Tab::ColorActive        - color the active tab (SetActiveColor sets color)
  - Tab::Collapsing         - "collapse" (hide) disabled tabs
  - Tab::Justified          - expand tab widths to make them fit neatly
  - Tab::Stacked            - allow multiple rows of tabs.  If not specified
                              tabs will be scrollable if too wide.  Not that
                              this style automatically disables Collapsing
                              and enables Justified
  - Tab::AllowDupPages      - allow one dialog to be shared by several pages
  - Tab::CreateOnDemand     - see below
  - Tab::CreateOnDemandAndKeepIt - see below
  - Tab::WideMargins        - increases the space to the left and right of
							  text on the tab, making the tab wider.  This can
							  help to increase legibility in some cases
  - Tab::AutoTabResize      - causes the tab area to be automatically resized
							  if the property dialog is made larger.  This is
							  of particular use where the property dialog is
							  placed inside a sizeable frame (eg. an MDI child)
  - Tab::ButtonsOnRight     - resize the tab area allowing for an OK button
							  on the right hand side of the dialog box.  By
							  default propdlg resizes assuming the OK button
							  is on the bottom of the dialog
  - Tab::UseSmallFont       - convert all controls to a non bold font
							  (actually the MS Sans Serif 8pt).  All controls
							  are converted except static controls with the
							  SS_NOPREFIX style ("no character underline")
  - Tab::SaveOnClosePage    - automatically call a page's SaveData function
							  whenever you leave that page (ie. when you
							  select another)
  - Tab::NoMDIClose         - prevents the ESC key from closing the property
							  dialog (useful for MDI child dialogs)
  - Tab::WizardFrame        - behaves like a normal tab dialog, except that
							  the tabs are effectively invisible.  Extremely
							  useful for "wizard" dialogs
  - Tab::UseTabBitmaps		- see below
  - Tab::CenterDialog		- causes the property dialog to be automatically
							  placed in the center of the screen
  - Tab::FocusOnFirstControl -places the focus on the first control on the
							  tab after a page is selected

- if you want to add static fields to the property dialog (eg. for an
  informative message), override the TPropertyDialog::AdjustMargin ()
  function.  This will allow you to change the area occupied by the tabs.


Using the Tab::CreateOnDemand style
-----------------------------------

Several people have asked me whether or not it is possible to only create
the physical pages when the tab is selected, as they are running into
resource problems.  Well now you can -- just add the Tab::CreateOnDemand
style when creating your TPropertyDialog and the system will do the rest.

Well, not quite.  Unfortunately it is still up to the programmer to
supply a way to store the data away (before destroying the old page your
SaveData function will be called).  The simplest way is to define an OWL
transfer buffer.  TPropertyTab::SaveData has now been changed to save away
to a transfer buffer by default (of course this has no affect if you haven't
defined a transfer buffer)

New - Tab::CreateOnDemandAndKeepIt style
----------------------------------------

In addition to Tab::CreateOnDemand there is another style called
Tab::CreateOnDemandAndKeepIt which is basically the same in that the
dialogs are not created initially, but once a page has been selected and
the dialog create, it is not then deleted again.  This can improve startup
performance, but again can lead to resource problems.

Note: this style is used *instead* of Tab::CreateOnDemand, not in addition
	  to it


TFontPropertyPage class
-----------------------

This is a new class which provides common-dialog style font selection
functionality on a property page, much as found in the latest MS-apps.

To use this class you need to first of all create an instance of TFontData
(or TPrinterFontData for use with printer fonts) and set up certain fields
there (if necessary), then just call TPropertyDialog::Add passing a pointer
to a "new"ed TFontPropertyPage.

NB. TFontData is derived from TChooseFontDialog::TData, so it should be rel-
atively straightforward to modify any existing app to use TFontPropertyPage.
TFontData differs in that it initialises the members to default values

  TPropertyDialog     Format (this, IDD_FORMATDLG, Tab::SingleHeight);
  TFontData           fontdata;

  Format.Add ("Font", new TFontPropertyPage (IDD_FONTDLG, fontdata));
  :

  Format.Execute ();


Wizard classes!
---------------

MS are strongly promoting the Wizards in their apps as a way to walk a user
through a complex setup process.  Now you can implement them simply in your
apps too using the new Wizard support in propdlg.

Basically a Wizard is a property dialog whose tabs just aren't visible.
Apart from that, all other things are the same - you use Add to add new pages
and SelectNext/SelectPrevious to move between pages.

Propdlg's Wizard support is supplied via two classes defined in WIZARD.H:

- TWizardDialog   - derives from TPropertyDialog and provides support for
					Next/Previous button enabling
- TWizardPage	  - derives from TPropertyPage and provides support for
					TBitmapControls on the page

The file WIZARD.RH contains the id definitions for the buttons that
TWizardDialog expects to work with, and WIZARD.RC contains a dialog box
that you can use as a holder for your pages or as a template for your own
Wizard dialogs

Note that you can make some pages get skipped by using the EnableTab function
which will allow you to make decisions about whether or not a particular page
should appear in the sequence depending on an earlier selection by the user.


Creating other dialogs from a property-page
-------------------------------------------

One occasion you may want to display a popup dialog when the user presses a
button on a property-page.  Because the pages are child windows, doing this
using the page as the parent to the popup causes the active window problems
when the popup dialog is closed.

To get around this you simply you can call the function GetParentWindow() to
return a pointer to the first parent window in the hierarchy which is not a
child window.  Just using the TPropertyDialog is OK too, but only when it is
modal (and therefore can be made active), and not when it is being used as
the client to another window (eg. in an MDI child window)

	TMyDialog (GetParentWindow (), IDD_MYDIALOG).Execute ();

This behaviour does not affect messageboxes, as TPropertyPage contains its
own MessageBox function which will automatically use the window returned by
GetParentWindow as its parent.


Using transfer buffers/arrays with property dialogs
---------------------------------------------------

Using transfer buffers (or my own transfer arrays) with the property dialog
classes is sometimes a necessary evil, particularly if you are using the new
CreateOnDemand style, as the page dialogs are only created when you select
that page and are destroyed when you move to another.  As a result you need to
specify some way to store the information away, and transfer mechanisms are
one such way.

To use the either transfer mechanism you need to define the structures at the
same time as you define the property dialog.  Then after creating each page
attach the relevant transfer buffer/array to that page:

  TPropertyDialog dlg (this, IDD_MYPROPDLG);
  TPropertyPage *page;
  TRANSFERPAGE1 tf1; // transfer structure of page 1
  TRANSFERPAGE2 tf2; // transfer structure of page 2 etc.

  page = new TYourPage1 (IDD_PAGE1);
  page->SetTransferBuffer (&tf1);
  dlg.Add ("First page", *page);

  page = new TYourPage2 (IDD_PAGE2);
  page->SetTransferBuffer (&tf2);
  dlg.Add ("Second page", *page);

  // here you will want to initialise the buffers prior to showing the dialog
  :

  dlg.Execute ();

For information on how my transfer arrays work and how to use them in your
application see the separate file TFARRY.TXT.  The supplied test program also
has an example of the use of transfer arrays with property dialogs which may
be a useful reference

NB. To preserve compatibility property dialogs won't use transfer arrays by
	default, but will use the old transfer buffers instead.  To use transfer
	arrays you need to add 'USETRANSFERARRAYS=TRUE' to the Compiler Defines


NEW - Place bitmaps on the tabs!
--------------------------------

Small icon-type bitmaps can now be placed on the tabs alongside the text.  To
do this, create a bitmap with the same id as the property dialog and use the
Tab::UseTabBitmaps style.

The single bitmap resource is divided horizontally into smaller "cells" so
that each page gets one cell.  This reduces the complexity of dealing with
many small bitmaps, but also means that the bitmaps drawn on each page must
be the same size.  The width of an individual bitmap is calculated as:

	bitmap width / number of pages

This code was developed jointly with Russell Morris (CIS: 75627,3615).

In addition to the basic Tab::UseTabBitmaps support, there is a new
constructor for TPropertyDialog which allows a different bitmap ID to be
specified and which also (optionally) allows a mask to be used when drawing
the bitmap.

When using the mask the bitmap is split vertically in two - the top half
contains the positive image, which should be given a black background.  The
bottom half is the mask which should be black on white.  See TABTEST2.BMP for
for an example of such a bitmap.  The Test program contains an example of this
masking in use.  To prove it works, try changing the button face colour in the
Color applet in Control Panel (the button face color is called "3D objects"
in Windows 95's Control Panel)


Limitations in this version
---------------------------

- it is necessary to place at least one control in the main property
  dialog box for it to work with ctl3d.  The simplest way around this
  problem is to place a single static control in your TPropertyDialog and
  make it invisible (actually this is a ctl3d limitation, not mine)

  Some people have said they don't get this problem, so it may depend on
  the version of CTL3D you are using..


Corrections/Amendments in this version
--------------------------------------

as at 8th July 1994 (with thanks to Bill Zink of TeamB):

- WS_TABSTOP style removed from the (private) tbProperty control
- now overrides DoExecute to create the TPropertyDialog modelessly, while
  still appearing modal to the caller (doesn't use BeginModal at present..)
- CmOk corrected to not GP fault if you haven't added any tabs to a
  TPropertyDialog (if you don't add tabs, TPropertyDialog should look
  and work EXACTLY like TDialog, making it useful as a common base class)
- double height tabs can now be variable width (uses an iteration mechanism
  increasing the width until two lines of text will fit on the tab)

as at 25th August 1994:

- tabs can be "stacked" to occupy more than one row (as they do in Word's
  Tools|Options dialog)
- tabs can now be scrolled horizontally if there is insufficient space for
  them all to be displayed.  Left and right scroll buttons appear which can
  be used to move between pages beyond the visible area
- New "CloseAction" action function added to all the Ok and Close action of
  TPropertyDialog to be overriden (for use by client dialogs in TMDIChild
  dialogs
- ForEachPage and FirstPageThat functions added to TPropertyDialog
- TPropertyDialog::Add is now virtual to allow extra functionality to be
  added (eg. to allow common attributes to be set up on every page)
- fixed a bug which caused tab widths to be calculated incorrectly when
  using double height tabs and long single words
- tab widths now calculated in advance to improve performance on slower
  machines
- AfterSelected functions added to allow actions to be carried out after a
  particular page becomes active (eg. setting up of a description field)
- tabs can now be selected using "hot-key" combinations much like buttons
- enum now used to enable tab styles for:
	- fixed/variable width
	- single/double height tabs
	- stacked/scrolling tabs
	- justified and collapsed tabs
	- coloring of active tab
	- reuse of one dialog across several pages
	- create-on-demand to only create the physical pages when selected
- TFontPropertyPage class also supplied.  This may not be fully debugged
  yet -- please report any bugs or problems you have using this class

as at 17th September 1994:

- there is a no longer a need to put the OK and Cancel buttons on each page.
  If you wish to put them on the property dialog you may do so, but you
  should put the OK button at the bottom of the dialog because the default
  TPropertyDialog::AdjustMargin will automatically adjust to take account of
  it at that position.  Both tabbing and hot-keying can be used to jump to a
  control either on the page or in the property dialog itself
- tabs now use same color scheme as the main dialog (based on Control Panel)
- support for transfer arrays now 'built in' via the USETRANSFERARRAYS macro
- WideMargins tab style added.  This adds a little extra space to the text on
  the tab making it a little wider.  The 'SetWideMarginWidth' function allows
  the amount of extra space to be changed

as at 22nd November 1994:

- keyboard hook now allows Ctrl+PgUp and Ctrl+PgDn to be used to change
  pages from any control (just as Word and Excel do)
- new tab styles:
    ButtonsOnRight  causes default AdjustMargin to assume OK button is on
                    the right of the dialog, not the left
    UseSmallFont    causes all page controls to be set to use a non-bold
                    font (except static controls which don't have the
                    SS_NOPREFIX style).  This gives a more Windows95-style
					appearance to your dialogs
    NoMDIClose      stops OK and Cancel buttons (and therefore Enter and
					Cancel) from closing the property dialog.  Intended for
                    use where a property is used inside an MDI child
    WizardFrame     used by wizards.  Causes page to occupy most of the
                    dialog, with the tabs out of visible range.  This means
                    you can develop wizards whilst keeping all of propdlg's
                    functionality
- new Wizard dialogs - TWizardDialog and TWizardPage provide a basic
  framework for developing wizard controls
- new TBitmapControl - allows bitmaps to be placed on the dialog, as most
  wizards do.  Supports plain, recessed and raised border styles.  This
  control is supplied in the file bmpctl.cpp and is completely independent
  of both propdlg and the wizard code

as at 17th September 1995:

- ROUNDEDMARGIN macro defined near the top of propdlg.cpp provides control
  over the degree of rounding on the corners of the tabs.  Set this value to
  2 for visual compatibility with previous versions.  A value of 4 provides
  good rounded corners
- changes made to allow the tab control to be moved anyway in the tab
  sequence of the property dialog.  The TPropertyDialog::MoveTabsAfterControl
  function is provided to support this functionality.  Also the test app
  contains a dialog for testing this functionality and demonstrating the use
  of this function
- tab style no longer defined as an enum, allowing me to use a full 32 bits
  for styles (the enum limit of 15 styles was finally reached..)
- Tab::UseTabBitmaps style added (see description in main text)
- Tab::CenterDialog added to automatically center the property dialog on
  the screen.  A CenterWindow function is also provided in propdlg.h for
  centring dialogs that don't use propdlg.
- BeforeLeaving function added to complement the AfterSelected function
  and allow the property page/dialog a chance to check the page contents
  before another page is selected (particularly useful for Wizards)
- propdlg.cpp split into three parts.  The two new parts: proptab.cpp and
  propmisc.cpp are both #included into propdlg.cpp and shouldn't be added
  to the project file by themselves.  New code segments are also generated
  for the two extra parts to prevent the 64K segment limit being reached
- Tab::ColorActive now colors the whole tab, not just the border.  By setting
  the COLORWHOLETAB to FALSE at the top of propdlg.cpp the old behaviour can
  by restored
- SetTabText function added to allow tab text to be changed on the fly
- EnableParents now removed.  This code was necessary in older versions of
  Borland C++ because of a bug in the BeginModal function which has now been
  corrected.  This should fix problems some people have encountered with
  the modal dialog behaviour
- OnRightClick function added to TPropertyPage.  This virtual function is
  called when the right mouse button is clicked on the tab for that page.
- new CreateOnDemandAndKeepIt style added.  This is like CreateOnDemand, but
  once the page has been created for the first time, it is retained.  This
  obviously makes the property dialog as resource hungry as if CreateOnDemand
  wasn't used.  The benefit is that it makes the dialog appear a lot sooner
  initially.
- EvCtlColor functions added as a workaround for CTL3D being disabled in Win95.
  This means that at least the dialog background will appear in gray
- Wizard dialog now has a "correct" 3D separator above the buttons to bring it
  in line with the Windows 95 UI style guide.


Getting in touch with the author
--------------------------------

If you have any experiences good or bad using this code, I would
appreciate hearing from you.  In the past it is the great feedback that
have led to a lot of the new features being added, which benefits
everybody.

I am distributing this as CharityWare.  If you find this code of great
use and you feel you'd like to make a financial contribution you can
send your contributions to:

	Steve Saxon
	47 Southwold
	Bracknell
	Berkshire RG12 8XY
	England

I'm afraid you'll have to send cash (preferably not coins!) as it isn't
worth my paying 15 pounds to have cheques cleared (unless you are
sending hundreds of dollars!).  Please can you use registered post to reduce
the chance of your contribution getting 'lost' in the mail

I have intentionally kept this code as simple as possible because I know
that Windows95 will be out soon(ish).  My intention is to port these
classes to Windows95 hopefully without too many interface changes, (I'm
not a masochist!).  Again, let me know if this would be of interest to
you.

Sadly the addition of such features as scrolling and multiple rows
mean that the classes are now, by necessity, getting complex -- but hey,
thats progress!

Steve Saxon
London, England.
CIS: 100321,2355

nb. This source can also be accessed by anonymous ftp at ftp.sai.com
	in the file /pub/propdl.zip
