Symbian OS C++ for Mobile Phones VOL 1 PHẦN 4 docx

73 250 0
Symbian OS C++ for Mobile Phones VOL 1 PHẦN 4 docx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

ViewCmdHitFleet() shows how the controller ties together the two fleets provided by the engine, so that the engine's iOppFleet is what I see on the screen, while the engine's iMyFleet is the real data for the 'opponent's' fleet. void CGameController::ViewCmdHitFleet(TInt aX, TInt aY) { __ASSERT_ALWAYS(IsMyTurn(), Panic(EHitFleetNotMyTurn)); __ASSERT_ALWAYS(!(iEngine->iOppFleet.IsKnown(aX, aY)), Panic(EHitFleetAlreadyKnown)); // Hit fleet iEngine->iOppFleet.SetShipType(aX, aY, iEngine- >iMyFleet.ShipType (aX, aY)); iEngine->iOppFleet.SetHit(aX,aY); // Update view iAppView->DrawTilesNow(); // If game is won, transition to finished if(iEngine->IsWon()) { iState = EFinished; iEnv->InfoMsg(R_GAME_CONGRATULATIONS); } } Firstly, the controller asserts that it has been called in the right circumstances. The conditions asserted should always be true, but just in case I didn't implement OfferKeyEventL() properly in the view, or because of some other problem I didn't think of – those are usually the worst kinds of problem! I make this assertion so the program can quickly panic if it gets called in the wrong circumstances. We'll see when we get to the Battleships version of the controller, with nine states and many functions that are only valid in certain states, that these assertions are extremely useful. I use two simple lines to transfer the knowledge of the real fleet from 'my fleet' to the 'opponent's fleet', and to say I hit that square: iEngine->iOppFleet.SetShipType(aX, aY, iEngine->iMyFleet.ShipType (aX, aY)); iEngine->iOppFleet.SetHit(aX,aY); If I hit a ship, the engine takes care of ensuring that surrounding squares, on the opponent's fleet, are marked as sea. After this, I update the opponent's fleet view using DrawTilesNow(). Finally, I check whether this means I've won the game. If so, I write an information message to say so, and set the state to finished. In real Battleships, the real complexity in the whole game arises from the fact that this line, iEngine->iOppFleet.SetShipType(aX, aY, iEngine->iMyFleet.ShipType (aX, aY)); won't work. Instead of simply doing an object look-up, I have to send a message to the real opponent, wait for the response, and meanwhile allow the user of this game to close the file, temporarily or permanently abandon the game, resend the message in case it got lost, and so on. 9.7 The App UI Now that we've reviewed the engine, view, and controller, we can return to the app UI. Back in Chapter 4, I described the app UI as if it was the main class in a GUI application. In a way, that's true: the entire menu tree of any GUI application is handled through the app UI, and in a typical large application, that amounts to a lot of commands. But we now have another perspective on the app UI: it is just another source of events to be handled by the controller. This isn't an incompatible statement; it's just a different perspective. Here's the app UI declaration in appui.h: class CGameAppUi : public CEikAppUi { public: void ConstructL(); ~CGameAppUi(); private: // From CEikAppUi void HandleCommandL(TInt aCommand); void HandleModelChangeL(); // Commands void CmdStartL(); void CmdZoomInL(); void CmdZoomOutL(); private: // Uses CGameController* iController; // Has CGameAppView* iAppView; }; There are no surprises in the command-handling framework. Cmd-ZoomInL() and CmdZoomOutL() are handled by passing them straight to the controller: void CGameAppUi::CmdZoomInL() { iController->ZoomInL(); } void CGameAppUi::CmdZoomOutL() { iController->ZoomOutL(); } CmdStartL() checks to see if a game is already in progress, and queries the player if so: void CGameAppUi::CmdStartL() { // User-friendly check if(iController->IsMyTurn()) { if(!iEikonEnv->QueryWinL(R_GAME_QUERY_ABANDON)) return; } iController->Reset(); iAppView->DrawTilesNow(); } If the game had finished anyway, or if the user confirmed that they really did want to start a new game, then the app UI asks the controller to reset, and gets the app view to redraw. Back in Chapter 4, I introduced the resource file as being something quite heavily associated with the app UI. However, Solo Ships doesn't have a toolbar or any dialogs, so its resource file is not enormous. Here it is (minus #includes and suchlike): NAME SHIP RESOURCE RSS_SIGNATURE { } RESOURCE TBUF { buf="Battleships"; } RESOURCE EIK_APP_INFO { menubar=r_game_menubar; hotkeys=r_game_hotkeys; } RESOURCE HOTKEYS r_game_hotkeys { control= { HOTKEY { command=EEikCmdExit; key="e"; }, HOTKEY { command=EEikCmdZoomIn; key="m"; }, HOTKEY { command=EGameCmdStart; key="n"; } HOTKEY { command=EEikCmdZoomOut; key="o"; } }; } RESOURCE MENU_BAR r_game_menubar { titles= { MENU_TITLE { menu_pane=r_game_file_menu; txt=STRING_r_game_file_menu; }, MENU_TITLE { menu_pane=r_game_view_menu; txt=STRING_r_game_view_menu; } }; } RESOURCE MENU_PANE r_game_file_menu { items= { MENU_ITEM { command=EGameCmdStart; txt= STRING_r_game_EGameCmdStart; }, MENU_ITEM { command=EEikCmdExit; txt= STRING_r_game_EEikCmdExit; } }; } RESOURCE MENU_PANE r_game_view_menu { items= { MENU_ITEM {command=EEikCmdZoomIn; txt=STRING_r_game_EEikCmdZoomIn; }, MENU_ITEM {command=EEikCmdZoomOut; txt=STRING_r_game_EEikCmdZoomOut;} } }; RESOURCE TBUF r_game_reset { buf=STRING_r_game_reset; } RESOURCE TBUF r_game_already_known { buf=STRING_r_game_reset; } RESOURCE TBUF r_game_query_abandon { buf=STRING_r_game_query_abandon; } RESOURCE TBUF r_game_congratulations { buf=STRING_r_game_congratulations; } There are only four hotkeys (zoom in and out, new-game, and exit), and two menus, with two options each (new-game and exit, zoom in and zoom out). In addition, there are three strings for use in info-messages and one for use in a query dialog. Note Released UIQ applications should not have an Exit command, though it can be useful in debug builds for checking against memory leaks. But I'm not following the UIQ style guide very closely for this application. The other app UI functions are all related to persistence. I've been saving up that topic for a section of its own, so now's the time to tackle it. 9.8 Persistence A key decision for the user interface designs that run on Symbian OS is whether to expose the file system to the end user or not. Typically, the designs that run on communicator/PDA machines (Psion PDAs, Nokia 9200 family) do allow users to interact directly with files, while designs for phones (UIQ, Nokia Series 60) don't. A directly exposed file system can confuse users, and gives an experience more like using a PC than a phone. At a certain level, whether the file system is exposed or not is irrelevant to a program's data storage: in either case, a file system exists, and is where persistent data is stored. But when designing how your application interacts with the user, this is a crucial consideration. UIQ applications that allow the user to select an item (what in the PC world would be called a 'document') to work on, such as the Jotter application, each must invent their own means of presenting the available items, and allowing the user to pick one. The application framework, historically first developed for PDAs, was designed with an expectation that the file system would be exposed, and therefore comes with functionality that enables shell programs to interact with suitably-written applications to load, save, and switch between files. An application that follows the required rules is called a file-based application. The chief requirement for a file-based application is that each running instance must have exactly one external document associated with it. Depending on how the application is launched from the shell, the framework can instruct to the application to either load a specified existing document or to create a new empty document. The framework can also instruct an already running application to switch to using another document. Three familiar cases from the Windows world are impossible:  A blank document called, say, Document1, which isn't yet associated with a file. This isn't allowed because it complicates the UI when closing the application.  No document at all – just the File and Help menus. This isn't allowed, and isn't really needed, because application startup is fast enough anyway.  Multiple documents, which you can cycle around using the Window menu. However, some UI designs allow multiple open instances of an application. Despite the lack of an exposed file system, UIQ applications can still be file-based, although in a slightly simplified way. A file-based UIQ application doesn't open different files: whenever it's run, it always opens the same document file. Solo Ships works like this, so we can now look at how to implement this approach. 9.8.1 Solo Ships as a File-based Application Solo Ships supports three document-related operations:  Run the application and create a new default document: this only occurs the first time that the application is run.  Run the application and open the existing document.  Exit the application and save the data to the document. Fortunately for the programmer, the application framework handles most of this. As an application programmer, you have to implement the following:  A function to store the document data.  A function to read the stored document data.  A function to set the document to an initial, default, state.  A C++ destructor for the document. 9.8.2 Store and Restore The application framework needs to share an understanding with applications about how the application document is saved to file. For this reason, file-based applications aren't free to use whatever file format they wish, but must use a structured format set by the framework. In Symbian OS, structured files are called stores, and the elements within a store are called streams. An application document's store has three streams, as shown below in Figure 9.5: Figure 9.5 The store consists of:  A special stream called a stream dictionary. This provides an index to the other streams in the store.  A stream that stores a small amount of data to identify the application to which the store relates.  A stream that contains the document data. The framework handles creating a file store with the appropriate structure, and sets the application identifier stream. You have to supply code for storing and restoring the document data from the third stream. Stores are discussed in detail in Chapter 13. Document responsibilities CGameDocument contains the basic functions you need to store and restore document data. Here's StoreL(): void CGameDocument::StoreL(CStreamStore& aStore, CStreamDictionary& aStreamDict) const { TStreamId id = iController->StoreL(aStore); aStreamDict.AssignL(KUidExample,id); } The framework calls this function, passing objects that represent the store, and the stream dictionary. This code calls iController->StoreL() to store the controller's data to the stream, and returns the stream ID of the stream so created. The next line makes an entry in the stream dictionary: it records that the data for identified application (KUidExample, the application UID), is stored in the stream with the specified ID. There's also a corresponding RestoreL(): void CGameDocument::RestoreL(const CStreamStore& aStore, const CStreamDictionary& aStreamDict) { // New controller initialized from store TStreamId id = aStreamDict.At(KUidExample); CGameController* controller = CGameController::NewL(aStore, id); delete iController; iController = controller; } This time, you look up the ID of the document data stream in the dictionary and restore from it. One possibility would be to call iController->RestoreL() and overwrite the data in the existing controller object. An alternative, as shown here, is to construct an entirely new controller object by using CGameController::NewL(aStore,id). After I've constructed the new one successfully, I delete the old one, replacing it with the new controller. Note Applications may store more than one stream using the stream dictionary provided. Many applications use this to store different kinds of data. Storing the controller data The document just passed the buck to the controller. Here's how the controller stores its data: TStreamId CGameController::StoreL(CStreamStore& aStore) const { RStoreWriteStream stream; TStreamId id = stream.CreateLC(aStore); iEngine->ExternalizeL(stream); ExternalizeL(stream); stream.CommitL(); CleanupStack::PopAndDestroy(); // stream return id; } The idea here is to create a stream, write both the engine and any controller data to it, and then close the stream. Symbian OS programs conventionally calls functions that write an object's data to a stream ExternalizeL(). The engine's ExternalizeL() function looks like this: void CGameEngine::ExternalizeL(RWriteStream& aStream) const { aStream << iMyFleet; aStream << iOppFleet; aStream.WriteUint8L(iFirstPlayer); } It externalizes the engine's two fleet objects, and the flag that records whose turn it is. The first two lines show a neat idiom to use when working with streams. Symbian OS overloads the operator << for streams, so that it calls ExternalizeL() on the argument, hence the naming convention. So the first line in the above function could have been equivalently, if less concisely, written as iMyFleet.ExternalizeL(aStream); So, the function called is in fact this: void TFleet::ExternalizeL(RWriteStream& aStream) const { for(TInt i = 0; i < 64; i++) aStream.WriteUint8L(iSquares[i]); aStream.WriteUint8L(iMyFleet); for(i = 0; i < 10; i++) aStream << iShips[i]; aStream.WriteInt8L(iKnownShips); aStream.WriteInt8L(iRemainingShips); aStream.WriteInt8L(iRemainingSquares); aStream.WriteInt8L(iSquaresHit); } It writes the state of the 64 game squares, the ships, and some flags. The << idiom is used again, this time to call the ship class's ExternalizeL() function: void TShip::ExternalizeL(RWriteStream& aStream) const { aStream.WriteUint8L(iType); aStream.WriteInt8L(iLength); aStream.WriteInt8L(iRemaining); aStream.WriteInt8L(iStartX); aStream.WriteInt8L(iStartY); aStream.WriteInt8L(iDx); aStream.WriteInt8L(iDy); } The ship data is just a series of 8-bit integers. In all these functions, we don't expect the integer values to be more than 8 bits, so we specify this is what we want to store. This saves some space compared to storing a full 32 bits for each integer. Finally, the controller itself externalizes some extra persistent data: namely, its state and the current zoom factor: void CGameController::ExternalizeL(RWriteStream& aStream) const { aStream.WriteUint8L(iState); aStream.WriteInt32L(iZoomFactor); } Restoring the controller data We saw that the document's RestoreL() uses the controller's restoring NewL() function that takes CStreamStore and TStreamId arguments, which is coded as follows: CGameController* CGameController::NewL(const CStreamStore& aStore, TStreamId aStreamId) { CGameController* self = new(ELeave) CGameController; CleanupStack::PushL(self); self->RestoreL(aStore, aStreamId); CleanupStack::Pop(); return self; } RestoreL() is private, like ConstructL(), so that it can't be accidentally called by CGameController's clients. Here it is: void CGameController::RestoreL(const CStreamStore& aStore, TStreamId aStreamId) { iEnv = CEikonEnv::Static(); RStoreReadStream stream; stream.OpenLC(aStore,aStreamId); iEngine = new(ELeave) CGameEngine; iEngine->InternalizeL(stream); InternalizeL(stream); CleanupStack::PopAndDestroy(); // stream } The first task is to get a GUI environment pointer. That's needed by the controller, whether it's constructing from scratch or restoring from a document. Next, it creates a new engine object and requests it to initialize itself from the specified stream. As you would expect, InternalizeL()is the conventional name for functions that read data from a stream. The controller's and engine's InternalizeL() functions are pretty well the reverse of the ExternalizeL() functions already seen: they add little that's new, so I'll move quickly on. 9.8.3 Creating a Default Document When the application is opened with a new file (which for UIQ is only when the application is started for the first time), it can't restore from anything, so instead it needs a new default document. The framework creates the actual new document file: the folder for this is device- specific (in the standard UIQ emulator it's C:\Documents\<app-name>), while the default document name is read from the first TBUF in the resource file ('Solo Ships' in this case). The application class has the responsibility of setting up the application appropriately for a default state: CApaDocument* CGameApplication::CreateDocumentL() { CGameDocument* doc = new(ELeave) CGameDocument(*this); CleanupStack::PushL(doc); doc->ConstructL(); CleanupStack::Pop(); return doc; } The document second-phase constructor creates a new controller: void CGameDocument::ConstructL() { iController = CGameController::NewL(); } This uses the conventional NewL(), which constructs a default controller, rather than restoring one from file. 9.8.4 App UI and the Document Finally, we need to link the app UI to the document. Most importantly, the command handler for EEikCmdExit which, in all our applications until now, has just been called Exit(), now includes SaveL(): [...]... CAppUi::HandleCommandL(TInt aCommand) { _LIT8(KViewCmdData1, "P1 My"); _LIT8(KViewCmdData2, "P1 Opp"); _LIT8(KViewCmdData3, "P2 My"); _LIT8(KViewCmdData4, "P2 Opp"); switch (aCommand) { case ECmdViewPlayer1MyShips: ActivateViewL(TVwsViewId(KUidTpShips,KP1MyViewUID), KCmd1UID, KViewCmdData1); break; case ECmdViewPlayer1OppShips: ActivateViewL(TVwsViewId(KUidTpShips,KP1OppViewUID), KCmd2UID, KViewCmdData2); break;... be an answer to the question that was posed Almost every Windows user I know, who isn't a professional programmer has lost data because of this Windows dialog This illustrates an important aspect of all programming for Symbian OS: write for inexperienced computer users, and make it clear what's going to happen when they select a menu or dialog option The Symbian OS way is to ask a straight yes/no question... user-friendly synonym for 'control' I'll always use 'control' when referring to programming 10 .1. 6 Dialog Processing Dialogs should help the user to enter valid data and, if it's not valid, should point out the error as early and as helpfully as possible This works in various ways: For some controls, you can specify validity criteria: for numeric editors, for instance, you can specify a range For some controls,... the message information This caused a panic as waiting for the user to confirm the dialog caused a view server time-out 9.9.6 Sound Effects A view is primarily about displaying things, but it can be about interacting with the user through sounds too Symbian OS can play audio files of various formats (gsm 6 .10 , au, wav, wve and raw audio data), as well as tones and streaming PCM audio For tp-ships,... I want to continue with the delete operation and I have to answer 'No' or 'Yes' The most important point about this dialog is that it's a straightforward query, with a Yes/No answer Compare it with a similar dialog on Windows as shown in Figure 10 .2 Figure 10 .2 I got this by typing Ctrl+F4 (a shortcut for File | Close) in Word, as I was typing the previous paragraph The question being asked here is,... note that buttons can't be focused: OK is always on the Confirm hardware key Without a keyboard, other buttons are selected with a pointer 10 .1. 3 A Multipage Dialog A sample multipage dialog taken from the UIQ Agenda application can be seen in Figure 10 .4 Figure 10 .4 I got this dialog by selecting the Preferences menu item on the Edit menu pane in Agenda This allowed me to customize my Agenda view, such... associated with the entire dialog – not with each page It's bad style to change it when the pages change 10 .1. 4 Cue Text You should make every effort to ensure that the meaning of the controls in your dialogs is transparently obvious to most users Be prepared to work hard at this: you'll need to choose text and functionality with care, order lines and pages in your dialogs sensibly, and be particularly... control as possible, these options will always be with us, and cue text in dialogs will, therefore, always have a role somewhere 10 .1. 5 Controls Each line in a dialog is a captioned control with two or three components: a caption, to tell the user what the line is for; a control, to allow something to be displayed and/or edited; a tag, used by some controls to indicate measurement units, for example,... three dialogs to show you the kinds of things you can do with them 10 .1. 1 A Query Dialog General query dialogs tend not to feature in some UIs such as UIQ, but where they do occur, they tend to be tailored to the specific context Here's a typical example: Figure 10 .1 I got this by selecting a file in the QExAppFileH example from the UIQ C++ SDK and then pressing the Delete button on the resulting dialog... Window().SetOrdinalPosition(0); (static_cast(iEikonEnv->EikAppUi()))-> SetActiveView(*this); // display the view switch information if (aCustomMessageId.iUid) { TBuf buf,buf2; buf.Format(_L("ID: %x: "), aCustomMessageId.iUid); buf2.Copy(aCustomMessage); buf.Append(buf2); iEikonEnv->InfoMsg(buf); } } #else This version formats the message ID and data into a string buf and displays it in an information . 'my' and 'opposition' fleet views as he wished.  A player should not be able to access the views for the other player (as that shows the true positions of the opponent's. the engine, so that the engine's iOppFleet is what I see on the screen, while the engine's iMyFleet is the real data for the 'opponent's' fleet. void CGameController::ViewCmdHitFleet(TInt. 'hider' view; and that the next player would start his turn by choosing to view his own fleet or his target. This gives a total of five views (2 × 'my fleet', 2 × 'opposition

Ngày đăng: 13/08/2014, 08:21

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan