Application Architecture

The term application architecture refers to the collection of application framework classes that collectively make up an application. Applications can have slightly different architectures, based upon their required UI designs. These variations are covered in detail later, in the Designing an Application UI subsection, which describes the merits and drawbacks of each and guides you towards choosing the design that is most applicable for your application, based on clearly defined UI requirements. Regardless of these UI design variations, the functionality of each of the architectures overlaps significantly. Therefore, the first part of this section focuses on the commonality across the various architectures ”the core application classes. The next subsection examines the functionality that these classes offer, and also their interplay with the system.

Core Application Classes

All Series 60 UI applications share some basic functionality:

  • They provide a User Interface displaying information and allowing user interaction.

  • They respond to various user-initiated events (such as the user choosing a menu option).

  • They respond to various system-initiated events (such as Window Server events that cause redrawing of the screen).

  • They are able to save and restore application data.

  • They can uniquely identify themselves to the framework.

  • They provide descriptive information to the framework about the application (such as icons, captions and so on).

The application framework classes that provide this functionality fit into the following high level categories: View, Document, Application and Application UI (or AppUi ). Figure 4-2 shows how the classes interrelate to make up an application's basic form.

Figure 4-2. The basic anatomy of an application.

The Application class is notably the least coupled ”its role is static. Besides serving as the application's main entry point, it delivers application- related information back to the framework, such as representational icons and captions, as can be seen in the Series 60 Application Launcher menu. The Application class does not involve itself with the application's data and algorithms. Classes in this category derive from CAknApplication .

The Document class, as its name indicates, provides a context for persisting application data. As documents are typically made to be edited, this class also provides a method for instantiating the AppUi class. Classes in this category derive from CAknDocument .

The AppUi is not, in itself, a drawable control. More accurately, it is a recipient of numerous framework-initiated notifications, such as key presses from the user, or important system events. The AppUi will either handle events itself, or if appropriate, relay them to the Views that it owns for them to handle. Classes in this category derive from CAknAppUi .

View is an umbrella term describing any representation of the model's data on the screen. It does not refer to specific UI controls that may be used to make these representations. The actual concrete application framework class used for this category depends on the application architecture used and will be discussed later in the Designing an Application UI subsection.

Also present in Figure 4-2 is the Model , which doesn't map to a specific Symbian OS or Series 60 class, but encapsulates an application's data and its algorithms. The model is owned by the document and can use the document's facilities for storing data. It is worth noting that numerous classes hold references to the model ”this is a visible demonstration of its importance.

Note also, that Series 60 applications have a limitation (by design) of only one open document (file) per application.

Application Initialization

Figure 4-3 demonstrates the framework's role in initializing an application, using the particular case of the ShapeDrawer application.

Figure 4-3. Initializing an application.

Each of the methods shown here must be created in order to provide a minimal Series 60 application.

First, all Series 60 UI applications implement a global function, E32Dll() , that is called by the framework in the first step, when the application is launched. It is referred to as the DLL entry point and must be present in your application. Remember that every Series 60 UI application is a polymorphic DLL.

 // DLL entry point, return that everything is OK GLDEF_C TInt E32Dll(TDllReason)    {    return KErrNone;    } 

The second step in the initialization of an application is for the framework to call NewApplication() , which is the single function exported by the DLL:

 EXPORT_C CApaApplication* NewApplication()    {    return new CShapeDrawerApplication;    } 

This creates an instance of your application class (step three), in this case CShapeDrawerApplication , and returns a pointer to it. The framework subsequently uses this pointer to complete construction of the application.

As the Cleanup Stack does not yet exist, the overloaded new (ELeave) operator cannot be used for construction at this stage.

