Working with Multiple Windows

Not all LVT programs need to have one and only one top-level window. In this chapter, we will extend our scribble program to use multiple windows.

When using multiple windows, we need to dedice if we wish to continue to have a main window -- which will cause the program to exit when it is closed -- or if we want to have several, independant windows, and not exit the program until the all of them are closed. In the latter case, we need to handle exiting the program ourselves.

One way in which we can accomplish this is to use the fact that an App object keeps track of how many top-level windows are open. Once this reaches 0, we can terminate the application by calling App::Exit. This is not the only way to do it, but it is the simplest. This is the method we'll use for our modified scribble program.

Our new ScribblerWnd class definition looks like this:

// Global application object
App app;

#include <lvt/lvt.h>

using namespace LVT;

class ScribblerWnd : public Wnd
{
public:
    ScribblerWnd();

protected:
    virtual bool OnCreate();
    virtual void OnDestroy();
    virtual void OnMenuCommand(int id);
    virtual void OnPaint();
    virtual void OnLButtonDown(int x, int y);
    virtual void OnLButtonUp(int x, int y);
    virtual void OnMouseMove(int x, int y);

private:
    std::vector<Polyline> m_lines;
    bool m_mouseDown;
};

Our new class definition is identical to the old one with the addition of two new message handlers: OnCreate and OnDestroy. In addition, our App is now global, instead of being local to main.

Let's look at the implementation of the two new message handlers:

bool ScribblerWnd::OnCreate()
{
    // Create our main menu.
    Menu mainMenu, fileMenu;
    fileMenu.Append("New", MENU_FILE_NEW);
    fileMenu.Append("Close", MENU_FILE_CLOSE);
    fileMenu.AppendSeparator();
    fileMenu.Append("Exit", MENU_FILE_EXIT);

    mainMenu.Append("File", fileMenu);
    mainMenu.Append("Clear", MENU_EDIT_CLEAR);
    SetMenu(mainMenu);

    return true;
}

void ScribblerWnd::OnDestroy()
{
    // The window has been destroyed, so destroy the object.
    delete this;

    // Exit the app if no windows are left.
    if (app.WndCount() == 0)
        app.Exit();
}

Wnd::Create calls the OnCreate message handler. In OnCreate we can do any additional initialization we need for our window object. In this case, we initialize the window's menu, and increment the window count. We then return true to allow creation to succeed. Returning false causes creation to fail, and Wnd::Create to return false.

In OnDestroy we commit suicide with delete this. This is not the only way to ensure that our window objects are destroyed when they are closed, but it is easy to implement, and safe in our case, since as we will see later, we create our ScribblerWnd objects with new.

Once the object is destroyed, we check the number of open windows with App::WndCount. Once it reaches 0, we terminate the message loop with App::Exit.

Our menu is slightly different from last time, so let's look at the new OnMenuCommand handler:

void ScribblerWnd::OnMenuCommand(int id)
{
    ScribblerWnd *newWnd;

    // Do default menu command processing.
    Wnd::OnMenuCommand(id);

    switch (id)
    {
    case MENU_FILE_NEW:
        newWnd = new ScribblerWnd;

        newWnd->Create("MultiScribble");
        newWnd->Show();

        break;
    case MENU_FILE_EXIT:
        app.Exit();
        break;
    case MENU_EDIT_CLEAR:
        // Erase all the polylines in our list and redraw the window.
        m_lines.clear();
        Redraw();
        break;
    }
}

Our handler for MENU_EDIT_CLEAR is the same as in our previous example. For MENU_FILE_NEW, we create a new ScribblerWnd object, and then create and show it. Since we are not passing the window in to App::Run, we are responsible for showing the window ourselves.

For MENU_FILE_EXIT, we simply call App::Exit, which closes any open windows, and terminates the message loop.

That covers all of our multiple window handling. The only thing left is main, which we'll look at now:

int main(int argc, char *argv[])
{
    LVT::App myApp;
    ScribblerWnd myWnd;

    myApp.Init(&argc, &argv);

    myWnd.Create("Scribble");
	myWnd.Show();

    myApp.Run();

    return 0;
}

The new main function is virtually identical to the previous one, with the exception that we do not pass our initial window into App::Run, and therefore we must show our window explicitly.

Now that you understand the basics of working with LVT window opjects, we're ready to move on to more sophisticated drawing programs, which is the topic of the next section.