Topic : Using Fonts in MFC
Author : Unknown
Page : << Previous 4  Next >>
Go to page :

do this, we can either calculate the new caret position each time, or we can store the starting position of each character in a table and obtain the caret position from it whenever the caret needs to be moved. In the sample, the latter solution is used.


Sample 5\GDI demonstrates how to implemnt caret. It is based on sample 4\GDI.

First, some new variables and functions are added to class CGDIDoc for caret implementation:

(Code omitted)

Two variables m_nCaretIndex and m_nCaretVerSize are added. The first variable is the index indicating the position of the caret. The second variable is the caret's vertical size. This is necessary because for fonts with different sizes, we need to create different carets, whose vertical size should be the same with the current font's height.

Five functions are added to the class, among them, function CGDIDoc::GetCaretVerSize() provides us a way of obtaining the current caret's vertical size in class CGDIView; and function CGDIDoc:: GetCaretPosition() converts the caret index to a position within the client window (The function returns a POINT type value). It is implemented as follows:

(Code omitted)

The caret position is calculated through using function CDC::GetTextExtent(...), which will return the vertical and a horizontal size of a text string. We need a DC to select the current font in order to calculate the text dimension. In the sample, first a DC that does not belong to any window is created, then the current font is selected into this DC, and function CString::Left() is called to obtain a sub-string whose last character is located at the current caret position. The horizontal size obtained from function CDC:: GetTextExtent(...) for the sub-string is the caret's horizontal position. Because we have only one line text, the vertical position of the caret is always 0.

This function may be called within the member functions of CGDIView to retrieve the current caret position. The other two functions, CGDIDoc::ForwardCaret() and CGDIDoc::BackwardCaret() can be called to move the caret forward or backward. They are implemented as follows:

(Code omitted)

Instead of calculating the actual position of the caret, we just increment or decrement the caret index. The range of this index is from 0 to the total number of characters (If there are five characters, we have six possible positions for displaying the caret). If the index goes beyond the limit, we set it back to the boundary value.

At the end of above two functions, function CGDIDoc::GetCGDIView() is called to access class CGDIView, then CGDIView::RedrawCaret() is called to update the caret. This will cause the caret to be displayed in a new position. To access a view from the document, we need to call function CDocument:: GetFirstViewPosition() and then call CDocument::GetNextView(...) repeatedly until we get the correct view. For an SDI application, we need to call this function only once. However, some applications may have more than one view attached to the document (Like an MDI application). In this case, we need to use RUNTIME_CLASS macro to judge if the class is the one we are looking for. In the sample, CGDIDoc:: GetCGDIView() is implemented as a general function, it can also be used in an MDI application to obtain a specific view from the document. The following is its implementation:

(Code omitted)

We will implement function CGDIView::RedrawCaret() later.

In the sample, member variable m_nCaretIndex is initialized in the constructor along with m_szText:


m_szText="This is just a test string";



Also, when the document is first created or when a new font is selected, we need to update the value of m_nCaretVerSize, so functions CGDIDoc::OnNewDocument() and CGDIDoc::OnDialogFont() are updated as follows:

Old version of CGDIDoc::OnNewDocument():

(Code omitted)

New version of CGDIDoc::OnNewDocument():

(Code omitted)

Old version of CGDIDoc::OnDialogFont():

(Code omitted)

New version of CGDIDoc::OnDialogFont():

(Code omitted)

Function CDC::GetOutputTextMetrics(...) is called to obtain the information of the selected font. It will return a lot of information about the font such as its height, average width. This information is stored in a TEXTMETRIC type object, and the font's height can be retrieved from its tmHeight member.

In the above function, CGDIView::CreateNewCaret(...) is called to create a new caret. We will implement this function in the next step. As we will see, passing TRUE to this function will cause the old caret to be destroyed automatically.

In class CGDIView, two new functions are declared (They are called from CGDIDoc::ForwardCaret(), CGDIDoc::BackwardCaret(), CGDIDoc::OnDialogFont()):

(Code omitted)

Function CGDIView::RedrawCaret() will erase the current caret and draw it at the new position. Function CGDIView::CreateNewCaret() will create a new caret and destroy the old one if necessary. The following code fragment shows their implementations:

