Author : Unknown
Page : << Previous 5 Next >>
Shape
Sample 7\GDI is based on sample 6\GDI.
New Functions
Besides moving the caret before or after one character at a time, we sometimes need to move the caret to the next or the previous word. This will give the user a faster way of putting the caret at the appropriate position. The method of moving caret to the next or previous word is almost the same with moving it to the next or previous character, the only difference between them is how to calculate the new caret position. Because words are separated by blanks, if we want to move caret one word leftward or rightward, we can just find the previous or next blank, calculate the distance, then move the caret to the new position.
Also, we may want to let the user use HOME key and END key to move the caret to the beginning or the end of the text. Still, almost every text editor supports changing the caret position with a single mouse clicking.
The following new functions are declared in class CGDIDoc to implement above-mentioned functionalities:
(Code omitted)
As implied by the function names, CGDIDoc::HomeCaret() will move the caret to the beginning of the text, CGDIDoc::EndCaret() will move the caret to the end of the text. The implementation of these two functions is very simple, all we need to do is setting m_nCaretIndex to a proper value then updating the caret:
(Code omitted)
For other two functions CGDIDoc::ForwardCaretToBlank() and CGDIDoc::BackwardCaretToBlank(), we need to find out the position of the nearest blank, and set the value of m_nCaretIndex to it:
(Code omitted)
Within the two functions, a local variable szSub is used to store the sub-string before or after the caret, and function CString::Find(...) or CString::ReverseFind(...) is called to find the position of the nearest blank. In case the blank is not found, we need to move the caret to the beginning or end of the text. If it is found, we just increment or decrement m_nCaretIndex by an appropriate value.
On the view side, we need to move the caret when any of the following keys is pressed together with CTRL: HOME, END, Left and Right ARROW. These keystroke events can be trapped by handling WM_KEYDOWN message. To detect if the CTRL key is held down, we can call API function ::GetKeyState(...) to check the key state.
Function ::GetKeyState(...) can be used to check the current state of any key. We need to pass the virtual key code (such as VK_CONTROL, VK_SHIFT...) to this function when making the call. The returned value is a SHORT type integer. The high order of this value indicates if the key is held down, the lower order indicates if the key is toggled, which is applicable to keys such as CAPS LOCK, NUM LOCK or INSERT.
Moving Caret Using Keyboard
Function CGDIView::OnKeyDown(...) is modified as follows to add the new features to the application:
(Code omitted)
Changes are made to VK_LEFT and VK_RIGHT cases. First we call ::GetKeyState(...) using virtual key code VK_CONTROL and extract the high order byte from the return value. If it is non-zero, function CGDIDoc:: BackwardCaretToBlank() or CGDIDoc::ForwardCaretToBlank() is called to move the caret to the nearest blank. Other wise we move the caret one character leftward or rightward.
In case the key is VK_END or VK_HOME, we call function CGDIDoc::EndCaret() or CGDIDoc ::HomeCaret() to move the caret to the beginning or end of the text.
Moving Caret Using Mouse
Moving the caret by mouse clicking is a little bit complex. Because all we can obtain from the message parameter is the mouse's current position, we need to convert it to a caret index before moving the caret. To do so, we need to go over all the starting positions of characters, calculate the absolute distance between each character and the current mouse cursor. The index that results in the smallest distance will be used as the new caret index. In the sample, function CGDIDoc::SetCaret(...) is declared for this purpose, which converts geometrical coordinates to caret index and move the caret to the new place. The following is its implementation:
(Code omitted)
In order to find out all the possible caret positions, we need to obtain the dimension of different sub- strings. For example, the caret positions of text "abcde" can be calculated from the dimensions of following sub-strings: "a", "ab", "abc", "abcd", "abcde". In the above function, first a DC that does not belong to any window is created, then the current font is selected into this DC, and function CDC::GetTextExtent(...) is called to obtain the dimension of each sub-string. Because we have only one line text, only the horizontal size is meaningful to us.
A loop is implemented to do the comparison. For the nth loop, we create a sub-string that contains text's first character to nth character, obtain its dimension, and calculate the distance from the position of the last character of the sub-string to the current position of mouse cursor. After the loop finishes, we choose the smallest distance and set m_nCaretIndex to the corresponding caret index.
Cursor Shape
For a text editor, when the mouse is over its editable area, usually the mouse cursor should be changed to an insertion cursor. This indicates that the user can input text at this time. This feature can also be implemented in our sample application.
To set mouse cursor's shape, we need to call API function ::SetCursor(...). The input parameter to this function is an HCURSOR type handle.
A cursor can be prepared as a resource and then be loaded before being used. After the cursor is loaded, we can pass its handle to function ::SetCursor(...) to change the current cursor shape. Besides the cursor prepared by the user, there also exist some standard cursors that can be loaded directly.
We can call function CWinApp::LoadCursor(...) to load a user-defined cursor, and call function CWinApp::LoadStandardCursor(...) to load a standard cursor. The following table lists some of the standard cursors:
(Table omitted)
In the sample, an HCURSOR type variable is declared in class CGDIView, and the insertion cursor is loaded in function CGDIView::OnInitialUpdate():
class CGDIView : public CScrollView
{
protected:
HCURSOR m_hCur;
......
}
void CGDIView::OnInitialUpdate()
{
......
m_hCur=AfxGetApp()->LoadStandardCursor(IDC_IBEAM);
}
We need to respond to WM_SETCURSOR message in order to change the cursor shape. By handling this message, we have a chance to customize the default cursor when it is within the client window of the application. Upon receiving this message, we can check if the mouse position is over the editable text. If so, we need to change the cursor by calling API function ::SetCursor(...). In this case, we need to return a TRUE value and should not call the default message handler. If the cursor should not be changed, we need to call the default message handler to let the cursor be set as usual.
To check out if the cursor is over editable text, we need to know the text dimension all the time. In the previous steps, we already have a variable CGDIDoc::m_nCaretVerSize that is used to store the vertical size of the caret (also the text), so here we just need another variable to store the horizontal size of the text. In the sample, a new variable n_nTextHorSize is declared in class CGDIDoc for this purpose:
(Code omitted)
Besides the new variable, function CGDIDoc::GetTextHorSize() is also added, which lets us access the value of CGDIDoc::m_nTextHorSize outside class CGDIDoc.
We need to set the value of m_nTextHorSize when the document is first initialized and when the font size is changed (In the following two functions, m_nTextHorSize is assigned a new value):
(Code omitted)
Message handler of WM_SETCURSOR can be added through using Class Wizard. The corresponding function CGDIView::OnSetCursor(...) is implemented as follows:
(Code omitted)
We call ::GetCursorPos(...) and CWnd::ScreenToClient(...) to retrieve the current cursor position in the client window's coordinate system. Then functions CGDIDoc::GetTextHorSize() and CGDIDoc:: GetCaretVerSize() are called to retrieve the dimension of the text. If the mouse cursor is within this rectangle, we call ::SetCursor(...) to change it to insertion cursor. In this case, we must return a TRUE value to avoid this message from being further processed (by default the mouse cursor will be set to arrow cursor).
Handling WM_LBUTTONDOWN to Move Caret
The caret can be moved when the current mouse cursor is an insertion cursor. To implement this, we need to call function CGDIDoc::SetCaret(...) after receiving WM_LBUTTONDOWN message. In the sample, this message handler is added through using Class Wizard, and the corresponding function CGDIView:: OnLButtonDown(...) is implemented as follows:
(Code omitted)
In this function, first we check if the mouse cursor is the insertion cursor. If not, it means that the mouse is not over the text string. If so, we call function CGDIDoc::SetCaret(...) and pass current mouse position to it. This will cause the caret to move to the new position.
8 One Line Text Editor, Step 5: Selection
Sample 8\GDI is based on sample 7\GDI.
Highlighting the Selected Text
The next feature we will add is to let the user select text using mouse. If this is implemented, it is easy for us to add cut, copy and paste functionalities.
To add the selection feature, we need two new text indices: one indicates the beginning of the selection, one indicates the end of the selection. In the sample, two variables
Page : << Previous 5 Next >>