User:CBM-c-/VS-List Browsers part 4

From Vectorworks Developer
Revision as of 14:33, 1 January 2021 by CBM-c- (talk | contribs) (Create Subpage for List Browser 4)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

For better understanding please use the comprehensive example at Dialog with List Browser. By --_c_ (talk) 03:08, 31 December 2020 (EST) (previously Orso B. Schmid)


Part 1: Creation Part 2: Columns Part 3: Rows and Cells Part 4: Events


Events

A List Browser is capable of returning an event on user's click. Using events offers an easy way to store the last selected row and column. It is always advisable to use events since this reduces your code. The user's selection is parsable through GetLBEventInfo. It returns a set of three informations:

  • event triggered
  • row selected
  • column selected

There is a subtle difference in the type of event triggered, depending on the control type of the column involved. Documented is the event "-4", upon row selection, but undocumented is that the event returned can also be "-2" if the cell clicked belongs to a column whose Control Type uses Column Data Items. Mind please that the row returned might be "-1" if the user clicks on white space within the List Browser. This can happen if your List Browser is higher than the rows displayed. After the last row some white space displays and clicks on this space cause event "-1". It is *highly* recommendable to parse for List Browsers events exclusively in the dialog events CASE statement, straight where you code the response to user's action on the listBrowserID item. The event returned will be "-1" if the List Browsers hasn't been clicked at least once (for example just at dialog start). Or even more misleading values. Read this attentively. [EDIT Dieter Geerts 2014-03-15] : I could not reproduce the '-1' event type. I guess it is solved in the current version of VW, 2014.

GetLBEventInfo( dialogID:LONGINT; componentID:LONGINT; 
	VAR eventType:INTEGER; VAR rowIndex:INTEGER; VAR columIndex:INTEGER):BOOLEAN;

Sets variables with the last event raised, the last selected row and last selected column.

  • You should always use this routine in an IF condition for the case that it returns FALSE.
  • You shall pay a lot of care if you call GetLBEventInfo outside the CASE statement for the involved List Browser item ID. See below. GetLBEventInfo should be used exclusively in the CASE statement for your List Browser. The events returned are not reliable otherwise.
  • If you store selection indexes in global variables, you'll do well to initialize them to -1 and code accordingly. If for some reason GetLBEventInfo returns false, you risk misleading values otherwise (for example "0" which would lead to believe a first column or row has selected).
  • Setting a sorting column somewhere in your script raises an event. I believe it shouldn't. This will record as last user event and will mislead you. Be careful if you change sorting column after column creation.
Event flags
  • no event: row selection for on-click-toggle control types but no Data Items List is available
  • event = -2: row selection for on-click-toggle control types when items are available
  • event = -4: row selection for control types Static and Number
  • event = -8: the user scrolls the LB using the arrow keys. This event sets no vars for the rows or cols: rowIndex is always -1, columIndex is always -1. The involved row will highlight nevertheless. But you have no way to find out what is selected until the user actually clicks on a row.
  • event = -9: the user types a string while the focus is on the LB. This behaves the same as event -8. From Julian Carr on the V-list.
  • event = -10: column selection
Events on drag-drop
[DG - 2014-03-16]

You need to use the data that comes with the dialog event handler here to catch the drag-drop events!

  • event = -4; data = rowIndex: When starting, a normal event is thrown, so just a select event.
  • event = -4; data = -50: The first event on releasing the item(s).
  • event = -4; data = 43703408: The second event on releasing the item(s).
  • event = -4; data = -51: The third and last event on releasing the item(s).

So check for the -51 data to react on drag-drop of items. And here I go. Orso's usual dissection:

Beastly misleading
  • event = 0: you called GetLBEventInfo outside the CASE block AND didn't set a sorting column somewhere in your code
  • eventRow = 0: see above
  • eventCol = 0: see above
  • event = -10: you called GetLBEventInfo outside the CASE block AND set a sorting column somewhere in your code!
  • eventRow = -1: see above
  • eventCol = 0: see above
Probable hint of crashy VW
  • event = -<long integer value>: you called GetLBEventInfo outside the CASE block and you got some garbage value from previous codes. I saw this happen but cannot reproduce.
  • eventRow = -<long integer value>: see above
  • eventCol = -<long integer value>: see above