(Code omitted)

In both functions, we retrieve the caret position from the document. Before moving the caret, we first call function CWnd::HideCaret() to hide the caret. After setting the new position, we call CWnd::ShowCaret() to show the caret again. Also, function CWnd::CreateSolidCaret(...) is called to create the caret, since we pass 0 to its horizontal dimension, the horizontal size of the caret will be set to the default size. The vertical size is retrieved from the document.

We need to create the caret once the view is created, so the default function CGDIView:: OnInitialUpdate() is modified as follows:

(Code omitted)

Here we just call function CGDIView::CreateNewCaret(...) and pass a FALSE value to its parameter because there is no caret needs to be destroyed.

Now we must respond to the events of left arrow and right arrow key strokes. As we know, when a key is pressed, the system will send WM_KEYDOWN message to the application, with the key code stored in WPARAM parameter. Under Windows(, all keys are defined as virtual keys, so there is no need for us to check the actual code sent from the keyboard. In order to know which key was pressed, we can examine WPARAM parameter after WM_KEYDOWN message is received. Here, the virtual key code of the left arrow key and right arrow key are VK_LEFT and VK_RIGHT respectively.

In the sample, WM_KEYDOWN message handler is added to class CGDIView through using Class Wizard, and the corresponding function CGDIView::OnKeyDown(...) is implemented as follows:

(Code omitted)

In this function, WPARAM parameter is mapped to nChar parameter. If the key stroke is from left arrow key, we call CGDIDoc::BackwardCaret() to move the caret leftward. If the key stroke is from right arrow key, we call CGDIDoc::ForwardCaret() to move the caret rightward.

6 One Line Text Editor, Step 3: Enabling Input

Sample 6\GDI is based on sample 5\GDI, it allows the user to input characters.

New Member Functions

We need to trap keyboard stroking events in order to let the user input characters. Since our data is stored in the document, we need to first provide some member functions that can be called from the view to let the new characters be added. For this purpose, two new functions are declared in class CGDIDoc:

(Code omitted)

Function CGDIDoc::AddChar(...) allows us to insert characters to the string at the position indicated by the caret, and function CGDIDoc::DeleteChar(...) allows us to delete the character before or after the caret. Let's first take a look at the implementation of function CGDIDoc::AddChar(...):

(Code omitted)

We divide the text string into two parts, the first part is the sub-string before the caret, and the second part is the sub-string after the caret. The new characters are inserted between the two sub-strings. Parameter uChar indicates the new character, and uRepCnt specifies how many characters will be added. After the character is added, we update the view and move the caret forward.

For function CGDIDoc::DeleteChar(...), it can be used for two situations: one corresponds to "BACK SPACE" key stroke, the other corresponds to "DELETE" key stroke. If parameter bBefore is true, the character before the current caret should be deleted. Otherwise, the character after it needs to be deleted. The following is the implementation of function CGDIDoc::DeleteChar(...):

(Code omitted)

To delete the character before the current caret, we divide the text into two sub-strings, delete the last character of the first sub-string, and re-combine them. Then we update the view, and move caret one character left. When deleting the character after the caret, we do not need to change the position of the caret.

Message WM_CHAR

Now we need to use the above two member functions. In the sample, message WM_CHAR is handled to implement keyboard input. The difference between WM_CHAR and WM_KEYDOWN messages is that WM_CHAR is sent only for printable characters along with the following five keys: ESCAPE, TAB, BACK SPACE and ENTER. Message WM_KEYDOWN will be sent for all types of key strokes.

In the sample, the message handler of WM_CHAR is CGDIView::OnChar(...), it is implemented as follows in the sample:

(Code omitted)

We neglect the ENTER, TAB and ESCAPE key strokes. For BACK SPACE key stroke, we delete the character before the current caret. For all other cases, we insert character at the current caret position.

The DELETE key stroke can not be detected by this message handler, we need to trap and handle it in function CGDIView::OnKeyDown(...):

(Code omitted)

Of course the printable key strokes will also be detected by this message handler. However, if we handle character input in this function, we need to first check if the character is printable. This will make the program a little bit complex.

7 One Line Text Editor, Step 4: Caret Moving & Cursor

Page : << Previous 4  Next >>