[ Next ] [ Previous ] | Chapter 23 |
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.
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:
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 |
![]() |
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. |
![]() |
Text view |
Displays a simple text list to represent container items. |
![]() |
Tree view |
Displays a hierarchical view of the container items. Three
types of Tree views are available: Tree text, Tree icon, and Tree name.
|
![]() |
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 |
![]() |
Table 23.1 describes the container styles and their meanings.
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 |
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 _ITEMINFOProgrammers always should be sure to specify the style bit that corresponds to the type of object record they decide to use.
{ MINIRECORDCORE mrcRecord;
CHAR achItem[256];
ULONG lUnitsSold;
float fRevenue;
} ITEMNFO,*PITEMINFO;
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
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.
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 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
|
![]() Icon View |
![]() Name/flowed View |
![]() Text/flowed view
|
psiYears = (PSALESINFO)PVOIDFROMMR(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.
WinSendMsg(pcdData->hwndCnr,
CM_ALLOCRECORD,
MPFROMLONG(ulExtra),
MPFROMSHORT(MAX_YEARS) ) );
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;
The tree view is next in the list in order of complexity. It offers
three different variations, which are described in Table
23.3
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.
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
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 cb, pNextFieldInfo 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. |
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
|
![]() Detail 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;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.
riRecord.cRecordsInsert = MAX_MONTHS;
WinSendMsg(pcdData->hwndCnr,
CM_INSERTRECORD,
MPFROMP(psiMonths),
MPFROMP(&riRecord));
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);Finally, the splitbar is initialized by sending the CM_SETCNRINFO message.
fiiInfo.pFieldInfoOrder = (PFIELDINFO)CMA_FIRST;
fiiInfo.cFieldInfoInsert = MAX_COLUMNS;
fiiInfo.fInvalidateFieldInfo = TRUE;
WinSendMsg(pcdData->hwndCnr,
CM_INSERTDETAILFIELDINFO,
MPFROMP(pfiInfo),
MPFROMP(&fiiInfo));
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));
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.
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) |
CONTAIN3.C
|
![]() |
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. |
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);
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. |
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);
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.MAK
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 :As was said, this really is simple. The callback function is just as easy to understand.
{ USHORT usId;
usId = MI_SORTBYUNITS;
WinSendMsg(pcdData->hwndCnr,
CM_SORTRECORD,
MPFROMP(sortRecords),
MPFROMP(&usId));
}
break;
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)It checks to see by what criteria the user wanted to filter the records and returns the appropriate value.
{ 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 */
}
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.
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. |
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 |