This section introduces the generic Symbian OS UI framework using a simple example application. It describes the fundamental classes that form the application framework and compares the differences between an implementation of the filesystem browser application for S60 (3rd Edition) and for UIQ (3.0).
This section also discusses common strategies used to maximise code re-use when writing a GUI application to run on both UI platforms.
Now this tutorial has described what you need to start work on an application for S60 or UIQ, it will walk through a simple filesystem browser application ("FileBrowse") and explain some of the aspects of application development on these platforms. The example is a simple illustration of the fundamental application framework classes, working with application resource files, how to use a list box control and how to handle user input.
When the file browser application starts, it displays the 'C' and 'D' drives of the phone or emulator in a list box, giving the user to option to browse through the directories of either drive.
The user sees folders and files, which are distinguished by different icons, and may open directories to view their contents, but may not open files, receiving an information message if they try to do so.
On UIQ, the user navigates by tapping on a directory to open it, or uses
the up and down keys and the confirmation key to open the directory selected.
On S60, the user navigates using the joystick and may either open a selected
directory by pressing the confirmation key of the joystick, or the
'Open
' option from the left soft key menu.
When a new folder is opened, the application scans its content and the content in the list box is updated to reflect it.
The user navigates back up the directory hierarchy on UIQ or S60 by selecting the top 'go back' directory. In addition, on S60, the user may use the right soft key, to go back to the parent directory.
The classes described in this section provide the basic functionality of an application and provide the interface needed for the platform framework to drive the application. A quick summary of each of the classes follows; they will be described in more detail as the implementation of the filesystem browser application is discussed.
The core Symbian OS UI framework is called Uikon
. One of the
most important libraries within it is eikcore.dll
, which contains
the UI framework classes such as CEikApplication
,
CEikAppUI
and CEikonEnv
.
UIQ and S60 extend the the framework by adding libraries to provide platform-specific controls. The UIQ-specific library is called Qikon and the S60-specific library is called Avkon. You can view these as UI layers on top of the core Symbian OS UI. Each contains different components, appropriate to the platform in question, although, because they both have Uikon as a base, their APIs are often very similar.
When creating a UI application for either platform, you will derive from base classes supplied by the platform-specific libraries (which themselves usually derive from classes in the Symbian OS framework), and call framework functions specific to the platform. To do this, you will need to include the appropriate header files and link against libraries accordingly.
For example, for UIQ, you will link against qikctrl.lib
,
while for S60 the equivalent is avkon.lib
. Likewise for header
files; the header files to include for each of the application framework
classes are shown in the table below.
Every Symbian OS GUI application, regardless of the UI platform on which it is based, will use the application framework architecture, which means it will derive from a particular set of UI-specific classes: application, document, application UI and application view.
An object of this class is the first to be created by the GUI framework when the application starts. When instantiated, the object is used to identify the application, by returning its UID. It also creates the application’s document class.
The document class handles any persistent non-GUI data for the application and instantiates the application UI class.
The application UI handles UI events generated from the user’s actions (except low-level touch screen events and alphanumeric keyboard input). It also instantiates the application’s default view.
This is the root GUI control which activates the main application window and acts as a container for other controls used.
The following table shows the generic Symbian OS application framework classes, with the S60 and UIQ equivalents:
|
Note that the 'CEik
' prefix of the generic Symbian OS
classes is replaced with 'CQik
' for UIQ classes and
'CAkn
' for S60 classes. This convention is used throughout the UI
application framework, for classes, headers and libraries. Additionally, in
general, if a class does not have any prefix, such as CCoeControl
,
it is part of the generic Symbian OS set.
Note: If you're wondering why the classes are prefixed with
'CEik
' rather than 'CUik
', the reason is historical.
The original Symbian OS UI framework was called Eikon, in the days when Symbian
OS itself was known as 'EPOC'. The framework was renamed Uikon in a later
release of Symbian OS, but the class names themselves were not changed.
No. As the previous section described, although each of the platforms are based on the generic Symbian OS Uikon framework, S60 and UIQ each extend it differently. When you write an application for UIQ, you must use the UIQ specific classes to achieve the correct look and feel for the application. The resulting code will be different from that of an S60 application, which would use a set of classes, specific to S60. To appreciate the extent of the differences, compare the source code for the S60 filesystem browser which accompanies this tutorial with that for the UIQ version.
You can reduce the amount of work required to write an application to run on both S60 and UIQ platforms by separating the code into two parts:
platform-specific code for the application's user interface (which will be different for each platform)
generic code, which is commonly known as an “application engine”, which runs on both platforms.
The idea is to facilitate the code's portability simply by minimising the amount of code that will need to be ported. Typically, the application engine will need little or no modification while the application UI will need re-designing and re-coding. Splitting the code into an engine and UI also means that development can proceed in parallel, with separate specialist developers working on the engine and UI sections.
Not only can the engine code often be re-used without many changes; it can also be tested automatically on each platform by using generic console test code without UI components as described later in The filesystem browser engine class and console test code.
The split can be very distinct, with the engine forming a separate
component, typically a dll, or it can be more simple, such as putting the
implementation of the engine code in a separate source file, which can be
shared by every version of the application. The filesystem browser example uses
the second approach, because it has a single, very simple engine class,
CRFsEngine
, to illustrate the technique of splitting an
application into a UI and an engine. The engine class is described later in
The filesystem browser engine class.
The following diagrams show the class hierarchies for the filesystem browser application for both S60 and UIQ implementations.
This section will examine each of the application framework classes in more detail and relate them to the implementation of the filesystem browser application on both S60 and UIQ platforms.
Every Symbian OS application must implement the following functions which are called by the framework as it starts the application. The first one is a non-leaving function which creates a new instance of the application class. For the filesystem browser example application used in this tutorial, the code is as follows:
EXPORT_C CApaApplication* NewApplication()
{
return new CFileBrowseApplication;
}
This factory function is expected by the framework to have exactly this prototype. It constructs and returns an instance of the application class, or NULL if it cannot be instantiated.
The second function is the application’s entry point function, called
E32Main()
, which looks like this:
GLDEF_C TInt E32Main()
{
return EikStart::RunApplication(NewApplication);
}
E32Main()
calls EikStart::RunApplication()
,
passing as an argument a pointer to the factory function which creates an
instance of the application class.
The application class represents the properties that are the same for every instance of the application. This includes the information specified in the registration file, for instance the caption and the capabilities, and other information, for instance the UID.
At a minimum, it must also implement two functions:
AppDllUid()
, which returns the application’s UID
CreateDocumentL()
, which is a factory function for
creating an object of the document class. Note that, although the application
creates the document, the framework is responsible for destroying it.
The application class in both S60 and UIQ versions of the filesystem
browser is called CFileBrowseApplication
and its implementation is
identical in each. For AppDllUid()
:
TUid CFileBrowseApplication::AppDllUid() const
{
return KUidFileBrowseID; // Defined in filebrowseglobals.h
}
This function is called by the framework to get the application’s UID,
just after calling NewApplication()
. One of the reasons it needs
the UID is to check whether there is a running instance of the application that
it can switch to. The value returned must be the same as the value of the UID3
value specified in the mmp
file. Once the UID has been verified,
the framework calls the CreateDocumentL()
method, which is
implemented for the filesystem browser as follows:
CApaDocument* CFileBrowseApplication::CreateDocumentL()
{
return CFileBrowseDocument::NewL(*this);
}
As described above, the document class is constructed and returned by the
application’s CreateDocumentL()
function, which is called
internally by the application framework. It represents the data that relates to
a particular instance of the application, and typically owns the application's
engine.
In the filesystem browser example, the document class does not store any data but could be extended, for example, to store the current directory opened by the user. If the application is closed and then restarted later, this stored value can be used to open the directory previously viewed, rather than default to view the root directories of the C and D drives on startup.
To implement persistent application data, the document class should store
and restore the necessary information by overriding the StoreL()
and RestoreL()
methods, both of which are empty by default. Where
an engine provides functions to save and restore its data, the document would
call these.
Even if the application does not have savable data, an application must
still instantiate the document class, which should implement the
CreateAppUiL()
function, inherited from the document’s base
class, CEikDocument
. This function instantiates an object of the
application UI class. For both S60 and UIQ implementations of the filesystem
browser, the code is the same:
CEikAppUi* CFileBrowseDocument::CreateAppUiL()
{
return new(ELeave) CFileBrowseAppUi;
}
This function is called by the framework. Note that
CreateAppUiL()
only carries out first phase construction. In other
words, it does not call the app UI's ConstructL()
— the
framework is responsible for calling this. The framework will also take
ownership of the app UI object, so the destructor of the document class does
not need to destroy it.
The application UI class is constructed and returned by the document’s
CreateAppUiL()
function, described above. Its second phase
construction method, ConstructL()
is an important method. It is
called by the framework and should include a call to the base class second
phase constructor (either CQikAppUi::ConstructL()
for UIQ or
CAknAppUi::ConstructL()
for S60 – each of which ultimately calls
CEikAppUi::BaseConstructL()
). Among other things, the base class
constructor method will read the application’s resource file and create the
visible GUI elements.
The ConstructL()
method should also construct the
application view. For S60, the implementation is as follows:
void CFileBrowseAppUi::ConstructL()
{
CAknAppUi::ConstructL();
iBaseView = CFileBrowseBaseView::NewL(ClientRect());
iBaseView->SetMopParent(this);
AddToStackL(iBaseView);
}
For UIQ it differs slightly:
void CFileBrowseAppUi::ConstructL()
{
BaseConstructL();
CFileBrowseBaseView* baseView = CFileBrowseBaseView::NewLC(*this);
AddViewL(*baseView);
CleanupStack::Pop(baseView);
}
In the UIQ implementation, AddViewL()
registers the view
with the system and adds it implicitly to the control stack, which enables the
view to receive key and pointer input. The first view that is added becomes the
default view for the application.
In the S60 implementation of ConstructL()
, this is done by
calling AddToStackL()
explicitly (the view is removed from the
control stack in the application UI class destructor). The control stack has a
prioritised list of controls that should be offered key events to process, and
ensures that the events are offered to those controls in priority order. In
addition to controls from GUI applications, the stack also contains controls
associated with any front-end processors or active dialogs and handles debug
key combinations.
The S60 implementation also calls SetMopParent()
to set
itself as the parent of the view control object.
The ownership of the view is transferred to the app UI, and it is later unregistered and deleted automatically in the app UI's destructor. It is interesting to note that, in S60, the derived application UI class takes ownership of the view object and is responsible for deleting it, while in the UIQ implementation, the base class takes this responsibility and the destructor for the application UI class does nothing at all. The destructor for the S60 application UI class is as follows:
CFileBrowseAppUi::~CFileBrowseAppUi()
{
if (iBaseView)
{// Remove it from the control stack
RemoveFromStack(iBaseView);
delete iBaseView;
}
}
In S60, the application UI class also handles command events, which may
originate from a range of sources such as menu bars, or the soft keys. This is
done by implementing the HandleCommandL()
function:
void CFileBrowseAppUi::HandleCommandL(TInt aCommand)
{
ASSERT(iBaseView);
switch (aCommand)
{
case ECmdFileBrowseExit:
// Generated by the left soft key 'Exit' option
case EEikCmdExit:
// Generated by a system command to close the application
Exit();
break;
case ECmdFileBrowseOpen:
iBaseView->CmdOpenL();
break;
case EAknSoftkeyBack:
iBaseView->CmdBackL();
break;
default: // All other commands are passed to the base class
CAknAppUi::HandleCommandL(aCommand);
break;
}
}
Here a command is an instruction, identified by an integer value known as
a command ID. Command IDs are enumerated constants defined in header files that
are traditionally given the extension .hrh
. In the code shown
above, the commands ECmdFileBrowseOpen
and
ECmdFileBrowseExit
are defined by the filesystem browser example
code (in filebrowse.hrh
) while the other commands
(EEikCmdExit
, EAknSoftkeyBack
and all other commands
handled) are defined by the system.
In the S60 implementation, the UI framework calls
HandleCommandL()
when either of the menu options available from
the left soft key are selected – which defers to the application
view to handle the ‘Open
’ command and calls
Exit()
to handle 'Exit
'.
The right soft key generates an EAknSoftkeyBack
command (because it has been set up to do so in the resource file for the
application, as described in the section on
Filebrowse resource file for S60). The back command is also passed to the view class to handle. All
other commands are passed to the CAknAppUi
base class.
In UIQ, commands are usually handled by application views, which are discussed in the next section.
In a simple case, such as the filesystem browser which accompanies this
tutorial, there is a single view class which derives from the Uikon base class,
CCoeControl
. In more complex examples, it is common for an
application to use more than one view, in which case the application UI class
is responsible for creating and destroying the views, registering them with the
view server, adding them to the control stack, activating them, passing events
to the correct view for handling and setting a default view.
For both the S60 and UIQ implementations of the filesystem browser, the
CFileBrowseBaseView
view class is responsible for the following:
construction and control initialisation
updating data in the list box
handling list box control events
instantiation and ownership of the engine class,
CRFsEngine
. (Typically, in more complex multiple-view examples,
the document or application UI class will create and own the engine object).
The view class is where most of the divergence between code for the S60 and UIQ platforms can be observed. For this reason, the view class for each will be discussed separately.
In UIQ, views are the primary means of displaying data, and handling
commands and other user input events. Views can be configured and their initial
commands and controls set in resource files using a number of resource
structures, including QIK_VIEW_CONFIGURATION
,
QIK_VIEW
, and QIK_VIEW_PAGE
. Each view has a class,
derived from the framework class CQikViewBase
, which handles
construction and activation of the view, command handling, and use of the
view's controls.
A view object is constructed in several stages to minimise the startup
time of the application. The first stage constructs as little as possible. The
static factory function, NewL()
, follows the standard Symbian OS
two phase construction rules. The function instantiates the object, which calls
CQikViewBase
’s constructor, and then calls the second phase
ConstructL()
method, which calls
CQikViewBase::BaseConstructL()
to register the view with the view
server to allow navigation between applications.
The next stage of construction occurs in the
ViewConstructL()
method which is called the first time the view
needs to be activated. For applications which use multiple views, the
ViewConstructL()
method is valuable because it reduces application
start up time by only initialising views which are used immediately the
application starts. It also avoids wasting memory, particularly if some views
are not be used at all.
ViewConstructL()
will fully construct the view by reading
the view’s configuration resource structure and initialising its controls, in
the case of the filesystem browser, the list box.
CFileBrowseBaseView::ViewConstructL()
also constructs the engine
object, used to scan the filesystem, by calling
CRFsEngine::NewL()
.
The last stage is the initialisation of the view by
ViewActivatedL()
, which is called each time the view becomes
visible. This method can be used to re-populate the view with user data, if
necessary. For the filesystem browser, ViewActivatedL()
calls
ShowRootL()
which displays the C and D drives of the phone.
In UIQ, all views are uniquely identified by a view ID (a
TVwsViewId
object), consisting of the UID of the application and
an ID that uniquely identifies the view within the application. The view ID is
returned by CQikViewBase::ViewId()
, a pure virtual method which is
called for each view by CQikAppUi::AddViewL()
. All view classes
must implement this method.
In UIQ, a command can be located on a softkey, in a toolbar or in a menu,
depending on the interaction style of the phone. To allow for this flexibility,
commands are defined in a more abstract way, rather than being explicitly coded
as particular GUI elements such as menu items. This is usually done in a
resource file using the QIK_COMMAND_LIST
structure. Commands are
associated with views, and so are handled within view classes. This is done by
reimplementing the HandleCommandL(CQikCommand& aCommand)
method defined by CQikViewBase
. This looks something like this:
/**
Handles all commands in the view.
Called by the UI framework when a command has been issued.
The command Ids are defined in an .hrh file.
*/
void CHelloWorldView::HandleCommandL(CQikCommand& aCommand)
{
switch(aCommand.Id())
{
// Here you can take care of your view specific commands.
/**
case EFooCmd:
{
DoSomethingL();;
break;
}
*/
// Go back and exit command will be passed to the CQikViewBase to handle.
default:
CQikViewBase::HandleCommandL(aCommand);
break;
}
}
Note that, in UIQ, applications do not provide an exit command. When you
start an application, it remains running and persistent; you do not close it
but simply switch away from them to use other applications. It is useful though
to provide an exit command in debug builds only, to test that the
application exits cleanly without leaking memory. The UIQ implementation of the
filesystem browser does this in its r_filebrowser_commands
resource.
The S60 application view derives from CCoeControl
and is, in
effect, a control which itself owns a list box control which is used to display
the filesystem. The application view is fully constructed by static factory
function, NewL()
which instantiates the object and then calls the
second phase ConstructL()
method to perform any initialisation
which may leave.
void CFileBrowseBaseView::ConstructL(const TRect& aRect)
{
CreateWindowL();
iEngine = CRFsEngine::NewL();
SetUpListBoxL();
ShowRootL();
SetRect(aRect);
ActivateL();
}
The ConstructL()
method calls CreateWindowL()
to create the views associated window. When the filesystem engine object has
been constructed, by calling CRFsEngine::NewL()
and the list box
created and initialised, ConstructL()
calls SetRect()
to set the area on the screen that the view will occupy and
ActivateL()
to indicate that the view is ready to draw itself.
The code used to create and initialise the list box is described in the CFileBrowseBaseView implementation section below.
Many elements of a user interface, for instance dialogs, the button bar and menu panes, generally known as resources, are defined using a Symbian OS-specific resource language rather than directly in C++. Resources are expressed in text files which are then compiled into a compressed binary form using the Symbian resource compiler.
Resources are added to a project by including them within START
RESOURCE...END
blocks within the project's mmp
file, as
described in the section on mmp file syntax. The IDEs invokes the resource compiler as needed when you build
a project, and you can also compile resource files separately on the command
line using the abld resource
command. See
Building code using the command line for more details.
An advantage of being able to separate the resources from the C++ code is that the resource files can be edited separately, for instance by a localisation team, without any impact on the rest of the program, in particular, there is no need to recompile it.
The resource language syntax is documented in the Symbian OS Library: see Resource file source format.
The table below describes the resource files used in the filesystem browser. It is followed by section which describes in depth how resources are defined in the filebrowse resource file.
|
The following diagram summarises how each of the resource files are compiled and combined with other binary files, built from the application source code and bitmaps, to generate an installable application.
Each resource file must include a NAME
statement before the
first resource definition:
NAME SFSB
The four character long name identifies the resource file, so it must be unique among the resource files used by the application.
The following three unnamed resources are also required:
RESOURCE RSS_SIGNATURE { }
This lets you specify a version number, but can be empty as is the case for both S60 and UIQ implementations of the filesystem browser example.
RESOURCE TBUF { buf = ""; }
This is the application's default filename; this is only required for applications that need to load or save their data when opened or closed (called file-based applications), otherwise it can be an empty string,
RESOURCE EIK_APP_INFO {}
In S60, this can be used to specify the toolbar, menu bar and associated hot keys to use when the application is launched, as described below in filebrowseapp.rss in S60.
It was used in UIQ 2.1 but is no longer used in UIQ 3.0, although it must still be specified as an empty resource.
The rest of the resource file contains resource definitions in any order,
each prefixed with RESOURCE
to indicate a separate resource
structure. Since the filesystem browser application resource files for UIQ and
S60 are almost completely different, they will be discussed separately. The
only common factor in both resource files are the string resource definitions
which hold strings that are used in the C++ source code, for instance, the text
used in information messages. These are defined as follows:
RESOURCE TBUF r_filebrowse_driveC { buf = STRING_r_filebrowse_driveC; }
RESOURCE TBUF r_filebrowse_driveD { buf = STRING_r_filebrowse_driveD; }
RESOURCE TBUF r_filebrowse_infonote { buf = STRING_r_information_note; }
The resource strings to be displayed are not hard-coded into the resource
file, but defined in the separate localisation header file
(filebrowseapp.rls
) as follows:
rls_string STRING_r_filebrowse_driveC "Drive C:"
rls_string STRING_r_filebrowse_driveD "Drive D:"
rls_string STRING_r_information_note "Cannot open file"
An rls_string
is similar to a C++ #define
macro: for example, "Cannot open file" will be substituted in the resource file
wherever STRING_r_information_note is referred to. But an
rls_string
doesn't by itself define resources that you can use in
C++ code.
The UIQ platform supports the demands of different phone input and screen modes by using high level, global settings (UI configuration parameters) to define screen layout (landscape or portrait, screen size), touch screen, menu or soft key input and orientation. These UI configuration parameters combine to create the look and feel of different configuration modes. The platform pre-defines five modes: Softkey Style, Pen Style, Softkey Style Small, Softkey Style Touch and Pen Style Landscape. See the UIQ SDK or information on the UIQ website for more information.
In a resource file, the QIK_VIEW_CONFIGURATIONS
resource
structure is used to define which UI configuration modes an application
supports. If a mode is supported, the framework will automatically switch view
layout and commands when the window-server changes to that mode. The filesystem
browser supports the five modes and, for each, specifies the same view to be
displayed in a QIK_VIEW
structure, which itself identifies the
pages to display using QIK_VIEW_PAGES
.
For each page, the content is specified in a container, using the
QIK_CONTAINER_SETTINGS
resource structure, which can contain an
array of controls, each within a QIK_CONTAINER_ITEM_CI_LI
structure.
The filesystem browser view consists of a single control, a list box, whose behaviour is defined in the resource file as follows:
// Specifies controls and how they are laid out
RESOURCE QIK_CONTAINER_SETTINGS r_filebrowse_baseview_page_control
{
// Specifies layout settings
layout_manager_type = EQikRowLayoutManager;
layout_manager = r_row_layout_default;
// Specifies controls
controls =
{
// A reference to the listbox control
QIK_CONTAINER_ITEM_CI_LI
{
unique_handle = EFileBrowseListbox;
type = EQikCtListBox;
control = r_filebrowse_baseview_listbox;
layout_data = r_row_layout_data_fill;
}
};
}
// Layout manager
RESOURCE QIK_ROW_LAYOUT_MANAGER r_row_layout_default
{
default_layout_data = QIK_ROW_LAYOUT_DATA {};
}
// Layout parameters
RESOURCE QIK_ROW_LAYOUT_DATA r_row_layout_data_fill
{
vertical_alignment = EQikLayoutVAlignFill;
vertical_excess_grab_weight = 1;
}
// Resource that defines a listbox
RESOURCE QIK_LISTBOX r_filebrowse_baseview_listbox
{
layouts = { r_filebrowse_baseview_normal_layout_pair };
}
// Layout for listbox
RESOURCE QIK_LISTBOX_LAYOUT_PAIR r_filebrowse_baseview_normal_layout_pair
{
// standard layout
standard_normal_layout = EQikListBoxIconLine;
}
For more information about these resource structures, you should consult the appropriate sections of the UIQ SDK.
The contents of filebrowseapp.rss
for the S60 implementation
is significantly less complex, as it uses the approach of configuring the list
box control mostly in C++ code rather than using the resource file as in the
UIQ implementation. The list box is simply identified in the resource file as
follows:
RESOURCE LISTBOX r_filebrowse_baseview_listbox
{
flags = EAknListBoxSelectionList;
}
For more information about the LISTBOX
resource, see the
Uikon Resources reference in the
Symbian OS Library and the S60 SDK for information about the different list box
types provided by Avkon. All other list box customisation occurs in the code,
as described in the CFileBrowseBaseView implementation section below:
In either the S60 or UIQ versions of the application, the filesystem can be navigated by selecting items in the list box, moving back up the directory hierarchy using the first item in the list box.
In the S60 application, the user can either navigate up the directory hierarchy using the top 'back' directory, or the right soft key.
Additionally, the S60 application also has a menu which can be invoked using the left soft key, to open a directory or exit the application. The right soft key can be used to navigate back up the directory hierarchy. The resource file structures for this are as follows:
// Define command input and controls (menu and Control Button Array)
RESOURCE EIK_APP_INFO
{
menubar=r_filebrowse_menubar;
cba=R_AVKON_SOFTKEYS_OPTIONS_BACK;
}
// Top level menu resource - defines the menu pane used
RESOURCE MENU_BAR r_filebrowse_menubar
{
titles=
{
MENU_TITLE { menu_pane=r_filebrowse_menu; txt=""; }
};
}
// Defines the menu items that comprise a menu pane
RESOURCE MENU_PANE r_filebrowse_menu
{
items=
{
MENU_ITEM { command=ECmdFileBrowseOpen;
txt = STRING_r_filebrowse_loc_Open; },
MENU_ITEM { command=EAknCmdExit;
txt = STRING_r_filebrowse_loc_Exit; }
};
}
The MENU_PANE resource structure is used to add items to the menu (note
that the options are, again, defined using strings from the localisation header
file rather than hard-coded into the resource). A command is associated with
each option, which is defined in either the filebrowse.hrh
example
header file (ECmdFileBrowseOpen
) or the avkon.hrh
system header file (EAknCmdExit
). The menu pane is referenced by
the MENU_TITLE
section of the MENU_BAR
resource
structure. Note that the title of the menu, which is not used in S60, is left
blank.
The commands and labels associated with the soft keys are set up using
the cba
attribute of the EIK_APP_INFO
structure
(where cba stands for 'Control Button Array'). In the filesystem browser
application, a standard pair is used from avkon.rsg
, which sets
the left soft key label to "Options
" and the right soft
key to "Back
", which, when pressed, sends a
EAknSoftkeyBack
command to the HandleCommandL()
method of the application UI class.
The display for CFileBrowseBaseView
is quite
straightforward, consisting of a single list box which displays the
subdirectories and files of a particular directory in the phone's filesystem.
This section will examine some of the more important features of the view code
for both the S60 and UIQ implementations. For more information, consult the
App framework CONE section of the Symbian OS
Library, or the relevant sections of the UIQ or S60 SDKs.
A control is a rectangular area of the screen that can be drawn and can
handle key and pointer events. All controls are derived from
CCoeControl
. Some are intended as base classes for other controls,
while others are intended to be instantiated and used as they are. Application
developers can write their own concrete controls, or can use the ones supplied
by UIQ or S60.
In the filesystem browser, the application view is a control. It is known
as a compound control because it owns another control, the list
box, iListBox
, which displays files and directories.
The characteristics of the list box can be mostly specified in a resource file, as in the UIQ filesystem browser, or by settings in C++ code as in the S60 implementation. The list box handles the basics of input, such as highlighting the selected item, so you don't need to write code to do this.
In the S60 implementation, the list box is created and initialised by
CFileBrowseBaseView::ConstructL()
which calls a private method in
the view class, SetUpListBoxL()
, defined as follows:
void CFileBrowseBaseView::SetUpListBoxL()
{
iListBox = new (ELeave) CAknSingleGraphicStyleListBox();
iListBox->SetContainerWindowL(*this);
TResourceReader reader;
iEikonEnv->CreateResourceReaderLC(reader, R_FILEBROWSE_BASEVIEW_LISTBOX);
// Create the list box
iListBox->ConstructFromResourceL(reader);
CleanupStack::PopAndDestroy(); // reader
// Add this to observe the list box
iListBox->SetListBoxObserver(this);
SetupListIconsL();
// Add vertical scroll bars (which are visible when necessary)
iListBox->CreateScrollBarFrameL(ETrue);
iListBox->ScrollBarFrame()->SetScrollBarVisibilityL(CEikScrollBarFrame::EOff, \
CEikScrollBarFrame::EAuto);
}
The list box is created as an object of class
CAknSingleGraphicStyleListBox
and its container window and
observer object (both are the owning view class object) are set by calling
SetContainerWindowL()
and SetListBoxObserver()
respectively upon it. The scroll bars are configured to be invisible for
horizontal scroll and automatic for vertical scrolling, which means that they
are displayed when there are more entries in the list box than can be displayed
on the screen without scrolling.
The icons that the list box will display for a file or folder are created
in icon 'slots' in advance by the SetupListIconsL()
method (in
comparison to the UIQ implementation, which creates an object to represent the
icons each time they are required). For more information about list box
controls in S60 or UIQ, you should consult the SDK documentation for the
appropriate platform, or refer to the Additional resources section of this
tutorial.
Compound controls normally need to observe their components for events
such as when the user scrolls through the contents of a list box or clicks to
select an item. MQikListBoxObserver
is a UIQ interface designed
for handling list box-specific events, while the S60 implementation uses
MEikListBoxObserver
.
In both cases, the view sets itself as a list box observer by calling:
iListBox->SetListBoxObserver(this);
The list box observer interface specifies a single function called
HandleListBoxEventL()
, that the view class implements to handle
events generated when an item in the list box is selected, either by a pointer
tap (in UIQ) or key press selection.
Static()
All controls in a GUI application have an iEikonEnv
member,
which is a pointer to the GUI environment, CEikonEnv
.
CEikonEnv
provides many useful functions, for instance access to
standard fonts, formatting and colours, a connection with the file server, and
the ability to display information messages. Objects that are not controls can
get a GUI environment pointer by calling CEikonEnv::Static()
.
Views may be redrawn either by the system, or by the application. System-initiated redraws occur when the window is first created and when another window is moved away, exposing the control. Application-initiated redraws are needed when the application's data has changed, for instance, in the filesystem browser example, when an item is added to a list box.
CAknInformationNote
and InfoWinL()
The S60 filesystem browser uses an information note to indicate to the
user that they cannot open a file. The CAknInformationNote
class
is a pre-defined dialog class which requires no definition in the resource
file. It is used as follows
HBufC* noteBuf = StringLoader::LoadLC(R_FILEBROWSE_INFONOTE);
CAknInformationNote* note = new (ELeave) CAknInformationNote();
note->ExecuteLD(*noteBuf);
CleanupStack::PopAndDestroy(noteBuf)
This code follows a standard pattern:
Prepares a string to display in the information note. The static
StringLoader::LoadLC()
method looks up the string associated with
the resource identifier passed to it, then allocates a heap buffer to store and
return it. Note that in the .rss
file, resource IDs like
r_filebrowse_infonote
are specified using lower case, but when
they are used in the C++ code, as in the above code, they must be referred to
using upper case. This is due to a quirk in the resource compiler that builds
resource IDs into the generated .rsg
file as their upper case
equivalent.
Creates the information note object.
Calls ExecuteLD()
, passing in the string to display.
This displays the information note as a waiting dialog, which
means that the function executes synchronously and only returns when the dialog
is closed, either by the user or when the time it is to be displayed has
elapsed. For non-waiting dialogs, an alternative overload of
ExecuteLD()
displays the dialog then returns immediately, and
notifies the caller asynchronously when the dialog closes later.
Destroys the heap buffer.
The CAknInformationNote
dialog which indicates that the
file cannot be opened in the S60 version of the filesystem browser.
The UIQ filesystem browser uses a stock dialog common to all the Uikon
platforms to indicate that a file cannot be opened. The
CEikonEnv::InfoWinL()
call can display one or two lines of text in
a waiting information dialog which the user must dismiss before continuing to
use the application.
The information dialog resulting from a call
CEikonEnv::InfoWinL()
to indicate that the file cannot be opened
in the UIQ version of the filesystem browser.
The application has an icon and a caption associated with it in both the
main 'Applications' screen (used to navigate the installed applications
available on the smartphone) and when the application itself is running. The
application caption and compiled icon bitmap file (.mbm
) are both
specified in the localisable resource file described in the
Resources section.
If no images are provided, or the size is invalid, a default icon is substituted. This is the case in the filesystem browser application, which only supplies icons for the file and folder images displayed within the application.
The "FileBrowse" caption displayed for the filesystem browser and the default application icon for S60 (top) and UIQ (bottom).
Each icon actually consists of two bmp files, one for the image itself and the other a mask for the bitmap. The mask is used to give a transparent effect; for every black pixel in the mask, the corresponding image pixel is displayed. For any other coloured mask pixel, the equivalent image pixel is not displayed, which results in image transparency. For the filesystem browser, the same mask image is used for both the file and folder icons.
Icon bitmaps are typically stored in a project's gfx
(S60)
or images
(UIQ) directory. They should be specified in the project
definition file, as described in the mmp file syntax section.
For more information see the Defining application icons, captions and properties section of the Symbian OS Library.
The filesystem browser engine class, CRFsEngine
, is a very
simple example to illustrate the use of generic engine code with
platform-specific application UI code. The class is defined (in
rfsengine.h
) and implemented (in rfsengine.cpp
) by
both S60 and UIQ projects but, in fact, the files are identical and a single
copy could be shared by both implementations. If engine code is more complex,
it is often desirable to implement it as a separate dll which each version of
the filesystem browser application can link against. However, in this case, the
class is simply a wrapper around access to the Symbian OS file server, through
the file server session class RFs
. The class wraps a call to
RFs::GetDir()
to get the contents of a directory.
For more information about using the Symbian OS filesystem, consult the File Server Client Side documentation in the Symbian OS Library.
The Symbian OS console can be used to test application engine code, even
before the UI which uses it has been written. The filesystem browser example
includes test code for the CRFsEngine
class, under the
\test
directory. The .mmp
file for the console test
is as follows:
TARGET FileBrowseTest.exe
TARGETTYPE exe
UID 0
SOURCEPATH .
SOURCE FileBrowseTest.cpp
// Include the code for CRFsEngine which will be tested
SOURCEPATH ..\src
SOURCE rfsengine.cpp
USERINCLUDE .
SYSTEMINCLUDE \epoc32\include ..\inc
LIBRARY euser.lib efsrv.lib
There are several differences between this file and the .mmp
file for a typical GUI application:
UIDs are only relevant for applications launched from within the GUI environment; so, although the UID line must be included, its value can be zero
because the console application doesn't have a user interface, or things like an icon or caption, it does not need any resource files or a registration file
the console test only links against the core Symbian OS user library
(euser.lib
) and filesystem library (efsrv.lib
) since
it does not use any of the UI framework base classes or supporting code.
The implementation of the E32Main()
entry point of the
executable is as follows. The numbered lines are explained in more detail
below.
GLDEF_C TInt E32Main() // 1
{
__UHEAP_MARK; // 2
// Create the cleanup stack
CTrapCleanup* cleanup = CTrapCleanup::New(); // 3
TRAPD(result, CallExampleL()); // 4
__ASSERT_ALWAYS(result==KErrNone, User::Panic(KFileBrowseTest, result)); // 5
// Destroy the cleanup stack
delete cleanup; // 3
__UHEAP_MARKEND; // 2
return 0;
}
1. GLDEF_C
(for "Global definition") is an empty macro.
It is only used to denote entry point functions such as this global function.
2. __UHEAP_MARK
and __UHEAP_MARKEND
are
debug-only macros that are used to check that the program does not leak memory.
In debug builds, if there is a difference in the number of heap cells allocated
between the __UHEAP_MARK
and the __UHEAP_MARKEND
, the
code will panic. During development of any type of application, these macros
can be used around your code to test whether it deallocates memory properly.
Note that the GUI framework calls all the code in an application,
from startup to exit, within its own pair of __UHEAP_MARK
and
__UHEAP_MARKEND
macros.
3. Console programs must provide a cleanup stack for for themselves.
CTrapCleanup::New()
is used to create an instance of the cleanup
stack. It should be deleted before the console program closes.
4. As described in The cleanup stack and leaves, TRAPD
is a trap harness macro.
The code calls the test method called CallExampleL()
and if it
leaves, stores the resulting leave code in the variable
calledresult
(which TRAPD
declares as a
TInt
). If CallExampleL()
doesn't leave, then
result
will be set to KErrNone
.
5. __ASSERT_ALWAYS
is one of two assertion macros which
are the standard way of asserting that a condition is true, and invoking a
panic function if it is not.
__ASSERT_ALWAYS
assertions are called in both debug and
release builds to prevent release code from continuing after an unrecoverable
error. __ASSERT_DEBUG
are only called in debug builds and are
useful for tracking down fatal bugs during development. The use of debug
assertions is strongly recommended to test the internal consistency of your
code logic.
The code for CallExampleL()
is as follows. It creates a test
console using the CConsoleBase
class which is defined in
e32cons.h
:
LOCAL_C void CallExampleL()
{
console = Console::NewL(KFileBrowseTest, TSize(KConsFullScreen, KConsFullScreen));
CleanupStack::PushL(console);
RunTestsL(); // Runs the tests on class CRFsEngine
CleanupStack::PopAndDestroy(console); // close and clean up the console
}
The code shown is "boiler plate code" which any test console can use.
Test code for a class or library should then be implemented within the
RunTestsL()
method, as you can see within the
FileBrowseTest.cpp
file for the filesystem browser engine's test
code.