Warning
  • eventRow = -1: clicking on white space below the last row (for example when your row count doesn't fill the whole available List Browser space).
  • eventCol = -1: see above.
  • eventCol = 0: default value, before first user's click on the List Browser (for example just after dialog launch). this might be regarded as a bug.
  • Resizing columns doesn't return an event parsable with GetLBEventInfo.
Furthermore
it can be observed that image based control types behave differently:
  • event = -2: row selection for control types: Static Icon, Multiple Icons
  • event = -2: row selection for control types: Radio, Multi State, Single Instance when Column Data Items are defined.
Must have something to do with the Column Data Items wanting to change or these controls expecting them.
Returns FALSE
  • on row selection for control types: Radio, Multi State, Single Instance when Column Data Items are missing (this is a hint that you forgot to set up Column Data Items).
Unexpectedly returns TRUE
  • calling the routine before first user's click on the List Browser (for example just after dialog launch).... and you called it outside the CASE statement for your List Browser ID.
{put in SetupDialogC case }
CASE item OF
SetupDialogC:
	BEGIN
		{ ... }
	END;
	
listBrowserID: IF GetLBEventInfo(dlog, listBrowserID, event, eventRow, eventCol) THEN
		alrtDialog(concat('event: ', event, ' last sel row: ', eventRow, ' last sel col: ', eventCol));
END;
{ ... }


Drag and drop

Drag and drop permits to the user to change order of the rows. This feature is disabled by default. Upon selecting a row and beginning a drag, the List Browser will wait for the user to drop it at some other position: it triggers user interaction. This feature can only be set on columns with control Number. Other columns will simply behave weird. An example can be observed on the "Design Layers" tab of NNA's Organizations dialog, when the option "Details" is activated: the stacking order is dealt with Drag and Drop. This feature can be made even more interesting when combined with List Browsers Events. At this point you can pass rows of data between two List Browsers. If you code such functionality you must pay great attention to reset the last selected indexes of both List Browsers. There is to my knowledge no NNA dialog using drag and drop between two (or more) List Browsers so user's of your dialog won't recognize the possibility on their own. They are not used to it. Abundant alerts should inform the user that a drop on another List Browser is awaited. Then it works like a charm. Among my favorites, but it takes some heavy coding to set up properly.

For this to work you need
BUG
on Windows during the user interaction (when the user is dragging a row) the whole screen in all monitors will flicker mercilessly, excluding the Vectorworks document window, which stay put and quiet. This is a long standing issue, but after all we don't look at anything else but our List Browser, while we are about to drop rows. (Fixed by VW 14 SP3)
EnableLBDragAndDrop(dlog: LONGINT; LB: LONGINT; 
	enable: BOOLEAN) : BOOLEAN;

Enables/disables drag and drop on a List Browser. It only works for columns of type Number.

result
always TRUE, like SetLBDragDropColumn. Simply pass the result to a temporary boolean variable that you can trash.
SetLBDragDropColumn( dlog : LONGINT; LB : LONGINT; 
	columnIndex: INTEGER) : BOOLEAN;

Declare the drag and drop column.

result
it doesn't look capable of returning false. Even if the List Browser doesn't have a single column, it will happily return TRUE.

See also: Using List Browsers Events

EnableLBDropOnIndices(dlog, LB : LONGINT; 
	iStartIndex, iEndIndex : INTEGER; bEnable : BOOLEAN) : BOOLEAN;

By default all rows accept dropping. With this call is possible to disable, or re-enable, some rows from dropping. It shall be noticed that this is targeting rows, not columns.

iStartIndex, iEndIndex
range of rows where dropping is disabled.


Sorting

By default sorting is activated upon creating a List Browser, but it's possible to enable, disable and set sorting order (ascending or descending) during dialog execution. A few observations:

  • Sorting image cells is meaningless, you can disable it since it will only upset your user.
  • Sorting text cells on Mac behaves differently than on PC: this I wish to explore further.
  • Sorting numeric cells sort them alphabetically, see the cell Owner Number.
NOTE
setting a sorting column by script raises a List Browser event, which is a bug. This should only happen on user interaction. See List Browsers Events.
EnableLBSorting(dialogID: LONGINT; componentI LONGINT; 
	enableSorting: BOOLEAN);

Enables/disables sorting for a whole List Browser. Sorting is TRUE by default.

GetLBColumnSortState( dialogID:LONGINT; componentID:LONGINT; 
	columnIndex:INTEGER):INTEGER;

Returns sorting state of a column.

GetLBSortColumn( dialogID:LONGINT; componentID:LONGINT):INTEGER;
SetLBSortColumn( dialogID:LONGINT; componentID:LONGINT; columnIndex:INTEGER; 
	isAscending:BOOLEAN);

Returns or set the current sort column in a List Browser whose sorting is enabled.


Select

Script-side selection is very well combined with List Browsers Events, which allows to keep easy track of the last user's selection, if any.

SetLBSelection(dialogID: LONGINT; componentID: LONGINT; 
	firstItemIndex: INTEGER; lastItemIndex: INTEGER; select: BOOLEAN): BOOLEAN;

Select/deselects a range of rows from row "firstItemIndex" to row "lastItemIndex". If the selection is outside the List Browser's scroll window, the window will automatically scroll to show the selection. See EnsureLBItemIsVisible for a similar behavior, but without selection.

GetNumSelectedLBItems(dialogID: LONGINT; componentID: LONGINT): INTEGER;

Count of currently selected rows. Among the most used routines, allows to loop down all selected rows for processing. Using List Browsers Events the last selected row and column will be collected. But this still doesn't inform you on other rows eventually selected. This check is not needed if EnableLBSingleLineSelection is active: the user cannot select more than one line.

IsLBItemSelected(dialogID: LONGINT; componentID: LONGINT; 
	itemIndex: INTEGER): BOOLEAN;

Checks if row "itemIndex" is currently selected (itemIndex = rowIndex). Like the previous routine, this check is not needed if EnableLBSingleLineSelection' is active and the script parses for List Browsers Events.

EnableLBSingleLineSelection(dialogID: LONGINT; componentID: LONGINT; 
	enable: BOOLEAN): BOOLEAN;

Enable/disable selection of multiple rows. By default multiple selection is enabled. This is affects the whole list browser.

EnableLBClickAllDataChange(dialogID: LONGINT; componentID: LONGINT; 
	enable: BOOLEAN): BOOLEAN;

Enable/disable changing all cells' values in a column when a modifier key (ctrl/alt on Mac/PC) is pressed. An example of this feature is the Navigation palette: the visibility column. Upon clicking on a row and while multiple rows are selected, the value displayed in the clicked row will apply to all selected rows. Is a good feature that can save some code, if your script allows for this functionality.

Examples:

PROCEDURE LB_SetSelAllRows(d, LB: LONGINT; select: BOOLEAN);
	VAR
		temp_b : BOOLEAN;
	BEGIN
		IF GetNumLBItems(d, LB) > 0 THEN
			temp_b := SetLBSelection(d, LB, 0, GetNumLBItems(d, LB)-1, select);
	END;
	
PROCEDURE LB_DeleteSelRows(d, LB: LONGINT);
	VAR
		i : INTEGER;
		temp_b : BOOLEAN;
	BEGIN
		i := 0;
		WHILE i < GetNumLBItems(d, LB) DO
			IF IsLBItemSelected(d, LB, i) THEN
				temp_b := DeleteLBItem(d, LB, i)
			ELSE
				i := i + 1;
	END;


Destroy data

Sometimes what you need is to destroy data. Below the routine allowing for this to be achieved:

DeleteLBColumn(dialogID: LONGINT; componentID: LONGINT; 
	columnIndex: INTEGER): BOOLEAN;

Remove the column at "columnIndex" from the List Browser. Is seldom, but it can happen that you need to rebuild the List Browser's columns anew. One such circumstance is that you need to re-write the columns titles.

BUG: If you pass the index of a column (NOT Column Data Item) for deleting it, and this doesn't exists, the application will crash (VW 13 and 14).
RemoveAllLBColumnDataItems( dialogID:LONGINT; componentID:LONGINT; 
	columnIndex:INTEGER);

Remove all items in the List of Column Data Items defined for the column at "columnIndex".

RemoveLBColumnDataItem( dialogID:LONGINT; componentID:LONGINT; 
	columnIndex:INTEGER; columnDataItemIndex:INTEGER):BOOLEAN;

Remove the chosen item at "columnDataItemIndex" from the List of Column Data Items defined for the column at "columnIndex".

DeleteAllLBItems(dialogID: LONGINT; componentID: LONGINT): BOOLEAN;

Remove all rows in List Browser.

DeleteLBItem(dialogID: LONGINT; componentID: LONGINT; 
	itemIndex: INTEGER): BOOLEAN;

Remove the row at "itemIndex".


Update and Refresh

When the entire content of your List Browser shall be rebuilt destroying or modifying rows in a loop, you should always remember to disable the List Browser updates using EnableLBUpdates before the loop and reactivate it after the loop has finished executing. If you don't, you might bog down VW, which will recalculate the List Browser at each row, each cell inserted. Don't forget to re-enable updates otherwise you get a white list browser according to the event sent on user's click. To be noted that enabling doesn't really work well on PC (!expand!). A List Browser refresh might be needed after changing row's data. This applies specially for resource images which might be changing during your script execution. The routine RefreshLB coerces the refresh. If you change data in a loop please remember to keep "RefreshLB" out of it. This call should always be the last thing you do after renewing data in any fashion. This is among the most important settings when you are dealing with heavy data.

EnableLBUpdates(liDialogID, liComponentID :LONGINT; 
	bEnableUpdates :BOOLEAN);

Enable/disable updating cells in a List Browser. Disable always before looping rows with content modification and re-enable after the loop is terminated.

RefreshLB( dialogID:LONGINT; componentID:LONGINT):BOOLEAN;

Redraws the whole List Browser. Keep this outside content modifications involving loops.


Visibility

Useful routines for ensuring that List Browsers are visible:

SetFocusOnLB( dialogID:LONGINT; componentID:LONGINT):BOOLEAN;
EnsureLBItemIsVisible( dialogID:LONGINT; componentID:LONGINT; 
	index:INTEGER):BOOLEAN;

Make the List Browser window display a row even if outside the scroll bounds of the list browser's window. Upon refreshing a List Browser the displayed rows always start at row "0". You don't need this call if you coerce a selection with SetLBSelection: the selection will scroll automatically the window to fit.


Enabling

Sometimes you need to make your List Browser not accessible to the user, this can be obtained with EnableLB. The List Browser displays grayed and any user interaction is disabled ( Drag and Drop, on-click events). The List Browser is nevertheless fully enabled internally: you can still change content and display by script and it will update as expected. See Drag and Drop for disabling dropping only on a range of rows (instead of disabling the whole list browser).

EnableLB(dialogID: LONGINT; componentID: LONGINT; 
	enable: BOOLEAN): BOOLEAN;

Enable/disable a List Browser. Disabled: not clickable and grayed.


User Data

Elsewhere called "itemData". Never figured this out. One could think that it expects a resource index, similar to CreateControl, but it doesn't. Since it demands a Longint, I also tipped on indexed resources (records, symbols, images...) or a list created with BuildResourceList, but this must be a wrong concept. Another possibility is that it expects a handle to some sub-object, but we cannot pass it with Vectorscript. For example user data objects of type 76 accompanying some parametric objects or records of certain kinds (textures, sketches...). Last possibility: too much fantasy and is just unsupported. Or does it perhaps await data from another dialog? I have no idea if this should be initialized to "0" or "-1". And I still didn't encounter a situation where this matters. Intuitively I'd say that init on "0" is well. You will find a reference to this mysterious type of data in the routines below:

InsertLBColumnDataItem
targets a column and add something to the particular column data item using the parameter "itemData"? What is that we can add here?
GetLBColumnDataItemInfo
securely get infos of the mysterious thing added at the particular column data item. Whatever that might be.
SetLBItemData
broken in VW 2017, either this or GetLBItemData doesn't work any longer, but it did. It targets a cell. Changing it to any possible value didn't ever produce a result by me, but the value set with it could be fetched using GetLBItemData.
GetLBItemData
broken in VW 2017, either this or SetLBItemData doesn't work any longer, but it did, returning what was set with SetLBItemData.


Part 1: Creation Part 2: Columns Part 3: Rows and Cells Part 4: Events