Dynamic List
Overview and characteristics
- The size of the list exceeds the limit to process/render all items in the view or just not known at design time in SceneComposer.
- The view sees the list items only as a fragment/subset of the original list.
- The items in the model may be inhomogeneous. In this case special behavior have to switch the presentation of the item in the view corresponding to the provided item data.
- The items in the view have homogeneous size.
- The list model is bound via Courier databinding.
- The list model has to be implemented in the Courier Model Component.
- The list view is generic. This means the list does not require to implement a specific data structure for the item data. Only a generic base class is required which can be achieved by a simple flag in data description XML file.
- (also for static list) The list view allows horizontal/vertical lists
- (also for static list) The list view allows to swipe (scrolling/ moving the list while touching and moving the finger slowly) / flick (fast move of the finger and releasing the touch screen result is an automatic scrolling of the list for a specified duration and a velocity that epends on the speed of the flick) the list by touch.
- (also for static list) The list view positioning is decoupled from the list view itself. This enables a customizable list navigation (e.g. scrollbar/sidebar)
- The list view requests a list fragment from the model
- The list model provides this list fragment (subset of the list at the requested start index and a specified buffer size). It has to be filled with data from external data sources (e.g. CAN bus or blutooth).
- The list model can provide a default list fragment to the view component before any specific view is loaded (can be done asynchronously if the model is deployed in a separate thread). This enables the view scene to immediately show the list content without the need to request the fragment first.
- The list view items can again be bound by Courier data binding to specific data items in special binding sources for data items. Those binding sources have to be marked in XML (dataContextItem="true").
Create a list in a binding source (all samples are included in cgi_studio_player)
- First of all create a new binding source or reuse an existing.
<bindingSource name="SampleBindingSource" access="asynchronous" uid="{5fb25ecc-8223-42b5-8f00-edce8fe042f3}">
</bindingSource>
- This binding source does not yet provide a list. Hence, we have to add a list item to that binding source.
<bindingSource name="SampleBindingSource" access="asynchronous" uid="{5fb25ecc-8223-42b5-8f00-edce8fe042f3}">
<list name="SampleList" uid="{1241d345-07af-46ec-b0cd-90a687c56140}" />
</bindingSource>
- By default this list will be able to support any fragment size that is requested. Hence, the fragment has not a fixed size capacity but a dynamically growing capacity. This implies the use of allocating memory with different block sizes.
- At this point we are already able to define the list binding itself in SceneComposer.
Create the List view binding to the list data model
- Hence, we create a new SceneComposer solution or use an existing one.
- Then we will create a new Scene (for CGI Studio 3.6.0 the list control is only available in 2D) or use an existing one.
- Drag a List control from the toolbox into the scene (Scene Editor or Scene Tree panel)
- Define the list size (e.g. 400x280). Otherwise the list will have an infinite size and will show as many items as you configure.
- Note: the list does not yet show any content because we have not yet configured any items.
- Now we will configure the binding for the list. You will see a gray chain symbol on the right side of the list control Items property
- Before we can define the binding we have to configure the application binding sources. Each application should have (like cgi_studio_player) a central XML file that refers to all included XML files (it is also the starting point the code generator).
- For CGI Studio 3.6.0 the solution has to be closed and reopened again to reload the data binding.
- If you hover the with the mouse over the chain icon a tooltip will show the current binding (a gray chain symbol means there is no binding defined).
- Press the chain symbol and select the binding in the data binding editor dialog. Then press the OK button.
- You will notice the blue colored chain symbol indicating that this property is bound to a data binding item.
- Hover again over this chain symbol until the tool tip appears. You will see the current binding.
Create the binding source for the list item
- Since we have not yet defined how the data items will look like we have to go back to the data binding description in the XML file and add a new binding source for the list item itself.
<bindingSource name="SampleListEntry" access="asynchronous" uid="{86f39d72-8d42-43eb-87e7-475cdf384734}">
</bindingSource>
- To mark it as a binding source that can be used as a list item we have to enable the attribute dataContextItem. DataContext items are a more generic concept in CGI Studio that can be used to define scene node local data context containing one or more local binding sources. The list item is an example that uses this concept to enable the binding of view list item properties to list data items.
<bindingSource name="SampleListEntry" access="asynchronous" dataContextItem="true" uid="{86f39d72-8d42-43eb-87e7-475cdf384734}">
</bindingSource>
- Now we have to add some data items to that binding source.
<bindingSource name="SampleListEntry" access="asynchronous" dataContextItem="true" uid="{86f39d72-8d42-43eb-87e7-475cdf384734}">
<item name="UInt64Value" type="FeatStd::UInt64" uid="{5cb01d1b-9b9b-4982-99b2-4c82a4823d16}" />
<item name="FloatValue" type="FeatStd::Float" uid="{7a55879f-0a01-41bf-9d96-d3048280b79f}" />
Create the List item view binding to the list item data model
- After we have defined the structure of the list item we have to configure the binding in SceneComposer.
- First we need the nodes for the item. Let us drag in a stack node from the nodes section of the toolbox and rename that node as Item_1.
- Then we drag a Text Value control and a Slider control into that Stack. Furthermore we change the Format Text property to
““Item %05d”05d” which will result in showing the text Item followed by a number that we later will bind to the UInt64 data binding item that we just added before.
- Now we will add a little margin for the slider on the left, top, right and bottom.
- So we will bind the Value property of the Text Value control to /SampleListEntry/UInt64Value like we did it before with the list itself.
- Further, we will bind the slider Value property to /SampleListEntry/FloatValue.
- Now we have finished the configuration of the binding for this item.
- For CGI Studio 3.6.0 the list has to contain the item views that may be visible at the same time. This means if you have a list that shows 6 Items when the first item is aligned with the top border of the list then we need an additional item to handle the list state where the first item is only partially visible.
- You can do the instantiation of the other 6 items by just creating clones of the first one. You will also notice the scrollbar getting visible because the 7th item is outside the visible area.
- We have finished the configuration of the scene. We can now generate the asset.
Handling the List model code
- First we have to generate the code for the binding sources. In cgi_studio_player this can be done by starting the command script: cgi_studio_player/src/generate.cmd
- You will find the list sample code of the model (DynamicListComponent.cpp and DynamicListComponent.h) located in this folder: cgi_studio_player/src/Player/SampleComponents
- So, what we do here is we provide a list fragment at the startup to enable the view as soon as possible to show the list content:
bool DynamicListComponent::OnMessage(const Courier::Message & msg)
{
bool rc = false;
switch (msg.GetId()) {
case Courier::StartupMsg::ID:
(*mSampleBindingSource).mVariantValue = FeatStd::Variant(10.0F);
LoadSampleListFragmentMissingItems(0, 30);
(*mSampleBindingSource).mSampleList.UpdateListItemCount(SAMPLE_LIST_SIZE);
mSampleBindingSource.MarkItemModified(ItemKey::SampleBindingSource::SampleListItem);
mSampleBindingSource.SendUpdate(true);
break;
- The call of LoadSampleListFragmentMissingItems simply initializes the list content of the default fragment.
- With the call of UpdateListItemCount we define the virtual size of the complete list.
- The call of MarkItemModified will mark the list as modified. Hence, only the list is updated by the SendUpdate call (which will post a message to the view).
- The handling of the UpdateModelMsg will do two things. The first one handles requests of new fragments from the list view. The second one handles modification of data items with the list items that are requested by the view (e.g. by the slider)
case UpdateModelMsg::ID: {
- When we handle new fragments we will receive the start index(request.NewIndex) the size (request.OldIndex) of the requested fragment. Hence, we will update the fragment with these values. We do not yet send the update message because other messages may require further modification of the model. Hence we will send the update in the OnExecute method where we can be sure that the modifications are complete.
case Courier::ListRequestType::NewFragment:
switch (request.ItemKey()) {
case ItemKey::SampleBindingSource::SampleListItem:
LoadSampleListFragmentMissingItems(request.NewIndex(), request.OldIndex());
break;
}
break;
- When we handle the change of an data item we will get the exact information which list has been modified, which item, which data item in the item and the new value. The model still can reject this modification. In both cases the model should send an update of that data item to either reset the list that requested the change and/or update other views that are bound to the list but not yet visible (NOTE: for fragment lists only one bound list is allowed to be active because only one fragment per list is handled in the model).
case Courier::ListRequestType::ChangeDataItem:
switch (request.ItemKey()) {
case ItemKey::SampleBindingSource::SampleListItem:
switch (request.InnerItemKey()) {
case ItemKey::SampleListEntry::FloatValueItem: {
const FeatStd::Float* value = request.GetItem<FeatStd::Float>();
SetSampleListTestListValue(request.NewIndex(), *value);
}
break;
}
break;
}
break;