Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]-1

.pdf
Скачиваний:
108
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

Working with Dialogs and Controls

Figure 16-17

If you click the Finish button, the wizard takes care of entering the code necessary to support your new control variable. The class definition you’ll end up with after the wizard has added the new member is as follows:

class CScaleDialog : public CDialog

{

DECLARE_DYNAMIC(CScaleDialog)

public:

CScaleDialog(CWnd* pParent = NULL); // standard constructor virtual ~CScaleDialog();

// Dialog Data

enum { IDD = IDD_SCALE_DLG };

 

protected:

 

virtual void DoDataExchange(CDataExchange* pDX);

// DDX/DDV support

DECLARE_MESSAGE_MAP()

 

public:

 

// Stores the current drawing scale

 

int m_Scale;

 

};

 

839

Chapter 16

The interesting bits of the class definition are shaded. The class is associated with the dialog resource through the enum statement initializing IDD with the ID of the resource. It contains the variable m_Scale, which is specified as a public member of the class, so you can set and retrieve its value in a CScaleDialog object directly. There’s also some special code in the implementation of the class to deal with the new m_Scale member.

Dialog Data Exchange and Validation

A virtual function called DoDataExchange() has been included in the class by the Class wizard. If you look in the ScaleDialog.cpp file, you’ll find the implementation looks like this:

void CScaleDialog::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

DDX_Text(pDX, IDC_SCALE, m_Scale); DDV_MinMaxInt(pDX, m_Scale, 1, 8);

}

This function is called by the framework to carry out the exchange of data between variables in a dialog and the dialog’s controls. This mechanism is called Dialog Data Exchange, usually abbreviated to DDX. This is a powerful mechanism that can provide automatic transfer of information between a dialog and its controls in most circumstances, thus saving you the effort of programming to get the data yourself, as you did with the radio buttons in the pen width dialog.

In the scale dialog, DDX handles data transfers between the edit control and the variable m_Scale in the CScaleDialog class. The variable pDX passed to the DoDataExchange() function controls the direction in which data is transferred. After calling the base class DoDataExchange() function, the DDX_Text() function is called, which actually moves data between the variable, m_Scale, and the edit control.

The call to the DDV_MinMaxInt() function verifies that the value transferred is within the limits specified. This mechanism is called Dialog Data Validation, or DDV. The DoDataExchange() function is called automatically before the dialog is displayed, to pass the value stored in m_Scale to the edit control. When the dialog is closed with the OK button, it is automatically called again to pass the value in the control back to the variable m_Scale in the dialog object. All this is taken care of for you. You need only to ensure that the right value is stored in m_Scale before the dialog box is displayed, and arrange to collect the result when the dialog box closes.

Initializing the Dialog

You’ll use the OnInitDialog() function to initialize the dialog, just as you did for the pen width dialog. This time you’ll use it to set up the spin control. You’ll initialize the m_Scale member a little later when you create the dialog in the handler for a Scale menu item because it should be set to the value of the scale stored in the view. For now, add an override for the OnInitDialog() function to the CScaleDialog class, using the same mechanism that you used for the previous dialog, and add code to initialize the spin control as follows:

BOOL CScaleDialog::OnInitDialog()

{

CDialog::OnInitDialog();

// First get a pointer to the spin control

CSpinButtonCtrl* pSpin;

840

Working with Dialogs and Controls

pSpin = (CSpinButtonCtrl*)GetDlgItem(IDC_SPIN_SCALE);

//If you have not checked the auto buddy option in

//the spin control’s properties, set the buddy control here

//Set the spin control range

pSpin->SetRange(1, 8);

return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE

}

There are only three lines of code to add, along with four lines of comments. The first line of code creates a pointer to an object of the MFC class CSpinButtonCtrl. This class is specifically for managing spin buttons, and is initialized in the next statement to point to the control in our dialog. The function GetDlgItem() is inherited from CWnd via CDialog, and it retrieves the address of any control from the ID you pass as the argument. As you saw earlier, a control is just a specialized window, so the pointer returned is of type CWnd*; you therefore have to cast it to the type appropriate to the particular control, which is CSpinButtonCtrl* in this case. The third statement that you’ve added sets the upper and lower limits for the spin button by calling the SetRange() member of the spin control object. Although you have set the range limits for the edit control, this doesn’t affect the spin control directly. If you don’t limit the values in the spin control here, you would be allowing the spin control to insert values in the edit control that were outside the limits, so there would be an error message from the edit control. You can demonstrate this by commenting out the statement that calls SetRange() here and trying out Sketcher without it.

