[ Next ] [ Previous ] Chapter 23
[ Contents ] [ Chapter 22: Notebooks ] [ Chapter 24: Spin Buttons ]

Containers

It was a happy occasion when Tupperware containers were containers were invented. Not Only could leftover meatloaf be stored in them, but so could  crayons, plants, or almost anything  else you desired. The container didn't know about the specifics of the item you stored, nor did it care; it simply stored the items.

OS/2 also has a container that has a similar purpose: to store items. It doesn't care if the items are employee names or sales statistics or the batting averages of the 1929 Yankees . The items to be stored are defined by application. Additionally, the container control supports multiple views of the objects, in concordance with the CUA 1991 specification. Multiple-object selection methods are supported as well as direct editing of text and drag and drop. In short, the container can do anything  save wash your windows or butter your bread.

This extreme amount of functionality and flexibility is not without its price, unfortunately. The container is a very complex control that demands a fair of initialization, and almost every message sent to and from  the container a structure or two. This chapter discusses container basics and develops a couple of applications to demonstrate the concepts discussed; the more advanced topics will be left to the reader.

Container Views

When a user opens a container, the contents of that container are displayed in a window. A container window can present various views of its contents, and each view can provide different information about its container items. The following table describes the views the container control provides:
Table 23.0 Container's View 
  View Type 
 Contents Displayed  Sample
Icon view
Displays either icons or bit maps, with text beneath the icons or bit maps, to represent
container items. These are called icon/text
or bit-map/text pairs. Each icon/text or bit-map/text pair represents one container
item. This is the default view
Container icon view
Name view
Displays either icons or bit maps, with text to the right of the icons or bit maps, to represent container items. These are called icon/text or bit-map/text pairs. Each icon/text or bit-map/text pair represents one container item.
Container Name/flowed view
Text view
Displays a simple text list to represent container items.
Container Text/flowed view
Tree view
Displays a hierarchical view of the container items. Three types of Tree views are available: Tree text, Tree icon, and Tree name.
Tree view
Details view
Displays detailed information about each container item. The same type of data is displayed for each container item, arranged in columns. The data in each column can  consist of an icon or bit map, text, numbers, dates, or time
Container's detail view

Container Styles

Table 23.1 describes the container styles and their meanings.
Table 23.1 Container Styles
Style
 Description
CCS_EXTENDSEL Specifies that the extended selection model is to be used according to the CUA'91 guidelines.
CCS_MULTIPLESEL
Specifies that one or more items can be selected at any time.
CCS_SINGLESEL
Specifies that only a single item may be selected at any time. This is the default.
CCS_AUTOPOSITION
Specifies that the container should position items automatically when one of a specific set of events occurs. This is valid for icon view only.
CCS_VERIFYPOINTERS
Specifies that  the container should verify that all pointers used belong to the object list. It does not validate the accessibility pf the pointers. This should be used only during debugging, since it affects the performance of the container.
CCS_READONLY
Specifies that  no text should be editable
CCS_MINIRECORDCORE
Specifies that  the object records are of the type MINIRECORDCORE (instead of RECORDCORE)
CCS_MINIICONS
Style to have container support mini icons with the minirecord
CCS_NOCONTROLPTR
don't send WM_CONTROLPOINTER on WM_MOUSEMOVE

LPs or 45s ?

The basic data unit of a container is a structure that describes the state of an individual item withing the container. Depending on whether the CCS_MINIRECORDCORE style bit is specified, this is either a RECORDCORE or MINIRECORDCORE structure. There are advantages to using either;  the former requires more setup but is more flexible, while the later requires less setup but is more limiting. (Here we use RECORDCORE structure in our discussions but we use the MINIRECORDCORE in the samples.) Additional bytes at the end of the record can be specified when the record is allocated. Thus, typically a structure would be defined by the programmer, whose first field is the RECORDCORE structure; the structure would be typecast to the appropriate structure type for messages sent to or fro the container.
typedef struct   _ITEMINFO
{  MINIRECORDCORE   mrcRecord;
   CHAR           achItem[256];
ULONG   lUnitsSold;
   float            fRevenue;
} ITEMNFO,*PITEMINFO;
Programmers always should be sure to specify the style bit that corresponds to the type of object record they decide to use.

    Records are allocated using the CM_ALLOCRECORD message with the extra bytes needed beyond the RECORDCORE structure specified in the first parameter and the number of records to allocate specified in the second parameter. Obviously, for performance reasons, allocating one record at a time should be avoided. Instead, if possible, the number of records needed should be determined and allocated in one call. If more than one record is allocated, the head of a linked list of records is returned, with the link specified in the preccNextRecord field. Note that allocating memory for the records is not equivalent to inserting the records into container. This is done using the CM_INSERTRECORD message and, as before, should be done with as many records as possible to increase performance.
    The CM_INSERTRECORD message requires the first parameter to contain the head of the linked list of the (one or more) records to insert. The second parameter points to a RECORDINSERT structure
   typedef struct _RECORDINSERT
   {
      ULONG       cb;
     PRECORDCORE pRecordOrder;
      PRECORDCORE pRecordParent;
      ULONG       fInvalidateRecord;
      ULONG       zOrder;
      ULONG       cRecordsInsert;
   } RECORDINSERT;
   typedef RECORDINSERT *PRECORDINSERT;
