Daniel Rocha follows up the earlier introduction to the Symbian OS View Server with a look at more advanced techniques, such as changing menus and CBAs according to the view being displayed.
This article resumes from the point where the article The Symbian OS View Server stopped. I will present some advanced features you can add to your programs using some nice tricks with the View Server and UI Control Framework APIs. These include changing menus and CBAs according to the view being displayed, sending messages from one view to another and using external application views.
General Information
Changing the menu bar
In the previous article we defined a single menu in a resource file with the following code:
<code>
RESOURCE EIK_APP_INFO
{
menubar = r_ViewServer_menubar;
cba = R_AVKON_SOFTKEYS_OPTIONS_EXIT;
}
RESOURCE MENU_BAR r_ViewServer_menubar
{
titles =
{
MENU_TITLE {menu_pane = r_ViewServer_menu;}
};
}
RESOURCE MENU_PANE r_ViewServer_menu
{
items =
{
MENU_ITEM {command = EViewServerChangeCmd; txt = "Change View";},
MENU_ITEM {command = EAknSoftkeyExit; txt = "Exit";}
};
}
</code>
The line menubar = r_ViewServer_menubar; within EIK_APP_INFO struct defines the default menu bar for the application. This is the menu bar that is going to be displayed in every view that does not define its own menu. That's why you saw the same menu bar in all the views of our first application: they were not interested in having a different menu bar, so they didn't change the default one. This approach works fine if you have few views and you can concentrate all the actions needed in a single menu. However, in more complex applications, you will certainly need specific menu for each view's specific actions, and I will show you how to do that.
First of all, we need to define all the menus our views are going to use, and that is done, as usual, in the resource file:
<code>
RESOURCE EIK_APP_INFO
{
menubar = r_ViewServer_menubar;
cba = R_AVKON_SOFTKEYS_OPTIONS_EXIT;
}
RESOURCE MENU_BAR r_ViewServer_menubar
{
titles =
{
MENU_TITLE {menu_pane = r_ViewServer_menu;}
};
}
RESOURCE MENU_PANE r_ViewServer_menu
{
items =
{
MENU_ITEM {command = EViewServerChangeCmd; txt = "Second View";},
MENU_ITEM {command = EViewServerLaunchCalendarCmd; txt = "Launch Calendar";},
MENU_ITEM {command = EAknSoftkeyExit; txt = "Exit";}
};
}
// menubar for view two;
RESOURCE MENU_BAR r_ViewServer_menubar_two
{
titles =
{
MENU_TITLE {menu_pane = r_ViewServer_menu_two;}
};
}
RESOURCE MENU_PANE r_ViewServer_menu_two
{
items =
{
MENU_ITEM {command = EViewServerAboutCmd; txt = "Third View";},
MENU_ITEM {command = EViewServerFirstCmd; txt = "Previous";}
};
}
// menubar for about view;
RESOURCE MENU_BAR r_ViewServer_menubar_about
{
titles =
{
MENU_TITLE {menu_pane = r_ViewServer_menu_about;}
};
}
RESOURCE MENU_PANE r_ViewServer_menu_about
{
items =
{
MENU_ITEM {command = EViewServerParamCmd; txt = "Parameter View";},
MENU_ITEM {command = EViewServerChangeCmd; txt = "Previous";}
};
}
// menubar for param view
RESOURCE MENU_BAR r_ViewServer_menubar_param
{
titles =
{
MENU_TITLE {menu_pane = r_ViewServer_menu_param;}
};
}
RESOURCE MENU_PANE r_ViewServer_menu_param
{
items =
{
MENU_ITEM {command = EViewServerFirstCmd; txt = "First View";},
MENU_ITEM {command = EViewServerAboutCmd; txt = "Previous";}
};
}
</code>
Wow! A whole lot of new menus: r_ViewServer_menubar, r_ViewServer_menubar_two, r_ViewServer_menubar_about and r_ViewServer_menubar_param. Note the EViewServer*Cmd commands, they are necessary for event handling and are defined in ViewServer.hrh file, and included in both the resource file (for definition) and in the AppUi class (for checking and command handling). Note also that the menus themselves are not associated with any particular view; that leaves us with a great deal of flexibility in choosing the right menu for each view at runtime, using code within the view. Let's see how to do this.
First, let us add two more view classes so we can make it clearer how easy menu and CBA switching with the view server is. Those classes are: CViewServerAboutView and CViewServerParamView. The updated class model follows:
Fig. 1 - Updated Class Model
You can see that our new classes are nothing but standard views similar to those we are used to building: simple classes that inherit from CCoeControl and MCoeView base classes. So, we have defined the menus in the resource file, but how do we associate each one with a given view? The answer is straightforward: When a view is activated you must replace the current menu bar with some other, more suitable to the view being activated; this code has to be placed, as expected, in the view's ViewActivatedL() member function. The code below is implemented in the CViewServerMenuView class.
<code>
//activates this view, called by the framework
void CViewServerAboutView::ViewActivatedL(const TVwsViewId& /*aPrevViewId*/,
TUid /*aCustomMessageId*/,
const TDesC8& /*aCustomMessage*/) {
<strong> CEikonEnv* eikonEnv = CEikonEnv::Static();
MEikAppUiFactory* appUiFactory = eikonEnv->AppUiFactory();
CEikMenuBar* menuBar = appUiFactory->MenuBar();
menuBar->StopDisplayingMenuBar();
menuBar->SetMenuTitleResourceId(R_VIEWSERVER_MENUBAR_TWO);
</strong>
Window().SetOrdinalPosition(0);
MakeVisible(ETrue);
}
</code>
Line CEikonEnv* eikonEnv = CEikonEnv::Static(); gets a pointer to this application's CEikonEnv instance. You could also use the iEikonEnv pointer available to the view class. MEikAppUiFactory* appUiFactory = eikonEnv->AppUiFactory(); gets a pointer to this application's AppUiFactory, responsible for breaking the dependency of the AppUi on controls. From this pointer, you get a pointer to the CEikMenuBar, which represents the current menu bar being displayed by your application.
The next two lines are the most important one, as the code on them is responsible for the actual menu bar switching: menuBar->StopDisplayingMenuBar(); makes the current menu bar invisible, while menuBar->SetMenuTitleResourceId(R_VIEWSERVER_MENUBAR_TWO); sets the new Resource ID of the menu bar, which is the same R_VIEWSERVER_MENUBAR_TWO menu resource we have defined in the resource file. Note that SetMenuTitleResourceId is only used by Series 60; other environments will most likely use the ChangeMenuBarL() member function to perform the same task. And that's pretty much it: define a menu in the resource file, get a reference to it in the ViewActivatedL() function and perform the menu switching. Also, do not forget that all menu events (represented by the command item in the resource file definition) will still be handled by the CViewServerAppUi class, in the same way they were before. Add a handler for each menu command that performs a useful task; in our case, the R_VIEWSERVER_MENUBAR_TWO has two commands: EViewServerAboutCmd, which when handled by the AppUi activates the CViewServerAboutView, and EViewServerFirstCmd, that sends us back to the first view.
Changing the CBA
The CBA (Command Button Actions) is the area where commands can be accessed directly by the device's softkeys. That means that a developer making use of the CBA does not need to track keyboard and softkey events directly, but instead they will only deal with high-level commands, defined in the ".hrh" file and associated with the CBA in the Resource file. This saves you from the obligation of dealing with all possible key events that may be fired by the user for the simple action of opening a menu. Of course, if your application needs full key-event handling, you are free handling this yourself and not use the CBA.
Fig. 2 - The CBA highlighted.
First of all, we must define all the CBAs we want to use in the resource file as follows:
<code>
// CBA for about view
RESOURCE CBA r_ViewServer_cba_about {
buttons = {
CBA_BUTTON { txt = "MyOptions"; id = EAknSoftkeyOptions; },
CBA_BUTTON { txt = "Previous"; id = EViewServerChangeCmd;}
};
}
</code>
This CBA will be used by the CViewServerAboutView view class. It has two commands associated with it: EAnkSoftkeyOptions, which is standard for Series 60, and defined in avkon.hrh; and EViewServerChangeCmd, defined in ViewServer.hrh. The handling of these commands is done exactly in the same way as for menu commands: in CViewServerAppUi's HandleCommandL() function.
Changing the CBA is exactly like changing the menu: in the ViewActivateL() method of your view class, grab a reference to the CEikonEnv class, then use it to obtain a reference to the AppUiFactory, and from this to the CBA. This is shown in the code below, extracted from the CViewServerAboutView class.
<code>
//activates this view, called by the framework
void CViewServerAboutView::ViewActivatedL(const TVwsViewId& /*aPrevViewId*/,
TUid /*aCustomMessageId*/,
const TDesC8& /*aCustomMessage*/) {
CEikonEnv* eikonEnv = CEikonEnv::Static();
MEikAppUiFactory* appUiFactory = eikonEnv->AppUiFactory();
CEikMenuBar* menuBar = appUiFactory->MenuBar();
<strong>CEikButtonGroupContainer* cba = appUiFactory->Cba();</strong>
menuBar->StopDisplayingMenuBar();
menuBar->SetMenuTitleResourceId(R_VIEWSERVER_MENUBAR_ABOUT);
<strong> cba->SetCommandSetL(R_VIEWSERVER_CBA_ABOUT);
cba->DrawDeferred();</strong>
Window().SetOrdinalPosition(0);
MakeVisible(ETrue);
}
</code>
The highlighted code shows how to obtain a pointer to the current CBA from the appUiFactory object. Once you have it, all you need to do is call SetCommandSetL(); on the current CBA, which takes as parameter the Resource ID of the new CBA that will be displayed. This Resource ID is obviously defined in the resource file as shown previously. You must also call DrawDeferred() so that the Window server draws the new CBA as soon as it has the possibility to do it. And that's pretty much it! You can see the newly-changed CBA in the picture below:
Fig. 3 - The newly-changed CBA.
Sending messages from one view to another
When activating a different view, you can optionally send a message to it. This is very useful when you need to communicate some information that the next view will need for its normal processing. For example, in a simple wizard-like application, you can pass the values chosen in the current view to the next one so it chooses to display a different group of settings available to the user based on the selection made on the previous view. Doing so is only a matter of calling the complete version of the ActivateViewL() function, which takes four parameters: Application ID, View ID, Parameter ID and the actual message being sent.
Recall our menu definition for the CViewServerAboutView class:
<code>
RESOURCE MENU_PANE r_ViewServer_menu_about
{
items =
{
MENU_ITEM {command = EViewServerParamCmd; txt = "Parameter View";},
MENU_ITEM {command = EViewServerChangeCmd; txt = "Previous";}
};
}
</code>
In our example, the "Parameter View" menu option is associated with the EViewServerParamCmd, which is meant to be handled by the HandleCommandL() function of the CViewServerAppUi class. So let's take a look at the function definition:
<code>
// handle any menu commands
void CViewServerAppUi::HandleCommandL(TInt aCommand) {
switch(aCommand) {
case EEikCmdExit:
case EAknSoftkeyExit:
Exit();
break;
case EViewServerChangeCmd: {
ActivateViewL(TVwsViewId(KUidViewServerApp,KMenuViewId));
break;
}
case EViewServerAboutCmd: {
ActivateViewL(TVwsViewId(KUidViewServerApp,KAboutViewId));
break;
}
case EViewServerParamCmd: {
<strong> TBuf<128> text;
CAknTextQueryDialog* dlg = new(ELeave)CAknTextQueryDialog(text);
dlg->PrepareLC(R_PARAM_DIALOG);
if(dlg->RunLD()) {
TUid uid = {1};
TBuf8<128> tbuf;
tbuf.Copy(text);
ActivateViewL(TVwsViewId(KUidViewServerApp,KParamViewId),TUid::Uid(1),tbuf);
</strong> }
break;
}
case EViewServerFirstCmd: {
ActivateViewL(TVwsViewId(KUidViewServerApp,KViewId));
break;
}
case EViewServerLaunchCalendarCmd: {
ActivateViewL(TVwsViewId(KCalendarId,KDayViewId));
break;
}
default:
Panic(EViewServerBasicUi);
break;
}
}
</code>
The highlighted code shows us launching a standard Series 60 dialog (also defined in the resource file) to obtain some data input from the user, which will be passed forward to the next view. It could be a hardcoded message as well, no problems about that. TBuf8<128> tbuf; tbuf.Copy(text); converts the TBuf16 into the TBuf8 needed by the ActivateViewL() function. After the conversion, all we need to do is call
<code>
ActivateViewL(TVwsViewId(KUidViewServerApp,KParamViewId),TUid::Uid(1),tbuf);
</code>
And the text entered by the user is passed on to the next view, to be used in any way it likes. In our case, we will be printing the data on the screen, so there's not really a need for defining several message TUids, so we just pass the value "1", as it could be 0, 10, or Susan, if you like that :) The use of message ids is useful in the case of a view that can receive messages that should be interpreted in different ways, so you'd need to specify which way you want the message to be interpreted.
Unsurprisingly enough, we need to do something with the value in the CViewServerParamView so we can check that the parameter was passed correctly. Here is the code that does just that:
<code>
//activates this view, called by the framework
void CViewServerParamView::ViewActivatedL(const TVwsViewId& /*aPrevViewId*/,
TUid /*aCustomMessageId*/,
const TDesC8& aCustomMessage) {
CEikonEnv* eikonEnv = CEikonEnv::Static();
MEikAppUiFactory* appUiFactory = eikonEnv->AppUiFactory();
CEikMenuBar* menuBar = appUiFactory->MenuBar();
CEikButtonGroupContainer* cba = appUiFactory->Cba();
menuBar->StopDisplayingMenuBar();
menuBar->SetMenuTitleResourceId(R_VIEWSERVER_MENUBAR_PARAM);
cba->SetCommandSetL(R_AVKON_SOFTKEYS_OPTIONS_EXIT);
cba->DrawDeferred();
<strong> if(aCustomMessage.Length()>0) {
if(param) {
delete param;
param = NULL;
}
param = HBufC::NewL(aCustomMessage.Length());
param->Des().Copy(aCustomMessage);
}</strong>
Window().SetOrdinalPosition(0);
MakeVisible(ETrue);
}
</code>
Besides changing the menu and the CBA as we have already learned to do, this code copies the message received when the view was activated into an HBufC8*, that is an instance variable of the CViewServerParamView. Later on, this buffer's contents are drawn on the screen by the Draw() function:
<code>
// Draw this application's view to the screen
void CViewServerParamView::Draw(const TRect& /*aRect*/) const {
CWindowGc& gc = SystemGc();
TRect rect = Rect();
TPoint point(0,0);
const CFont* font = iEikonEnv->NormalFont();
_LIT(KTitle,"ViewServer - View Four");
_LIT(KCredit,"Parameter:");
gc.SetBrushColor(KRgbBlack);
gc.Clear(rect);
gc.SetPenColor(KRgbWhite);
gc.UseFont(font);
point.iX = rect.Width()/2 - font->TextWidthInPixels(KTitle)/2;
point.iY = rect.Height()/2;
gc.DrawText(KTitle,point);
gc.DiscardFont();
font = iEikonEnv->AnnotationFont();
<strong> HBufC* text = HBufC::NewLC(KCredit().Length() + param->Length());
TPtr16 ref = text->Des();
ref.Append(KCredit);
ref.Append(*param);</strong>
point.iX = rect.Width()/2 - font->TextWidthInPixels(*text)/2;
point.iY += font->AscentInPixels() + 10;
gc.UseFont(font);
<strong> gc.DrawText(*text,point);</strong>
gc.DiscardFont();
CleanupStack::PopAndDestroy();
}
</code>
You can use the emulator to run this test and check that the parameter passed is really there and is drawn nicely to the screen.
Utilizing external application views
Ok, we are almost there, and this one is really easy: just use the very same ActivateViewL() function you have been using throughout this article; the only two changes that must be done are: pass the application ID of the target application, not ours, and pass the id of the view you want to activate in that application.
The command that will launch the external view is defined in the ViewServer.hrh, associated in the resource file as below:
<code>
RESOURCE MENU_PANE r_ViewServer_menu
{
items =
{
MENU_ITEM {command = EViewServerChangeCmd; txt = "Second View";},
MENU_ITEM {command = EViewServerLaunchCalendarCmd; txt = "Launch Calendar";},
MENU_ITEM {command = EAknSoftkeyExit; txt = "Exit";}
};
}
</code>
<p>and handled in the CViewServerAppUi as follows:</p>
<code>
// handle any menu commands
void CViewServerAppUi::HandleCommandL(TInt aCommand) {
switch(aCommand) {
...
case EViewServerLaunchCalendarCmd: {
ActivateViewL(TVwsViewId(KCalendarId,KDayViewId));
break;
}
}
</code>
As you can see, the ActivateViewL() function takes now the KCalendarId parameter, that specifies the Application ID of the Calendar application, and the KDayViewId, that specifies the ID of the "Day" view of the same application. These values are declared earlier on the class, and have been taken from the Utilizing External Application Views v1.0 document by Forum Nokia. In this document you will find the application and message ids for many frequently used applications.
Conclusion
I shown you how the use of the Symbian View Server can help you develop complex applications more easily and with cleaner code, leading to less bugs, easier code maintenance and stronger portability. The use of the view server forces you to correctly layer your application in a way that means you can always change one part of the system without impacting the others. I also illustrated how to use semi-dynamic menus and dynamic CBAs for making the User Interface of the application more predictable and thus, easier to learn for the end user.
Most of the code create here will work, with some minor adjustments, in any version of the Symbian OS which has a View Server, so it is not tied to Series 60, UIQ or whatever; it is perfectly portable code.
In the next article I will close the View Server series by making use of the Series 60-only AvkonViews, that make even easier to use the view server by automating most of the view, menu and CBA switching, with an intensive use of the Resources file instead of C++ code.
See you there!
Many thanks to Daniel Rocha of rawsocket.org for permission to reproduce thus article. |