This document covers the following topics:
A list view control can be used to display data in icon or column-based form. It is a very powerful control. Nevertheless, if you wish to display your data in tabular form and support direct in-place editing of any column value, you should consider using the table control instead.
List view controls in Natural can display their data in one of four view modes: icon, small icon, list or report.
In icon view mode, the data is displayed in large icon form:
In small icon view mode, the item labels are displayed alongside the icons. As for the icon view, the items can optionally be displayed in arbitrary positions:
In list view mode, the items are displayed similarly to the small icon view, but cannot be freely positioned. Instead, they are displayed in columns:
In report view mode, each item occupies one row, and other data relating
to the items may be displayed alongside the item in separate columns. A column
header is also usually shown, as in the example below (although this can
optionally be hidden by setting the list view control's "No header
(x)" STYLE
flag):
In the report view, the columns can be resized by the user by dragging the column dividers. Alternatively, by double-clicking on the trailing column divider in the column header, the column's width is adjusted to fit the longest text within the column.
Note that the above example consists of eleven dialog elements: The
list view
control itself, four
list view items
and six list view
columns. Both the list view items and the list view columns have the
list view control as their PARENT
,
and are thus stored in the same SUCCESSOR
chain. Although they can be interspersed, it is a good idea from both an
organization and performance point-of-view to ensure that all list view columns
precede all list view items. For example, if you are dynamically inserting a
new column into a non-empty list view control as the last column, explicitly
set its SUCCESSOR
attribute to the handle of the first list view item, rather than not specifying
it at all or setting it to NULL-HANDLE
, which would cause the new
column to be placed at the end of the chain, after all list view items.
The first list view column created for a list view control has a special
significance, and is referred to (here) as the primary column. The
primary column always displays the list view item labels (i.e., their
STRING
attribute values). The other columns display what is known as sub-item
data. For example, the phrase "Currency sub-item" refers to the
data stored in the "Currency" column (see above
example). To refer to a particular value within the column, we would have to be
more precise. For example, the value "1277.18" above
could be referred to as the "Currency sub-item" for
the "Second item" item. Sub-items are not
dialog element types in Natural, and will be discussed in more detail
below.
If no list view columns have been created, no information will be
displayed in the list view control when it is in report view mode! However, it
should be noted that the only way to switch between the view modes is
programmatically by explicitly changing the list view control's
VIEW-MODE
attribute value. Therefore, if the application wishes to support multiple view
modes, it must provide a mechanism (e.g., a context menu) for switching between
them. So, in practice, the user should never see a list view control in report
view mode that has no columns, since the application would normally not allow
switching to this view mode in this case.
Images for the list view items may be defined by creating and associating an image list control with the list view control, then (for each item) selecting the required image from the image list via its index and/or image handle, as described in the section Working with Image List Controls.
Please note that you should set the image list control's "Large images (L)" and "Small images (S)" styles according to the view modes that are to be supported. The icon view mode requires the availability of large images, whereas the other view modes require the availability of small images.
In the icon and small icon view modes, the list view items may be
(re-)positioned by setting their RECTANGLE-X
and/or RECTANGLE-Y
attribute values. If no position is explicitly set on item creation, the items
are laid out on an imaginary grid, with a default grid spacing that can be
overridden by setting the list view control's
SPACING-X
and SPACING-Y
attribute values. The ARRANGE
action can be used at any time to either re-arrange the items to occupy
consecutive locations based on this logical grid, or to snap the items to their
nearest aligned logical grid position.
Note that in either of the two icon view modes, the list view item
positions are interpreted as being in view coordinates, rather than
being relative to the control's client area (as is the case in the other view
modes). Unlike the client coordinates, the view coordinates of the items do not
change when the icon view is scrolled. Conversion between view coordinates and
client coordinates requires the use of the list view control's
OFFSET-X
and OFFSET-Y
attributes, which return the origin of the client area in view coordinates.
Note that two list view control STYLE
flags can override an explicit postion specified by the program. Firstly, if
the control's "Auto-arrange (a)" style flag is
specified, the items are automatically re-arranged on the imaginary grid each
time an item is added or moved. In this case, an explicitly specified position
merely indirectly determines the item's position in the arranged icon list.
Secondly, if the control's "Snap to grid (r)" style
flag is set, any item position explicitly specified by the program will be
adjusted to the nearest aligned position on the imaginary grid. Note that this
style is superfluous if the "auto-arrange" style is
set.
Since users are often familiar with being able to modify list view item positions via drag and drop, it may be expected that the control automatically provides this capability. However, this is not the case. If the application wishes to support drag and drop, it must explicitly cater for it, as described in the next section.
In the list view mode, the items are always displayed in columns (as
already mentioned above) and are not re-positionable. However, the spacing
between adjacent columns may be set via the
SPACING
attribute.
Note that the list view control does not remember item positions when switching between view modes. For example, if you switch away from one of the icon view modes and then back to it again, the icons are always arranged. This behavior can be circumvented by providing explicit program code for saving and restoring item positions, as shown in the following example:
DEFINE SUBROUTINE SAVE-ITEM-POSITIONS #ITEM := #CONTROL.FIRST-CHILD REPEAT WHILE #ITEM <> NULL-HANDLE IF #ITEM.TYPE = LISTVIEWITEM #ITEM.CLIENT-KEY := 'RECTANGLE-X' #ITEM.CLIENT-VALUE := #ITEM.RECTANGLE-X #ITEM.CLIENT-KEY := 'RECTANGLE-Y' #ITEM.CLIENT-VALUE := #ITEM.RECTANGLE-Y END-IF #ITEM := #ITEM.SUCCESSOR END-REPEAT END-SUBROUTINE * DEFINE SUBROUTINE RESTORE-ITEM-POSITIONS #ITEM := #CONTROL.FIRST-CHILD REPEAT WHILE #ITEM <> NULL-HANDLE IF #ITEM.TYPE = LISTVIEWITEM #ITEM.CLIENT-KEY := 'RECTANGLE-X' IF #ITEM.CLIENT-VALUE <> ' ' #ITEM.RECTANGLE-X := VAL(#ITEM.CLIENT-VALUE) END-IF #ITEM.CLIENT-KEY := 'RECTANGLE-Y' IF #ITEM.CLIENT-VALUE <> ' ' #ITEM.RECTANGLE-Y := VAL(#ITEM.CLIENT-VALUE) END-IF END-IF #ITEM := #ITEM.SUCCESSOR END-REPEAT END-SUBROUTINE * DEFINE SUBROUTINE SWITCH-VIEW-MODE IF #VIEW-MODE <> #CONTROL.VIEW-MODE IF #CONTROL.VIEW-MODE = VM-ICON OR #CONTROL.VIEW-MODE = VM-SMALLICON PERFORM SAVE-ITEM-POSITIONS END-IF #CONTROL.VIEW-MODE := #VIEW-MODE IF #VIEW-MODE = VM-ICON OR #VIEW-MODE = VM-SMALLICON PERFORM RESTORE-ITEM-POSITIONS END-IF END-IF END-SUBROUTINE
where the following local data definitions are assumed:
01 #CONTROL HANDLE OF GUI 01 #ITEM HANDLE OF GUI 01 #VIEW-MODE (I4)
The actual view mode switch can then be made by setting
#VIEW-MODE
to the desired view mode (one of the VM-*
constants defined in the local data area NGULKEY1), setting
#CONTROL
to the handle of the list view control, and then calling
the SWITCH-VIEW-MODE
subroutine.
Items may be selected by the user either by clicking on them (optionally
whilst holding down the CTRL key to perform an extended selection),
or by defining a selection region by clicking within the list view control,
holding down the primary mouse button, and dragging. The latter technique is
known as marquee selection, and is only allowed if the control's
"Marquee select (m)"
STYLE
flag
is set (the default setting). Note that, if the control's
"Hot-track select (t)" style flag is set, it is not
necessary to click an item to select it. Instead, it is sufficient to simply
let the mouse cursor hover over it briefly.
Alternatively, items may be selected or deselected programmatically by
setting or clearing their SELECTED
attribute.
In either case, extended selection is only available if the control's
MULTI-SELECTION
attribute is set to TRUE
. Extended selection is the process of
selecting new items, or deselecting old ones, without the existing selection
being cleared first, and thus allows multiple (or no) items to be selected. In
the case of single selection list views, it is only possible for the user to
implicitly deselect an item by selecting a new one. Marquee selection is also
not available in this case.
The first (or only) selected item, if any, may be determined by querying
the list view control's SELECTED-SUCCESSOR
attribute, which returns NULL-HANDLE
if there is no selection. The
next selected item, if any, may be determined by querying a selected item's
SELECTED-SUCCESSOR
attribute. Iterative application of this technique allows complete enumeration
of all selected items, as shown in the section below on drag and drop.
For each item that is selected or deselected, a
CLICK
event is raised for the list view control (if not
suppressed), with the
handle of the corresponding item being set in the control's
ITEM
attribute. Because many items may be selected in quick succession (e.g., via
marquee selection), this event should not perform any lengthy processing. For
example, it may be better to simply set a logical variable to TRUE
in the CLICK
event handler, indicating that more involved processing is required,
and do the actual processing in the dialog's
IDLE
event handler in response to this flag being set. Don't forget to
clear the flag after doing the work!
If the list view control's "Check boxes (c)"
style flag is set, check boxes are displayed alongside each item. The item's
CHECKED
attribute may be used to retrieve or set an item's checked status
programmatically. The first checked item, if any, may be determined by querying
the list view control's CHECKED-SUCCESSOR
attribute, and querying this attribute for a checked item returns the handle of
the next checked item, if any, thus allowing complete enumeration of all
checked items, as shown in the following example, which simply counts the
number of checked items:
RESET #COUNT #ITEM := #LV-1.CHECKED-SUCCESSOR REPEAT WHILE #ITEM <> NULL-HANDLE ADD 1 TO #COUNT #ITEM := #ITEM.CHECKED-SUCCESSOR END-REPEAT
where the following local data definitions are assumed:
01 #LV-1 HANDLE OF LISTVIEW 01 #ITEM HANDLE OF LISTVIEWITEM 01 #COUNT (I4)
Whenever an item is checked or unchecked, a
CHECK
event is raised for the list view control, if not suppressed via the
SUPPRESS-CHECK-EVENT
attribute, with the handle of the corresponding item being set in the control's
ITEM
attribute.
When a user double-clicks on an item, an
ACTIVATE
event is raised (unless suppressed) for the list view
control. The application, if it decides to handle this event, normally performs
a default action on each selected control. The default action is user-defined
and can be different for each item. For example, activating an item
representing a text file might cause the file to be opened in an editor,
whereas activating an item representing an audio file might cause the file to
be played. Note that there may be other, non-default, actions applicable to one
or more of the selected items, but these are typically accessed via other
mechanisms. For example, they may be listed (typically along with the default
action) in a context menu displayed by the application.
If multiple selection is allowed (see above) and the CTRL key
is held down whilst double-clicking an item, the selection state of the item is
toggled before the ACTIVATE
event is raised.
If either of the control's "Underline hot
(u)" or "Underline cold (U)"
STYLE
flags are set, it is only necessary to
single-click on an item in order to activate it.
The ACTIVATE
event can also be triggered via the keyboard. This can be done in
either of two ways:
By pressing the key or key combination defined for the list view
control's ACCELERATOR
attribute. The control need not currently have the focus.
By pressing the ENTER key, if the list view control
currently has the focus. This method only works if the dialog neither contains
a default pushbutton, nor a pushbutton with the "OK Button
(O)" STYLE
flag
set.
In either case, no ACTIVATE
event is raised if no items are currently selected.
Each column in a list view (as displayed when the list view control is in report view mode) can contain (at most) one item of data per list view item, which is then (if present) displayed for the item in that column. As already mentioned above, this data is known as sub-item data.
In order to be able to support sorting correctly (see next section), the
sub-item data does not need to be alphanumeric, as it is per default, but can
be one of any of the pre-defined types supported by the column's
FORMAT
attribute. In addition, an edit mask can be applied to the column by setting
its EDIT-MASK
attribute. The values seen in the report view column by the user for a column
are the alphanumeric representations of the sub-item for that column, using the
associated edit mask (if any). The conversion between the sub-item data and the
displayed data is compatible with the MOVE
EDITED
statement if an edit mask is supplied. Otherwise, the
conversion between the internal and displayed data, and vice-versa, is
compatible with the conversion involved in copying the data to and from the
Natural stack (see the STACK TOP
DATA
and INPUT
statements). For example,
numeric values are displayed using the current decimal character (as defined by
the DC
parameter) if necessary, with a leading minus character if negative, date
values are displayed in the format defined by the
DTFORM
parameter setting, logical values are displayed as an
"X" if true and as a blank if false, and so on.
The first column defined for a list view control (the primary
column) has a special significance: It always displays the item's label.
Therefore, any changes to an item's sub-item data for the first coumn
automatically update the item's label, and vice-versa. Otherwise, and for all
other columns, the only means of updating the sub-item data is via the
SET-SUBITEM-DATA
action. When calling this action, the sub-item data must be supplied in a
format compatible with the internal data type, as specified by the
column's FORMAT
attribute value (alphanumeric by default).
For example, suppose we add a column to a list view control as shown below:
PROCESS GUI ACTION ADD WITH PARAMETERS HANDLE-VARIABLE = #LVCOL-DATE TYPE = LISTVIEWCOLUMN STRING = 'Date' PARENT = #LV-1 RECTANGLE-W = 83 STYLE = 'l' FORMAT = FT-DATE EDIT-MASK = 'YYYY/MM/DD' END-PARAMETERS GIVING *ERROR
The sub-item data for this column for a particular list view item,
#LVITEM-1
(say), can then be set to the current date as
follows:
PROCESS GUI ACTION SET-SUBITEM-DATA WITH #LVITEM-1 #LVCOL-DATE *DATX GIVING *ERROR
Note that we could also have used *TIMX
instead of *DATX
, because time values in Natural
are automatically convertible to date values. In either case, the value is then
displayed as the current date in YYYY/MM/DD format. For the primary column, the
display string is the item label, which means that the effects of the
modification will be visible even if the list view control is not currently in
report view mode.
Note also that the data is not supplied in display format. For example, the following will not work:
PROCESS GUI ACTION SET-SUBITEM-DATA WITH #LVITEM-1 #LVCOL-DATE '2004/11/03' GIVING *ERROR /* Does NOT work!
However, if the column happens to be the primary column, then it is alternatively possible to update the column data indirectly, by setting the item's label. For example:
#LVITEM-1.STRING := '2004/11/03'
Retrieval of the sub-item data may be achieved by calling the
GET-SUBITEM-DATA
action, which takes the same parameters as the
SET-SUBITEM-DATA
action. For example:
PROCESS GUI ACTION GET-SUBITEM-DATA WITH #LVITEM-1 #LVCOL-DATE #DATE GIVING *ERROR
where #DATE
is defined as follows:
01 #DATE (D)
One fact to bear in mind, however, is that there may be no sub-item data
to be retrieved. For example, the sub-item data may not have been created, or
may have been deleted (see below). Natural, however, does not support null
values. Therefore, by default, Natural resets the receiving fields when a null
value is returned (see the RESET
statement). However, if a
default value has been set for a list view column, this default value is
returned instead. Setting a default value for a column is done by calling
SET-SUBITEM-DATA
,
specifying NULL-HANDLE
in place of a list box item handle. For
example, for a numeric column, #LVCOL-NUM
, where only positive
values are allowed, we might choose to set the default value to -1, as shown
below:
#NUM := -1 PROCESS GUI ACTION SET-SUBITEM-DATA WITH NULL-HANDLE #LVCOL-NUM #NUM GIVING *ERROR
where #NUM
can be a field of any signed numeric format
(e.g. I2).
The use of default values allows a value to be chosen by the programmer that does not match any explicit value that can be used in the program. If necessary, the program should be changed to prevent the default value being entered as explicit data.
For both the SET-SUBITEM-DATA
and GET-SUBITEM-DATA
actions, it is possible to set or get (respectively) the sub-item data (for a
specific item) for multiple columns in a single statement. For example:
PROCESS GUI ACTION SET-SUBITEM-DATA WITH #LVITEM-1 #LVCOL-NUM #NUM #LVCOL-DATE #DATE GIVING *ERROR
In other words, multiple [column handle, receiving field] operand pairs may be specified.
To delete the subitem data (causing null values to be stored internally,
as if no data had been set), use the
DELETE-SUBITEM-DATA
action. For example:
PROCESS GUI ACTION DELETE-SUBITEM-DATA WITH #LVITEM-1 #LVCOL-DATE GIVING *ERROR
Again, multiple sub-items may be deleted for a specific item in a single statement, by specifying multiple column handles:
PROCESS GUI ACTION DELETE-SUBITEM-DATA WITH #LVITEM-1 #LVCOL-DATE #LVCOL-NUM GIVING *ERROR
When list view items are deleted, their associated sub-item data (if
any) is deleted with them. Note that if you wish to delete all items in a list
view control, but leave the list view columns intact, use the
CLEAR
action:
PROCESS GUI ACTION CLEAR WITH #LV-1 GIVING *ERROR
where #LV-1
is the list view control handle.
Sorting of the subitem data for a list view column may be achieved
either by the user (if the list view control's "No header
(x)" and "No sort header (y)"
STYLE
flags are not set), by clicking on a column header, or by the program, by
calling the SORT-ITEMS
action. In the latter case, items may be sorted even if no columns are
available, by passing the handle of the list view control itself rather than
the list view column. See the documentation for the
SORT-ITEMS
action for more information.
The sorting on clicking on a column in the column header of a list view
control is normally implicitly performed by Natural. However, before performing
the sort, Natural raises a CLICK
event (if
not suppressed) for the list view column. On returning from this event, Natural
checks whether the column is already sorted in the required direction, and
performs no further action if this is the case. This means that the application
can perform the sort itself instead of Natural, as long as it obeys the rules
for the sort direction (i.e., specifies descending sequence if the column is
currently sorted in ascending sequence, or ascending sequence otherwise). This
can be useful if the sort options (e.g. case-sensitivity) need to be dynamic,
or if it is required to perform application-specific code after the sort. An
example of a column CLICK
event
handler performing an explicit sort follows:
#CONTROL := *CONTROL T1. SETTIME IF #CONTROL.SORTED AND NOT #CONTROL.DESCENDING PROCESS GUI ACTION SORT-ITEMS WITH #CONTROL TRUE GIVING *ERROR ELSE PROCESS GUI ACTION SORT-ITEMS WITH #CONTROL GIVING *ERROR END-IF COMPRESS 'Sort took' *TIMD(T1.) 'tenths of a second' INTO #DLG$WINDOW.STATUS-TEXT
However, in most cases, it will probably be sufficient to let Natural perform the sort implicitly.
For alphanumeric data, the sort column's "Case insensitive
(i)" and "word compare (w)"
STYLE
flags
determine the default way in which the values are compared. If the sort is done
explicitly, the corresponding optional parameters to the
SORT-ITEMS
action, if specified, override these defaults. See the documentation for this
action for more details on these options.
Missing ("null") values compare low. That is, they appear at the bottom of the column when the column is sorted in descending sequence, or at the top of the column when the column is sorted in ascending sequence. Furthermore, if two column entries are identical, the existing relative position of the two items concerned is preserved.
Note that, if the list view control is in one of the icon view modes, sorting the items causes them to be re-arranged. Therefore, if you are using explicit item positions in either of the icon view modes, it is probably a good idea to disable any sort commands.
List view controls also possess a SORTED
attribute, implying that new items are inserted in their ascending or
descending sort position, depending on the value of the control's
DESCENDING
attribute, rather than being inserted at the end of the item list. For this to
work as expected, the items must already be sorted in the required direction.
For example, if an item's label (i.e., its
STRING
attribute) is modified, the application itself should, if required, ensure that
the list is maintained in sorted sequence. An example of how to do this is
provided in the next section.
Note that the SORTED
attribute does not influence the position of the items displayed in either of
the icon views. However, if an explicit sort is performed via the
SORT-ITEMS
action, the items are re-arranged in the sorted sequence. If you cannot avoid
doing a sort, and you are using explicit item positions in the icon view(s),
then you must explicitly save the icon positions prior to the sort and restore
them afterwards. For example:
PERFORM SAVE-ITEM-POSITIONS PROCESS GUI ACTION SORT-ITEMS WITH #CONTROL GIVING *ERROR IF #CONTROL.VIEW-MODE = VM-ICON OR #CONTROL.VIEW-MODE = VM-SMALLICON PERFORM RESTORE-ITEM-POSITIONS END-IF
where #CONTROL
is the handle of the list view control, and
where the subroutines defined above for saving and restoring the item positions
are used. Note that the icons are re-drawn (at their old positions), causing
some flicker. Therefore, if possible, try to avoid performing a sort whilst the
list view control is in one of the icon view modes. See the section below on
label editing for an example of how this may be done.
The process of label editing for list view controls is the same as for tree view controls. Therefore, for more information on this subject, please refer to the section Label Editing in Tree View and List View Controls.
Note that even if the SORTED
attribute is set, the items are not automatically re-sequenced after a label
editing operation has been completed. If this is required, this can be done as
shown in the example below. Firstly, we define some variables for later
use:
01 #SORTOBJ HANDLE OF GUI 01 #SORT (L) 01 #AUTO-ARRANGE (I4)
In addition, it is assumed that the list view control is named
#LV-1
.
In the list view control's
AFTER-EDIT
event, we cannot do the re-sequencing asynchronously, as this would interfere
with the (as yet incomplete) editing process. Instead, we simply set the
#SORT
flag to indicate that the re-sequencing should occur at a
later time, after the editing process has been completed:
#SORT := TRUE
In order to decide whether to perform a re-sequencing of the items, we
will need to check whether the items are already sorted. We will do this by
querying the SORTED
and DESCENDING
attributes of the primary column if the list view has columns, or those of the
list view control itself otherwise. The relevant object handle is set in the
dialog's AFTER-OPEN
event:
IF #LV-1.COLUMN-COUNT <> 0 #SORTOBJ := #LV-1.FIRST-CHILD ELSE #SORTOBJ := #LV-1 END-IF * EXAMINE #LV-1.STYLE FOR 'a' GIVING NUMBER #AUTO-ARRANGE IF #LV-1.SORTED AND #AUTO-ARRANGE <> 0 AND (#LV-1.VIEW-MODE = VM-ICON OR #LV-1.VIEW-MODE = VM-SMALLICON) #SORT := TRUE END-IF
In addition, we obtain the status of the list view control's
"Auto-arrange (a)"
STYLE
flag. If this is set, we can sort the items even if the control is in an icon
mode, as we don't require the icon positions to be fixed. Furthermore, if the
control is sorted, we indicate that we require the items to be initially
re-sequenced. This is due to the fact (mentioned earlier) that the item
positions are not updated on item insertion in the icon modes if the control's
SORTED
flag is set. The application thus needs to perform an initial sort itself in
this case.
The actual work of re-sequencing the items is done asynchronously in the
dialog's IDLE
event:
IF #SORT IF #AUTO-ARRANGE <> 0 OR (#LV-1.VIEW-MODE <> VM-ICON AND #LV-1.VIEW-MODE <> VM-SMALLICON) IF #SORTOBJ.SORTED PROCESS GUI ACTION SORT-ITEMS WITH #SORTOBJ #SORTOBJ.DESCENDING GIVING *ERROR END-IF RESET #SORT END-IF END-IF
Note that the sort is only done for the icon view modes for the list
view control if it is auto-arranged. Otherwise, the #SORT
flag is
not reset, causing the re-sequencing to be deferred until the first
IDLE
event after the control is switched to a non-icon view mode.
If you wish to support just a single context menu for a control, you can
simply set the control's CONTEXT-MENU
attribute to the handle of the context menu you wish to display, and leave it
set to this value. However, it is often required to be able to display more
than one context menu for a particular control, whereby this approach is too
inflexible.
To address the above problem, the
CONTEXT-MENU
event was introduced (not to be confused with the attribute of the
same name as mentioned above!). This event (if not suppressed) is raised for
the target control immediately before its
CONTEXT-MENU
attribute is evaluated, allowing the application to dynamically set this
attribute to the handle of the appropriate context menu first.
As an example, assume that we have defined two context menus in the
dialog editor: one containing item-related commands,
#CTXMENU-ITEMS
, and one containing generic commands (e.g., for
switching the view mode for a list view control),
#CTXMENU-DEFAULT
. In this case, the following
CONTEXT-MENU
event could be used:
#CONTROL := *CONTROL IF #CONTROL.SELECTED-SUCCESSOR <> NULL-HANDLE #CONTROL.CONTEXT-MENU:= #CTXMENU-ITEMS ELSE #CONTROL.CONTEXT-MENU := #CTXMENU-DEFAULT END-IF
where the following local data definition is assumed:
01 #CONTROL HANDLE OF GUI
In this example, the context menu #CTXMENU-ITEMS
will be
displayed if the user right clicks at a position occupied by an item, or
#CTXMENU-DEFAULT
otherwise.
Of course, this technique can be refined further to display context menus specific to the type(s) of the selected item(s).
Drag and drop may be used for re-positioning items within a list view control, as well as for data transfer to and from other windows. There is no difference between the two cases. The re-positioning scenario is merely a special case where the drop is made in the same list view control in which the drag was initiated.
The basic technique for providing drag and drop support is described in the section Using the Clipboard and Drag and Drop. In particular, it should be noted that it is still necessary to place some data on the drag and drop clipboard even if it is only required to support re-positioning of the items within the control, in order to inform Natural that drag and drop should be initiated. Secondly, there is a caveat specific to list view controls, in that the re-positioning of items can change the origin of the list view control, so that the top near corner of the list view control's display area may no longer begin at the default origin of (0, 0).
The following example provides some code for demonstrating the use of drag and drop with the list view control, in order to support the following operations:
Re-positioning of list view items.
Dragging and dropping text from another application (e.g. WordPad) onto a list view item in order to change its label.
The first step is to ensure that the drag and drop modes are set
correctly for the list view control. In the List View
Control Attributes window in the dialog editor, set the
Drag mode selection box to
"Move" and the Drop mode
selection box to "Copy+Move". This causes the
control's DRAG-MODE
and DROP-MODE
attributes to be set to DM-MOVE
and DM-COPYMOVE
,
respectively, in the generated source code for the dialog.
Next, the required local variables that are going to be used below must be defined:
01 #CONTROL HANDLE OF GUI 01 #DROP-ITEM HANDLE OF GUI 01 #ITEM HANDLE OF GUI 01 #SELECTED (L) 01 #AVAIL (L) 01 #X (I4) 01 #Y (I4) 01 #ORIG-X (I4) 01 #ORIG-Y (I4)
Having done this, we can write the necessary event handlers. The logical
place to start is with the
BEGIN-DRAG
event:
#CONTROL := *CONTROL * PROCESS GUI ACTION INQ-CLICKPOSITION WITH #CONTROL #ORIG-X #ORIG-Y GIVING *ERROR * ADD #CONTROL.OFFSET-X TO #ORIG-X ADD #CONTROL.OFFSET-Y TO #ORIG-Y * PROCESS GUI ACTION SET-CLIPBOARD-DATA WITH 'DUMMYPRIVFMT' 0 GIVING *ERROR
This code consists of three parts:
Getting the click position in client coordinates.
Converting the click position to view coordinates (as mentioned above, the origin of the list view window is not always "(0, 0)").
Putting some dummy data on the drag and drop clipboard, otherwise Natural will not initiate the drag and drop operation. We choose a private clipboard format because, being only dummy data, we deliberately don't want other applications to recognize it.
Next, we provide a handler for the
DRAG-ENTER
event:
PROCESS GUI ACTION INQ-DRAG-DROP WITH 1x #CONTROL GIVING *ERROR * IF #CONTROL = *CONTROL IF #CONTROL.VIEW-MODE = VM-ICON OR #CONTROL.VIEW-MODE = VM-SMALLICON #CONTROL.SUPPRESS-DRAG-DROP-EVENT := NOT-SUPPRESSED ELSE #CONTROL.SUPPRESS-DRAG-DROP-EVENT := SUPPRESSED END-IF ELSE #CONTROL := *CONTROL PROCESS GUI ACTION INQ-FORMAT-AVAILABLE WITH CF-TEXT #AVAIL GIVING *ERROR IF #AVAIL #CONTROL.SUPPRESS-DRAG-DROP-EVENT := NOT-SUPPRESSED ELSE #CONTROL.SUPPRESS-DRAG-DROP-EVENT := SUPPRESSED END-IF END-IF
The above code consists of three parts:
The INQ-DRAG-DROP
action is called to determine the handle of the drag source control.
If the drag source is the current control, then drag source and drop
target are identical. In other words, this is the item re-positioning case.
Because item re-positioning is only allowed in one of the icon views, we
unsuppress the DRAG-DROP
event to allow the drop in this case. Otherwise, we suppress this
event in order to prohibit a drop such that the "no drop" drag
cursor appears. Note that we could have also prevented a drag from occuring at
all by not putting the data on the drag and drop clipboard in this case.
However, although not demonstrated in this example, it is often desired to drag
one or more items from a list view control to another window, even if the
source control is in list or report view mode, for which the above code
provides a better basis.
If the drag source and drop target are different, an attempt is being made to transfer data from another window. In this case, we check to see if data is available in the required format, CF-TEXT, and allow the drop if so. Otherwise the drop is prohibited.
To provide drop emphasis during the dragging of external data across the
list view control, a DRAG-OVER
event handler is supplied:
PROCESS GUI ACTION INQ-DRAG-DROP WITH 1X #CONTROL 1X #X #Y GIVING *ERROR * IF #CONTROL <> *CONTROL #CONTROL := *CONTROL IF #CONTROL.SUPPRESS-DRAG-DROP-EVENT = NOT-SUPPRESSED PROCESS GUI ACTION INQ-ITEM-BY-POSITION WITH #CONTROL #X #Y #ITEM GIVING *ERROR IF #ITEM <> #DROP-ITEM IF #DROP-ITEM <> NULL-HANDLE #DROP-ITEM.SELECTED := #SELECTED END-IF #DROP-ITEM := #ITEM IF #DROP-ITEM <> NULL-HANDLE #SELECTED := #DROP-ITEM.SELECTED #DROP-ITEM.SELECTED := TRUE END-IF END-IF END-IF END-IF
The above code performs the following:
The INQ-DRAG-DROP
action is called to determine the handle of the drag source control and the
current drop position.
If the drag source and drag target are identical (item
re-positioning), no further action is taken, as no drop emphasis is required in
this case. Otherwise the INQ-ITEM-BY-POSITION
action is used to find the list view item (if any) at the current drop
position.
The current target item ("drop item") is tracked in
#DROP-ITEM
. Every time this changes, the current selection state
of the new drop item is first checkpointed in #SELECTED
, and then
the item is selected to provide the drop emphasis by setting its
SELECTED
attribute to TRUE
. In addition the selection state of the old drop
item (if any) is restored to its previously checkpointed value.
Note that the drop emphasis is not applied if a drop is not possible
(i.e., if the SUPPRESS-DRAG-DROP-EVENT
attribute is set to SUPPRESSED
).
To perform the actual drop, a
DRAG-DROP
event handler is supplied:
PROCESS GUI ACTION INQ-DRAG-DROP WITH 1x #CONTROL 1x #X #Y GIVING *ERROR * IF #CONTROL = *CONTROL ADD #CONTROL.OFFSET-X TO #X ADD #CONTROL.OFFSET-Y TO #Y SUBTRACT #ORIG-X FROM #X SUBTRACT #ORIG-Y FROM #Y #ITEM := #CONTROL.SELECTED-SUCCESSOR REPEAT WHILE #ITEM <> NULL-HANDLE ADD #X TO #ITEM.RECTANGLE-X ADD #Y TO #ITEM.RECTANGLE-Y #ITEM := #ITEM.SELECTED-SUCCESSOR END-REPEAT ELSE IF #DROP-ITEM <> NULL-HANDLE PROCESS GUI ACTION GET-CLIPBOARD-DATA WITH CF-TEXT #DROP-ITEM.STRING GIVING *ERROR #DROP-ITEM.SELECTED := #SELECTED RESET #DROP-ITEM END-IF END-IF
The above code performs the following:
The INQ-DRAG-DROP
action is called to determine the handle of the drag source control and the
current drop position.
The event handler differentiates between the case where the drag source and drop target controls are identical (item re-positioning) and the case where they are distinct (drag from external source).
In the item re-positioning case, the drop position is converted to view coordinates to obtain the "(x, y)"-displacement from the original position, which is then applied to each selected item to perform the move.
In the external source case, the text data is retrieved from the
clipboard directly into the STRING
attribute of the current drop item (if any) to update its label. It is not
necessary to first check whether text is available on the drag and drop
clipboard, as we did that in the
DRAG-ENTER
event, prohibiting a drop and associated
DRAG-DROP
event from taking place at all if this is not the case. After updating the
label, the drop item's previous selection state (prior to the drag) is restored
and #DROP-ITEM
reset ready for the next drag operation (if
any).
Lastly, in the case that the user cancels the drag operation, or exits
the bounds of the list view control without having performed a drop, a
DRAG-LEAVE
event handler is supplied:
IF #DROP-ITEM <> NULL-HANDLE #DROP-ITEM.SELECTED := #SELECTED RESET #DROP-ITEM END-IF
The above code simply clears the drop emphasis (if any), by restoring
the old drop item selection state. To satisfy the logic provided for the other
event handlers, #DROP-ITEM
is reset, indicating the absence of a
drop item. This is essentially the same code as performed at the end of the
DRAG-DROP
event, since the DRAG-LEAVE
event is not called in the case of a drop. Therefore, any drop emhasis
resetting needs to be done in both places.