class CTView
{
public:
CRect pageSize; // printed area
CRect tableSize; // scrollable area
CRect frameSize; // the graph region
CFRect frameExtent;
CRect viewPortSize; // the viewable area
float scale; // the scale factor for screen viewing
int tableWidth, tableHeight; // table dimensions for scrolling
CTModel* pmodel; // pointer to the graph instance
float mx, my, cx, cy; // factors for graph to page conversions
int scrollX, scrollY; // scroll positions
CPoint start, old; // points for resizing the axes
BOOL mouseDraw; // is the mouse clicked?
BOOL isXExtentAuto; // should frame's X extent be automatically set
BOOL isYExtentAuto; // should frame's Y extent be automatically set
public:
CTView(); // CTView constructor
~CTView(); // CTView destructor
void SetScale(void); // calculate the conversion factors for graph to page conversions
int px(float); // convert x values from graph to page coordinates
int py(float); // convert y values from frame to page coordinates
CRect pR(CFRect); // convert rectangle from frame to tpage coordinates
float InvPx(int); // convert x values from page to frame coordinates
float InvPy(int); // convert y values from page to frame coordinates
CFRect InvPR(CRect); // convert rectangle from page to frame coordinates
CPoint table2Port(CPoint); // convert point from table to viewport coordinates
CRect table2Port(CRect); // convert rectangle from table to viewport coordinates
CPoint port2Table(CPoint); // convert point from viewport to table coordinates
CRect port2Table(CRect); // convert rectangle from viewport to table coordinates
CPoint page2Port(CPoint); // convert point from page to viewport coordinates
CRect page2Port(CRect); // convert rectangle from page to viewport coordinates
CPoint port2Page(CPoint); // convert point from viewport to page coordinates
CRect port2Page(CRect); // convert rectangle from viewport to page coordinates
void Border(CDC* pdc, CRect r); // draw a transparent rectangle
void DrawTable(CPaintDC*); // draw graph and page frames
void OnLButtonDown (CClientDC*, CPoint); // implement a mouse click and drag
void OnLButtonUp (CClientDC*, CPoint); // " " " " " "
void OnMouseMove (CClientDC*, CPoint); // " " " " " "
void OnHScroll(UINT, UINT, int&); // implement a horizontal scroll
void OnVScroll(UINT, UINT, int&); // implement a vertical scroll
void SetExtent(); // reset the frame extent
};
An awful lot of the code deals with shifting the scales between the
three drawing areas and as such is pretty standard C++ rather than
anything Windows dependent.There are four areas to think about. The Table, Page and Frame (which all sit there ready to be drawn into) and then the Port, which is the area of the viewing Window and shifts around these looking in.
The tableSize, pageSize and frameSize all then refer to the 'physical' postion of these in relation to each other, whilst the frameExtent is the 'virtual' size of the frame. On the screen the frameSize may be 100mm to 200mm, but it's extent may be anything, 0 to 1, 0 to 1000, -1 to 1 or even 1 to -1. The scaling takes care of all this relatively automatically and lets you plot to where you want on the page.
The functions such as page2Port() and port2Page() handle these conversions with px(), pR(), InvPy() etc handling switches between Frame and Page.
To actually draw or write onto the screen requires the use of the DC. For example, if pdc is a pointer to the DC, then:
CRect r(100, 100, 200, 200); pdc->Rectangle (r);will draw the rectangle on the screen. There are many other commands that do other related jobs to draw lines, circles, write text, change the line colour, change the font etc. You name it and there's probably half a dozen ways to do it, although, this being an MS product, none of them will do quite what you want in the way that you you would expect them to.
As a little added bonus, this Template also includes an implementation of a mouse click and drag, along with some rubber-banding. The difficulty with this is that there is always the need to keep tabs on which coordinates you are on right now which means switching between Device and Logical coordinates using the DPtoLP() and LPtoDP() functions.
For example, the OnLButtonUp() function occurs at the end of a click and drag event. The position in device coordinates is passed from the Controller to the function itself, which then has to straight away convert them to Logical coordinates and check if the point is within the required region before erasing the previously drawn rubber-banded rectangle. It then needs to convert the point to the correct coordinate system before reseting the frameExtent (to rescale the axis to the area the mouse has defined).
void CTView::OnLButtonUp (CClientDC* pdc, CPoint mousePos)
{
pdc->DPtoLP(&mousePos, 1);
if (page2Port(frameSize).PtInRect(mousePos) && mouseDraw)
{
// this sets up the pen for rubber banding
CPen pen(PS_DOT, 1, RGB(155,155,155)), *oldPen;
oldPen = pdc->SelectObject(&pen);
pdc->SetROP2(R2_NOTXORPEN);
CRect r = CRect(start.x, start.y, old.x, old.y);
// this draws a dashed rectangle around the bounded area
Border(pdc, r);
pdc->SelectObject(oldPen);
CRect rr = port2Page(r);
rr.NormalizeRect();
CFRect g = InvPR(rr);
CFRect g1 = frameExtent;
frameExtent = InvPR(rr);
mouseDraw = FALSE;
}
}
This kind of switching between coordinate systems is a large part of
what the View has to do when handling input from the mouse. A real App
might also not redraw the whole Window everytime, but check to see which
parts had been exposed and only redraw those in order to save time.
This again would need a lot of coordinate switching as the info about
which areas were covered or exposed wouls be passed in Device
coordinates and would need to be converted to Logical.