GEOS-SC : Flex UI : Sections : How To Bring Everything Together

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:

  1. Declare the class Notepad2App in the header file and add the appropriate EventHandlers.
  2. Register the application, add code for the constructor, and build the user interface.
  3. Attach a FlexMenu to the frame for file operations.
  4. Add the helper function CreateDialogUI() to generate the dialogs for the application.
  5. Add the EventHandler MenuItemChosen() to handle any menu events.
  6. Add the EventHandler ItemStateChanged() to handle any list events.
  7. Add code to Save and Revert a file to ItemStateChanged().
  8. Compile and run the application.


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. FlexLists differ from FlexMenus 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