cb is the size of the structure in bytes. pRecordOrder specifies the record after which the record(s) are to be inserted. CMA_FIRST or CMA_END also can be specified to indicate that the record(s) should go at the front or end of the record list. pRecordParent specifies the parent record and can be NULL to indicate a top-level record. This field is valid only for tree view.  fInvalidateRecord is TRUE if the records are to be invalidated (and thus redrawn) after being inserted. zOrder specifies the Z-order of the record and can be either CMA_TOP or CNA_BOTTOM to specify the top and bottom of the Z-order.  cRecordsInsert specifies the number of records that are being inserted

Half Full or Half Empty ?

We stated before that the container supports multiple views of its objects. This is a perfect time to elaborate because it introduces us to the CNRINFO  structure, which is used to control a variety of container characteristics.
   /**********************************************************************/
   /* CNRINFO data structure, describes the container control.           */
   /**********************************************************************/
   typedef struct _CNRINFO     /* ccinfo */
   {  ULONG       cb;                  /* size of CNRINFO struct        */
      PVOID       pSortRecord;         /* ptr to sort function,         */
/*  RECORDCORE                   */
      PFIELDINFO  pFieldInfoLast;      /* pointer to last column in left pane of a split window. */
      PFIELDINFO  pFieldInfoObject;    /* Pointer to a column to represent an object.  This is */
      /* the column which will receive IN-USE emphasis.        */
  PSZ         pszCnrTitle;         /* text for container title. One string separated by line */
     /* separators for multi-lines  */
      ULONG       flWindowAttr;        /* container attrs - CV_*, CA_*  */
      POINTL      ptlOrigin;           /* lower-left origin in virtual coordinates. CV_ICON view */
      ULONG       cDelta;              /* Application defined threshold or number of records from */
     /* either end of the list.      */
      ULONG       cRecords;            /* number of records in container*/
      SIZEL       slBitmapOrIcon;      /* size of bitmap in pels        */
      SIZEL       slTreeBitmapOrIcon;  /* size of tree bitmaps in pels  */
      HBITMAP     hbmExpanded;         /* bitmap  for tree node         */
      HBITMAP     hbmCollapsed;        /* bitmap  for tree node         */
      HPOINTER    hptrExpanded;        /* icon    for tree node         */
      HPOINTER    hptrCollapsed;       /* icon    for tree node         */
      LONG        cyLineSpacing;       /* space between two rows        */
      LONG        cxTreeIndent;        /* indent for children           */
      LONG        cxTreeLine;          /* thickness of the Tree Line    */
      ULONG       cFields;             /* number of fields  in container*/
      LONG        xVertSplitbar;       /* position relative to the container (CV_DETAIL); */
/* if  0xFFFF then unsplit         */
   } CNRINFO;
   typedef CNRINFO *PCNRINFO;

CNRINFO  structure contains a large number of fields. Note that not every one of them needs to be initialized. Instead, only the needed fields are initialized; fields which were initialized are cited as a combination of flags specified in the second parameter of the CM_SETCNRINFO message. To change the vie to icon view, for example:

                  CNRINFO          ciInfo;
                  ciInfo.cb = sizeof(CNRINFO);
                  ciInfo.flWindowAttr = CV_ICON;
                  WinSendMsg(pcdData->hwndCnr,
                             CM_SETCNRINFO,
                             MPFROMP(&ciInfo),
                             MPFROMLONG(CMA_FLWINDOWATTR));

Since we're talking about views of an object, let's look at the various combinations of view flags to specify the different view types. Table 23.2 provides a list of view flags.

Table 23.2 Container's View flags
Constant
 Description
CV_TEXT Specifies that the text alone should be displayed. This can be combined with CV_FLOW flag.
CV_NAME Specifies that the icon should be displayed with the text to the right. This can be combined with  CV_FLOW flag.
CV_ICON Specifies that the icon or bitmap should be displayed with the text below it.
CV_DETAIL The details view shows data in a columnar format. This is discussed in more detail in Details View.
CV_FLOW Specifies that, ones a column is filled, the list should continue in an adjacent column.
CV_MINI use mini icon
CV_TREE
Used for records that have children. Three view types can be used with the tree view (See Tree View) The three view shows a hierarchical view of the data
CV_GRID gridded icon view
CV_EXACTLENGTH Exact match for SearchString

The following sections look at each view type in detail.

Icon, Name, and Text Views

The icon view is perhaps the most widely known because it is the default view for the folders on the desktop. It consists of an icon or bitmap representing the object, with text directly beneath it. The text can be "directly edited" - the user can, using the mouse and/or keyboard directly edit the text. (The application controls whether the container retains the changes.)
    If the container was created with the CCS_AOUTOPOSITION style, the objects are arranged automatically whenever any of the following events occur:
This arranging occurs as if the container were sent a CM_ARRANGE message.
    The name view consist of the icon or bitmap representing the object with the text immediately to the right. As with the icon view, the text can be edited directly. If CV_FLOW is not specified, objects are arranged vertically in a single column. If CV_FLOW is specified, a new column is created to the right if the objects extend beyond the bottom of the container.
    The text view consists of the text only, and the objects are arranged in the same manner as the name view, with the same semantics regarding the specification of the CV_FLOW flag.
    The following application illustrates these three views of a container's contents.

CONTAIN1.C
CONTAIN1
.RC
CONTAIN1
.H
CONTAIN1.M
AK
CONTAIN1
.DEF
Container icon view
Icon View
Container Name/flowed view
Name/flowed View
Container Text/flowed view
Text/flowed view
The code should be easy to digest. First, the records are allocated using the CM_ALLOCRECORD structure
            psiYears = (PSALESINFO)PVOIDFROMMR(
WinSendMsg(pcdData->hwndCnr,
                           CM_ALLOCRECORD,
                            MPFROMLONG(ulExtra),
                                      MPFROMSHORT(MAX_YEARS) ) );
Then the allocated records are initialized by calling the initSales function; after each record is initialized it is inserted using the riRecord structure that was initialized earlier.

            psiCYear = psiYears;
            for (usIndex = 0; usIndex < MAX_YEARS; usIndex++)
            {  initSalesInfo(pcdData, psiCYear, usIndex);

               riRecord.pRecordParent = NULL;
               riRecord.cRecordsInsert = 1;

               WinSendMsg(pcdData->hwndCnr,
                          CM_INSERTRECORD,
                          MPFROMP(psiCYear),
                          MPFROMP(&riRecord));
               psiCYear = (PSALESINFO) psiCYear->mrcStd.preccNextRecord;
            } /* endfor */
It is true that the the source code should "practice what we preach" in terms of inserting more than one record at a time to increase performance, but simplicity was deemed more important to allow better understanding of the code.
Finally, the container is switched into icon view by sending ourselves a WM_COMMAND message to simulate the selection of the corresponding menu item.
            WinSendMsg(hwndClient,
                       WM_COMMAND,
                       MPFROMSHORT(MI_ICON),
                       0);
The WM_COMMAND code  to switch between container view is rather simple as well. For space reasons, here we present only the code or switching to icon view.
            case  MI_ICON :
               { CNRINFO          ciInfo;
                  ciInfo.cb = sizeof(CNRINFO);
                  ciInfo.flWindowAttr = CV_ICON;

                  WinSendMsg(pcdData->hwndCnr,
                             CM_SETCNRINFO,
                             MPFROMP(&ciInfo),
                             MPFROMLONG(CMA_FLWINDOWATTR));

                  WinSendMsg(pcdData->hwndCnr,
                             CM_ARRANGE,
                            NULL,
                             NULL);
               }
               break;

Tree View

The tree view is next in the list in order of complexity. It offers three different variations,  which are described in  Table 23.3

Table 23.3 Tree View Variations
View  Description
Tree icon view
Objects in the tree are represented by icons or bitmaps with the text to the right. If an item is expandable, a separate bitmap as drawn to the left of the object. This view is specified by adding CV_ICON and CV_TREE flags to the flWindowAttr field.
Tree name view
This is the same as the tree icon view except that an object's expandability is shown on the icon or bitmap of the object, and not as a separate bitmap; the TREEITEMDESC structure contains the bitmap or icon handles for both expanded and collapsed views. The caveat here is that the TREEITEMDESC structure is pointed to by the RECORDCORE structure but not by the MINIRECORDCORE structure. This view is specified by adding the CV_NAME and CV_TREE flags to the  flWindowAttr field.
Tree text view
Objects in the tree are represented by text only. The feedback on the expandability of an object is represented by a separate bitmap to the left of the text. This view is specified by adding the CV_TEXT and CV_TREE flags to the  flWindowAttr field.
In addition to specifying the view type, the amount of space (in pels) for indentation and the thickness of the tree lines may be specified when CA_TREELINE is specified. The indentation and thickness are specified in the cxTreeIndent and cxTreeLine fields of the CNRINFO structure, respectively. If a value less than 0 is specified for either field, the default for that field is used.

Details View

The details view is by far the most difficult of the five view types to program, but its ability to show a lot of information at once overshadows this complexity. This view supports the following data types: bitmap/icon, string, unsigned long integer, date, and time. For latter three, national language support (NLS) is enabled, meaning that the proper thousands separator character is used, the time information is ordered correctly, and so on. There is no support for  decimal types, so any decimals will have to be converted to there strings equivalents to display numbers of this type.
    The major item of interest when using the details view is the FIELDINFO structure, which describes a single column that is displayed in this view. As with the object records, memory for the FIELDINFO structures is allocated via message: CM_ALLOCDETAILFIELDINFO. The first parameter specifies the number of FIELDINFO structures to allocate, and a pointer to the first structure is returned. As with CM_ALLOCRECORD, this is the head of a linked list of structures if more than one is allocated and the link to the next record is specified in the pNextFieldInfo field.

   typedef struct _FIELDINFO      /* fldinfo */
   { ULONG      cb;                  /* size of FIELDINFO struct       */
      ULONG      flData;              /* attributes of field's data     */
      ULONG      flTitle;             /* attributes of field's title    */
      PVOID      pTitleData;          /* title data (default is string). If CFT_BITMAP, must be HBITMAP */
      ULONG      offStruct;           /* offset from RECORDCORE to data */
      PVOID      pUserData;           /* pointer to user data           */
      struct _FIELDINFO *pNextFieldInfo; /* pointer to next linked FIELDINFO structure  */
      ULONG      cxWidth;             /* width of field in pels         */
   } FIELDINFO;

cb specifies the size of the structure in bytes.  flData specifies  the type of the data in this field and any associated attributes of the column via one ore more CFA_ constants listed in table 23.4
Table 24.2 CFA_ Constants
Constant  Description
CFA_LEFT
Specifies that the data is to be horizontally aligned left.
CFA_RIGHT
Specifies that the data is to be horizontally aligned right.
CFA_CENTER
Specifies that the data is to be horizontally centered.
CFA_TOP
Specifies that the data is to be vertically aligned top.
CFA_VCENTER
Specifies that the data is to be vertically centered.
CFA_BOTTOM
Specifies that the data is to be vertically aligned bottom.
CFA_INVISIBLE
Specifies that the column is not to be shown
CFA_BITMAPORICON
Specifies that offStruct points to a bitmap or icon handle to be displayed in the column, depending on the current setting of flWindowAttr in the CNRINFO structure last used to set the container attributes.
CFA_SEPARATOR
Specifies that there should be a vertical separator to the right of the column.
CFA_HORZSEPARATOR
(flTitle only)Specifies that the column title should have a horizontal separator dividing it from the data.
CFA_STRING
Specifies that offStruct points to a pointer to a string to be displayed in the column.
CFA_OWNER
Specifies that the column is to be owner-drawn.
CFA_DATE
Specifies that offStruct points to a CDATE structure.
CFA_TIME
Specifies that offStruct points to a CTIME structure.
CFA_FIREADONLY
Specifies that the column data should be read-only
CFA_FITITLEREADONLY
(flTitle only)Specifies that the column should be read-only
 CFA_ULONG
Specifies that offStruct points to a ULONG
CFA_RANGE
 ???
CFA_NEWCOMP
(CLASSFIELDINFO  in wpobject.h ) Tells the system to use strings specified in pNewComp
CFA_OBJECT
(CLASSFIELDINFO  in wpobject.h )
 Tells the system that the applications wants to use its own comparison function in which the first parameter is a pointer to an object. For example:
LONG MyComp(WPObject *obj, PSZ str2)
CFA_LIST
 ???
CFA_CLASS
 ???
CFA_IGNORE
 ???

flTitle
specifies attributes about the heading for this column and is also a combination of CFA_ constants.  pTitleData points to the column title data; this is a bitmap or icon if CFA_BITMAPORICON is specified in flTitle; otherwise it is a pointer to a string. offStruct specifies the offset from the beginning of the RECORDCORE structure to where the data resides. pUserData points to  any application-specific data for this column. pNextFieldInfo points to the next FIELDINFO structure in the linked list. cxWidth specifies the width of the column. If 0, the column will be autosized to be the width of the widest element.

The fields cbpNextFieldInfo and cxWidth are initialized by the container in the CN_ALLOCDETAILINFO processing. The application is responsible for initializing the remaining fields.

Gotcha!
If flData specifies CFA_STRING, then offStruct specifies the offset of the pointer to the text and not the text itself
Gotcha!
The column heading data is not copied into the container's workspace. Thus they must be global, static, or dynamically allocated data.
Gotcha!
A common mistake when specifying CFA_DATE or CFA_TIME for a column is to improperly convert an FDATE structure to a CDATE structure and FTIME structure to a CTIME  structure.

Splitbars

Details view also provides the option of having a single splitbar between columns. A splitbar is a vertical bar that can be moved with the mouse. This is useful if the data displayed in a column extends beyond the space available. If a splitbar is used, horizontal scrollbars are displayed on the bottom of the container for each subselection bounded by a container edge or a splitbar.
    As might be expected, a splitbar is added to the details view using the CM_SETCNRINFO message. The pFieldInfoLast and xVertSplitbar fields are initialized in CNRINFO structure. The former points to the FIELDINFO structure to the immediate left of the splitbar; and the latter specifies where the splitbar is to be positioned initially. After initializing these fields, the CM_SETCNRINFO message is sent, specifying CMA_PFIELDINFOLAST | CMA_XVERTSPLITBAR as the second parameter.

The following sample application adds tree and details view to the last sample application. Additionally, it demonstrates the use of a splitbar in the details view.

CONTAIN2.C
CONTAIN2
.RC
CONTAIN2
.H
CONTAIN2.M
AK
CONTAIN2
.DEF

Container's detail view
Detail View
Container tree view Tree View

As before, we allocate a number of records using the CM_ALLOCRECORD structure; withing the loop to initialize each record (which represents a year of sales figures), we allocate 12 records to represent each month initialize these records, and insert them into the container, specifying the year record previously inserted as the parent. This establishes a hierarchical structure that we may observe by placing the container in tree view.

               psiMonths = (PSALESINFO)PVOIDFROMMR(
WinSendMsg(pcdData->hwndCnr,
                   CM_ALLOCRECORD,
                   MPFROMLONG(ulExtra),
                   MPFROMSHORT(MAX_MONTHS)));
               psiCMonth = psiMonths;
               for (usIndex2 = 0; usIndex2 < MAX_MONTHS; usIndex2++)
               {  initSalesInfo(pcdData,
                                psiCYear,
                                psiCMonth,
                                usIndex2);
                  psiCMonth = (PSALESINFO)
                     psiCMonth->mrcStd.preccNextRecord;
               } /* endfor  */
               riRecord.pRecordParent = (PRECORDCORE)psiCYear;
               riRecord.cRecordsInsert = MAX_MONTHS;

               WinSendMsg(pcdData->hwndCnr,
                          CM_INSERTRECORD,
                          MPFROMP(psiMonths),
                          MPFROMP(&riRecord));
Finally, we call initColumns to set up the datail view. It allocates a fixed number of FIELDINFO structures by sending a CM_ALLOCDETAILFIELDINFO message to the container.
   pfiInfo = (PFIELDINFO)PVOIDFROMMR(WinSendMsg(pcdData->hwndCnr,
                                                CM_ALLOCDETAILFIELDINFO,
                                                MPFROMLONG(MAX_COLUMNS),
                                                0));
Each FIELDINFO structure is then initialized, and then all of the FIELDINFO structures are inserted.
   pfiCurrent->flData = CFA_BITMAPORICON|CFA_HORZSEPARATOR|CFA_CENTER|CFA_SEPARATOR;
   pfiCurrent->flTitle = CFA_STRING|CFA_CENTER;
   pfiCurrent->pTitleData = "Icon";
   pfiCurrent->offStruct = FIELDOFFSET(SALESINFO,
                                       mrcStd.hptrIcon);
......
   fiiInfo.cb = sizeof(fiiInfo);
   fiiInfo.pFieldInfoOrder = (PFIELDINFO)CMA_FIRST;
   fiiInfo.cFieldInfoInsert = MAX_COLUMNS;
   fiiInfo.fInvalidateFieldInfo = TRUE;

   WinSendMsg(pcdData->hwndCnr,
              CM_INSERTDETAILFIELDINFO,
              MPFROMP(pfiInfo),
              MPFROMP(&fiiInfo));
Finally, the splitbar is initialized by sending the CM_SETCNRINFO message.
   memset(&ciInfo, 0, sizeof(ciInfo));
   ciInfo.cb = sizeof(CNRINFO);
   ciInfo.pFieldInfoLast = pfiLefty;
   ciInfo.xVertSplitbar = CX_SPLITBAR;

   WinSendMsg(pcdData->hwndCnr,
              CM_SETCNRINFO,
              MPFROMP(&ciInfo),
              MPFROMLONG(CMA_PFIELDINFOLAST|CMA_XVERTSPLITBAR));

Of Emphasis and Pop-ups

Object emphasis is a visual cue to the user that something about the object is different from the norm. Cursored, selected, in-use, source, target and picked emphasis are six defined by the container. Of these six types, defined in Table 23.5 only first two are set automatically by the container. The latter two must be explicitly set by the application via the CM_SETRECORDEMPHASIS message.
Table 23.5 Emphasis Types
Types






 Emphasis Constant  Description
Cursored
CRA_CURSORED
Set whenever the input focus belongs to the object. This is shown as a dotted-line rectangle around the object.
Selected
CRA_SELECTED
Set whenever the object was selected using the mouse button on the spacebar. The selection style of the container defines how records previously selected behave when a new record is selected. This is shown as an inverted background around the object.
In-use
CRA_INUSE
Set whenever the object is defined to be in use by the application. This is shown as a crosshatch pattern in the background of the object.
Source
CRA_SOURCE
Set whenever the the object is a source of some action This record also could be in the selected state, but doing so is not required. This is shown as a dashed-line rectangle with rounded corners around the object.
Target
CRA_TARGET
Target emphasis is used during direct manipulation. When a user drags one container item over another, the item beneath the dragged item displays target emphasis. Two forms of target emphasis (visible feedback) are available: a black line and a black border. These forms of emphasis indicate the target, where the container item is dropped if the user releases the drag button.
Picked
CRA_PICKED
record picked (Lazy Drag)

The following sample removes the action bar from the window and instead uses pop-up menus to provide the actions available to the user.
CONTAIN3.C
CONTAIN3
.RC
CONTAIN3
.H
CONTAIN3.M
AK
CONTAIN3
.DEF
Pop-up menu

The WM_CONTROL notification specifies the record under the mouse when the pop-up menu was request. If there was no record, NULL is specified instead.
                        psiSales = (PSALESINFO)PVOIDFROMMP(mpParm2);
                        if (psiSales != NULL)
                        {
                           if ((psiSales->mrcStd.flRecordAttr
                              &CRA_SELECTED) == 0)
                           {
                              WinSendMsg(pcdData->hwndCnr,
                                         CM_SETRECORDEMPHASIS,
                                         MPFROMP(psiSales),
                                         MPFROM2SHORT(TRUE, CRA_SOURCE));
                              psiSales->bEmphasized = TRUE;
                           } else {
                             emphasizeRecs(pcdData->hwndCnr, TRUE);
                          }  /* endif */
                        } else {
                           WinSendMsg(pcdData->hwndCnr,
                                      CM_SETRECORDEMPHASIS,
                                      0,
                                      MPFROM2SHORT(TRUE, CRA_SOURCE));
                           pcdData->bCnrSelected = TRUE;
                        }  /* endif */
The records are selected using CM_SETRECORDEMHASIS message; this message sets the appropriate bit in the flRecordAttr field and redraw the record. Conceivably this could be done explicitly, but why go through the extra work ? The method of determining which records are given source emphasis follows that of Workplace Shell, and can be summarized in the following manner:
Gotcha!
The documentation does not state how the container is given source emphasis this is done by specifying NULL for the record pointer in mpParm1. The container does not keep track of whether it has source emphasis or not and blindly draws this emphasis using the XOR method. Thus, if two CM_SETRECORDEMPHASIS messages are sent, both specifying that source emphasis is to be removed from the container, no visible difference will be seen.
After the records have been given source emphasis in the appropriate manner, the pointer position is determined and the menu is popped up via the WinPopupMenu message.
                        WinQueryPointerPos(HWND_DESKTOP, &ptlMouse);
                        WinMapWindowPoints(HWND_DESKTOP,
                                           hwndClient,
                                           &ptlMouse,
                                           1);
                        WinPopupMenu(hwndClient,
                                     hwndClient,
                                     pcdData->hwndMenu,
                                     ptlMouse.x,
                                     ptlMouse.y,
                                     M_VIEWS,
                                     PU_HCONSTRAIN | PU_VCONSTRAIN |
                                     PU_KEYBOARD | PU_MOUSEBUTTON1 |
                                    PU_MOUSEBUTTON2| PU_NONE);

Direct Editing

As stated earlier, the user can edit directly with a mouse click. The application must be aware of this possibility and be able to process this event properly. When the user selects the proper combination of mouse clicks or keystrokes, the container sends the application a WM_CONTROL message with a CN_BEGINEDIT notification code. The data in the second parameter is a pointer to the CNREDITDATA structure.
   typedef struct _CNREDITDATA     /* cnredat */
   {  ULONG        cb;
      HWND         hwndCnr;
      PRECORDCORE  pRecord;
      PFIELDINFO   pFieldInfo;
      PSZ *ppszText;  /* address of PSZ        */
      ULONG        cbText;    /* size of the new text  */
      ULONG        id;
   } CNREDITDATA;
cb is the size of the structure in bytes. hwndCnr is the handle of the container window. pRecord is a pointer to the RECORDCORE structure of the object being edited. If the container titles are being edited, this field is NULL. pFieldInfo is a pointer to the FIELDINFO structure if the current view is detail view and the column titles are not being edited. Otherwise, this field is NULL. ppszText points to the pointer to the current text if the notification code is CN_BEGINEDIT or CN_REALLOCPSZ. For CN_ENDEDIT notification, this points to the pointer to the new text. cbText specifies the number of bytes in the text. id is the identifier of the window being edited and is a CID_ constant.
    The CN_BEGINEDIT notification allows the application to perform any preedit processing, such as setting a limit on the text length. After the user direct editing, he container sends a CN_REALLOCPSZ  notification to the container's  owner before copying the new text into the application's text string to allow any postedit processing to be done.
Gotcha!
The application must return TRUE from CN_REALLOCPSZ notifications, or else the container will discard the editing changes.

Of Sorting and Filtering

The final, great abilities we will look at are sorting and filtering records, which are done with a little assistance from the application. Sorting is concept that programmers should be familiar with; filtering, however, might not be so familiar. Its idea is analogous to a strainer that would be used when cooking. Item that meet the criteria demanded by the strainer (that they are smaller than a defined threshold) can continue on their merry way. Items that do not, mat not. "continuing" in the sense of the container is the visibility state of the record. If record meets the threshold, it remains visible; if it doesn't, it is hidden. It should be noted that filtered records are not deleted - they simply aren't shown. Defining the threshold such that all records will meet it will reshow all of the records.
    The sorting and filtering callback functions are defined in the following manner. For sorting (CM_SORTRECORD message), we have:
SHORT EXPENTRY pfnCompare(PRECORDCORE p1,
  PRECORDCORE p2,
PVOID pStorage);

For filtering(CM_FILTER message), we have
BOOL PFN pfnFilter(PRECORDCORE p,
   PVOID pStorage);
Of course, if the container was created with the CCS_MINIRECORDCORE style, the RECORDCORE pointers are instead MINIRECORDCORE pointers.
    The sorting function behaves like strcmp - if the first record is "less than" the second, a negative number should be returned; if the first is "equal to" the second, 0 should be returned; if the first record is "greater than" the second, a positive number should be returned. The container takes care of the rest.
    Filtering is just as easy - if the record meets the criteria and should remain visible, TRUE should be returned. Otherwise, return FALSE.

The following sample illustrates both sorting and filtering.

CONTAIN4.C
CONTAIN4
.RC
CONTAIN4
.H
CONTAIN4.M
AK
CONTAIN4
.DEF

By running this sample, it will be seen that two sort menu items are provided, sort by units sold and sort by revenue. The code actually to sort the records is quite simple.
            case  MI_SORTBYUNITS :
               { USHORT           usId;
                  usId = MI_SORTBYUNITS;
                  WinSendMsg(pcdData->hwndCnr,
                             CM_SORTRECORD,
                             MPFROMP(sortRecords),
                             MPFROMP(&usId));
               }
               break;
As was said, this really is simple. The callback function is just as easy to understand.

SHORT EXPENTRY sortRecords(PSALESINFO psiFirst,
PSALESINFO psiSecond,
                PUSHORT pusSortBy)
{
   switch (*pusSortBy)
   {
      case  MI_SORTBYUNITS :
         if (psiFirst->ulNumUnits < psiSecond->ulNumUnits)
         { return -1;
         } else
            if (psiFirst->ulNumUnits == psiSecond->ulNumUnits)
            { return 0;
            } else {
               return 1;
            }  /* endif */

      case  MI_SORTBYYEAR :
         return  strcmp(psiFirst->mrcStd.pszIcon,
                        psiSecond->mrcStd.pszIcon);
      default  :
         return 0;
   } /* endswitch */
}
It checks to see by what the user requested the items to be sorted and then checks the appropriate field in the SALESINFO structure. That is all there is to sorting; there isn't anything difficult about it.
Filtering is even easier; the same defines four filter choices: revenues greater than $300, greater than $400, greater than $500 and no filtering at all. Again, the code that actually filters the records is trivial.
            case  MI_FILTER300DOLLARS :
               {  USHORT           usId;
                  usId = MI_FILTER300DOLLARS;
                  WinSendMsg(pcdData->hwndCnr,
                             CM_FILTER,
                             MPFROMP(filterRecords),
                             MPFROMP(&usId));
               }
               break;
This is almost identical to code that initiates the sorting. The callback is simpler than the sorting callback.

BOOL EXPENTRY filterRecords(PSALESINFO psiInfo,PUSHORT pusFilterBy)
{  switch (*pusFilterBy)
   {
      case  MI_FILTER300DOLLARS :
         return (psiInfo->fSales > 300.0);
      case  MI_FILTER400DOLLARS :
         return (psiInfo->fSales > 400.0);
      case  MI_FILTER500DOLLARS :
         return (psiInfo->fSales > 500.0);
      case  MI_FILTERNONE :
         return  TRUE;
      default  :
         return  TRUE;
   } /* endswitch */
}
It checks to see by what criteria the user wanted to filter the records and returns the appropriate value.
How mush easier can it get ?

Where Does Direct Manipulation Fit In ?

From what we can see of the container's capabilities, it was obviously designed to be an advanced control; thus, we would expect it to support direct manipulation (drag and drop). However, as Chapter 20: Drag and Drop  makes clear, direct manipulation is a very complex mechanism that could not possibly be supported entirely by the container. Instead, the container sends its owner a WM_CONTROL message with one of seven notification codes specific to direct manipulation.
Table 23.6 Container Notification Codes
 Notification
 Explanation
CN_DRAGAFTER
Sent to container's owner whenever the container receives a DM_DRAGOVER message. The CN_DRAGAFTER notification code is sent only if the CA_ORDEREDTARGETEMPHASIS or CA_MIXEDTARGETEMPHASIS attribute of the CNRINFO data structure is set and the current view is the name, text, or details view.
CN_DRAGLEAVE
 Sent to container's owner when the container receives a DM_DRAGLEAVE message.
CN_DRAGOVER
Sent to container's owner  when the container receives a DM_DRAGOVER message. The CN_DRAGOVER notification code is sent only if the CA_ORDEREDTARGETEMPH attribute of the CNRINFO data structure is not set or the current view is the icon view or tree view.
CN_DROP
Sent to container's owner  when the container receives a DM_DROP message. 
CN_DROPHELP
Sent to container's owner when the container receives a DM_DROPHELP message.
CN_INITDRAG
Sent to container's owner when the drag button is pressed and the pointer is moved while the pointer is over the container control. 
CN_DROPNOTIFY
Sent to container's owner   when a pickup set is dropped over the container.

Chapter 20: Drag and Drop presents information about what is to be done when one of these notifications is received.

Summary

The container control, while at times cumbersome to initialize and interact with, is a very useful addition to the library of standard controls provided with Presentation Manager. It is very flexible, providing many different viewing methods, and support the CUA'91 user interface guidelines. With a little imagination and a great deal of programming, this control could greatly enhance the user interface of an application.


[ Next ] [ Previous ] Chapter 23
[ Contents ] [ Chapter 22: Notebooks ] [ Chapter 24: Spin Buttons ]