If you want to set the buddy control using code rather than setting the value of Auto buddy in the spin button’s properties to True, the CSpinButtonCtrl class has a function member to do this. You would need to add the statement:

pSpin->SetBuddy(GetDlgItem(IDC_SCALE));

at the point indicated by the comments.

Displaying the Spin Button

The dialog is to be displayed when the Scale menu option (or its associated toolbar button) is selected, so you need to add a COMMAND event handler to the CSketcherView class corresponding to the ID_VIEW_SCALE message through the Properties window for the class. You can then add code as follows:

void CSketcherView::OnViewScale()

 

{

 

CScaleDialog aDlg;

// Create a dialog object

aDlg.m_Scale = m_Scale;

// Pass the view scale to the dialog

if(aDlg.DoModal() == IDOK)

 

{

 

m_Scale = aDlg.m_Scale;

// Get the new scale

InvalidateRect(0);

// Invalidate the whole window

}

 

}

 

841

Chapter 16

You create the dialog as a modal dialog in the same way as for the pen width dialog. Before the dialog box is displayed by the DoModal() function call, you store the scale value provided by the m_Scale member of CSketcherView in the dialog member with the same name; this ensures that the control displays the current scale value when the dialog is displayed. If the dialog is closed with the OK button, you store the new scale from the m_Scale member of the dialog object in the view member with the same name. Because you have changed the view scale, you need to get the view redrawn with the new scale value applied. The call to InvalidateRect() does this.

Of course, you must not forget to add the m_Scale data member to the definition of CSketcherView, so add the following line at the end of the other data members in the class definition:

int m_Scale;

// Current view scale

You should also modify the CSketcherView constructor to initialize m_Scale to 1. This results in a view always starting out with a scale of one to one. Note: If you forget to do this, it’s unlikely that your program will work properly.

Because you are referring the CScaleDialog class in the CSketcherView class implementation, you must add an #include directive for ScaleDialog.h to the beginning of the SketcherView.cpp file. That’s all you need to get the scale dialog and its spin control operational. You can build and run Sketcher to give it a trial spin before you add the code to use a view scale factor in the drawing process.

Using the Scale Factor

Scaling with Windows usually involves using one of the scaleable mapping modes, MM_ISOTROPIC or MM_ANISOTROPIC. By using one or other of these mapping modes, you can get Windows to do most of the work. Unfortunately, it’s not as simple as just changing the mapping mode, because neither of these mapping modes is supported by CScrollView. If you can get around that, however, you’re home and dry. You’ll use MM_ANISOTROPIC for reasons that you’ll see in a moment, so let’s first understand what’s involved in using this mapping mode.

Scaleable Mapping Modes

As I’ve said, there are two mapping modes that allow the mapping between logical coordinates and device coordinates to be altered and these are the MM_ISOTROPIC and MM_ANISOTROPIC modes. The MM_ISOTROPIC mode has the property that Windows will force the scaling factor for both the x and y axes to be the same, which has the advantage that your circles will always be circles. The disadvantage is that you can’t map a document to fit into a rectangle of a different shape. The MM_ANISOTROPIC mode, on the other hand, permits scaling of each axis independently. Because it’s the more flexible mode of the two, you’ll use MM_ANISOTROPIC for scaling operations in Sketcher.

842

Working with Dialogs and Controls

The way in which logical coordinates are transformed to device coordinates is dependent on the following parameters, which you can set:

Parameter

Description

 

 

Window Origin

The logical coordinates of the upperleft corner of the window. This is set

 

by calling the function CDC::SetWindowOrg().

Window Extent

The size of the window specified in logical coordinates. This is set by

 

calling the function CDC::SetWindowExt().

Viewport Origin

The coordinates of the upper-left corner of the window in device coordi-

 

nates (pixels). This is set by calling the function CDC::SetViewportOrg().

Viewport Extent

The size of the window in device coordinates (pixels). This is set by call-

 

ing the function CDC::SetViewportExt().

 

 

The viewport referred to here has no physical significance by itself; it serves only as a parameter for defining how coordinates are transformed from logical coordinates to device coordinates.

Remember that:

Logical coordinates (also referred to as page coordinates) are determined by the mapping mode. For example, the MM_LOENGLISH mapping mode has logical coordinates in units of 0.01 inches, with the origin in the upper-left corner of the client area, and the positive y-axis direction running from bottom to top. These are used by the device context drawing functions.

