Selecting a Line of Text

The example in this section is taken from a simple word-processing application. It includes code that enables the user to set the position of the caret by clicking anywhere on a line of text, and to select (highlight) a line of text by double-clicking anywhere on the line.

LRESULT APIENTRY MainWndProc(hwndMain, uMsg, wParam, lParam)

HWND hwndMain;

UINT uMsg;

WPARAM wParam;

LPARAM lParam;

{

HDC hdc; /* handle of device context */

TEXTMETRIC tm; /* font size data */

int i, j; /* loop counters */

int cCR = 0; /* count of carriage returns */

char ch; /* character from input buffer */

static int nBegLine; /* beginning of selected line */

static int nCurrentLine = 0; /* currently selected line */

static int nLastLine = 0; /* last text line */

static int nCaretPosX = 0; /* x-coordinate of caret */

static int cch = 0; /* number of characters entered */

static int nCharWidth = 0; /* exact width of a character */

static char szHilite[128]; /* text string to highlight */

static DWORD dwCharX; /* average width of characters */

static DWORD dwLineHeight; /* line height */

static POINTS ptsCursor; /* coordinates of mouse cursor */

static COLORREF crPrevText; /* previous text color */

static COLORREF crPrevBk; /* previous background color */

static PTCHAR pchInputBuf; /* address of input buffer */

static BOOL fTextSelected = FALSE; /* text-selection flag */

switch (uMsg) {

case WM_CREATE:

/* Get the metrics of the current font. */

hdc = GetDC(hwndMain);

GetTextMetrics(hdc, &tm);

ReleaseDC(hwndMain, hdc);

/* Save the average character width and height. */

dwCharX = tm.tmAveCharWidth;

dwLineHeight = tm.tmHeight;

/* Allocate a buffer to store keyboard input. */

pchInputBuf = (LPSTR) GlobalAlloc(GPTR,

BUFSIZE * sizeof(TCHAR));

return 0;

case WM_CHAR:

switch (wParam) {

case 0x08: /* backspace */

case 0x0A: /* linefeed */

case 0x1B: /* escape */

MessageBeep(0xFFFFFFFF);

return 0;

case 0x09: /* tab */

/* Convert tabs to four consecutive spaces. */

for (i = 0; i < 4; i++)

SendMessage(hwndMain, WM_CHAR, 0x20, 0);

return 0;

case 0x0D: /* carriage return */

/*

* Record the carriage return, and position the

* caret at the beginning of the new line.

*/

pchInputBuf[cch++] = 0x0D;

nCaretPosX = 0;

nCurrentLine += 1;

break;

default: /* displayable character */

ch = (char) wParam;

HideCaret(hwndMain);

/*

* Retrieve the character's width, and display the

* character.

*/

hdc = GetDC(hwndMain);

GetCharWidth32(hdc, (UINT) wParam, (UINT) wParam,

&nCharWidth);

TextOut(hdc, nCaretPosX,

nCurrentLine * dwLineHeight, &ch, 1);

ReleaseDC(hwndMain, hdc);

/* Store the character in the buffer. */

pchInputBuf[cch++] = ch;

/*

* Calculate the new horizontal position of

* the caret. If the new position exceeds the

* maximum, insert a carriage return and

* reposition the caret at the beginning of

* the next line.

*/

nCaretPosX += nCharWidth;

if ((DWORD) nCaretPosX > dwMaxCharX) {

nCaretPosX = 0;

pchInputBuf[cch++] = 0x0D;

++nCurrentLine;

}

ShowCaret(hwndMain);

break;

}

SetCaretPos(nCaretPosX, nCurrentLine * dwLineHeight);

nLastLine = max(nLastLine, nCurrentLine);

break;

.

. /* Process other messages. */

.

case WM_LBUTTONDOWN:

/*

* If a line of text is currently highlighted, redraw

* the text to remove the highlighting.

*/

if (fTextSelected) {

hdc = GetDC(hwndMain);

SetTextColor(hdc, crPrevText);

SetBkColor(hdc, crPrevBk);

TextOut(hdc, 0, nCurrentLine * dwLineHeight,

szHilite, lstrlen(szHilite));

ReleaseDC(hwndMain, hdc);

ShowCaret(hwndMain);

fTextSelected = FALSE;

}

/* Save the current mouse-cursor coordinates. */

ptsCursor = MAKEPOINTS(lParam);

/*

* Determine which line the cursor is on, and save

* the line number. Do not allow line numbers greater

* than the number of the last line of text. The

* line number is later multiplied by the average height

* of the current font. The result is used to set the

* y-coordinate of the caret.

*/

nCurrentLine = min((int)(ptsCursor.y / dwLineHeight),

nLastLine);

/*

* Parse the text input buffer to find the first

* character in the selected line of text. Each

* line ends with a carriage return, so it is possible

* to count the carriage returns to find the selected

* line.

*/

cCR = 0;

nBegLine = 0;

if (nCurrentLine != 0) {

for (i = 0; (i < cch) &&

(cCR < nCurrentLine); i++) {

if (pchInputBuf[i] == 0x0D)

++cCR;

}

nBegLine = i;

}

/*

* Starting at the beginning of the selected line,

* measure the width of each character, summing the

* width with each character measured. Stop when the

* sum is greater than the x-coordinate of the cursor.

* The sum is used to set the x-coordinate of the caret.

*/

hdc = GetDC(hwndMain);

nCaretPosX = 0;

for (i = nBegLine;

(pchInputBuf[i] != 0x0D) && (i < cch); i++) {

ch = pchInputBuf[i];

GetCharWidth32(hdc, (int) ch, (int) ch, &nCharWidth);

if ((nCaretPosX + nCharWidth) > ptsCursor.x)

break;

else

nCaretPosX += nCharWidth;

}

ReleaseDC(hwndMain, hdc);

/* Set the caret to the user-selected position. */

SetCaretPos(nCaretPosX, nCurrentLine * dwLineHeight);

break;

case WM_LBUTTONDBLCLK:

/* Copy the selected line of text to a buffer. */

for (i = nBegLine, j = 0; (pchInputBuf[i] != 0x0D) &&

(i < cch); i++)

szHilite[j++] = pchInputBuf[i];

szHilite[j] = '\0';

/*

* Hide the caret, invert the background and foreground

* colors, and then redraw the selected line.

*/

HideCaret(hwndMain);

hdc = GetDC(hwndMain);

crPrevText = SetTextColor(hdc, RGB(255, 255, 255));

crPrevBk = SetBkColor(hdc, RGB(0, 0, 0));

TextOut(hdc, 0, nCurrentLine * dwLineHeight, szHilite,

lstrlen(szHilite));

SetTextColor(hdc, crPrevText);

SetBkColor(hdc, crPrevBk);

ReleaseDC(hwndMain, hdc);

fTextSelected = TRUE;

break;

.

. /* Process other messages. */

.

default:

return DefWindowProc(hwndMain, uMsg, wParam, lParam);

}

return NULL;

}