Managing scrollable contents: Horizontal List
The Mosaic class Core::HorizontalList implements a special kind of view intended to display items arranged horizontally side by side. This so-called Horizontal List can be used to compose the appearance of a GUI component, in particular to present lists with options, menu items or data records the user can inspect, scroll and select. The list can, for example, display picture thumbnails stored in the device:
The Horizontal List view is optimized to efficiently manage with very long lists consisting of thousands of items. For this purpose, the view retains only few of the items in the memory. Later when the user scrolls the list, the view takes care of the old items being replaced with new contents so that the user has the impression of a continuous list. The view is also optimized regarding the speed permitting even very long lists being scrolled smoothly.
The consequence of the mentioned optimizations is that to work efficiently the list view expects all items to have equal width and be of equal type. Even when mixing of items with different width or different type within one and the same Horizontal List is supported in the meantime, it will have a significat impact on the performance - especially in case of a list containing thousends of items.
The following sections are intended to provide you an introduction and useful tips of how to work with the Horizontal List. For the complete reference please see the documentation of the Core::HorizontalList class. Moreover, please note, that there is a similar view Vertical List intended to manage and display the items arranged vertically one below the other.
Add new Horizontal List
To add a new Horizontal List just at the design time of a GUI component do following:
★First ensure that the Templates window is visible.
★In Templates window switch to the folder Views.
★In the folder locate the template Horizontal List.
★Drag & Drop the template into the canvas area of the Composer window:
★Eventually name the new added Horizontal List.
As you see in the screenshot above, the new added Horizontal List appears per default accompanied by a slot method (usually named OnLoadItem). This method is important to provide the list with contents for the actually displayed items. It is added just to help you to start with the Horizontal List and to avoid that new lists appear empty. Being an advanced developer, you will probably replace this method by your own particular version. How this is done is addressed in the section below.
IMPORTANT
The Horizontal List doesn't provide any own appearance. Instead it manages and displays the items controlled by it. In order to be recognized while assembling the GUI component in the Composer window, the Horizontal List is slightly tinted with green color. This effect exists just for your convenience. In the Prototyper as well as in the target device the Horizontal List itself is invisible.
Inspect the Horizontal List
As long as the Horizontal List is selected you can inspect and modify its properties conveniently in the Inspector window as demonstrated with the property Bounds in the screenshot below:
This is in so far worth mentioning as all following sections describe diverse features of the Horizontal List by explicitly referring to its corresponding properties. If you are not familiar with the concept of a property and the usage of Inspector window, please read first the preceding chapter Compositing component appearance.
Arrange the Horizontal List
Once added, you can freely move the Horizontal List, or you simply grab one of its corners and resize it in this way. You can control the position and the size of this view also by directly modifying its property Bounds. If you want the Horizontal List to appear behind other views you can reorder it explicitly.
Please note, that resizing the Horizontal List has no effect on the current scroll position. Accordingly, it is possible, that after the list view has been enlarged a gap appears between the last item and the right edge of the Horizontal List. Is such case you have to adjust the scroll offset explicitly.
Specify the width of an item within the list
The Horizontal List view is optimized to handle very efficiently large contents consisting of thousands of items - as long as all items have equal width. The first step when working with the Horizontal List is thus to specify in its property ItemWidth the width in pixel of a single item you intend to display in the list. This, however, is only applicable when all items within the list will really have equal width.
If the list is intended to display items of different width, then you have to implement a slot method and assign it to the list's property OnQueryItemWidth. The Horizontal List will thereupon use the slot method to query the individual width of the items whenever this information is needed. The implementation of the slot method should do following:
★Evaluate the list's variable Item. It contains the number of the item in question. The first item has the number 0 (zero), the second 1, and so far.
★Estimate the width of the item. You do this, for example, by accessing a data base where item's data is stored.
★Assign the estimated width to the list's variable QueriedItemWidth (Note the preceding Queried in its name and don't confuse it with the property ItemWidth).
Since the slot method will be invoked very often, it is essential to optimize it as good as possible. Following example demonstrates the implementation of such OnQueryItemWidth slot method:
// Get the number of the item to query its width. var int32 itemNo = HorizontalList.Item; var int32 width; // Estimate the width of the item. For example, even items are 40 pixel wide // while odd items occupy only 24 pixel. if ( itemNo % 2 ) width = 24; else width = 40; // Store the just estimated width in the list. HorizontalList.QueriedItemWidth = width;
TIP
If all you want is an additional header and footer item with different size, you can configure the Horizontal List to reserve space on the left of the first and on the right of the last item where you can arrange the header and footer contents. See Add padding at the begin and at the end of the list.
Specify the class of the items within the list
The second step when working with the Horizontal List is to specify in its property ItemClass which kind of view or GUI component you intend to use to display the items. Per default, the property is configured with Views::Image class, which means that the list manages and displays simple Image views. If you want the list to display Text views, you have to change this property to Views::Text. With the Inspector Assistant you can conveniently select the right class when you edit the initialization expression for the property ItemClass.
In more advanced application cases, you can configure this property with a class representing one of your own GUI components. For example, if you want to implement an application displaying videos stored in a PVR data base, you would probably initialize this property with the class of a GUI component intended to display the video information like its title, duration or thumbnail. The Horizontal List will then automatically create, manage and display the instances of your GUI component.
The usage of the property ItemClass is reasonable only when all items within the list will be of the same type, e.g all items are Image views. If the list is intended to display items of different types, then you have to implement a slot method and assign it to the list's property OnQueryItemClass. The Horizontal List will thereupon use the slot method to query the individual class of the items whenever this information is needed. The implementation of the slot method should do following:
★Evaluate the list's variable Item. It contains the number of the item in question. The first item has the number 0 (zero), the second 1, and so far.
★Estimate the class of the item. You do this, for example, by accessing a data base where the item's data is stored.
★Assign the estimated item's class to the list's variable QueriedItemClass (Note the preceding Queried in its name and don't confuse it with the property ItemClass).
Since the slot method will be invoked very often, it is essential to optimize it as good as possible. Following example demonstrates the implementation of such OnQueryItemClass slot method:
// Get the number of the item to query its class. var int32 itemNo = HorizontalList.Item; var class itemClass; // Estimate the class of the item. For example, even items are Text views // while odd items are Image views. if ( itemNo % 2 ) itemClass = Views::Image; else itemClass = Views::Text; // Store the just estimated class in the list. HorizontalList.QueriedItemClass = itemClass;
Implement the OnLoadItem slot method to load the items
The Horizontal List view doesn't maintain actively the entire stock of items in the memory. Instead, it manages a cache small enough to store the currently visible and few of the above and below lying items. Even, if the original stock consists of thousands of items, the Horizontal List stores only very few of them. This approach however means, that the list requires a technique to load new items in its cache as soon as these are exposed, for example, after the user has scrolled the list. The items are thus loaded on demand. To load an item, the Horizontal List sends a signal to a slot method stored in its property OnLoadItem.
This slot method has thus the job to obtain the data associated with the requested item (from your data storage, data base, etc.) and to initialize the corresponding item view so it displays this information. Please recall the above described properties ItemClass and OnQueryItemClass. These properties determine the class of the view to be used to display the item. The implementation of the slot method is thus intended to initialize an instance of exact this class.
The following code demonstrates the original implementation of the default version of the slot method provided with the template you used to add a new Horizontal List. As you remember, the Horizontal List is per default configured to handle with Image view items. The slot method is accordingly implemented to handle with instances of the Views::Image class:
// Get the number of the item to load. The list component takes care of the // creation of the corresponding item view. Just access it ... var int32 itemNo = HorizontalList.Item; var Views::Image itemView = (Views::Image)HorizontalList.View; // The implementation of this slot method does not match the item class // specified in the associated list component. Or the slot method is not // called in context of the OnLoadItem list operation. if ( itemView == null ) return; // Configure the item view ... itemView.Bitmap = Resources::SymbolIconsLarge; itemView.FrameNumber = ( itemNo % itemView.Bitmap.NoOfFrames ); switch ( itemNo % 4 ) { case 1 : itemView.Color = #0000FFFF; case 2 : itemView.Color = #FF0000FF; case 3 : itemView.Color = #00FF00FF; default : itemView.Color = #FFFF00FF; } // Ensure that the item has correct size. The position of the item will be // managed by the list component. itemView.Bounds.size = HorizontalList.ViewSize;
When calling the OnLoadItem slot method, the Horizontal List provides in its variable Item the index of the item to load. The items are numbered strictly starting with the number 0 (zero) for the first item. You evaluate this variable in order to determine which item is requested to be loaded. Knowing this number you can query your particular data storage or data base and get the information related to the requested item.
In the second variable View the Horizontal list provides a direct access to the view instance representing the corresponding item on the screen. Modifying properties of this instance will thus result in the item changing its appearance. In every case View does refer to a valid instance of the class specified in the property ItemClass or queried via slot method connected to property OnQueryItemClass as explained in the section above.
The variable View is declared with the very basic class Core::View. Before you can access and initialize the view you have thus to perform an object runtime casting and test whether it was successful. Afterwards you can access the view and initialize it as required with the obtained data. In the above implementation, the view (being a Image view) is initialized with a bitmap resource selected depending on the number of the item. You can imagine, that after obtaining information for the affected item from the data storage or data base, you can assign this information to the view.
If all items within the Horizontal List are of the same type specified in the property ItemClass, the implementation can limit to simply runtime cast the variable View to one and the same class. In turn, if the list is intended to use different item types (determined by the slot method OnQueryItemClass, see above), the implementation of OnLoadItem has to use the class corresponding to the item being loaded actually. For more details see also Mix items of different height and different type.
Finally, the third list's variable ViewSize is used to correctly adjust the size of the view. Usually you limit to assign this value to the view's Bounds property.
IMPORTANT
When the content of the Horizontal List is scrolled, the list recycles the existing item views instead of creating new instances again and again. Thus when implementing the OnLoadItem slot method, you should consider that the provided View instance eventually contains old information. Therefore the best is to always initialize the view with the complete available information.
Usually after adding a new Horizontal List you will open the per default provided OnLoadItem slot method for editing and modify its implementation as required in your application case. You can, of course, rename or even remove the default slot method and replace it by your own. In such case, don't forget to assign the new slot method to the property OnLoadItem of the affected Horizontal List.
The following example contains the implementation of a list managing picture thumbnails stored in a device. It demonstrates, in particular, how the Horizontal List is configured to use a GUI component instead of simple view to display the items and how the OnLoadItem initializes the properties of such GUI component when the list loads the items on demand:
Please note, the example presents eventually features available as of version 12.00
Specify the number of items within the list
With the property NoOfItems you determine how many records in total are available in your data storage, data base, etc.. Accordingly, the Horizontal List will allow the user to scroll and see all those items. For example, if you intend to implement a spin control where the user can scroll and select the hour, you would initialize the property NoOfItems with 24 (or with 12 if your country's time convention is the 12-hour clock).
Please note, that at the runtime, when the user scrolls the list, the associated OnLoadItem method may be called for all the items in the specified range 0 .. NoOfItems-1. This means, the value in the NoOfItems property should always correspond to the total number of available records in your data storage.
When the content in the data storage changes at the runtime, e.g. the records disappear or new records are added to the storage, you should always adjust the property NoOfItems. Doing that causes the list to be refreshed implicitly.
Please note, that changing the NoOfItems property has no effect on the current scroll position. Accordingly, it is possible, that after the number of items has been reduced a gap appears between the last item and the right edge of the Horizontal List. Is such case you have to adjust the scroll offset explicitly.
Configure an endless list
With the property Endless you can control how the Horizontal List should behave when its content is scrolled beyond the last item. The default setting of this property is false. In this case the list is considered as finite without any further contents being displayed after the last or before the first item.
Setting this property to the value true will cause the list to behave as if its content were infinite. In particular the list displays all items according to their order and after the last item has been reached the list starts again with the first item. The effect is as if the list items were arranged on an endless band.
IMPORTANT
Enabling the endless list mode will cause the eventually specified left and right padding areas to be ignored. Moreover, if the list area is bigger than the area occupied by all available items, several duplicates of one and the same item will appear within the list area.
Force the list to reload items
The Horizontal List uses internally a cache to maintain the currently visible items. The corresponding implementation is optimized with regard to the memory usage and the performance. Thus the Horizontal List decides by itself when and which items are loaded. For example, when the user scrolls the list, the newly exposed items not yet being available in the cache are loaded automatically. This however has the disadvantage, that when something changes in the data storage, the Horizontal List is not updated implicitly.
By calling the method InvalidateItems you can explicitly inform the Horizontal List, that some items have changed their contents. Depending on whether the affected items are actually visible or not, the Horizontal List will reload them again by sending signals to the OnLoadItem slot method as described above.
In other words, to maintain coherent the Horizontal List view with the corresponding data storage, you should always call the method InvalidateItems when you recognize that data records in the data storage has been changed. The method expects two parameters determining the index of the first and the last affected item. Accordingly you can inform the list that one or a complete range of items are not up to date. Calling the method multiple times is accumulated. The following code demonstrates it:
// Example 1: // The item #7 has been changed, so ask the list to eventually reload it. HorizontalList.InvalidateItems( 7, 7 ); // Example 2: // All items have been changed (e.g. after loading new contents for the // data storage). HorizontalList.InvalidateItems( 0, HorizontalList.NoOfItems - 1 ); // Example 3: // The items #7, #12 and #13 have been changed. HorizontalList.InvalidateItems( 7, 7 ); HorizontalList.InvalidateItems( 12, 13 );
Let's assume you have a Horizontal List displaying counter values taken from an array. Thus this array will store for every list item a number (the corresponding counter). If you want one counter being incremented, the following code takes care of it and forces the list to reload the corresponding item:
// Step 1: In the data store: increment the counter corresponding to the 'item_number' DataArray[ item_number ] = DataArray[ item_number ] + 1; // Step 2: Force the list to refresh the item_number HorizontalList.InvalidateItems( item_number, item_number );
The following example project demonstrates the above implementation of a list displaying counters. When the user taps on an item, the counter is incremented and the list is updated:
Please note, the example presents eventually features available as of version 12.00
Access list items and views
As already mentioned the Horizontal List is optimized to handle efficiently with thousands of items. For this purpose the list maintains only few item views in the memory and reuses them while the user scrolls the list. Thus you should consider that the Horizontal List is just a display mechanism and not the primary resource for maintaining and getting data!
Even knowing the valid item index, there is no direct way to get the corresponding item data from the Horizontal List. Similarly, the views associated to items are not guaranteed to be available. The Horizontal List provides a method GetViewForItem() you can use to access the view associated with an item. This, however, works only if the affected view is currently in memory. If the item in question is not visible, the associated view is probably not available causing the method GetViewForItem() to return null.
The right approach to work with lists is following:
•Manage the complete item related information in a storage, e.g. an array.
•Implement the OnLoadItem slot method to load the requested item with the corresponding information from the storage.
•When the content in the storage changes, invalidate the affected item only. The list will take care of the necessary reload operation.
•When the user interacts with the list causing some item contents being changed, the alternation should affect the data in the storage only. Then invalidate the affected item. Don't use the actually visible views to temporarily store data or other status or selection information. Store the status or selection information in the storage!
•If it is inevitable to directly access a view, use the method GetViewForItem(). If the method returns null, the view is not available - the item evidently lies outside the list area.
CAUTION
Please note, while the horizontal list is actually reloading its items, the list changes the order of views preventing the method GetViewForItem() from being able to access the right view. To avoid that your implementation accesses a wrong view, the method GetViewForItem() returns null if it has been called in context of an active OnLoadItem slot method. In other words, don't use GetViewForItem() while the list loads items.
Mix items of different width and different type
Starting with Embedded Wizard 12 the Horizontal List can be configured to support items of different width and different type. To achieve this, you implement two additional slot methods and assign them to the list's properties OnQueryItemWidth and OnQueryItemClass. The list will invoke the methods automatically whenever it is necessary to know the individual width and the individual class of an item. How you implement such slot methods is explained in the sections Specify the width of an item within the list and Specify the class of the items within the list.
IMPORTANT
The slot methods associated to the list's properties OnQueryItemWidth and OnQueryItemClass will affect the performance of the list. You can imagine that in order to calculate the position of an item N, the list has in worst case to query the width of all preceding items. The list will invoke in such case the OnQueryItemWidth slot method up to N-1 times. It is thus essential to optimize the implementation of slot methods as good as possible. Also the more items contained within the list, the bigger the performance impact resulting from the flexibility to support different item width and class.
If your application case does requires the list to support different item types, you have additionally to adapt the slot method associated to list's property OnLoadItem. The adaptation should ensure that all desired item classes are handled correctly during the item loading operation. For example, when the list is intended to mix Text and Image items, your adaptation has to estimate the type of the item being loaded (text or image) and accordingly initialize the item view. The following implementation demonstrates the adaptation. Please note the pseudo-code starting with Your_DataBase_. In real application case you will perform here a dedicated query operation on the storage or data base where the item information is maintained:
// Get the number of the item to load. var int32 itemNo = HorizontalList.Item; // Is the item a Text view? Query the storage where the original // data for the items is stored. if ( Your_DataBase_Is_Text_Item( itemNo )) { // The list component takes care of the creation of the corresponding item // view. Just access it ... We know, it is a Text view. var Views::Text itemView = (Views::Text)HorizontalList.View; // Configure the Text view ... itemView.String = Your_DataBase_Get_Text( itemNo ); itemView.Font = Resources::FontMedium; itemView.Color = #000000FF; // Ensure that the item has correct size. The position of the item will be // managed by the list component. itemView.Bounds.size = HorizontalList.ViewSize; } // Or is it an Image view? if ( Your_DataBase_Is_Image_Item( itemNo )) { // Now we know, it is an Image view. var Views::Image itemView = (Views::Image)HorizontalList.View; // Configure the Image view ... itemView.Bitmap = Your_DataBase_Get_Image( itemNo ); // Ensure that the item has correct size. The position of the item will be // managed by the list component. itemView.Bounds.size = HorizontalList.ViewSize; }
The example below demonstrates the implementation of a menu containing several different settings items. The different item types (switch, button, etc.) are implemented as separate item classes (as separate GUI components). The selection of the appropriate class is handled in the slot method associated to the list's property OnQueryItemClass. As explained above, the OnLoadItem slot method takes care then of the correct initialization of the item view according to its class. Additionally, the example implements a slot method associated to the list's property OnQueryItemWidth so the diferrent item types have also different width:
Please note, the example presents eventually features available as of version 12.00
Scroll the list items
With the Horizontal List you can easily scroll the displayed items. The scrolling is controlled by the property ScrollOffset. With positive values the items are scrolled to the right. With negative values to the left. The following example shows how this property affects the position of the displayed items within one and the same Horizontal List (the thin blue borders indicate the Horizontal List areas):
An alternative approach to scroll the items is to use the method EnsureVisible(). This method expects in its parameter an index of the item you want to expose within the Horizontal List. By calling this method, the entire list content is scrolled until the specified item has become visible.
Per default, calling EnsureVisible causes the items being scrolled instantly. By preparing a Change int32 effect and passing it in a parameter to the EnsureVisible() method, the scrolling can be performed with a smooth animation. The following Chora code demonstrates the approach:
// The item you want to ensure to be visible within the Horizontal List. var int32 itemNo = ...; // Create a new animation effect instance. var Effects::Int32Effect effect = new Effects::Int32Effect; // Configure the animation duration and the timing (easing) effect.CycleDuration = 250; // milliseconds effect.NoOfCycles = 1; effect.Timing = Effects::Timing.Exp_InOut; // Finally instruct the Horizontal List to scroll its items with // the prepared animation until the item with the index 'itemNo' is // fully visible. HorizontalList.EnsureVisible( itemNo, true, effect, null );
The method EnsureVisible() scrolls the items only when it is necessary. If the specified item is already fully visible, the method returns immediately. Please note the last parameter of the method, which is null in the above example. Using this parameter you can specify a slot method to send a signal to as soon as the scrolling has been finished.
Accordingly, your implementation of the slot method can perform operations to complete the task. For example, it is usual to suppress user inputs while performing animations in order to avoid any interferences. With the mentioned slot method you can restore the event handling again when the animation is done. The following example implements this approach:
Please note, the example presents eventually features available as of version 12.00
An alternative approach to control running animations is to use the methods IsScrollEffectActive() and StopScrollEffect(). With the method IsScrollEffectActive() you can easily check whether the affected Horizontal List is actually performing an animation activated by a preceding EnsureVisible() or AdjustList() method invocation. If such animation is active, the method returns true and your implementation can react to this situation. The method StopScrollEffect(), in turn, allows you to immediately finish the active animation. With method parameters you determine whether the effect should stop at its actual position or whether it should skip to the previously estimated end position.
Please note, to avoid any interferences, invoking the EnsureVisible() method stops the animation started eventually by the preceding EnsureVisible() or AdjustList() invocation.
Adjust the scroll position
When the boundary area of the Horizontal List view is enlarged or the number of items managed by the list shrinks, the current scroll offset remains unchanged. This can result in an undesired gap appearing just between the last item and the right edge of the list view. Depending on your application case, you will probably need to adjust the list so it entire area is filled with items as good as possible.
This can be achieved conveniently by calling the method AdjustList. The method verifies whether there is some gap between the last item and the right edge of the Horizontal List, which can be filled by simply scrolling the list. Similar is calculated with the eventual gap between the left edge of the view and the first item. In all cases, the method tries to calculate the minimal scroll adjustment so the undesired gaps disappear.
Per default, calling AdjustList causes the items being scrolled instantly. By preparing a Change int32 effect and passing it in a parameter to the AdjustList() method, the scrolling can be performed with a smooth animation. The following Chora code demonstrates the approach:
// Create a new animation effect instance. var Effects::Int32Effect effect = new Effects::Int32Effect; // Configure the animation duration and the timing (easing) effect.CycleDuration = 250; // milliseconds effect.NoOfCycles = 1; effect.Timing = Effects::Timing.Exp_InOut; // Finally instruct the Horizontal List to adjust the scrolling offset // with the prepared animation. HorizontalList.AdjustList( effect, null );
The method AdjustList() scrolls the items only when it is necessary. If there is no gap to fill with items, or the number of items is too less to fill the entire list view, the method returns immediately. Please note the last parameter of the method, which is null in the above example. Using this parameter you can specify a slot method to send a signal to as soon as the scrolling has been finished.
Accordingly, your implementation of the slot method can perform operations to complete the task. For example, it is usual to suppress user inputs while performing animations in order to avoid any interferences. With the mentioned slot method you can restore the event handling again when the animation is done. This approach is demonstrated in the example from the section above.
An alternative approach to control running animations is to use the methods IsScrollEffectActive() and StopScrollEffect(). With the method IsScrollEffectActive() you can easily check whether the affected Horizontal List is actually performing an animation activated by a preceding AdjustList() or EnsureVisible() method invocation. If such animation is active, the method returns true and your implementation can react to this situation. The method StopScrollEffect(), in turn, allows you to immediately finish the active animation. With method parameters you determine whether the effect should stop at its actual position or whether it should skip to the previously estimated end position.
Please note, to avoid any interferences, invoking the AdjustList() method stops the animation started eventually by the preceding AdjustList() or EnsureVisible() invocation.
Modulate the opacity of the displayed items
Using the property Opacity you can modulate the opacity of the displayed items, so that they appear semi-transparent even if they are originally opaque. The valid values for the property Opacity are 0 .. 255, whereby the smaller the value the more transparent the resulting items. For example:
Connect Horizontal List with a Slide Touch Handler
For your convenience the Core::HorizontalList class implements an interface for easy coupling Horizontal Lists with Slide Touch Handlers. In this manner, the user can scroll the content displayed within the list by simply touching the associated Slide Touch Handler. Assuming that you have already a Horizontal List in your GUI component, then:
★Follow the instructions to add a new Slide Touch Handler.
★Arrange the Slide Touch Handler so it covers the area of the destined Horizontal List.
★Assign the Slide Touch Handler to the property SlideHandler of the Horizontal List.
★If desired, configure the property SnapNext of the Slide Touch Handler with the width of an item, so that the handler will automatically stop at the borders between items.
TIP
If the items within the Horizontal List implement any touch handler, it is convenient to reorder the Slide Touch Handler just behind the Horizontal List in order to ensure, that the handler doesn't overlap any of the items managed by the Horizontal List. Otherwise the items will be suppressed in the processing of their touch events. See also Combine several touch handlers together.
Select an item within the list
With the property SelectedItem you can specify the index of the item to be considered as currently selected within the list. Depending on your application case, the affected item can adapt its appearance and e.g. be shown highlighted. Moreover, if the Horizontal List is currently focused for keyboard inputs, the selected item will also be able to handle the keyboard events. Initializing the property SelectedItem with the value -1 deselects the currently selected item without selecting another one.
The property SelectedItem is convenient, if you intend to present to the user a list with options and you want the currently selected option being shown highlighted. The user can scroll the list and select another option, if desired. Such list can e.g. present languages available on the device:
When the property SelectedItem is modified, the Horizontal List informs the GUI components displaying the affected items, that their selection state has changed. This causes the UpdateViewState methods of the components being called. In the implementation of this method you can evaluate the current selection state and depending on it show or hide any highlight decorations. More about the state management and the implementation of the UpdateViewState method is found in the chapter Managing component state.
The following example demonstrates how the property SelectedItem is used. It implements a simple language selection list. The user can scroll the list and select the desired language by simply taping on the corresponding item. The current language is highlighted as demonstrated in the screenshot above:
Please note, the example presents eventually features available as of version 12.00
Implement keyboard navigation
The above described property SelectedItem is ideal to implement lists which the user can control by using the keyboard. The property SelectedItem is used then to refer to the item which is currently selected. When the user presses a key e.g. Left or Right, the value of the property SelectedItem is decreased or increased causing the selection being moved accordingly. To implement such keyboard navigation:
★Follow the instructions to add a new Key Press Handler.
★Configure the property Filter of the Key Press Handler with the value Core::KeyCode.CursorKeys to react to cursor keys only.
★Add a new slot method to your GUI component.
★Assign the slot method to the property OnPress of the previously added Key Press Handler.
★Open the slot method for editing.
★In the Code Editor implement the following Chora code:
// Which item is currently selected? var int32 itemNo = HorizontalList.SelectedItem; // When the user presses the 'LEFT' or 'UP' key, get the index of the next // higher item if (( KeyHandler.Code == Core::KeyCode.Left ) || ( KeyHandler.Code == Core::KeyCode.Up )) itemNo = itemNo - 1; // When the user presses the 'RIGHT' or 'DOWN' key, get the item of the // item below. if (( KeyHandler.Code == Core::KeyCode.Right ) || ( KeyHandler.Code == Core::KeyCode.Down )) itemNo = itemNo + 1; // The upper or lower end of the list has been reached if (( itemNo < 0 ) || ( itemNo >= HorizontalList.NoOfItems )) return; // Select the item ... HorizontalList.SelectedItem = itemNo; // ... and eventually scroll the list, so the item is fully visible. HorizontalList.EnsureVisible( itemNo, true, null, null );
In particular, when controlling your device by the keyboard, it is interesting to know, that as long as the Horizontal List is focused for keyboard inputs the currently selected item is also able to handle the events. In other words, when you press a key, the corresponding event arrives also to the GUI component of the currently selected item.
The following example demonstrates the implementation of a Horizontal List with keyboard navigation:
Please note, the example presents eventually features available as of version 12.00
Arrange other views on the content of the Horizontal List
Sometimes it is necessary to know the position of individual items within the Horizontal List or to query which item lies at a given position. This can, for example, be the case when the user taps the list and you want to perform operation associated with the affected item.
The Core::HorizontalList class provides for such application cases various useful methods. The following table gives you a short overview of them:
Method |
Description |
---|---|
Returns the rectangular area containing one or more items specified in the parameters of the method. |
|
Returns the index of the item lying at the given position. The items are numbered starting with 0 (zero). If no item is found at the position, the method returns -1. |
|
Returns the rectangular area within the list view reserved for the padding specified in the property PaddingLeft. See also section below. |
|
Returns the rectangular area within the list view reserved for the padding specified in the property PaddingRight. See also section below. |
You can call those methods whenever your GUI component implementation requires the corresponding information. More sophisticated, however, is to join the update mechanism provided natively by the Horizontal List. Precisely, when you assign a slot method to its property OnUpdate, the slot method will receive postsignals every time the list is scrolled or changed. Accordingly, within the slot method you can react on this notification and e.g. arrange other views at the right position. The following steps describe how to do this:
★First add a new slot method to your GUI component.
★Assign the slot method to the property OnUpdate of the Horizontal List.
★Open the slot method for editing.
★In the Code Editor implement your desired arrangement algorithm by using the values returned from the above described Horizontal List methods.
Let's assume, you want to add a scrollbar to your Horizontal List. The position and the size of the scrollbar should reflect the current scrolling situation within the Horizontal List. For this purpose implement the slot method as follow. In this example, the scrollbar is represented by a simple Filled Rectangle view:
// Get the boundary area of the Horizontal List var rect viewRect = HorizontalList.Bounds; // Get the complete area enclosing all list items var rect contentRect = HorizontalList.GetItemsArea( 0, HorizontalList.NoOfItems - 1 ); // From the proportion between the both areas calculate the relative position // of the left and right end of the scrollbar. The max. width of the scrollbar // is the width of the Horizontal List itself (viewRect.w). var int32 x1 = (( viewRect.x1 - contentRect.x1 ) * viewRect.w ) / contentRect.w; var int32 x2 = (( viewRect.x2 - contentRect.x1 ) * viewRect.w ) / contentRect.w; // Limit the left end of the scrollbar if ( x1 < 0 ) x1 = 0; // Limit the right end of the scrollbar if ( x2 > viewRect.w ) x2 = viewRect.w; // Arrange the scrollbar, so it appears below the Horizontal List with 10 pixel // margin. The scrollbar itself is 10 pixel wide. Scrollbar.Bounds = rect( x1 + viewRect.x1, viewRect.y2 + 10, x2 + viewRect.x1, viewRect.y2 + 20 );
The following example demonstrates the implementation of the scrollbar:
Please note, the example presents eventually features available as of version 12.00
Add padding at the begin and at the end of the list
In some cases it is necessary to display a header or footer contents just before the first or after the last list item. The Horizontal List, however, is restricted to display items only. The solution is to reserve some space on the left of the first or/and on the right of the last item and within your GUI component arrange there the desired header or footer contents. The additional space is specifying in the properties PaddingLeft and PaddingRight. Both properties expect the space being expressed in pixel.
As soon as the property PaddingLeft is greater than 0 (zero), the Horizontal List calculates with an additional space just before the first item. This gap is considered as being part of the entire list. When the list scrolls the gap is scrolled with it. Similarly, when the property PaddingRight is greater than 0 (zero), the Horizontal List will manage an additional gap just after the last item. The following figure demonstrates how the settings of those properties affect the layout of the Horizontal List. The reserved padding areas are used then to accommodate header and footer contents:
Once configured the desired left or right padding, you can arrange in your component some header or footer contents to overlap the padding areas. Usually, you will implement for this purpose a slot method and assign it to the property OnUpdate as described above. The Horizontal List provides the methods GetPaddingLeftArea() and GetPaddingRightArea() you can call from the OnUpdate slot method in order to obtain the current position and the size of the respective padding area. Knowing the area, it is simple to arrange there the desired header or footer contents. Following is an example of how the OnUpdate slot method is implemented in such case:
HeaderComponent.Bounds = HorizontalList.GetPaddingLeftArea(); FooterComponent.Bounds = HorizontalList.GetPaddingRightArea();
Using this implementation, the header and footer components are updated automatically when the items in the list are scrolled. The additional components, however, don't belong to the Horizontal List. They are ordinary sibling views of it. Accordingly, unlike the items within the list, the header and the footer are permanently visible even if they are arranged outside the boundary area of the Horizontal List. As long as the Horizontal List occupies the entire space of the GUI component, this is unproblematic. However, if the list view is smaller, then you have to ensure that header or footer components disappear when they are scrolled outside the list view area.
This can be achieved by displaying the header and the footer components embedded within an Outline Box, which is configured to exactly overlap the Horizontal List. As soon as the header or footer components leave the boundary area of the Outline Box, they are clipped and not visible. To do this:
★Follow the instructions to add a new Outline Box to your GUI component.
★Arrange the Outline Box, so it exactly overlaps the Horizontal List.
★Embed the header and/or the footer components within the Outline Box.
Please note, that being individually specified by the properties PaddingLeft and PaddingRight, neither the header nor the footer have to correspond to the width of a regular list item. This can thus be used to manage within the list a kind of an additional leading and trailing item with different width even if the list is restricted to its items having the same width.
TIP
If you have connected the Horizontal List with a Slide Touch handler configured to automatically snap at the borders between two items, you should also specify the width of the left and right padding in the corresponding properties SnapFirst and SnapLast of the Slide Touch Handler. With it, the handler will stop at the borders between the header, footer and the regular items.
The following example demonstrates, how the additional header and footer are managed with a Horizontal List. This example uses also the Outline Box to clip header and footer as soon as these leave the area of the Horizontal List. The user has the impression, that the header and footer are regular list contents:
Please note, the example presents eventually features available as of version 12.00
Control the visibility of the Horizontal List
The Horizontal List itself is not visible, except in the Composer window, where it appears tinted with green color. The Horizontal List provides however a property Visible. This property affects the visibility of the items displayed within the list. Also the opacity may affect the resulting appearance of the items in the Horizontal List.
TIP
In the complete GUI application an individual view is visible on the screen only when all of its superior Owner components are visible too, the view itself does lie within the visible area of the superior components and the view is not covered by other sibling views nor components.