Device coordinates (also referred to as client coordinates in a window) are measured in pixels in the case of a window, with the origin at the upper-left corner of the client area, and with the positive y axis direction from top to bottom. These are used outside of a device context, for example for defining the position of the cursor in mouse message handlers.

Screen coordinates are measured in pixels and have the origin at the upper-left corner of the screen, with the positive y-axis direction from top to bottom. These are used when getting or setting the cursor position.

The formulae used by Windows to convert from logical coordinates to device coordinates are:

xDevice =_xLogical - xWindowOrgi * xViewportExt + xViewportOrg xWindowExt

yDevice =_yLogical - yWindowOrgi * yViewportExt + yViewportOrg yWindowExt

843

Chapter 16

With coordinate systems other than those provided by the MM_ISOTROPIC and MM_ANISOTROPIC mapping modes, the window extent and the viewport extent are fixed by the mapping mode and you can’t change them. Calling the functions SetWindowExt() or SetViewportExt() in the CDC object to change them has no effect, although you can still move the position of (0,0) in your logical reference frame by calling SetWindowOrg() or SetViewportOrg(). However, for a given document size expressed by the window extent in logical coordinate units, you can adjust the scale at which elements are displayed by setting the viewport extent appropriately. By using and setting the window and viewport extents, you can get the scaling done automatically.

Setting the Document Size

You need to maintain the size of the document in logical units in the document object. You can add a protected data member, m_DocSize, to the CSketcherDoc class definition to store the size of the document:

CSize m_DocSize;

// Document size

You will also want to access this data member from the view class, so add a public function to the CSketcherDoc class definition as follows:

CSize GetDocSize()

{ return m_DocSize; } // Retrieve the document size

You must initialize the m_DocSize member in the constructor for the document, so modify the implementation of CSketcherDoc() as follows:

CSketcherDoc::CSketcherDoc() : m_Element(LINE)

, m_Color(BLACK) ,m_PenWidth(0) ,m_DocSize(CSize(3000,3000))

{

// TODO: add one-time construction code here

}

You’ll be using notional MM_LOENGLISH coordinates, so you can treat the logical units as 0.01 inches, and the value set gives you an area of 30 inches square to draw on.

Setting the Mapping Mode

You can set the mapping mode to MM_ANISOTROPIC in an override for the inherited OnPrepareDC() in the CSketcherView class. This function is always called for any WM_PAINT message, and you have arranged to call it when you draw temporary objects in the mouse message handlers; however, you have to do a little more than just set the mapping mode. You’ll need to create the function override in CSketcherView before you can add the code. Just open the Properties window for the CSketcherView class and click the Overrides toolbar button. You can then add the override by selecting OnPrepareDC from the list and clicking on <Add> OnPrepareDC in the adjacent column. You then are able to type the code directly in the Editor pane. The implementation of OnPrepareDC() is:

844

 

 

Working with Dialogs and Controls

 

 

 

 

void CSketcherView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)

 

{

 

 

CScrollView::OnPrepareDC(pDC, pInfo);

 

 

CSketcherDoc* pDoc = GetDocument();

 

 

pDC->SetMapMode(MM_ANISOTROPIC);

// Set the map mode

 

CSize DocSize = pDoc->GetDocSize();

// Get the document size

 

// y extent must be negative because we want MM_LOENGLISH

 

DocSize.cy = -DocSize.cy;

// Change sign of y

 

pDC->SetWindowExt(DocSize);

// Now set the window extent

 

// Get the number of pixels per inch in x and y

 

int xLogPixels = pDC->GetDeviceCaps(LOGPIXELSX);

 

int yLogPixels = pDC->GetDeviceCaps(LOGPIXELSY);

 

// Calculate the viewport extent in x and y

 

long xExtent = static_cast<long>(DocSize.cx)*m_Scale*xLogPixels/100L;

 

long yExtent = static_cast <long>(DocSize.cy)*m_Scale*yLogPixels/100L;

 

pDC->SetViewportExt(static_cast<int>(xExtent),

 

static_cast<int>(-yExtent)); // Set viewport extent

 

}

 

The override of the base class function is unusual here in that you have left the call to CScrollView:: OnPrepareDC() in and added the modifications after it rather than where the comment in the default code suggests. If the class was derived from CView, you would replace the call to the base class version because it does nothing, but in the case of CScrollView this isn’t the case. You need the base class function to set some attributes before you set the mapping mode. Don’t make the mistake of calling the base class function at the end of the override version though — if you do, scaling won’t work.

