The first is derived from CApp and is the main App class with responsibilities of setting everything going at the start and killing it off at the end.
// Define the application object class
class CApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
The single initialisation function has various definitions that may be
included to determine how the program starts out, e.g. maximised,
minimised etc, although it's mostly just standard stuff that is pasted
from one App to the next and never looked at again.
BOOL CApp::InitInstance()
{
m_pMainWnd = new CWindow();
// m_nCmdShow = SW_SHOWMAXIMIZED; // maximise window at creation
m_nCmdShow = SW_SHOW;
m_pMainWnd -> ShowWindow( m_nCmdShow );
m_pMainWnd -> UpdateWindow();
return( TRUE );
}
The second part is a bit more interesting and is derived from the
CWindow class. In MS terms this main parent Window refers to the frame
which will sit around the whole shebang, and so of course for an SDI
App this class effectivly means the complete thing. In an MDI App there
would be sub Windows which would have their own instances of a derived
CWindow class and also their own Message Maps.
// Define the window class
class CWindow : public CFrameWnd
{
CMenu *menu;
CTModel model;
CTView view;
CPoint start, old;
BOOL started;
CRgn frameRegion;
public:
CWindow();
~CWindow();
void OnPaint();
void OnMouseMove (UINT, CPoint);
void OnLButtonDown (UINT, CPoint);
void OnLButtonUp (UINT, CPoint);
void OnHScroll (UINT, UINT, CScrollBar*);
void OnVScroll (UINT, UINT, CScrollBar*);
void PrepareDC (CDC*);
afx_msg void OnNew();
afx_msg void OnOpen();
afx_msg void OnClose();
afx_msg void OnSave();
afx_msg void OnSaveAs();
afx_msg void OnImport();
afx_msg void OnExport();
afx_msg void OnPrintSetup();
afx_msg void OnPrint();
afx_msg void OnQuit();
afx_msg void OnAxes();
afx_msg void OnTicks();
afx_msg void OnTitles();
afx_msg void OnAbout();
DECLARE_MESSAGE_MAP()
};
The message map is the core of the Controller and is set up with the
DECLARE_MESSAGE_MAP() function (this needs to be called during
initialisation but not worried about otherwise). The Message Map
interprets Windows Messages sent by the OS and redirects them to
whichever call-back function has been defined.
BEGIN_MESSAGE_MAP(CWindow, CFrameWnd)
ON_COMMAND(IDM_FILE_NEW, OnNew)
ON_COMMAND(IDM_FILE_OPEN, OnOpen)
ON_COMMAND(IDM_FILE_CLOSE, OnClose)
ON_COMMAND(IDM_FILE_SAVE, OnSave)
ON_COMMAND(IDM_FILE_SAVEAS, OnSaveAs)
ON_COMMAND(IDM_FILE_PRINTSETUP, OnPrintSetup)
ON_COMMAND(IDM_FILE_PRINT, OnPrint)
ON_COMMAND(IDM_FILE_QUIT, OnQuit)
ON_COMMAND(IDM_STYLE_AXES, OnAxes)
ON_COMMAND(IDM_HELP_ABOUT, OnAbout)
ON_WM_PAINT()
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_HSCROLL()
ON_WM_VSCROLL()
END_MESSAGE_MAP()
This rather strange looking beast is pretty much the Message Map
itself. It's a macro that links each utilised Windows Message to a
Call-Back function. Some of these, such as ON_COMMAND(IDM_FILE_NEW,
OnNew) have the message and function defined explicitely, whereas for
others, such as ON_WM_MOUSEMOVE(), there exists a standard function
name that has to be used, in this case OnMouseMove().All of these Call-Back functions must be members of this Controller class and so, to preseve the MVC framwork, the functions here will typically call an identically named function in either the Model or View which will contain the actual code to carry out the implementation, eg.:
void CWindow::OnMouseMove (UINT flag, CPoint mousePos)
{
if (flag == MK_LBUTTON){
CClientDC dc(this);
PrepareDC(&dc);
view.OnMouseMove (&dc, mousePos);
}
}
Anyone who has come to this via C++ (I only half did) will be wondering
why this Message Map is used at all, since the C++ language contains
alternative ways of carrying out this exact task. The reason is quite
simply that the MFC is huge, literally thousands of functions, and to
do this in the traditional C++ manner would require every single
existing function needing to have an entry in a lookup table. For a
program that might only use a single call-back function this would mean
a huge waste and so this message map macro defintion is a simply a neat
little trick to save on resources.One of the most important functions contained in the Message Map is OnPaint(). This is called everytime the window is either moved, resized or exposed in some way, and simply tells the program that it needs to be redrawn.
void CWindow::OnPaint()
{
// get the viewport size
GetClientRect(&view.viewPortSize);
// set the scroll range for the x axis
SetScrollRange(SB_HORZ,0, view.tableSize.Width()
- view.viewPortSize.Width());
// set the scroll range for the y axis
SetScrollRange(SB_VERT,0, view.tableSize.Height()
- view.viewPortSize.Height());
// set the Frame size
view.SetExtent();
// set the Frame's scaling parameters
view.SetScale();
// get the device context for the window
CPaintDC dc(this);
// prepare the DC
PrepareDC(&dc);
// draw out the region outlines
view.DrawTable(&dc);
}
The first few parts of this obtain information about the Window's size
and position of the Sroll bars since this is obviously neccessary in
order to drawn out the viewing area correctly.Next the Frame, which is the scalable drawing area on the 'Page', is sorted out (see the section on the View for more information on this.)
And then, the Device Context (DC) has to be obtained and prepared. The DC may be thought of (rather tweely) as a combination of the Windows' paper, drawing board, pencil case, and everything else, all rolled into one. It refers to anything that the ouput goes to, including the screen, printer, plotter, but the details are hidden by the Windows' GDI, so it is up to the equipment's manufacterures to prepare software drivers to fit their bit of tackle into the Windows' OS and make it look to the programer as if it is identical to any other thing he can send output to.
The way in which the DC takes it's input is to an extent modifiable, and this is set up in PrepareDC().
void CWindow::PrepareDC(CDC *pdc)
{
// set the scale to 1 unit = 0.01 mm
pdc->SetMapMode(MM_LOMETRIC);
// change to a metric that allows scalable units
// previous unit preserved until further changes.
pdc->SetMapMode(MM_ISOTROPIC);
// set cr1 and cr2 to the window size in logical units
CRect cr1, cr2;
GetClientRect(&cr1);
GetClientRect(&cr2);
// convert cr2 to device units
pdc->LPtoDP(cr2);
// rescale the units to (view.scale):1
pdc->SetWindowExt(view.scale * cr1.right, view.scale * cr1.bottom);
// sets it up so that the origin is in the top left hand
// corner and +ve Y points down
pdc->SetViewportExt(cr2.right, -1 * cr2.bottom);
}
One of the most critical things is the idea of the unit size. The
Windows' default is the pixel, but this is rather hopeless since the
pixel size of a VDU may be orders of magnitude different from that of
the latest laser printer. Luckily this is easily fixed by setting it to
MM_LOMETRIC which gives a unit of 0.01mm.Next we would like to shift and stretch things slightly so that they fit onto our screen in the way that we want. To do this we shift to a unit size given by MM_ISOTROPIC. This initiually preserves the previous units (MM_LOMETRIC) but allows us to go on and modify it. All that is done in this case is to scale the whole thing by a factor of 2 so that a complete A4 page can be seen easily on a standard monitor, and then to shift the Origin up to the top left hand corner of the screen.
There are built in Windows functions that allow the viewing area to be modified in anyway you want, it can be scaled to any units, the origin can be shifted to anywhere on or off the screen, and the +ve direction reversed which means that the standard +ve Y axis pointing downward can be changed to a more Cartesian +ve Y pointing up.
There is a downside to all of this though, which is that many of the available functions, such as marking areas, checking to see if a point is within a region etc, simply don't work anymore once the axes have been modified in this way. For this reason, this template doesn't change the axes in any way other than a simple scale factor and setting the origin to the top LH corner.
To replace this lost functionality, the idea of a 'Frame' is written into the program, which is a scalalble area that can be positioned anywhere on the 'Page' and then written or plotted to in it's own units (see the View for further details.)
The final line of OnDraw()
view.DrawTable(&dc);in the fine tradition of MVC simply passes all of the work onto the View to do the actual drawing of the Window.