|
|
|
Working with Dialogs and Controls |
|
|
|
|
|
|
|
|
|
Operator |
Usage |
|
|
|
|
|
= |
Copies one string to another, as in: |
|
|
Str1 = Str2; |
// Copies contents of Str1 to Str2 |
|
|
Str1 = “A normal string”; |
// Copies the RHS string to Str1 |
|
+ |
Concatenates two or more strings, as in: |
|
|
Str1 = Str2 + Str3 + “ more”; |
// Forms Str1 from 3 strings |
|
+= |
Appends a string to an existing CString object. |
==Compares two strings for equality, as in: if(Str1 == Str2)
//do something...
< |
Tests if one string is less than another. |
<= |
Tests if one string is less than or equal to another. |
> |
Tests if one string is greater than another. |
>= |
Tests if one string is greater than or equal to another. |
The variables Str1 and Str2 in the table above are CString objects. CString objects automatically grow as necessary, such as when you add an additional string to the end of an existing object. For example, in the statements,
CString Str = “A fool and your money “;
Str += “are soon partners.”;
the first statement declares and initializes the object Str. The second statement appends an additional string to Str, so the length of Str automatically increases.
Generally, you should avoid creating CString objects on the heap as far as possible. The memory management necessary for growing them means that operations will be slow.
Adding the Text Menu Item
Adding a new menu item should be easy by now. You just need to open the menu resource with the ID IDR_SketcherTYPE in Resource View by double-clicking it, and add a new menu item, Text, to the Element menu. The default ID, ID_ELEMENT_TEXT, that appears in the Properties window for the item is fine, so you can leave that as it is. You can add a prompt to be displayed on the status bar corresponding to the menu item, and because you’ll also want to add an additional toolbar button corresponding to this menu item, you can add a tool tip to the end of the prompt line, using \n to separate the prompt and the tool tip.
Don’t forget the context menu. You can copy the menu item from IDR_SketcherTYPE. Right-click the Text menu item and select Copy from the pop-up. Open the menu IDR_CURSOR_MENU, extend the no element menu, right-click the empty item at the bottom, and select Paste. All you then need to do is to drag the item to the appropriate position __above the separator — and save the resource file.
Chapter 16
Add the toolbar button to the IDR_MAINFRAME toolbar and set its ID to the same as that for the menu item, ID_ELEMENT_TEXT. You can drag the new button so that it’s positioned at the end of the block defining the other types of element. When you’ve saved the resources, you can add an event handler for the new menu item.
In the Class View pane, right-click CSketcherDoc and display its Properties window. Add a COMMAND handler for the ID_ELEMENT_TEXT ID and add code to it as follows:
void CSketcherDoc::OnElementText()
{
m_Element = TEXT;
}
Only one line of code is necessary to set the element type in the document to TEXT.
You also need to add a function to check the menu item if it is the current mode, so add an UPDATE_ COMMAND_UI handler corresponding to the ID_ELEMENT_TEXT ID, and implement the code for it as follows:
void CSketcherDoc::OnUpdateElementText(CCmdUI* pCmdUI)
{
// Set checked if the current element is text
pCmdUI->SetCheck(m_Element == TEXT);
}
This operates in the same way as the other Element pop-up menu items.
You must also add a line to the OurConstants.h header file:
const unsigned int TEXT = 105U;
You can add this statement at the end of the other element type definitions in the header file. The next step is to define the CText class for an object of type TEXT.
Defining a Text Element
You can derive the class CText from the CElement class as follows:
// Class defining a text object class CText: public CElement
{
public:
// Function to display a text element
virtual void Draw(CDC* pDC, CElement* pElement=0);
// Constructor for a text element
CText(CPoint Start, CPoint End, CString aString, COLORREF aColor);
virtual void Move(CSize& aSize); |
// Move a text element |
protected: |
|
|
Working with Dialogs and Controls |
|
|
CPoint m_StartPoint; |
// position of a text element |
CString m_String; |
// Text to be displayed |
CText(){} |
// Default constructor |
};
I added this manually, but I’ll leave it to you to decide how you want to do this. This definition should go at the end of the Elements.h file following the other element types. This class definition declares the virtual Draw() and Move() functions, as the other element classes do. The data member m_String of type CString stores the text to be displayed, and m_StartPoint specifies the position of the string in the client area of a view.
Look at the constructor declaration in a little more detail. The CText constructor declaration defines four parameters that provide the following essential information:
Parameter |
Defines |
|
|
|
CPoint |
Start |
The position of the text in logical coordinates. |
CPoint |
End |
The corner opposite Start that defines the rectangle enclosing the text. |
CString aString |
The text string to be displayed as a CString object. |
COLORREF aColor |
The color of the text. |
|
|
|
The pen width doesn’t apply to an item of text, because the appearance is determined by the font. Although you do not need to pass a pen width as an argument to the constructor, the constructor needs to initialize the m_PenWidth member inherited from the base class because it is used in the computation of the bounding rectangle for the text.
Implementing the CText Class
You have three functions to implement for the CText class:
The constructor for a CText object.
The virtual Draw() function to display it.
The Move() function to support moving a text object by dragging it with the mouse.
I added these to the Elements.cpp file.
The CText Constructor
The constructor for a CText object needs to initialize the class and base class data members:
CText::CText(CPoint Start, CPoint End, CString aString, COLORREF aColor)
{
m_Pen = 1; |
// Set the pen width |
m_Color = aColor; |
// Set the color for the text |
m_String = aString; |
// Make a copy of the string |
Chapter 16
m_StartPoint = Start; |
// Start point for string |
m_EnclosingRect = CRect(Start, End); m_EnclosingRect.NormalizeRect();
}
This is all standard stuff, just like you’ve seen before for the other elements.
Drawing a CText Object
Drawing text in a device context is different to drawing a geometric figure. The implementation of the Draw() function for a CText object is as follows:
void CText::Draw(CDC* pDC, CElement* pElement)
{
COLORREF Color(m_Color); |
// Initialize with element color |
if(this==pElement) |
|
Color = SELECT_COLOR; |
// Set selected color |
// Set the text color and output the text pDC->SetTextColor(Color);
pDC->TextOut(m_StartPoint.x, m_StartPoint.y, m_String);
}
You don’t need a pen to display text. You just need to specify the text color using the SetTextColor() function member of the CDC object and then use the TextOut() member to output the text string. This displays the string using the default font.
Because the TextOut() function doesn’t use a pen, it isn’t affected by setting the drawing mode of the device context. This means that the raster operations (ROP) method that you use to move the elements leaves temporary trails behind when applied to text. Remember that you used the SetROP2() function to specify the way in which the pen would logically combine with the background. By choosing R2_NOTXORPEN as the drawing mode, you could cause a previously drawn element to disappear by redrawing it — it would then revert to the background color and thus become invisible. Fonts aren’t drawn using a pen, so it won’t work with the text elements. You’ll see how to fix this problem in the next chapter.
Moving a CText Object
The Move() function for a CText object is simple:
void CText::Move(CSize& aSize)
{
m_StartPoint += |
aSize; |
// |
Move |
the |
start point |
m_EnclosingRect |
+= aSize; |
// |
Move |
the |
rectangle |
}
All you need to do is alter the point defining the position of the string, and the data member defining the enclosing rectangle, by the distance specified in the aSize parameter.
Working with Dialogs and Controls
Creating a Text Element
After the element type has been set to TEXT, a text object should be created at the cursor position whenever you click the left mouse button and enter the text you want to display. You therefore need to open the dialog that permits text to be entered in the OnLButtonDown() handler. Add the following code to this handler in the CSketcherView class:
void CSketcherView::OnLButtonDown(UINT nFlags, CPoint point)
{
CClientDC aDC(this); |
// Create a device context |
OnPrepareDC(&aDC); |
// Get origin adjusted |
aDC.DPtoLP(&point); |
// convert point to Logical |
// In moving mode, so drop the element |
if(m_MoveMode) |
|
{ |
|
m_MoveMode = FALSE; |
// Kill move mode |
m_pSelected = 0; |
// De-select element |
GetDocument()->UpdateAllViews(0); // Redraw all the views
}
else
{
CSketcherDoc* pDoc = GetDocument();// Get a document pointer if(pDoc->GetElementType() == TEXT)
{
CTextDialog aDlg; if(aDlg.DoModal() == IDOK)
{
//Exit OK so create a text element CSketcherDoc* pDoc = GetDocument();
CSize TextExtent = aDC.GetTextExtent(aDlg.m_TextString);
//Get bottom right of text rectangle - MM_LOENGLISH
CPoint BottomRt(point.x+TextExtent.cx, point.y-TextExtent.cy); CText* pTextElement = new CText(point, BottomRt,
aDlg.m_TextString, pDoc->GetElementColor());
//Add the element to the document pDoc->AddElement(pTextElement);
//Get all views updated
pDoc->UpdateAllViews(0,0,pTextElement);
}
return;
} |
|
|
m_FirstPoint = point; |
// |
Record the cursor position |
SetCapture(); |
// |
Capture subsequent mouse messages |
}
}
Chapter 16
The code to be added is shaded. It creates a CTextDialog object and then opens the dialog using the DoModal() function call. The m_TextString member of aDlg is automatically set to the string entered in the edit box, so you can use this data member to pass the string entered back to the CText constructor if the OK button is used to close the dialog. The color and pen width are obtained from the document using the GetElementColor() and GetPenWidth() members that you have used previously. The position of the text is the point value holding the cursor position that is passed to the handler.
You also need to calculate the opposite corner of the rectangle that bounds the text. Because the size of the rectangle for the block of text depends on the font used in a device context, you use the GetTextExtent() function in the CClientDC object, aDC, to initialize the CSize object, TextExtent, with the width and height of the text string in logical coordinates.
Calculating the rectangle for the text in this way is a bit of a cheat, which could cause a problem after you start saving documents in a file because it’s conceivable that a document could be read back into an environment where the default font in a device context is larger than that in effect when the rectangle was calculated. This shouldn’t arise very often, so no need to worry about it here, but as a hint — if you want to pursue it — you could use an object of the class CFont in the CText definition to define a specific font to be used. You could then use the characteristics of the font to calculate the enclosing rectangle for the text string.
You could also use CFont to change the font size so that the text is also zoomed when the scale factor is increased; however, you also need to devise a way to calculate the bounding rectangle based on the font size currently being used, which varies with the view scale.
The CText object is created on the heap because the list in the document only maintains pointers to the elements. You add the new element to the document by calling the AddElement() member of CSketcherDoc, with the pointer to the new text element as an argument. Finally, UpdateAllViews() is called with the first argument 0, which specifies that all views are to be updated.
For the program to compile successfully, you need to add a #include directive for TextDialog.h to the SketcherView.cpp file. You should now be able to produce annotated sketches using multiple scaled and scrolled views, such as the ones shown in Figure 16-23.
Summar y
In this chapter, you’ve seen several different dialogs using a variety of controls. Although you haven’t created dialogs involving several different controls at once, the mechanism for handling them is the same as you have seen because each control can operate independently of the others.
The most important points that you’ve seen in this chapter are:
A dialog involves two components: a resource defining the dialog box and its controls, and a class that is used to display and manage the dialog.
Information can be extracted from controls in a dialog using the DDX mechanism. The data can be validated using the DDV mechanism. To use DDX/DDV you need only to use the Add the control variable option for the Add Member Variable wizard to define variables in the dialog class associated with the controls.
Working with Dialogs and Controls
Figure 16-23
A modal dialog retains the focus in the application until the dialog box is closed. As long as a modal dialog is displayed, all other windows in an application are inactive.
A modeless dialog allows the focus to switch from the dialog box to other windows in the application and back again. A modeless dialog can remain displayed as long as the application is executing, if required.
Common Controls are a set of standard Windows controls that are supported by MFC and the resource editing capabilities of Developer Studio.
Although controls are usually associated with a dialog, you can add controls to any window.
Exercises
You can download the source code for the examples in the book and the solutions to the following exercises from http://www.wrox.com.
1.Implement the scale dialog using radio buttons.
2.Implement the pen width dialog using a list box.
3.Implement the pen width dialog as a combo box with the drop list type selected on the Styles tab in the properties box. (The drop list type allows the user to select from a drop-down list but not to key alternative entries in the list.)
17
Storing and Printing
Documents
With what you have accomplished so far in the Sketcher program, you can create a reasonably comprehensive document with views at various scales, but the information is transient because you have no means of saving a document. In this chapter, you’ll remedy that by seeing how you can store a document on disk. You’ll also investigate how you can output a document to a printer.
In this chapter, you’ll learn about:
Serialization and how it works
How to make objects of a class serializable
The role of a CArchive object in serialization
How to implement serialization in your own classes
How to implement serialization in the Sketcher application
How printing works with MFC
What view class functions you can use to support printing
What a CPrintInfo object contains and how it’s used in the printing process
How to implement multipage printing in the Sketcher application
Understanding Serialization
A document in an MFC-based program is not a simple entity — it’s a class object that can be very complicated. It typically contains a variety of objects, each of which may contain other objects, each of which may contain still more objects... and that structure may continue for a number of levels.
Chapter 17
You want to be able to save a document in a file, but writing a class object to a file represents something of a problem because it isn’t the same as a basic data item like an integer or a character string. A basic data item consists of a known number of bytes, so to write it to a file only requires that the appropriate number of bytes be written. Conversely, if you know a value of type int was written to a file, to get it back you just read the appropriate number of bytes.
Writing objects is different. Even if you write away all the data members of an object, that’s not enough to be able to get the original object back. Class objects contain function members as well as data members, and all the members, both data and functions, have access specifiers; therefore, to record objects in an external file, the information written to the file must contain complete specifications of all the class structures involved. The read process must also be clever enough to synthesize the original objects completely from the data in the file. MFC supports a mechanism called serialization to help you to implement input from and output to disk of your class objects with a minimum of time and trouble.
The basic idea behind serialization is that any class that’s serializable must take care of storing and retrieving itself. This means that for your classes to be serializable — in the case of the Sketcher application, this will include the CElement class and the shape classes you have derived from it — they must be able to write themselves to a file. This implies that for a class to be serializable, all the class types that are used to declare data members of the class must be serializable too.
Serializing a Document
This all sounds rather tricky, but the basic capability for serializing your document was built into the application by the Application wizard right at the outset. The handlers for the File > Save, File > Save As, and File >| Open menu items all assume that you want serialization implemented for your document, and already contain the code to support it. Take a look at the parts of the definition and implementation of CSketcherDoc that relate to creating a document using serialization.
Serialization in the Document Class Definition
The code in the definition of CSketcherDoc that enables serialization of a document object is shown shaded in the following fragment:
class CSketcherDoc : public CDocument
{
protected: // create from serialization only CSketcherDoc();
DECLARE_DYNCREATE(CSketcherDoc)
//Rest of the class...
//Overrides public:
virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar);
//Rest of the class...
};