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.
- 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.
- 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++.
- 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)
{
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