After setting the mapping mode and obtaining the document extent, you set the window extent with the y extent negative. This is just to be consistent with the MM_LOENGLISH mode that you were using previously — remember that the origin is at the top, so y values in the client area are negative with this mapping mode.

The CDC member function GetDeviceCaps() supplies information about the device that the device context is associated with. You can get various kinds of information about the device, depending on the argument you pass to the function. In this case, the arguments LOGPIXELSX and LOGPIXELSY return the number of pixels per logical inch in the x and y directions. These values are equivalent to 100 units in your logical coordinates.

You use these values to calculate the x and y values for the viewport extent, which you store in the local variables xExtent and yExtent. The document extent along an axis in logical units divided by 100 gives the document extent in inches. If this is multiplied by the number of logical pixels per inch for the device, you get the equivalent number of pixels for the extent. If you then use this value as the viewport extent, you get the elements displayed at a scale of 1 to 1. If you simplify the equations for converting between device and logical coordinates by assuming the window origin and the viewport origin are both (0,0), they become:

xDevice = xLogical * xViewportExt xWindowExt

yDevice = yLogical * yViewportExt

 

yWindowExt

845

 

Chapter 16

If you multiply the viewport extent values by the scale (stored in m_Scale), the elements are drawn according to the value of m_Scale. This logic is exactly represented by the expressions for the x and y viewport extents in your code. The simplified equations with the scale included are:

xDevice = xLogical * xViewportExt * m_Scale xWindowExt

yDevice = yLogical * yViewportExt * m_Scale yWindowExt

You should be able to see from this that a given pair of device coordinates varies in proportion to the scale value. The coordinates at a scale of 3 are three times the coordinates at a scale of 1. Of course, as well as making elements larger, increasing the scale also moves them away from the origin.

That’s all you need to scale the view. Unfortunately, at the moment the scrolling won’t work with scaling, so you need to see what you can do about that.

Implementing Scrolling with Scaling

CScrollView just won’t work with the MM_ANISOTROPIC mapping mode so clearly you must use another mapping mode to set up the scrollbars. The easiest way to do this is to use MM_TEXT, because in this case the logical coordinates are the same as the client coordinates — pixels, in other words. All you need to do, then, is to figure out how many pixels are equivalent to the logical document extent for the scale at which you are drawing, which is easier than you might think. You can add a function to CSketcherView to take care of the scrollbars and implement everything in there. Right-click the CSketcherView class name in Class View and add a public function ResetScrollSizes() with a void return type and no parameters. Add the code to the implementation, as follows:

void CSketcherView::ResetScrollSizes(void)

 

{

 

CClientDC aDC(this);

 

OnPrepareDC(&aDC);

// Set up the device context

CSize DocSize = GetDocument()->GetDocSize();

// Get the document size

aDC.LPtoDP(&DocSize);

// Get the size in pixels

SetScrollSizes(MM_TEXT, DocSize);

// Set up the scrollbars

}

 

After creating a local CClientDC object for the view, you call OnPrepareDC() to set up the MM_ANISOTROPIC mapping mode. Because this takes account of the scaling, the LPtoDP() member of the aDC object converts the document size stored in the local variable DocSize to the correct number of pixels for the current logical document size and scale. The total document size in pixels defines how large the scrollbars must be in MM_TEXT mode — remember MM_TEXT logical coordinates are in pixels. You can then get the SetScrollSizes() member of CScrollView to set up the scrollbars based on this by specifying MM_TEXT as the mapping mode.

It may seem strange that you can change the mapping mode in this way, but it’s important to keep in mind that the mapping mode is nothing more than a definition of how logical coordinates are to be converted to device coordinates. Whatever mode (and therefore coordinate conversion algorithm) you’ve set up applies to all subsequent device context functions until you change it, and you can change it

846

Working with Dialogs and Controls

whenever you want. When you set a new mode, subsequent device context function calls just use the conversion algorithm defined by the new mode. You figure how big the document is in pixels with MM_ANISOTROPIC because this is the only way we can get the scaling into the process, and then switch to MM_TEXT to set up the scrollbars because you need units for this in pixels for it to work properly. Simple really, when you know how.

Setting Up the Scrollbars

