|
|
|
This guide explains the UIQ control List Box (CQikListBox).
The List Box is used to display lists ranging from straight forward text lists to more
advanced lists with items made up of any combination of icons, thumbnails and texts.
The List Box can be seen throughout UIQ in numerous places, from the icon and grid views in the Application Launcher and the Control Panel to the list views in Agenda, Contacts and Messaging.
The control consists of two major parts, the List Box itself and the List Box Model. The List Box is the CCoeControl
which displays the data, while the Model contains the data itself. You add and remove data items from the Model. The
layout of an item is decided by which Layout is connected to it.
See the API documentation for List Box (CQikListBox).
The List Box inherits from CCoeControl.
This section explains how the control is constructed, used and destroyed. Source code examples are used and explained to illustrate how the List Box control is used.
There are a number of includes that are needed for the listbox. They all contain QikListBox. The most common ones are:
// The main List Box class
#include <QikListBox.h>
// Interface to the List Box model
#include <MQikListBoxModel.h>
// Interface to a data item in a model
#include <MQikListBoxData.h>
// Interface to implement a List Box observer
#include <MQikListBoxObserver.h>
Use the following LIBRARY directive in the project's mmp-file:
LIBRARY qiklbx.lib
Use the following control identifier when specifying the control in resource data files. It is used by the framework when constructing the control from resource data:
EQikCtListBox
The List Box can be constructed using data in a resource file. Use the
QIK_LISTBOX
struct defined in
QikListBox.rh. The most important parts
of that structure are:
STRUCT QIK_LISTBOX
{
BYTE type = EQikListBoxDefault;
LLINK view = 0;
LLINK layouts[];
STRUCT item_data[];
}
Default values for many the List Box resource structs are set to EQikListBoxDefault.
If you leave any values out, as we have done in this example, the default values will be loaded internally in the List Box.
The most important thing to remember is that you must specify at least one layout for any given listbox; no default layouts are added. Failing to do so raises a panic.
The most common elements in the struct:
type: The type of listbox to be created. The default is set to EQikRowListBox. Other
types can be found in TQikListBoxType.
view: Use view if you want to change any settings in the List Box View (Row View, Grid View, and Custom View).
You must link to an appropriate struct, QIK_LISTBOX_ROW_VIEW
for a Row View and QIK_LISTBOX_GRID_VIEW for a Grid View. If you
do not want to change any settings in the view, the default value 0 will load the defaults for that view.
layouts: A List Box needs at least one entry in layouts to work. layouts is an array of
QIK_LISTBOX_LAYOUT_PAIR
structs that defines the layouts available
for data items in a List Box. A data item must either use a specific layout existing in the array layouts[]
of layouts in the List Box, or use the default set in the List Box.
item_data: The List Box can be filled with data directly from the resource file, for instance, for List Boxes
with a fixed number of predefined items.
STRUCT QIK_LISTBOX_LAYOUT_PAIR
{
LONG standard_normal_layout = EQikListBoxDefault;
LONG standard_highlight_layout = EQikListBoxDefault;
LLINK custom_normal_layout = 0;
LLINK custom_highlight_layout = 0;
}
A List Box needs one or more layouts to function. These layouts come in pairs, where the second one, the highlight, can be omitted. If the second one is omitted, a highlighted item uses the same kind of layout as a non-highlighted item.
A layout can either use a standard layout, or a custom layout defined in a resource file. The standard
layouts are found in TQikListBoxStandardLayout.
For a given layout, normal or highlight, you must choose between using a standard layout and a custom layout. You cannot use both types for the same layout. However, you can mix and match by, for example, having a standard normal layout and a custom highlight layout.
standard_normal_layout is left out if you want to use a custom layout. Otherwise, it uses one of the enumerations from TQikListBoxStandardLayout. For example, EQikListBoxLine is a simple one-row-high text field.
standard_highlight_layout is left out if you want to use a custom layout. Otherwise, it uses one of the enumerations from TQikListBoxStandardLayout.
The highlight layout can be omitted but the normal layout is mandatory. When the highlight layout is omitted, highlighted items use the normal layout.
A common use for the highlight layout is to create a highlighted item that is larger than a normal item. Using EQikListBoxLine for the normal layout while using EQikListBoxTwoLines for the highlight layout gives the user two lines of text in the highlight item which makes it possible to present more information. A good example of usage of a highlight layout is found in the Contacts list view.
custom_normal_layout links to a QIK_LISTBOX_LAYOUT resource struct.
custom_highlight_layout links to a QIK_LISTBOX_LAYOUT resource struct.
A common way to construct controls is to specify them in the resource files and let the framework construct them from there. Specifying the controls in resource files is the preferred way of constructing controls since it allows for easier modifications compared to creating them entirely from source code.
This section covers different ways of constructing a List Box.
The example below describes how to construct a List Box using the view framework. The view supports both Pen style and Softkey style; support of both styles in a view is optional. List Box specific resources are described in 4) and 5).
1) Declare an enumeration for the controls to be used in the view in an .hrh file. Hrh files are files to be included both in resource files (*.rss) and C++ files:
/* Declare the controls' IDs in an .hrh file for use both in resource and cpp */
enum TListBoxControl
{
EMyViewListBox,
EMyViewPage,
EMyViewNumberOfControls
};
2) Declare the view and page to be used in your resource (*.rss) file:
RESOURCE QIK_VIEW r_my_listbox_layout
{
pages = r_my_listbox_layout_pages;
}
RESOURCE QIK_VIEW_PAGES r_my_listbox_layout_pages
{
pages =
{
QIK_VIEW_PAGE
{
page_id = EMyViewListBox;
page_content = r_my_listbox_page_control;
}
};
}
3) Define your Page in your resource file:
// The page contains a List Box that fills the entire application space.
RESOURCE QIK_CONTAINER_SETTINGS r_my_listbox_page_control
{
layout_manager_type = EQikRowLayoutManager;
layout_manager = r_row_layout_default;
controls =
{
QIK_CONTAINER_ITEM_CI_LI
{
unique_handle = EMyViewListBoxCtrl;
type = EQikCtListBox;
control = r_my_listbox_listbox;
// This makes the List Box fill the entire application space.
layout_data = r_row_layout_data_fill;
}
};
}
4) Define your List Box in your resource file:
#include <QikListBox.rh>
RESOURCE QIK_LISTBOX r_my_listbox_listbox
{
layouts = { r_my_listbox_normal_layout_pair };
}
5) Define at least one layout pair in your resource file:
#include <QikListBoxStandardLayouts.hrh>
RESOURCE QIK_LISTBOX_LAYOUT_PAIR r_my_listbox_normal_layout_pair
{
standard_normal_layout = EQikListBoxLine;
}
6) Define the UI configurations in your resource file:
RESOURCE QIK_VIEW_CONFIGURATIONS r_my_listbox_ui_configurations
{
configurations =
{
QIK_VIEW_CONFIGURATION
{
ui_config_mode = KQikPenStyleTouchPortrait;
view = r_my_listbox_layout;
command_list = r_my_listbox_commands;
},
QIK_VIEW_CONFIGURATION
{
ui_config_mode = KQikSoftkeyStylePortrait;
view = r_my_listbox_layout;
command_list = r_my_listbox_commands;
}
};
}
7) The command list for the view:
RESOURCE QIK_COMMAND_LIST r_my_listbox_commands
{
items=
{
QIK_COMMAND
{
id=EEikCmdExit;
type=EQikCommandTypeScreen;
// Indicate that it will only be created in debug builds
stateFlags=EQikCmdFlagDebugOnly;
text="Close (debug)";
}
};
}
8) Define the Layout manager for the view in your resource file:
RESOURCE QIK_ROW_LAYOUT_MANAGER r_row_layout_default
{
default_layout_data = QIK_ROW_LAYOUT_DATA {};
}
RESOURCE QIK_ROW_LAYOUT_DATA r_row_layout_data_fill
{
vertical_alignment = EQikLayoutVAlignFill;
vertical_excess_grab_weight = 1;
}
9) The view framework constructs the view described in this example with this code:
void CListBoxListView::ViewConstructL()
{
ViewConstructFromResourceL(R_MY_LISTBOX_UI_CONFIGURATIONS);
}
10) The resource above is very similar to the Easy List Box example application, which looks like this:
You can create a List Box directly from code, in the same manner as above, but still make use of resources to simplify creation.
You can create List Boxes directly from code, without using resource files. Be sure to add at least one
layout pair to the List Box if you create it with ConstructL as it will not add any layout pairs on
its own. It is usually easier to construct List Boxes with ConstructFromResourceL.
The List Box can be constructed from resource files in dialogs as well.
To construct a dialog from resource, a valid resource definition of that dialog
must be in one of the project's resource files. CEikDialog is deprecated and it
is recommended to use either CQikSimpleDialog
or CQikViewDialog as described in
UIQ Controls - Dialog.
A List Box must be the only focusable control in a dialog to function on a softkey phone. It is possible to have several List Boxes in the same dialog by using the new View-dialog There should be a link to the View Dialogs here and having each list box on a separate tab page. However, if you use tab pages, you cannot use the arrow functionality, as seen in Messaging and Contacts as well, because the arrow keys are already used to switch tabs on a softkey phone.
An example of a dialog resource containing the control is given below.
For more information about the dialog class and its resource struct see CEikDialog
and DIALOG in the API documentation.
1) Declare a dialog resource containing the List Box control:
RESOURCE DIALOG r_my_listbox_dialog
{
title = "List box title";
flags = EEikDialogFlagWait | EEikDialogFlagNotifyEsc;
items =
{
DLG_LINE
{
type = EQikCtListBox;
id = EMyDialogListBoxId;
itemflags = EQikDlgItemHideHighlight|EQikDlgItemShareAvailableHeight;
control = QIK_LISTBOX
{
layouts = { r_my_listbox_layout_pair };
};
}
};
}
RESOURCE QIK_LISTBOX_LAYOUT_PAIR r_my_listbox_layout_pair
{
standard_normal_layout = EQikListBoxLine;
}
EQikDlgItemHideHighlight hides the dialog highlight, as the List Box contains a highlight of its own.
EQikDlgItemShareAvailableHeight makes the List Box use up the rest of the height in the dialog. List Boxes
do not have any useful output from MinimumSize(), which is why the List Box will have an incorrect size
if you forget these flags.
2) Launch the dialog using the following source code. The dialog resource ID is passed as an argument:
CEikDialog* dlg = new (ELeave) CEikDialog();
dlg->ExecuteLD(R_MY_LISTBOX_DIALOG);
The function returns immediately if EEikDialogFlagWait has not
been specified in the dialog resource. If EEikDialogFlagWait is specified,
it returns when the dialog exits. The dialog framework will, in both situations, delete the
dialog appropriately as indicated by the D suffix of the ExcecuteLD function name.
3) The result should look similar to the Control Panel item for Themes:
This section covers the most common functions used for interacting with the control.
When constructing the control with resource data, no reference to the control is available in the view class.
When constructing the control with code, the preferred way might be to not save a reference to the control.
In both these cases, the LocateControlByUniqueHandle function
is used to get a pointer to the control by supplying the control's unique handle. When constructing the view and the control
from code, you must explicitly set this unique handle by calling the method SetUniqueHandle.
See the code examples below.
Note that the function will return NULL if the control could not be found.
Always check the pointer before using it!
// Set the unique handle
lbx->SetUniqueHandle(EMyViewListBox);
// Get a pointer to the List Box control
CQikListBox* lbx = LocateControlByUniqueHandle<CQikListBox>(EMyViewListBox);
if(lbx)
{
// Use lbx.
}
After you have created your List Box you can get the model by calling CQikListBox::Model()
which returns an MQikListBoxModel&. Using this, you can either retrieve a data object to
inspect or modify, or add/remove items. Clearing the whole List Box can be done with RemoveAllDataL(),
but be sure to not update your List Box by first clearing it and then repopulating it. Only add, remove, or update
the items needed.
The general pattern for updating a List Box is to get the reference to the Model, call the Begin function, do your adds and/or removes, and end with the End function. Do not call other parts of the system inside the Begin/End and make sure to not Begin in one active object call and End in another. The List Box will update itself when the End function is called, and be sure to balance your cleanup stack as the Begin puts items on it, which End removes. The following is an example of a Model update.
CMyClass::UpdateListBox()
{
// Get a pointer to the List Box control
CQikListBox* lbx = LocateControlByUniqueHandle<CQikListBox>(EMyViewListBox);
if(lbx)
{
MQikListBoxModel& model(lbx->Model());
model.ModelBeginUpdateLC(); // Something has now been put on the Cleanup Stack
// Do your adds/removes/etc here
model.ModelEndUpdateL(); // This updates the List Box and removes the items on the Cleanup Stack added by ModelBeginUpdateLC
}
}
Adding an item to the Model is done by creating a new item in the Model and then filling it in. When you
create a new item, the Model returns a pointer to an MQikListBoxData interface which is
reference counted. This effectively means that you have to call Close() on it when you are
done with it, or else it is not deleted when it is longer needed by anyone, which results in a
memory leak.
One way of making it easier for you to remember to Close() the data object is to put a
Cleanup Item on the Cleanup Stack. There is already a function in Symbian OS that can do this for you,
CleanupClosePushL(), or you can let Model do it for you. When you are done with the Data object
you can simply call CleanupStack::PopAndDestroy().
CMyClass::UpdateListBox(const TDesC& aText)
{
// Get a pointer to the List Box control
CQikListBox* lbx = LocateControlByUniqueHandle<CQikListBox>(EMyViewListBox);
if(lbx)
{
MQikListBoxModel& model(lbx->Model());
model.ModelBeginUpdateLC(); // Something has now been put on the Cleanup Stack
MQikListBoxData* data = model.NewDataLC(MQikListBoxModel::EDataNormal); // Puts an item on the Cleanup Stack using CleanupClosePushL()
data->AddTextL(aText, EQikListBoxSlotText1); // EQikListBoxSlotText1 is a slot in a layout. In this case the one and only text slot in EQikListBoxLine
CleanupStack::PopAndDestroy(data); // Does a Close() on data
model.ModelEndUpdateL(); // This updates the List Box and removes the items on the Cleanup Stack added by ModelBeginUpdateLC
}
}
A common way to fill a List Box is to take objects in your application's data format and loop through them to add to the List Box. An example which does that is as follows:
CMyClass::UpdateListBox(const RArray<TYourDataFormat>& aDataArray)
{
// Gets a pointer to the List Box control
CQikListBox* lbx = LocateControlByUniqueHandle<CQikListBox>(EMyViewListBox);
if(lbx)
{
MQikListBoxModel& model(lbx->Model());
model.ModelBeginUpdateLC(); // Something has now been put on the Cleanup Stack
const TInt count = aDataArray.Count();
for(TInt i=0; i<count; i++)
{
AddMyDataL(aDataArray[i]);
}
model.ModelEndUpdateL(); // This updates the List Box and removes the items on the Cleanup Stack added by ModelBeginUpdateLC
}
}
CMyClass::AddMyDataL(const TYourDataFormat& aData)
{
MQikListBoxData* data = model.NewDataLC(MQikListBoxModel::EDataNormal); // Puts an item on the Cleanup Stack using CleanupClosePushL()
data->AddTextL(aData.ExampleDes(), EQikListBoxSlotText1); // EQikListBoxSlotText1 is a slot in a layout. In this case the one and only text slot in EQikListBoxLine
// Copy the rest of aData into data as well
CleanupStack::PopAndDestroy(data); // Does a Close() on data
}
To remove just one single item you can call CQikListBox::RemoveItemL(TInt aItemIndex) which removes
it from the model and wraps the calls to Begin and End. But if you want to remove more than one item, you should
get the Model and remove all of them inside the Begin/End. This will ensure that there is only one single update
at the end and it is done in not only the fastest possible way, but also in the most future proof way, preparing for the
number of transition effects on the List Box to increase.
// aRemoveArray should be sorted from low to high
CMyClass::RemoveItemsInListBox(const RArray<TInt>& aRemoveArray)
{
// Get a pointer to the List Box control
CQikListBox* lbx = LocateControlByUniqueHandle<CQikListBox>(EMyViewListBox);
if(lbx)
{
MQikListBoxModel& model(lbx->Model());
model.ModelBeginUpdateLC(); // Something has now been put on the Cleanup Stack
// Remember to remove the data from the end and forward, to ensure that the indexes are correct
for(TInt i=aRemoveArray.Count() - 1; i>=0; i--)
{
// Removes the data from the model and closes it. It will not be destroyed until everyone has closed all their pointers.
model.RemoveDataL(aRemoveArray[i]);
}
model.ModelEndUpdateL(); // This updates the List Box and removes the items on the Cleanup Stack added by ModelBeginUpdateLC
}
}
To remove all items in a List Box, you can either call CQikListBox::RemoveAllItemsL() or
MQikListBoxModel::RemoveAllDataL(), which both wrap the Begin/End.
To update your data you have to retrieve it from the model, update it and inform the model that you have updated it. For example:
CMyClass::UpdateListBox(const TDesC& aText, TInt aIndex)
{
// Get a pointer to the List Box control
CQikListBox* lbx = LocateControlByUniqueHandle<CQikListBox>(EMyViewListBox);
if(lbx)
{
MQikListBoxModel& model(lbx->Model());
model.ModelBeginUpdateLC(); // Something has now been put on the Cleanup Stack
MQikListBoxData* data = model.RetrieveDataL(aIndex); // The data object is now Open()
CleanupClosePushL(data); // And now on the Cleanup Stack so Close() will be called if a leave occurs
// EQikListBoxSlotText1 is a slot in a layout. In this case the one and only text slot in EQikListBoxLine
data->SetTextL(aText, EQikListBoxSlotText1); // This replaces the old text
CleanupStack::PopAndDestroy(data); // Does a Close() on data
model.DataUpdatedL(aIndex); // Tells the List Box that this data item has been updated
model.ModelEndUpdateL(); // This updates the List Box and removes the items on the Cleanup Stack added by ModelBeginUpdateLC
}
}
You can either use void MQikListBoxModel::Sort(TLinearOrder<MQikListBoxData> aOrder) or
void CQikListBox::SortL(TLinearOrder<MQikListBoxData> aOrder). The CQikListBox
version wraps the calls to Begin/End, so it cannot be used within an update (since Begin/Ends cannot be nestled
in the List Box Model). So if you want to update the Model, and then sort it, choose the version in the
Model instead and run the sort just before the End.
The algorithm used in the standard Model that comes with the List Box uses User::QuickSort. This is a so called instable sort routine. The best way to avoid problems because of this, unless you want to
implement your own sort routine, is to write a very precise compare that compares as many criteria as possible.
To be notified of List Box events you must create an observer for
the List Box. Create an observer object, by creating a class that inherits
from MQikListBoxObserver and
add it to the List Box with CQikListBox::SetListBoxObserver().
The observer then receives events sent to the HandleListBoxEventL member function.
The following source code is an example of how to add an object as an observer
and how to receive events from the List Box. In this example, CMySinglePageView
inherits from MQikListBoxObserver, but you can let any class inherit from it.
You can only have one observer at a time.
It is very important to not alter the List Box from within the HandleListBoxEventL as it is called from the List Box itself. This includes manipulating the Model.
void CMySinglePageView::ViewConstructL()
{
// Construction code
…
// Adding this object as an observer
lbx->SetListBoxObserver(this);
}
void CMySinglePageView::HandleListBoxEventL(CQikListBox* aListBox, TQikListBoxEvent aEventType, TInt aItemIndex, TInt aSlotId)
{
switch(aEventType)
{
case EEventItemTapped:
break;
case EEventItemHighlighted:
break;
case EEventItemConfirmed:
break;
case EEventSelectionChanged:
break;
case EEventMatchBufferChanged:
break;
case EEventTopReached:
break;
case EEventBottomReached:
break;
case EEventEmptyListBoxActioned;
break;
case EEventHighlightMoved:
break;
case EEventSlotIndexChanged:
break;
case EEventDimmedItemConfirmedAttempt:
break;
default:
break;
}
}
For more details on the TQikListBoxEvent type,
see MQikListBoxObserver in the API documentation.
Subclassing the List Box is not recommended for anything but simple non-graphical modifications.
It is possible to subclass CQikListBoxViewBase for writing custom List Box Views, but this is a very
advanced subject and is not handled in this guide. It is recommended to use the Row or Grid View instead of a
Custom view and to use custom Layouts and Ornaments for specialized List Box looks. See
MQikListBoxLayoutOrnament for more information. MQikListBoxLayoutOrnament doesn't actually include any information right now, this has to be rectified