Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How To Build A Simple Text Editor? A Tutorial

0.00/5 (No votes)
7 Aug 2011 3  
This article illustrates the application of ATL/WTL by building a simple text editor based on the WTL objects
Sample Image1 - a simple text editor.

Introduction

Text editor is a commonly used application that can process text file. The main functions of a text editor include "Open/Save file", "Edit/View content", etc. In this tutorial, we will show how to build a simple text editor based on WTL objects.

How to Use the Code?

We suppose that the reader has a Visual C++ with ATL/WTL support ready. If so, the source code can be directly used as a VC++ project. If not, the reader should do some extra work to prepare the development environment.

  1. Obtain and install a Visual C++ program on your system. The version of Visual C++ should be above 6.0 since Visual C++ provides built-in complete STL support after version 6.0. If you have to use VC6.0, you need to install an STL library, such as STLPort, in your system and set up your VC6.0 to use the STL library.
  2. Obtain and install a Windows Driver Kits (WDK) that is compatible with your Visual C++ program. WDK includes a standalone ATL/MFC implement that is necessary to WTL. Make sure that the paths of WDK head file, library file, and executable file are included in the environment setting of your Visual C++.
  3. Obtain and install an open source WTL implement. The up to date WTL version is 8.1. In the WTL install package, there are several install scripts for the corresponding versions of VC++.

Create a WTL Project

By creating C++ project via WTL project wizard, we can choose the base class for the document view. The class CEdit and CRichEditCtrl are both possible to be the base of a simple text editor. The CRichEditCtrl class is a WTL wrapper of the Windows ActiveX control "richedt20.dll", while CEdit is a WTL implement based on Windows API. As a simple text editor program, the CEdit class is a sufficient start point. What we need to do is to enrich the function of CEdit to a simple text editor.

Open & Save Text

The initial project created via WTL project wizard provides a basic program framework. As a complete executable program, it provides the basic GUI including window, manu, toolbar, workarea and statusbar, and the user friendly entry point to customize its program behavior.

We can input text on the workarea, but the input result cannot be saved, also the program cannot open an exist text file. The reason is that the default file open/save behavior is left empty. We first add the file open and save functions to the program as follows.

Read File and Show the Contents in the CEdit View

HANDLE hFile = ::CreateFile(sPath, GENERIC_READ, 0, 0, 
	OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, 0);
if(INVALID_HANDLE_VALUE == hFile) return false;

DWORD dwSizeLow = GetFileSize(hFile, 0);
char* pbBuff = (char*)malloc(dwSizeLow+1);
DWORD pcb = 0;

DWORD dwRet = ::ReadFile(hFile, pbBuff, dwSizeLow, (LPDWORD)&pcb, NULL);
pbBuff[dwSizeLow] = '\0';

::CloseHandle(hFile);

ATLASSERT(::IsWindow(m_hWnd));

::SendMessage(m_hWnd, WM_SETTEXT, 0, (LPARAM)pbBuff);
delete pbBuff;

Save the Contents in the CEdit View into a File

HANDLE hFile = ::CreateFile(sPath, GENERIC_WRITE,
0, 0, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0);
if(INVALID_HANDLE_VALUE == hFile) return false;

ATLASSERT(::IsWindow(m_hWnd));

DWORD dwSizeLow = ::GetWindowTextLength(m_hWnd);
char* pbBuff = (char*)malloc(dwSizeLow+1);
::SendMessage(m_hWnd, WM_GETTEXT, (WPARAM)dwSizeLow, (LPARAM)pbBuff);
pbBuff[dwSizeLow] = '\0';

DWORD pcb = 0;
DWORD dwRet = ::WriteFile(hFile, pbBuff, dwSizeLow, (LPDWORD)&pcb, NULL);

::CloseHandle(hFile);

Basic Text Edit Function

The CEdit class supports basic text editing operation, we can input text and copy/paste/cut the text in the workarea. However, the editing operatings of CEdit manipulate its inner data structure. Since our text edit program needs to do some customize operation, we therefore implement the basic editing operation based on our own data structure.

Define the Data Structure

We use a list of STL string to store the contents.