The call to AppDllUid() by the framework in step four returns the UID of the application:

 TUid CShapeDrawerApplication::AppDllUid() const    {    // Return the UID for the ShapeDrawer application    return KUidShapeDrawerApp;    } 

This must return the value specified in the application's .mmp file and can be used to determine whether an instance of the application is already running.

The application startup process also allows the framework to obtain pointers to the newly created Document and AppUi classes. In the case of the Document, the framework receives a pointer to the abstract CApaDocument base class through a call to CShapeDrawerApplication::CreateDocumentL() in step five/six:

 CApaDocument* CShapeDrawerApplication::CreateDocumentL()    {    // Create a ShapeDrawer document and return a pointer to it    CApaDocument* document = CShapeDrawerDocument::NewL(*this);    return document;    } 

Note that the Document receives a reference to the Application, as it needs to be able to call AppDllUid() when handling application files.

In the case of the AppUi, the framework receives a pointer to the CEikAppUi base class through a call to CShapeDrawerDocument::CreateAppUiL() in step seven/eight:

 CEikAppUi* CShapeDrawerDocument::CreateAppUiL()    {    // Create the application user interface, and return a pointer to it    CEikAppUi* appUi = new (ELeave) CShapeDrawerAppUi();    return appUi;    } 

This is how the system framework is able to call methods in these classes when necessary.

These implementations , though brief and straightforward, are enough to create the application framework. What remains is to consider the other important functions needed to provide an executable skeleton application.

Important AppUi Methods

The AppUi exposes many functions that the framework calls in order to inform each application of various events. The following AppUi functions are important to know about:

  • HandleKeyEventL() ” For handling user key presses.

  • HandleForegroundEventL() ” Called when the application switches to or from the foreground. The default implementation handles changes in keyboard focus.

  • HandleSystemEventL() ” Delivers Window Server-generated events.

  • HandleApplicationSpecificEventL() ” Notification of custom events that you can define yourself. The default implementation handles notification of color -scheme changes.

  • HandleCommandL() ” For handling user selection of menu options.

Below is a partial implementation of the ShapeDrawer example's HandleCommandL() method. Its parameter is set by the system to represent the command issued. Apart from handling menu options, it demonstrates the typical approach toward invoking Avkon View-Switching, and this will be explained later in the Avkon View-Switching Architecture subsection:

 void CShapeDrawerAppUi::HandleCommandL(TInt aCommand)    {    switch (aCommand)       {       case EShapeDrawerSwitchToListView:          {          TUid viewId;          viewId.iUid = EShapeDrawerListViewId;          ActivateLocalViewL(viewId);          break;          }       case EShapeDrawerSwitchToGraphicView:          {          TUid viewId;          viewId.iUid = EShapeDrawerGraphicViewId;          ActivateLocalViewL(viewId);          break;          }       case EAknSoftkeyExit:       case EEikCmdExit:          if (CheckDiscSpaceL())             {             SaveL();             }          Exit();          break;          ...       default:          User::Panic(KShapeDrawerPanicName, EShapeDrawerUnknownCommand);          break;       }    } 

It is usual for a switch statement to be used to ensure the appropriate action is taken, depending on the command received. Note that some of these commands are custom defined ”for example, EShapeDrawerSwitchView , as defined in ShapeDrawer.hrh ”and others, such as EAknSoftKeyExit , are defined by the system.

The command EAknSoftkeyExit is called when the Exit soft key is pressed. The command EEikCmdExit is called by the system when the application needs to close ”this can be due to user selection of the Exit option from the Options menu or to system shutdown of the application. These commands and menus in general are covered in more detail in Chapter 5.

Note also that the function will Panic if an unexpected command is given.

Designing an Application UI

The three common application architecture design approaches used for application UIs are:

  • Traditional Symbian OS Control-Based Architecture

  • Dialog-Based Architecture

  • Avkon View-Switching Architecture

This subsection explores these three application architectures, giving example code to show how to implement each option and to clarify the benefits and drawbacks of each approach.

View Terminology

Before exploring these different architectures, you need to be clear on the terminology used in this chapter:

  • A "view" is a conceptual term meaning "the representation of the model's data on the screen," and this does not refer to a specific architecture. A view is actually rendered by one or more ( CCoeControl -derived) UI controls, organized in a hierarchy. The parent control is often referred to as the Container , except where a Dialog is used to implement the view.

  • In the Avkon View-Switching Architecture, the term " Avkon view " refers to a class that is registered with the systemwide View Server and controls the view's instantiation and destruction. As you will see, an Avkon view is a CAknView -derived class and not an actual control itself ”typically it owns a Container control to create its "view."

Each of these architectures offers different approaches to designing application UIs ”all architectures offer a means of delivering "views," or visual representations of application data, and a mechanism by which users can interact with it. Each architecture also has features and capabilities that differentiate it from the others. The material in the sections that follow will overview the respective features, demonstrate the programming tasks required in order to create each architecture and provide guidance in choosing the appropriate one.

Before addressing the architectures individually, it is helpful to compare and contrast them in general terms. Dialog-Based and Traditional Symbian OS-Based Architectures, though very different, are conceptually much more similar to one another than to the Avkon View-Switching Architecture. They are conceptually similar because:

  • They are characterized solely by the type of UI controls they use to generate views.

  • Architecturally, they are nearly identical. That is, in both designs the AppUi class simply "owns" the view controls and is therefore responsible for managing them directly.

The Avkon View-Switching Architecture is fundamentally different from these two approaches. (You will see this immediately when inspecting it from an architectural perspective.) Whereas the other architectures are characterized by the UI controls they specify and the different features these controls offer, Avkon View-Switching does not specify the type of control to be used for rendering a view. Moreover, this architecture facilitates a new way to manage views. Whereas views in the other architectures are managed by the AppUi, the Avkon View-Switching Architecture delegates the management responsibility to a systemwide View Server. Avkon View-Switching is best understood after understanding the more fundamental designs. Therefore, it is presented last in this section.

Traditional Symbian OS Control-Based Architecture

As previously stated, the Traditional Symbian OS Control-Based Architecture is created such that the AppUi owns its view controls directly ”these controls always inherit directly from CCoeControl . The standard term for a view class that derives directly from CCoeControl is "Container."

Although Chapter 5 covers CCoeControl in great detail, a brief overview of its characteristics is required here, so that you can understand the capabilities of this approach. CCoeControl can be thought of as a blank canvas. By inheriting from this class, you can create a wide range of custom controls, the functionality and complexity of which are limited only by your ability and imagination . The only downside to this flexibility is that the control is literally like a blank canvas, and a lot of coding effort is required to provide any significant functionality.

In terms of handling view-switches, the AppUi is responsible for handling user-initiated view-switch requests . Subsequently, the AppUi ends up behaving like a giant switch, activating and deactivating Containers according to user or system input.

Figure 4-4 illustrates the framework classes for the Traditional Symbian OS Control-Based Architecture and shows you which classes you need to derive from when creating your own application. The fourth layer is the custom layer that represents the classes that you would write.

Figure 4-4. The application framework classes for a Traditional Symbian OS Control-Based UI application.

How to Use Traditional Symbian OS Control-Based Architecture

The HelloWorld application introduced in Chapter 1 uses the Traditional Symbian OS Control-Based Architecture. It will be used to demonstrate the essential steps in creating an application using this architecture. This application is very simple and only has one UI control, CHelloWorldContainer , but you could supplement the application with additional Containers.

Remember that the different architectures reflect only the implementation of the UI controls, and therefore there are no differences in the application class or document class. The AppUi will create the Container class, and the Container class will display the data. Note that in this very simplistic example there is no model class.

The class declaration for the CHelloWorldContainer class is shown below:

 class CHelloWorldContainer : public CCoeControl    { public: // Constructors and destructor    static CHelloWorldContainer* NewL(const TRect& aRect);    static CHelloWorldContainer* NewLC(const TRect& aRect);    ~CHelloWorldContainer(); private:    void ConstructL(const TRect& aRect); private: // from CoeControl    void SizeChanged();    TInt CountComponentControls() const;    CCoeControl* ComponentControl(TInt aIndex) const;    void Draw(const TRect& aRect) const; private: //data    CEikLabel* iLabel;                   // example label    }; 

The first thing to notice is that the Container class derives from CCoeControl ”this is the base class for all controls. The " Hello World " text is displayed in the form of a label ” iLabel , the label itself being a control. CHelloWorldContainer implements four methods from CCoeControl ”all of them are called by the framework. SizeChanged() allows the control to respond to a change in its size . Draw() is called to draw the control. In this example, Draw() just clears the screen (the label control draws the text, and its Draw() method will also be called by the framework). CountComponentcontrols() returns the number of controls the Container owns ”in this case, just one (the label). For each control owned by the Container, the framework makes a call to ComponentControl() to retrieve it. More information on all of these methods can be found in the Controls section of Chapter 5.

The Container is constructed in the AppUi class:

 void CHelloWorldAppUi::ConstructL()    {    BaseConstructL();    iAppContainer = CHelloWorldContainer::NewL(ClientRect());    iAppContainer->SetMopParent(this);    AddToStackL(iAppContainer);    } 

Calling SetMopParent() on the Container is important to establish the child-parent relationship between controls. AddToStackL() pushes the Container on top of the control stack so that, for example, it can receive key events. Both of these methods will be examined in more detail in Chapter 5.

The menu is defined in the resource file, HelloWorld.rss :

 RESOURCE MENU_BAR r_helloworld_menubar    {    titles =       {       MENU_TITLE          {          menu_pane = r_helloworld_menu;          }       };    } RESOURCE MENU_PANE r_helloworld_menu    {    items =       {       MENU_ITEM          {          command = EHelloWorldCommand1;          txt = COMMAND_ONE;          },       MENU_ITEM          {          command = EAknCmdExit;          txt = text_softkey_exit;          }       };    } 

Resource files and structures are explained in more detail in Chapter 5. Note, however, that the HelloWorld example provides the menu option EHelloWorldCommand1 . If the user selects this menu option, then the framework will call the HandleCommandL() method in the AppUi class, which handles each command appropriately:

[View full width]
[View full width]
void CHelloWorldAppUi::HandleCommandL(TInt aCommand) { switch (aCommand) { ... case EHelloWorldCommand1: { HBufC* message = StringLoader::LoadLC(R_DIALOG_TEXT);// Pushes message onto the Cleanup Stack. CAknInformationNote* informationNote = new (ELeave) CAknInformationNote; informationNote->ExecuteLD(*message); CleanupStack::PopAndDestroy(message); // Removes message from the Cleanup Stack. break; } ... }

That covers the basics for an application using the Traditional Symbian OS Control-Based Architecture. The details of this example are not relevant to the architecture discussion here, but the functionality shown will be discussed in Chapters 5 and 6.

You can implement your own view-switching mechanism by using AddToStackL() and RemoveFromStackL() to swap between Containers (and therefore views) if using this architecture to implement an application with multiple views. These methods will be covered in Chapter 5, but note that if your application has multiple views, then an Avkon View-Switching Architecture may well be more appropriate. Further details on using controls to create fully interactive application UIs are provided in Chapters 5 through 8.

Dialog-Based Architecture

Like the Traditional Symbian OS-Based Architecture just described, the Dialog-Based Architecture similarly establishes the AppUi as the control-owning class. The difference is that the control that it owns inherits directly from one of a family of dialog classes. The idea is to use the built-in features of these classes in order to render data views and to handle switching between them.

Dialogs are covered in greater detail in Chapter 6, but some of their main characteristics are worth mentioning here in order to provide a basic rationale for this approach. Series 60 comes complete with a number of dialog classes that satisfy common requirements for data representation, entry and editing. A primary benefit of dialogs is that they require much less development work than controls that derive immediately from CCoeControl , as they automatically manage the layout of their child controls. Additionally, their content and layout can be changed in resource files, without rebuilding any C++ code.

A typical use case is where the main view requires a simple layout ”for example, a "settings" application. The dialog used for the main view is modeless; in other words, it does not need to keep the focus at all times. (See Chapter 6 for further information on the difference between modal and modeless dialogs.) Multipage dialogs may be used to provide a set of views in conformance with the Series 60 UI Style Guide.

Figure 4-5 illustrates the framework classes for the Dialog-Based Architecture and shows which classes you need to derive from when creating your application. The fourth layer is the custom layer that represents the classes that you would write.

Figure 4-5. The application framework classes for a Dialog-Based application.

How to Use Dialog-Based Architecture

The example, SimpleDlg , demonstrates how to create a simple application where the main application view is a dialog. Again, this application can be used to create a skeleton application, if required.

The dialog is defined in the resource file SimpleDlg.rss :

 RESOURCE DIALOG r_simple_dialog    {    flags = EEikDialogFlagNoDrag        EEikDialogFlagNoTitleBar        EEikDialogFlagFillAppClientRect        EEikDialogFlagCbaButtons        EEikDialogFlagModeless;    buttons = R_AVKON_SOFTKEYS_OPTIONS_BACK;    items =       {       DLG_LINE          {          id = ESimpleDlgCIdGameName;          type = EEikCtLabel;          control = LABEL             {             txt = GAME_NAME_TEXT;             };          }       };    } 

Resource files and structures are covered in more detail in Chapter 5.

The mode of the dialog is set by using the EEikDialogFlagModeless flag. Flags are also set to achieve the filling of the client rectangle and to suppress a title bar. The soft keys are defined by EEikDialogFlagCbaButtons to be set to Options and Back . Note that the dialog must be modeless in order for the AppUi to receive commands and events ” otherwise all input would be directed straight to the dialog itself.

Constructing and running the dialog is quite straightforward, as you would expect. This is performed in the AppUi class:

 void CSimpleDlgAppUi::ConstructL()    {    BaseConstructL();    iAppDialog = new (ELeave) CSimpleDlgDialog;    iAppDialog->SetMopParent(this);    iAppDialog->ExecuteLD(R_SIMPLEDLG_DIALOG);    AddToStackL(iAppDialog);    } 

Because the dialog is modeless, ExecuteLD() returns immediately after being called. The dialog has to be added to the control stack with the AddToStackL() because modeless dialogs do not do this for themselves. The application extends the dialog just as it would normally extend a CCoeControl -derived view in order to achieve the desired functionality.

One further point to note is that the dialog must be destroyed in the destructor of the AppUi:

 CSimpleDlgAppUi::~CSimpleDlgAppUi()    {    if (iAppDialog)       {       RemoveFromStack(iAppDialog);       delete iAppDialog;       }    } 

Again, the reason is that the dialog is modeless and so is not destroyed when the ExecuteLD() method returns. Note that the dialog must also be first removed from the control stack.

Avkon View-Switching Architecture

Figure 4-1 depicts the basic design for an application using the Avkon View-Switching Architecture. The architecture is clearly more complicated than the previous two. Specifically, another class has been introduced as an intermediary between the AppUi and the Container. This is a CAknView -based class, whose purpose will be clarified shortly. Another variation from previous designs is that the AppUi class inherets from CAknViewAppUi instead of just CAknAppUi . This is because these two classes ( CAknView and CAknViewAppUi) offer complementary functionality ”you never use one without the other.

In the other two architectures, the AppUi was directly responsible for handling view switching ”it had to manage the instantiation, deletion and display of the view-rendering controls. But the CAknView -based class significantly reduces the AppUi's role in this respect.

The AppUi still handles view-switch requests, but now, instead of deleting the old Container and instantiating a new one, the AppUi need only call one of its special view-activation functions ” ActivateViewL() , for example. These special CAknViewAppUi functions make an activation request to the View Server ”the View Server then explicitly coordinates the deactivation of the current view and the activation of the requested view by calling activation/ deactivation member functions in the relevant CAknView -based classes.

An implementation of this architecture is clearly demonstrated in the ShapeDrawer example application. However, before getting into the code, it will be helpful to look at the general features required of this architecture:

  • The application must be designed so that each CAknView -derived Avkon view class owns a Container, and the AppUi then owns each Avkon view.

  • The application's AppUi class must be derived from CAknViewAppUi instead of just CAknView , because the former offers methods to register, activate and deactivate Avkon views.

  • All Avkon views must be registered with the View Server.

  • The Avkon views have activation/deactivation member functions that can be called directly by the View Server. You must override these functions to provide correct handling of the subordinate Container.

The View Server follows a set of very simple rules. The primary rule is to ensure that one, and only one, Avkon view is active per application, at any given time. Upon registration, Avkon views are uniquely identified to the View Server by two UIDs: one to identify the owning application, and one to uniquely identify the view within that application.

This one-active-view-per-application scheme suits typical phone usage ”users do not usually rapidly task back and forth between views, so there is no need to hold a number of views open and waste the limited available memory.

Although view switching is not a new paradigm, Series 60 classes encapsulate and simplify this functionality. The process of handling view switches has been made more transparent for the developer than with other Symbian OS platforms, and the Avkon classes have integrated additional features into the paradigm. For example, each Avkon view can automatically have its own menu system by defining one in the AVKON_VIEW resource structure. Resource files and structures are covered in more detail in Chapter 5.

For each CAknView -base class, the activation/deactivation functions you need to implement are DoActivateL() and DoDeactivate() , and these are responsible for instantiating and displaying, or deleting the UI controls owned by the Avkon view. Again, this highlights that the View Server does not concern itself with UI controls directly. Note that the View Server will, on its own initiative, call DeactivateView() in order to enforce the one-active-view-per-application rule.

How to Use Avkon View-Switching Architecture

This subsection will show how you would go about implementing an Avkon View-Switching Architecture application. Figure 4-1 illustrates the Series 60 classes that a view-switching application must derive from. It is important to note that, when using this architecture, the CAknViewAppUi and CAknView classes must be used in conjunction with one another. Each Avkon view derives from CAknView and must provide an Id() function so that it can be identified by the system. It must also implement the DoActivateL() and DoDeactivateL() function. Additionally, it should implement the andleForegroundEventL() , HandleCommandL() and HandleStatusPaneSizeChange() functions to handle various events. The class declaration for CShapeDrawerListView shows a typical Avkon view:

 class CShapeDrawerListView: public CAknView    { public: // constructors and destructor    static CShapeDrawerListView* NewL();    static CShapeDrawerListView* NewLC();    ~CShapeDrawerListView();    CShapeDrawerAppUi& GetAppUi(); private: // from CAknView    TUid Id() const;    void HandleCommandL(TInt aCommand);    CShapeDrawerListView();    void DoActivateL(       const TVwsViewId& aPrevViewId,       TUid aCustomMessageId,       const TDesC8& aCustomMessage);    void DoDeactivate(); private: // constructors    void ConstructL(); private: // data    CShapeDrawerListViewContainer* iContainer;    TUid iId;    }; 

DoActivateL() is called by the View Server when a client requests that your view is activated. Its purpose is to instantiate and display the control that renders the view. As the function prototype above implies, it is possible to pass customized messages to the View being activated. This information could be used, for example, to prepopulate the view with data specified in the message. The passing of these message parameters is entirely optional.

Be aware that DoActivateL() may be called multiple times prior to DoDeactivateL() , and therefore your implementation must not assume that the view's control has not already been created and displayed. Such multiple calls can occur when a client requests reactivation of the view with some additional message parameters. Your DoActivateL() call must be prepared to handle this eventuality.

DoDeactivate() is called when your Avkon view is to be deactivated. This function is responsible for destroying its control. Your view will be deactivated when your application exits, or when another view in the same application is activated. This function must not leave.

HandleForegroundEventL() will be called only while your Avkon view is active (in other words, in-between calls to DoActivateL() and DoDeactivate() ). When your view comes to the foreground, it will receive HandleForegroundEventL(ETrue) . When your view is removed from the foreground, it will receive HandleForegroundEventL(EFalse) . This function will be called only when the foreground state actually changes. Note that it may be called a number of times during your view's active period, as the owning application comes and goes from the foreground. You may want to use this method for setting focus or for controlling screen updates.

HandleCommandL() will be called when the view's menu generates a command, and HandleStatusPaneSizeChange() will be called when the client rectangle size changes due to a change in the status pane.

This is the typical order of events that an Avkon view will receive over an active period.

Activating a new Avkon view:

  1. DoActivateL()

  2. HandleForegroundEventL(ETrue)

Deactivating an Avkon view:

  1. HandleForegroundEventL(EFalse)

  2. DoDeactivate()

As previously stated, the pair of view-activation function calls may happen a number of times during a view's active period.

Typically, each Avkon view will require its own menu options. It is easy to give each Avkon view its own unique set by simply associating the view with a resource. Therefore, to let an Avkon view define its own soft keys and/or menu, create an AVKON_VIEW resource in your resource ( .rss ) file, then pass the resource ID into the view's BaseConstructL() function. Again, further detail on resource files and structures can be found in Chapter 5.

The view resource for ShapeDrawer is shown here:

 RESOURCE AVKON_VIEW r_shapedrawer_graphicview    {    menubar = r_shapedrawer_menubar1;    cba = R_AVKON_SOFTKEYS_OPTIONS_BACK;    } 

The menubar r_shapedrawer_menubar1 is also defined in the resource file and specifies the menu options. Note that CBA (or Command Button Area) is an alternative name sometimes used for soft keys ”this comes from a Symbian OS convention. R_AVKON_SOFTKEYS_OPTIONS_BACK is a standard soft key definition (defined in avkon.rsg ) and sets the soft keys to Options and Back . Another definition is R_AVKON_SOFTKEYS_OPTIONS_EXIT , which sets the soft keys to Options and Exit .

The code to associate this resource with its Avkon view would be:

 void CShapeDrawerGraphicView::ConstructL()    {    BaseConstructL(R_SHAPEDRAWER_GRAPHICVIEW);    } 

All of the Avkon views in your application would normally be constructed in the AppUi object's ConstructL() method. They are registered with the View Server using AddViewL() , and finally the initial view is activated by setting it as the default view. The following code from the ShapeDrawer application demonstrates the AppUi as the owner of two Avkon views, CShapeDrawerGraphicView and CShapeDrawerListView , and shows the required initialization process.

 void CShapeDrawerAppUi::ConstructL()    {    BaseConstructL();    iView1 = CShapeDrawerGraphicView::NewL(iDocument);    AddViewL(iView1); // transfer ownership    iView2 = CShapeDrawerListView::NewL(iDocument);    AddViewL(iView2); // transfer ownership    SetDefaultViewL(*iView1);    } 

As mentioned before, Avkon views have no innate drawing capabilities. Therefore each Avkon view will typically own a Container class that derives from CCoeControl . An instance of this will therefore be present in the view:

 class CShapeDrawerGraphicView: public CAknView    {    ... private: // data    CShapeDrawerGraphicViewContainer* iContainer; // what this view will display    ...    }; 

The currently activated view receives commands via its HandleCommandL() method. This is where soft key-generated commands and commands generated from the user interacting with the view's associated menu are handled. For example:

 void CShapeDrawerListView::HandleCommandL(TInt aCommand)    {    switch (aCommand)       {       case EAknSoftkeyBack:          {          AppUi()->HandleCommandL(EEikCmdExit);          break;          }       case EShapeDrawerRemoveSelectedItem:          {          iContainer->RemoveCurrentItemL();          break;          }       default:          {          AppUi()->HandleCommandL(aCommand);          break;          }       }    } 

Note that commands that are not handled by the Avkon view are passed on to the AppUi. In the ShapeDrawer example, view-switching commands are passed to the AppUi:

 void CShapeDrawerAppUi::HandleCommandL(TInt aCommand)    {    switch (aCommand)       {       case EShapeDrawerSwitchToListView:          {          TUid viewId;          viewId.iUid = EShapeDrawerListViewId;          ActivateLocalViewL(viewId);          break;          }       case EShapeDrawerSwitchToGraphicView:          {          TUid viewId;          viewId.iUid = EShapeDrawerGraphicViewId;          ActivateLocalViewL(viewId);          break;          }          ...       default:          User::Panic(KShapeDrawerPanicName, EShapeDrawerUnknownCommand);          break;       }    } 

Local view switching, or switching to a view that is owned by your application, is performed by referring to the UID of the Avkon view that you want to switch to.

In order to perform an external view switch, slightly more information is required. Call one of the CCoeAppUi::ActivateViewL() functions, providing a TVWsViewId containing the target application's UID and the target view UID. The following code excerpt demonstrates this:

 const TUid KPhoneBookUid = { 0x101f4cce }; //from PbkUID.h ... const TUid KPhoneBookContactViewUid = { 1 }; ActivateViewL(TVwsViewId(KPhoneBookUid, KPhoneBookContactsViewUid)); 

As you can see, the mechanism for accessing an external view is similar to that for accessing local views. See the Using Standard Application Views section in Chapter 12 for more detailed information on external view switching.

All view-switching applications should publish their application and view UIDs by exporting them to a header file, so that they can be identified by other applications. Of course, if the application holds views that should not be accessible by other applications, then their publication should be withheld.

Choosing the Appropriate Application Architecture

When to Use Avkon View-Switching Architecture

Although view switching operates on a very simple set of rules, it provides a new way of thinking about application design and interapplication interaction. View switching allows applications to make better use of each other, in a way that makes navigation between applications seamless for the user.

Consider how users had to navigate between applications prior to the advent of view switching: Imagine that a user had just received an email from a new colleague. Apart from reading the message, it is plausible that they would want to add this new colleague to their contacts list. Without view switching, it would be necessary for them to manually start up the contacts application, manually navigate to the contact entry view, and then to enter the colleague's details. But with Avkon View-Switching Architecture, it is possible for the email application to provide a direct switch to the contact entry view of the contact application, and also to populate the view with some initial contact details.

The above scenario suggests using a view switch to access the contacts application in order to enter contact data, but this is not the only way to achieve this type of functionality ”Series 60 provides many APIs to manipulate data in standard applications by accessing their application engine components directly. Chapter 12 gives more information on this alternative approach.

With this new architecture, programming this view switch into an application is trivial ”you only have to request view activation from the View Server, passing in the application UID, the view UID and a package buffer containing any other relevant information. The View Server will then automatically start up the contacts application (if not already started) and switch directly to the appropriate view. Switching to standard applications is covered in more detail in Chapter 12.

This mechanism allows applications to smoothly interoperate , and in most cases, view switching is the best architecture choice. However, you need to be aware of its limitations. For example, the view-switching scheme has no built-in way to preserve the context of a view switch. That is, it does not provide a standard mechanism for navigating back to the previously active view (think of the "back" button on a Web browser). These limitations mean that if an application executes a remote view switch, the user has no visual clue as to how this view was reached. However, DoActivateL() does receive an identifier for the previously active view, so it is possible to develop your own "back" button functionality.

Another consideration is that you may wish to prevent external applications from being able to switch to certain views within your application. The View Server architecture offers no way to explicitly forbid the external activation of a view. There are workarounds, however. For example, you can make it very difficult to access views by not publishing their UIDs. What is important to understand is that, if it is absolutely necessary for some views to be unreachable by external applications, then the View Server cannot accommodate this requirement, and it will be necessary to use a different UI architecture.

When to Use Traditional Symbian OS Control-Based Architecture

In general, the default architecture for your Series 60 application would probably be the Avkon View-Switching Architecture. However, there are reasons why you might prefer the Traditional Symbian OS Control-Based design:

  • Your application requires only one view, and it is unlikely that other applications will want to perform an external switch to it. In this case, you would not get anything out of the Avkon View-Switching scheme; it would just be unnecessary overhead.

  • Your application has UI controls whose privacy must be guaranteed . If you are using the Avkon View-Switching Architecture, you can make it very difficult for other applications to switch to a view in your application by not publishing its ID, but there are no guarantees of privacy.

  • You are porting an application from a different Symbian OS platform to Series 60. In this case, you may wish to simply stick with the existing UI architecture, especially if it appears unlikely that other applications would want to perform external switches to any views within the application.

When to Use Dialog-Based Architecture

This approach is very suitable to applications whose UI controls are made up of a straightforward arrangement of traditional OS controls, such as data entry or settings applications. Controls can be defined in resource files, with the dialog handling layout and drawing automatically ”this is much easier than implementing custom drawing behavior.

The other consideration is the application's navigation requirements from screen to screen. The application is a candidate for this "dialog-based" approach if and only if there are no cyclical navigation paths among the application's views. Figure 4-6, along with the discussion that follows, will clarify this point.

Figure 4-6. Screen navigation in a Dialog-Based application.

Each box in the figure represents a different view in an application, and each number represents a different dialog class. Classes 1 and 3 are multipage dialogs ”the lettered variations represent their different pages. The arrows depict the screen navigation possibilities.

The rule is that, in navigating downward in the hierarchy (for example, from 1a to 3a), you must return along the same path when navigating back upward. Therefore, navigating from 1a to 3a to 1b is disallowed . If this were allowed, then the user could navigate in a big circle. Each cycle (1a-3a-1b-1a) would instantiate two new dialog objects (dialog 1 creates an instance of dialog 3, in navigating from 1a to 3a, and dialog 3 creates another instance of dialog 1 in navigating back to 1b), while at the same time still holding objects instantiated in past cycles in memory. So, apart from having no logical benefit, this cyclical navigation is a liability in terms of its undue memory consumption.

If you are considering using dialogs as your main view, then note that their navigation paths are limited, as well as their layout possibilities.

File Handling

In Chapter 3, you were introduced to files, streams and stores, and you learned about using InternalizeL() , ExternalizeL() and the operators << and >> to save data. In the Elements example code, an explicit command to save or retrieve some data was given. However, in a GUI application, if you need to persist application data, the framework can initiate the process automatically.

The document class contains two important functions that can be overwritten: StoreL() and RestoreL() . The framework calls RestoreL() automatically at application startup. This function is then responsible for loading the application's persisted data. The StoreL() function is called by the framework when the application exits in order to save application data ”you need to include a call to CEikAppUi::SaveL() to instruct the framework to make the call. The implementation below is for CShapeDrawerDocument::StoreL():

 void CShapeDrawerDocument::StoreL(CStreamStore& aStore,    CStreamDictionary& aStreamDic) const    {    // Get the model to save itself to the store    TStreamId modelStreamId = iModel->StoreL(aStore);    // Add an entry into the dictionary for the model data    aStreamDic.AssignL(Application()->AppDllUid(), modelStreamId);    } 

You can see that the first step in the document's StoreL() function is a call to the model's StoreL() ”where the model will externalize all of its data. The stream ID for the model data is then recorded in the stream dictionary. The ShapeDrawer application provides implementations of both the StoreL() and RestoreL() methods, and a review of this code will clarify their usage.

Series 60 differs from other Symbian OS platforms in that the default behavior of the document class is not to persist data using a file store . Therefore, Series 60 does not automatically open a file for document storage at application startup ”the implementation of CAknDocument::OpenFileL() is empty.

However, you can still accomplish this behavior by overriding OpenFileL() in your document class and calling the base Symbian OS implementation:

 CFileStore* CShapeDrawerDocument::OpenFileL(TBool aDoOpen,    const TDesC& aFilename, RFs& aFs)    {    return CEikDocument::OpenFileL(aDoOpen, aFilename, aFs);    } 

Note that if document support is not enabled, then StoreL() and RestoreL() will not be automatically called by the framework.

Also, support for Symbian OS .ini (application settings) files is not provided in Series 60 by default. This causes any Series 60 applications that try to open their .ini file to fail with a " Not Supported " error. To enable your application to use an .ini file, your application class's OpenIniFileLC() method must be overridden to call EikApplication::OpenIniFileLC() . For example:

 CDictionaryStore* CMyApplication::OpenIniFileLC(RFs& aFs) const    {    return CEikApplication::OpenIniFileLC(aFs);    } 

Please note that the above excerpt is not taken from any of the chapter examples.

Many standard Series 60 applications use "Shared Data Files" rather than traditional Symbian OS .ini files. These consist of user-readable (plain) text rather than the (binary) stream-based file stores ( CDictionaryFileStore ) that Symbian OS .ini files use. They are stored under \system\shareddata\ and named <UID of application>.ini ”for example, 101FDA63.ini for an application with a UID of 0x101FDA63 . The fixed location and coherent naming of these files mean that it is possible for other applications to see (and potentially change) the settings of any application that uses them. Note, however, that the Shared Data API used to access such files is not included in the public SDK.

Developing Series 60 Applications. A Guide for Symbian OS C++ Developers
Developing Series 60 Applications: A Guide for Symbian OS C++ Developers: A Guide for Symbian OS C++ Developers
ISBN: 0321227220
EAN: 2147483647
Year: 2003
Pages: 139 © 2008-2017.
If you may any questions please contact us: