Introduction | Terminology | How To Write An Application | How To Detect User Interaction | How To Bring Everything Together
Most applications consist of more than one type of Flex UI component. As applications get more complex, they use multiple EventListeners to monitor the user's actions. Consequently, the EventHandlers for each object may overlap and depend on each other.
Recall the address book example mentioned in the last section, consisting of a FlexTable
, FlexList
,
and FlexButton
with the corresponding EventHandlers: TableSelectionChanged()
, ItemStateChanged()
,
and ActionPerformed()
.
In this application design, one EventHandler may trigger another EventHandler.
For example, if the user changes the name of an addressbook entry
it will call TableSelectionChanged()
. Depending on the user's interaction,
TableSelectionChanged()
may then call ItemStateChanged()
to update the list.
This example will create a notepad application that uses multiple files for saving and a menu for file operations, by using the following Flex UI components:
To write a notepad application that uses multiple files to save data, allowing the user to select file operations from a menu:
Notepad2App
in the header file and add the appropriate EventHandlers.FlexMenu
to the frame for file operations.CreateDialogUI()
to generate the dialogs for the application.MenuItemChosen()
to handle any menu events.ItemStateChanged()
to handle any list events.ItemStateChanged()
.
Declare the class Notepad2App
in the header file and add the EventHandlers
MenuItemChosen()
and ItemStateChanged()
.
(resource | header | source)
class Notepad2App : public ItemListenerInterface, public MenuAdapter, public AppBase { public: ... // Override MenuItemChosen to handle MenuEvents. virtual void MenuItemChosen(MenuEvent&_event); // Override ItemStateChanged to handle ItemEvents for the list. virtual void ItemStateChanged(ItemEvent& event ); ... };
Register the application, code the constructor, and build the user interface in the source file notepad2.cpp
.
(resource | header | source)
// Register the application. class Notepad2AppResidentApplication : public ResidentApplication { public: Notepad2AppResidentApplication() : ResidentApplication(NOTEPAD2APP_NAME) {}; virtual AppBase *CreateAppBase(void) { return new Notepad2App; } }; static Notepad2AppResidentApplication notepad2App; // Specify the name of the application. static AppNameAttribute notepad2AppName(¬epad2App, NOTEPAD2APP_APP_TEXT); // Call the constructor to initialize our variables. Notepad2App::Notepad2App() : _notepad2AppMainFrame(NULL), _notepad2AppTextDisplay(NULL), _notepad2AppSaveDialog(NULL), _notepad2AppOpenDialog(NULL), _notepad2AppUIBuilt(FALSE), _notepad2AppDialogOpen(0) { } // Build the application. void Notepad2App::SetAppContext(const TCHAR *) { // Call the helper function to build the UI. if (!_notepad2AppUIBuilt) { if (AttachNotepad2AppUI() == FAILURE) { EC_WARN("Unable to build Notepad2 application."); Exit(); } else { _notepad2AppUIBuilt = TRUE; } } // Pass context to the USE_IT macro to suppress compiler warnings. USE_IT(context); }
Since the flag _notepad2AppDialogOpen
keeps track of which dialog is open,
we set its value in the constructor to indicate there is currently no open dialog.
Attach a FlexMenu
to the frame to handle file operations in the method AttachNotepadApp2UI()
.
(resource | header | source)
// Create the menu for the application and attempt to add it // to the frame. FlexMenu *menu = theUIFactory->CreateFlexMenu(HINT_PULL_DOWN_MENU, NOTEPAD2APP_FILE_MENU_TEXT); if (NULL == menu) { EC_WARN("Unable to create menu."); return FAILURE; } if (_notepad2AppMainFrame->Add(menu) != SUCCESS) { EC_WARN("Unable to add menu."); delete menu; return FAILURE; } // Create the menu buttons for the menu. for ( int i = 0; i < 3; i++) { FlexMenuButton *menuButton = theUIFactory->CreateFlexMenuButton(0, NOTEPAD2APP_MENU_ITEMS[i], (MenuItemID) i); if (NULL == menuButton) { EC_WARN("Unable to create button."); return FAILURE; } else { if (menu->Add(menuButton) != SUCCESS) { EC_WARN("Unable to add button."); delete menuButton; return FAILURE; } } } // Make the application a menu listener. menu->AddMenuListener(*this);
Create each FlexMenuButton
by calling the method CreateFlexMenuButton()
.
If the menu button is created successfully, add it to the menu.
After creating the menu, use the AddMenuListener()
method to add an EventHandler
for handling menu selections, then add the menu to the frame.
Add the helper function CreateDialogUI()
to create the dialogs for the application in notepad2.cpp
.
(resource | header | source)
Result Notepad2App::CreateDialogUI(static const TCHAR *title, FlexDialog **dialog) { // Create a dialog for the application and attempt to add it. (*dialog) = theUIFactory->CreateFlexDialog(HINT_DIALOG_WITH_NO_CLOSE_BUTTON, title); if (NULL == (*dialog)) { EC_WARN("Unable to create dialog."); return FAILURE; } if (Add(*dialog) != SUCCESS) { EC_WARN("Unable to add dialog."); delete (*dialog); return FAILURE; } // Create a layout for the dialog. DialogLayout *dialogLayout = new DialogLayout(); if (NULL == dialogLayout) { EC_WARN("Unable to create dialog layout."); return FAILURE; } (*dialog)->SetLayout(dialogLayout); // Create a label for the dialog. FlexLabel *label = theUIFactory->CreateFlexLabel(0, title); if (NULL == label) { EC_WARN("Unable to create label."); return FAILURE; } if ((*dialog)->Add(label) != SUCCESS) { EC_WARN("Unable to add label"); delete label; return FAILURE; } // Create a list to hold the filenames. FlexList *list = theUIFactory->CreateFlexList(0, NOTEPAD2APP_LIST_ROWS); if (NULL == list) { EC_WARN("Unable to create list."); return FAILURE; } if ((*dialog)->Add(list) != SUCCESS) { EC_WARN("Unable to add list."); delete list; return FAILURE; } // Add strings to the list. if (list->Add(NOTEPAD2APP_FILE1) == FAILURE) { EC_WARN("Unable to add string to list."); return FAILURE; } if (list->Add(NOTEPAD2APP_FILE2) == FAILURE) { EC_WARN("Unable to add string to list."); return FAILURE; } if (list->Add(NOTEPAD2APP_FILE3) == FAILURE) { EC_WARN("Unable to add string to list."); return FAILURE; } if (list->Add(NOTEPAD2APP_FILE4) == FAILURE) { EC_WARN("Unable to add string to list."); return FAILURE; } // Add a listener to the system and return SUCCESS. if (list->AddItemListener(*this) != SUCCESS) { EC_WARN("Unable to add item listener."); delete list; } return SUCCESS;
Use a DialogLayout
to arrange the components for the dialog.
A DialogLayout
is a layout that is designed specifically for managing the small space of a dialog box.
Use a FlexList
to display the possible files for saving and reverting the text buffer in the application.
A FlexList
is a Flex UI component that holds a list of string values, in this case, the FlexList
holds filenames.
FlexList
s differ from FlexMenu
s in that a FlexMenu
uses menu buttons
to generate events, while the FlexList
uses changes in the current selection to generate an events.
For each file use the FlexList
method Add()
to add its text string to the list.
After creating the list, use the AppBase
method Add()
to attach the dialog to the application base.
Add the EventHandler MenuItemChosen()
to handle menu events in notepad2.cpp
.
(resource | header | source)
void Notepad2App::MenuItemChosen(MenuEvent& event ) { // Get the id for the menu item chosen. uint32 id = (uint32)event._menuItemID; // As long as there is not an open dialog, // either make a New file, Save a file, or Open a file. if (_dialogOpen < 1) { switch (id) { case NOTEPAD2APP_NEW_MENU_ITEM: _notepad2AppTextDisplay->SetText(_TEXT("")); break; case NOTEPAD2APP_SAVE_MENU_ITEM: _notepad2AppDialogOpen = id; _notepad2AppSaveDialog->SetVisible(TRUE); break; case NOTEPAD2APP_OPEN_MENU_ITEM: _notepad2AppDialogOpen = id; _notepad2AppOpenDialog->SetVisible(TRUE); break; } } }
The _menuItemID
contains the id
for the selected menu item.
The switch
statement uses the id
to determine which FlexMenuButton
was selected,
thereby displaying the appropriate dialog and setting _dialogOpen
to the corresponding value.
Add the EventHandler ItemStateChanged()
to handle any list events in notepad2.cpp
.
(resource | header | source)
void Notepad2App::ItemStateChanged(ItemEvent& event ) { // Create a file pointer for SAVE and OPEN, // as well as a string for the filename. File *myFile = NULL; const TCHAR *filename = NULL; TCHAR *textBuffer; int32 bufferSize = 0; // Get the list and its index. FlexList *list = (FlexList *)event.GetSource();(); uint32 index = event.GetIndex(); (); // Set the filename depending on the index. switch (index) { case 0: filename = NOTEPAD2APP_FILE1; break; case 1: filename = NOTEPAD2APP_FILE2; break; case 2: filename = NOTEPAD2APP_FILE3; break; case 3: filename = NOTEPAD2APP_FILE4; break;
Use the method GetIndex()
to retreive the number of the currently selected string.
The switch
statement uses this index
to set the appropriate filename.
Add code to Save and Revert a file to ItemStateChanged()
.
(resource | header | source)
// Check if SAVE was clicked. if (_notepad2AppDialogOpen == NOTEPAD2APP_SAVE_MENU_ITEM) { // Set the size of the buffer to read. bufferSize = (_notepad2AppTextDisplay->GetCharCount() + 1; textBuffer = new TCHAR[bufferSize]; // Store the contents of the buffer. _notepad2AppTextDisplay->GetText(textBuffer, bufferSize); // Open the file CREATE and READ_WRITE myFile = theFileStoreManager.Open(filename, O_CREAT | O_RDWR ); if (myFile != NULL) { // Write the contents of the buffer to the file // and close the file. myFile->Write((uint8 *)textBuffer, bufferSize * sizeof(TCHAR); myFile->SetSize(bufferSize * sizeof(TCHAR)); } else { _notepad2AppTextDisplay->SetText(NOTEPAD2APP_ERROR); } // Delete the buffer, close the file and the dialog. delete [] textBuffer; myFile->Close(); _notepad2AppSaveDialog->SetVisible(FALSE); } // Check if OPEN was clicked. else if (_notepad2AppDialogOpen == NOTEPAD2APP_OPEN_MENU_ITEM) { // Open the file READ_ONLY and read the contents into the buffer, // closing the file when done. myFile = theFileStoreManager.Open(filename, O_RDONLY ); if (myFile != NULL) { myFile->GetSize(&bufferSize); textBuffer = new TCHAR[bufferSize]; // Attempt to read the buffer and copy the text to the text area. if (myFile->Read((uint8*)textBuffer, bufferSize) != bufferSize) { _notepad2AppTextDisplay->SetText(NOTEPAD2APP_ERROR); } _notepad2AppTextDisplay->SetText(textBuffer); myFile->Close(); delete [] textBuffer; } else { _notepad2AppTextDisplay->SetText(NOTEPAD2APP_ERROR); } // Close the dialog. _notepad2AppOpenDialog->SetVisible(FALSE); } // Unselect any list items and reset the file pointer. list->Deselect(index); myFile = NULL; // Mark the dialog box as unopened. _notepad2AppDialogOpen = 0; }
Compile and run the application.
Introduction | Terminology | How To Write An Application | How To Detect User Interaction | How To Bring Everything Together