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

along with two functions are added to class CGDIView for this purpose:

(Code omitted)

Here, variables m_nSelIndexBgn, m_nSelIndexEnd, functions CGDIDoc::GetSelIndexBgn() and CGDIDoc::GetSelIndexEnd() are added to the class. Before the selection is made, there may exist two situations: the text is currently being selected or there is no character being selected. To distiguish between the two situations, let's define that if any of m_nSelIndexBgn and m_nselIndexEnd is -1, or their values are the same, it indicates that there is no text being selected. The two variables are initialized in the constructor as follows:

(Code omitted)

In CGDIView::OnDraw(...), we need to retrieve the values of m_nSelIndexBgn and m_nSelIndexEnd, swap forground and background colors for the selected text when outputting text string to the client window. The following is the modified function CGDIView::OnDraw(...):

(Code omitted)

Two local variables nSelIndexBgn and nSelIndexEnd are declared here, they are used to store the values of CGDIDoc::m_nSelIndexBgn and CGDIDoc::m_nSelIndexEnd retrieved from the document. If the value of CGDIDoc::m_nSelIndexEnd is less than the value of CGDIDoc::m_nSelIndexBgn (In this case, the selection is made from right to left), we need to swap their values.

If there is no currently selected text, we simply call CDC::TextOut(...) as usual to output the plain text. Otherwise we swap the two indices if m_nSelIndexEnd is less than m_nSelIndexBgn, and set the text alignment by calling function CDC::SetTextAlign(...) using TA_UPDATECP flag. This will cause the output origin to be updated to the end of the text after each CDC::TextOut(...) call. If we do not set this alignment, the coordinates passed to function CDC::TextOut(...) indicate a position relative to the upper-left corner of the window. With TA_UPDATECP alignment style, when we call function CDC::TextOut(...), the coordinates passed to this function will be interpreted as a position relative to the new origin (which is the end of the text that is output by function CDC::TextOut(...) last time). This is very useful if we want to output several segments of strings. In the sample, the old alignment flag is stored in variable uTextAlign, and is restored after the text is output.

We divide the text string into three segments: the first segment starts from the beginning of the text and ends at the beginning of the selection. We output this sub-string using normal text and background colors. The second segment is the selected portion, before drawing this sub-string we need to swap the text and background colors so that the selected part will be drawn highlighted. The rest part is the third sub-string, which is also drawn using the normal text and background colors. Each time function CDC::TextOut(...) is called, the output coordinates are specified as (0, 0). If the alignment flag is not set to TA_UPDATECP, the three segments will all be drawn starting from the same origin.

Setting Selection Indices

Now we can change the values of CGDIDoc::m_nSelIndexBgn and CGDIDoc::m_nSelIndexEnd to highlight any portion of the text string. From user's point of view, this should happen when the mouse is clicked and dragged over the text. In order to implement this, we need to respond to WM_LBUTTONDWON, WM_LBUTTONUP and WM_MOUSEMOVE messages.

When left button is pressed down, we need to first reset the values of CGDIDoc::m_nSelIndexBgn and CGDIDoc::m_nSelIndexEnd to -1, because we need to unselect any currently highlighted text. Then we can update the value of CGDIDoc::m_nSelIndexBgn to the current caret index. When mouse moves with the left button held down, we need to set the value of CGDIDoc::m_nSelIndexEnd to the current caret index (the caret will move with the mouse cursor). The same thing needs to be done when mouse's left button is released. For these purposes, a new function ResetSelection() is declared in class CGDIDoc, which will be called to reset the selection indices. Also, function CGDIDoc::SetCaret(...) is modified. The following is a portion of the updated class CGDIDoc:

(Code omitted)

Function CGDIDoc::ResetSelection() is implemented inline, it just resets the values of m_nSelIndexBgn and m_nSelIndexEnd to -1, then updates the view window. The following shows the modified portion of function CGDIDoc::SetCaret(...):

(Code omitted)

We want to use this function to set both m_nSelIndexBgn and m_nSelIndexEnd, so that it can be called in response to any of the three mouse messages. If the value of m_nSelIndexBgn is -1, it means there is no currently selected text. In this case, we need to update m_nSelIndexBgn (This is the situation that the left button of the mouse is pressed down). In other cases (If m_nSelIndexBgn is not -1, the function must be called in response to mouse moving or left button up event), we need to update the value of m_nSelIndexEnd, then update the client window.

Handling Mouse Events

First function CGDIView::OnLButtonDown(...) is modified as follows:

Old Version:

(Code omitted)

New Version:

(Code omitted)

The only thing added to this function is resetting the selection indices stored in the document. Other changes are implemented in the updated function CGDIView::SetCaret(...).

Two other message handlers for WM_LBUTTONUP and WM_MOUSEMOVE are added through using Class Wizard. The corresponding functions are CGDIView::OnLButtonUp(...) and CGDIView::OnMouseMove(...) respectively.

Function CGDIView::OnMouseMove(...) is implemented as follows:

(Code omitted)

We find out if the left button is held down when the mouse is moving by checking MK_LBUTTON bit of parameter nFlags. If so, function CGDIDoc::SetCaret(...) is called to set the selection and update the client window. Function CGDIView::OnLButtonUp(...) is implemented exactly the same except that we don't need to check the status of left button here:

(Code omitted)

With the above knowledge, it is very easy for us to implement selection by using left/right ARROW key when SHIFT key is held down. To implement this, we need to check SHIFT key's status when either left or right ARROW key is pressed. If it is held, we can update the selection indices and redraw the client window.

9 One Line Text Editor, Step 6: Cut, Copy and Paste

Global Memory

Cut, copy and paste are supported almost by every application. They provide a way to exchange data among different applications. We must use globally shared data to implement clipboard data transfer. Once we send some data to the clipboard, it becomes public and can be accessed by all the programs. Any process in the system can clear the clipboard.

Because of this, we must allocate global memory to store our data in the clipboard. In Windows( programming, the following API functions can be used to manage global memory:

HGLOBAL ::GlobalAlloc(UINT uFlags, DWORD dwBytes);

LPVOID ::GlobalLock(HGLOBAL hMem);

HGLOBAL ::GlobalReAlloc(HGLOBAL hMem, DWORD dwBytes, UINT uFlags);

BOOL ::GlobalUnlock(HGLOBAL hMem);

HGLOBAL ::GlobalFree(HGLOBAL hMem);

Global memory is also managed through using handle. Unlike memory allocated using new key word, function ::GlobalAlloc(...) returns an HGLOBAL type handle to the allocated memory block if we allocate non-fixed global memory.

Generally, before accessing a non-fixed block of global memory, we must lock it by calling function ::GlobalLock(...), which will return the address of the memory block. After reading from or writing to this memory block, we need to call function ::GlobalUnlock(...) to unlock the memory again. We can free a block of global memory by calling function ::GlobalFree(...) (We can not free a block of memory when it is being locked).

We can also allocate fixed global memory, in which case the address of the memory will be returned by function ::GlobalAlloc(...) directly, and we do not need lock or unlock operation in order to access the memory.

Parameter nFlags of function ::GlobalAlloc(...) specifies how the memory will be allocated. There are many possible choices. For example, we can make the memory movable or fixed, and fill all the buffers with zero. The following is a list of some commonly used flags:

(Table omitted)

The most commonly used flag is GHND, which specifies that the memory block should be movable and all buffers should be initialized to zeros. For the clipboard usage, we also need to specify flag GMEM_SHARE, which will enhance the performance of clipboard operation.

Actually, in Win32 programming, the memory block allocated by one process can not be shared by other processes. Flag GMEM_SHARE exists just for the backward compatibility purpose. For a general application, we can not allocate a block of memory using flag GMEM_SHARE and share it with other applications. In Win32, this flag is solely used for clipboard and DDE (see chapter 15) implementation.

The memory size that will be allocated is specified by dwBytes parameter of function ::GlobalAlloc(...).

Apart from these functions, there is another set of functions whose functionality is exactly the same, the only difference is that they have a different set of function names:

HLOCAL ::LocalAlloc(UINT uFlags, UINT uBytes);

LPVOID ::LocalLock(HLOCAL hMem);

HLOCAL ::LocalReAlloc(HLOCAL hMem, UINT uBytes, UINT uFlags);

BOOL ::LocalUnlock(HLOCAL hMem);

HLOCAL ::LocalFree(HLOCAL hMem);

Everything is exactly the same except that all the "Global" keywords are changed to "Local" here. These functions are originated from the old Win16 programming, which uses 16-bit memory mode. In that case the memory can be allocated either from the local heap or global heap. In Win32 programming, there is only one heap, so two sets of functions become exactly the same. They exist just for the compatibility purpose. We can use either of them in our program. We can even call ::GlobalAlloc(...) to allocate memory and release it using ::LocalFree(...).

Clipboard Funcitons

To copy our own data to the clipboard, we need to first prepare the data. The

Page : << Previous 6  Next >>