Beech:Creating other frames

From WxWiki
Jump to: navigation, search

Introduction

Creating other frames

All of our work so far has used a single frame. We're not limited to a single frame, we can use as many as we wish and as a first step we will create a simple application that itself can create many instances of our original frame. This more closely follows real world applications.

In the screen shot to the left you can see this at work. In the background is the original frame and layered above that are many other frames.

Each of the additional frames has the same properties as the original.

Getting Started

#include "basic.h"
#include "basicframe.h"

IMPLEMENT_APP(BasicApplication)

bool BasicApplication::OnInit()
{
	appTitle = TITLE;
	appTitle.Append(ABOUT);

	BasicFrame * frame = new BasicFrame(appTitle, 50, 50, 450, 300);
	frame->Show(true);

	SetTopWindow(frame);

	return true;
}

Initially I take a very simple approach. I take our BasicFrame, along with all of its properties, and make it a class in its own right. You can see here that we have an interface file basicframe.h and we create an instance of BasicFrame called frame.

We add a new method to our BasicFrame (OnNewFrame):

void BasicFrame::OnNewFrame(wxCommandEvent & event)
{
	wxString theTitle;
	theTitle = wxGetTextFromUser(_("Enter a title"),
		_("Input text"), _("NO NAME"), this, -1, -1, true);

	BasicFrame * win = new BasicFrame(theTitle, 150, 150, 450, 300);
	windowList->Append(win);  
	win->Show(true);
}

We modify the destructor to be nice and tell us about each frame being destroyed:

BasicFrame::~BasicFrame()
{
	if(windowList->GetCount() > 1)
	{
		wxMessageDialog destructDialog( this, _("Destroying window"),
			_("In BasicFrame destructor"), wxOK);
		destructDialog.ShowModal();
	}
}

We add a method to just show how many frames have been created:

void BasicFrame::OnCount(wxCommandEvent & event)
{
	char buf[20];

	sprintf( buf, "%d frames", windowList->GetCount() );
	wxMessageDialog countDialog( this, buf, _("Frames"), wxOK );
	countDialog.ShowModal();
}

Apart from these changes there is little that is different from our previous examples.

The downside of creating frames this way is each of the frames is quite independent of the other frames, there is no parent->child relationship. This might be desirable but you might also want a behaviour which reflects a parent->child relationship. When a parent frame is destroyed you want all the child frames destroyed also.

Implementing Hierarchies of Frames

Frame hierarchy

If you read the wxWidgets documentation you find there is a common pattern in that each window can have a pointer to a parent window.

wxWindow( wxWindow* parent,
          wxWindowID id,
          ...
)

If we construct a window and hand over the parent pointer to the constructor then we establish a parent->child relationship between the creator (parent) and the created (child) windows.

The graphic shows that. The topmost window is the parent of window 1 which itself is the parent of windows 1a and 1b. Window 1b is the parent of window 1b.1 and the topmost window is the greatgrandparent of 1b.1. Achieving this is quite simple.

In the interface (basicframe.h) we modify the constructor interface so that it contains a pointer to the parent:

BasicFrame( wxWindow * parent,
            const wxChar * title,
            int xpos,
            int ypos,
            int width,
            int height
);

We reflect this is in the class constructor and hand over the parent to the wxFrame constructor:

BasicFrame::BasicFrame( wxWindow * parent, const wxChar * title,
                        int xpos, int ypos, int width, int height )
: wxFrame( parent, -1, title, wxPoint(xpos, ypos), wxSize(width, height) )

In the application we start with a NULL pointer since there is no parent to this window:

BasicFrame* frame = new BasicFrame(
	wxWindow * NULL, appTitle, 50, 50, 450, 300);

When we close a parent window all the child windows are automatically destroyed. If we use the frame Close method then this will also close, and destroy, all child windows. You can get yourself into a bit of a bind when closing windows and by far the safest way is to let the Close method do it for you. Close will call the destructor and all should be well. You can of course override Close and introduce some other behaviour as we have in some of the examples but there is a little problem with this particular example when it comes to closing parent frames. If the contents of the text control of a child frame have changed and we exit from the parent then we lose the changes in the child frame text control. We'll come back to this later but first I'd like to introduce another topic: communicating with the parent window.

Communication Between a Child and Parent Frame

Communication from a parent to a child frame is not difficult. The parent "knows" the child since it created it and accessing the child and its public members is simply a matter of referring to the child as in thisChild->publicMember or thisChild.publicMember according to whether the reference is to a dynamically created object or a statically created object. Communicating the other way: child to parent, is not so straightforward since the only reference the child might have to the parent is a pointer passed to the child frame when it is created. Here are three ways we can achieve this kind of communication:

  1. Using a global variable
  2. Using the parent reference passed to the child constructor, and
  3. "Discovering" the parent using a window method

In this example I have separated the original basicframe into a mainframe and basicframe classes. There will only be one mainframe, this is the parent frame but there may be many basicframes which are the child frames.

The implementation file for the application contains a global variable parentFrame which is of type Mainframe.

#include "basic.h"
#include "basicframe.h"
#include "mainframe.h"

MainFrame * parentFrame;

IMPLEMENT_APP(BasicApplication)

bool BasicApplication::OnInit()
{
	appTitle = _("Step 7b");

	parentFrame = new MainFrame((wxWindow *) NULL, appTitle, 50, 50, 450, 300);
	parentFrame->Show(true);

	SetTopWindow(parentFrame);

	return true;
}

The implementation file for the basicframe class contains an external declaration indicating that parentFrame is defined somewhere else.

#include "basic.h"
#include "basicframe.h"
#include "mainframe.h"

extern MainFrame * parentFrame;

BasicFrame::BasicFrame(	wxWindow * parent, const wxChar * title,
                        int xpos, int ypos, int width, int height)
...

The mainframe class contains a method called ChildMessage() as well the text control theText which we have used throughout this series of examples. We'll use these two Mainframe members as the targets for the child communications.

void MainFrame::ChildMessage(wxString s)
	{ theText->AppendText(s); }

Using a Global Variable

Using the global instance parentFrame is straightforward:

parentFrame->ChildMessage(_("\nA Message from the child ...\n"));

You may have been told by a zealous teacher or lecturer that using global variables is sinful. It is, but only a venal sin, it won't see you in programmer's Hell. Using too many global variables - that is really bad and could get you into lot's of trouble. The question is: How many is too many? The answer is: It depends on what undesirable side-effects might occur. Lock it in.

Using the Parent Reference Passed to the Child

When we create a child frame, like this:

BasicFrame * win = new BasicFrame(this, theTitle, 150, 150, 450, 300);

We hand over a pointer to this which is the class instance responsible for creating the child frame.

When we want to refer to the parent frame we have to cast the reference we received to a pointer of type MainFrame:

((MainFrame*)p)->theText->AppendText("\nWelcome\n\n");

It looks messy but it is simply directing the compiler to interpret the wxWindow-type pointer it received as a MainFrame pointer. We can legimately do this because the pointer is a pointer to a MainFrame object.

Retrieving the Parent Window

A window can determine if it has a parent by using the GetParent() window member function:

((MainFrame*)GetParent())->ChildMessage(x);

Again we have to cast the reference.

Summary

A little reminder: In presenting these I have shown only the salient details. You should remember that the examples also contain other code that is relevant, for example the window identifiers and the event table. I have left the bulk of the example programs out of the presentation in order to save space and minimize distraction.