Working with List View Controls

This document covers the following topics:


Introduction

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.

View Modes

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.

Setting Item Images

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.

Item Placement

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.

Item Selection

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.

Item Activation

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:

  1. By pressing the key or key combination defined for the list view control's ACCELERATOR attribute. The control need not currently have the focus.

  2. 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.

List View Columns and Sub-items

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

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.

Label Editing

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.

Multiple Context Menus

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

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:

  1. Re-positioning of list view items.

  2. 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:

  1. Getting the click position in client coordinates.

  2. Converting the click position to view coordinates (as mentioned above, the origin of the list view window is not always "(0, 0)").

  3. 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:

  1. The INQ-DRAG-DROP action is called to determine the handle of the drag source control.

  2. 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.

  3. 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:

  1. The INQ-DRAG-DROP action is called to determine the handle of the drag source control and the current drop position.

  2. 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.

  3. 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.

  4. 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:

  1. The INQ-DRAG-DROP action is called to determine the handle of the drag source control and the current drop position.

  2. 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).

  3. 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.

  4. 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.