You must set up the scrollbars initially for the view in the OnInitialUpdate() member of CSketcherView. Change the previous implementation of the function to:

void CSketcherView::OnInitialUpdate()

 

{

 

ResetScrollSizes();

// Set up the scrollbars

CScrollView::OnInitialUpdate();

 

}

 

All you do is call the ResetScrollSizes() function that you just added to the view. This takes care of everything — well, almost. The CScrollView object needs an initial extent to be set for OnPrepareDC() to work properly, so you need to add one statement to the CSketcherView constructor:

CSketcherView::CSketcherView()

 

: m_FirstPoint(CPoint(0,0))

// Set 1st recorded point to 0,0

, m_SecondPoint(CPoint(0,0))

// Set 2nd recorded point to 0,0

, m_pTempElement(NULL)

// Set temporary element pointer to 0

, m_pSelected(NULL)

// No element selected initially

, m_MoveMode(FALSE)

// Set move mode off

, m_CursorPos(CPoint(0,0))

// Initialize as zero

, m_FirstPos(CPoint(0,0))

// Initialize as zero

, m_Scale(1)

// Set scale to 1:1

{

 

SetScrollSizes(MM_TEXT, CSize(0,0));

// Set arbitrary scrollers

}

 

The additional statement just calls SetScrollSizes() with an arbitrary extent to get the scrollbars initialized before the view is drawn. When the view is drawn for the first time, the ResetScrollSizes() function call in OnInitialUpdate()sets up the scrollbars properly.

Of course, each time the view scale changes, you need to update the scrollbars before the view is redrawn. You can take care of this in the OnViewScale() handler in the CSketcherView class:

void CSketcherView::OnViewScale()

 

{

 

CScaleDialog aDlg;

// Create a dialog object

aDlg.m_Scale = m_Scale;

// Pass the view scale to the dialog

if(aDlg.DoModal() == IDOK)

 

{

 

m_Scale = aDlg.m_Scale;

// Get the new scale

ResetScrollSizes();

// Adjust scrolling to the new scale

InvalidateRect(0);

// Invalidate the whole window

}

 

}

 

847

Chapter 16

Using the ResetScrollSizes() function, taking care of the scrollbars isn’t complicated. Everything is covered by the one additional line of code.

Now you can build the project and run the application. You’ll see that the scrollbars work just as they should. Note that each view maintains its own scale factor, independently of the other views.

Working with Status Bars

With each view now being scaled independently, it becomes necessary to have some indication of what the current scale in a view is. A convenient way to do this would be to display the scale in the status bar that was created by default in the Sketcher application. By default the status bar appears at the bottom of the application window, below the horizontal scrollbar, although you can arrange for it to be at the top of the client area. The status bar is divided into segments called panes; the status bar in Sketcher has four panes. The one on the left contains the text Ready, and the other three are the recessed areas on the right that are used to record when CAPS lock, NUM lock and SCROLL lock are in effect.

It’s possible for you to write to the status bar that the Application wizard supplied by default, but you need to get access to the m_wndStatusBar member of the CMainFrame object for the application as this represents it. As it’s a protected member of the class, you must add a public member function to modify the status bar from outside the class. You could add the following public function member to the CMainFrame class to do this:

void CMainFrame::SetPaneText(int Pane, LPCTSTR Text)

{

m_wndStatusBar.SetPaneText(Pane, Text);

}

The implementation goes in the .cpp file and you must add a declaration for the function to the class definition. The SetPaneText() function sets the text specified by the second parameter, Text, in the pane identified by the first parameter, Pane, in the status bar object represented by m_wndStatusBar. The status bar panes are indexed from the left, starting at 0. Now you can write to the status bar from anywhere outside the CMainFrame class using this function. For example:

CMainFrame* pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd;

pFrame->SetPaneText(0, “Goodbye cruel world”);

This code fragment gets a pointer to the main window of the application and outputs the text string you see to the left-most pane in the status bar. This is fine, but the main application window is no place for a view scale. You may well have several views in Sketcher, so you really want to associate displaying the scale with each view. A better approach would be to give each child window its own status bar. The m_wndStatusBar in CMainFrame is an instance of the CStatusBar class. You can use the same class to implement your own status bars.

Adding a Status Bar to a Frame

The CStatusBar class defines a control bar with multiple panes in which you can display information. Objects of type CStatusBar can also provide the same functionality as the Windows common status bar control through a member function GetStatusBarCtrl(). There is an MFC class that specifically

848