#include <vector>
#include <string>

vector<string> m_contents;

Mapping between Different Data Structures

Each string includes a text line, and by default, we use two chars '\r' '\n' to finish a line and start a new line. As we know, unlike our method, the CEdit class treats the contents as a long char sequence. If we select text on the workarea, the CEdit function GetSel() will return the position of the current selection. We have to map the position obtained by GetSel() function to the position in our m_contents. For example, the content...

[
Hello world!

foo
]

...is treated as a sequence "Hello world!\r\n\r\n foo" by default, and we will treat it as a list of string "Hello world!\r\n" "\r\n" "foo". The position 17 in the sequence is 'f', while its corresponding position is [2, 0], i.e. the first char of the third string. The mapping functions are shown as below:

GetRowColFromPosition(int& nRow, int &nCol, int nPosition)
{
	int len = 0;
	int size = m_rawText.size();
	nCol = nPosition;
	for(nRow=0;nRow<size;nRow++) {
		len = m_rawText[nRow].size();
		if(nCol > len)
			nCol -= len;
		else {
			if(nCol == len && size - nRow > 1) {
				nCol = 0; nRow++;
			}
			break;
		}
	}
}

After the position mapping, it is still needed to handle the messages for text editing. The messages include WM_CHAR, WM_KEYDOWN, ...

Handle the WM_CHAR Message

When the editor captures a user input char via a WM_CHAR message, the CEdit class saves the change into its inner data structure and reflects it on the workarea. However, we can customize the program behaviour by overlap or expend the function "OnChar" to handle the WM_CHAR message.

	BEGIN_MSG_MAP(CEditExView)
		MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
		MESSAGE_HANDLER(WM_CHAR, OnChar)
	END_MSG_MAP()

The main procedure of the function OnChar firstly locates currently selected contents on the workarea, and then replaces the selected contents by the input char. It is important that the editor should recognize the control chars and handle them properly. For example, when the input is a "return" char VK_RETURN, the program should break a string object into two string objects but not simply replace the selected contents.

int CEditExView::InsertAt(int nSelectionStart, TCHAR chNewChar)
{
	int nInsertionPoint=nSelectionStart;
	int nRow = 0;
	int nCol = 0;
	GetRowColFromPosition(nRow, nCol, nInsertionPoint);

	if(chNewChar==VK_RETURN)
	{
		string line1 = m_rawText[nRow].substr(0, nCol);
		line1.append(1, '\r').append(1, '\n');
		string line2 = m_rawText[nRow].substr(nCol, m_rawText[nRow].size());
		m_rawText[nRow] = line2;
		m_rawText.insert(m_rawText.begin() + nRow, line1);
		nInsertionPoint++;
	}
	else if (chNewChar == VK_TAB)
	{
		for(int i=0;i<m_TabCount;i++) {
			nInsertionPoint = nCol++;
			m_rawText[nRow].insert(nInsertionPoint, 1, _T(' '));
		}
	}
	else
		m_rawText[nRow].insert(nCol, &chNewChar);

	return nInsertionPoint;
}

Handle the WM_KEYDOWN Message

The WM_KEYDOWN message here is used to handle the "insert" char VK_INSERT. Each time the VK_INSERT key is pressed, the edit mode should be shift between INSERT mode and REPLACE mode.

LRESULT CEditExView::OnKeyDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	UINT nChar = (TCHAR)wParam;

	BOOL bIsShiftKeyDown=::GetAsyncKeyState(VK_SHIFT)<0;
	BOOL bIsCtrlKeyDown=::GetAsyncKeyState(VK_CONTROL)<0;

	if(nChar == VK_INSERT) {
	if (!bIsShiftKeyDown && !bIsCtrlKeyDown)
		{
			// The standard CEdit control does not support over-typing.
			// This flag is used to manage over-typing internally.
			SetInsertMode(!GetInsertMode());
		}
	}
	return 0;
}

Points of Interest

Both CEdit and CRichEditCtrl provide the selection function, it can respond to the mouse actions to select the text on the workarea. I am wondering whether the selection works if we paint some strange things, such as images, on the workarea?

History

  • 31 July, 2011 - Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here