So I'm making a text editor with syntax highlighting. I'm subclassing richedit and using drawtext to overwrite(Exactly like in Iczelion's tutorials). I was trying to solve the highlighting part, and up till now, I've managed to paint over all of the text(no syntax discrimination). But I've run into a strange problem. The tabs in my DrawText do not match up with the tab indentation of RichEdit tab spacing. Here is my code as of now:
Here is my custom callback handler:
LRESULT CALLBACK EditControlProc(HWND hWnd,UINT uMsg, WPARAM wParam,LPARAM lParam)
{
switch(uMsg)
{
case WM_PAINT:
LRESULT hr;
hr = CallWindowProc(OldWindowProc,hWnd,uMsg,wParam,lParam);
highlightText(hWnd);
return hr; //send the result of the operation
break;
case WM_VSCROLL:
SendMessage(hWnd,WM_ERASEBKGND,(WPARAM)GetDC(hWnd),0);
return CallWindowProc(OldWindowProc,hWnd,uMsg,wParam,lParam);
default:
return CallWindowProc(OldWindowProc,hWnd,uMsg,wParam,lParam);
}
}
void highlightText(HWND hWnd)
{
HDC hDC;
RECT formatRectangle;
DWORD charIndex;
TEXTRANGEW textRange;
//Zero Out textRange
ZeroMemory(&textRange,sizeof(textRange));
//Hide the caret to prevent any unwanted caret issues
HideCaret(hWnd);
//First, we obtain the formatting rectangle
SendMessage(hWnd,EM_GETRECT, NULL, (LPARAM)&formatRectangle); //Get the formatting rectangle for the edit control
//Second, we obtain the character index for the first character
charIndex = SendMessage(hWnd,EM_CHARFROMPOS, NULL, (LPARAM)&formatRectangle); //use the formatting rectangle obtained above to get the index for the first character
//Note that only the first two members of the formatRectangle RECT structure are used,
//since the given message requires a POINTL structure. We reuse our RECT structure to save
//some memory. Also, the contents of formatRectangle are not altered by this call
//this is also the reason why we use charIndex as well
//Now we obtain the line number
charIndex = SendMessage (hWnd,EM_LINEFROMCHAR, charIndex,NULL);
//Finally, we obtaint the index of the first character of the line. We want to perform our syntax highlighting from the first character of the line,
//It is possible that the first visible character may not be the first character of the first visible line
charIndex = SendMessage(hWnd,EM_LINEINDEX,charIndex,NULL);
//now we specify the range for the buffer
textRange.chrg.cpMin = charIndex;
textRange.chrg.cpMax = SendMessage(hWnd,EM_CHARFROMPOS,NULL, (LPARAM)&formatRectangle.right); //We use the same trick as before. Instead of passing a POINTL struct,
//we pass two members of the formatRectngle RECT structure
wchar_t tempBuffer[10000];
textRange.lpstrText = (LPWSTR) tempBuffer;
//Get the Text Range
SendMessageA(hWnd,EM_GETTEXTRANGE,NULL, (LPARAM)&textRange);
//Now, prepare for highlighting
hDC = GetDC(hWnd);
//Set clipping area
HRGN oldRegion = (HRGN) SelectObject(hDC, CreateRectRgn(formatRectangle.left,formatRectangle.top,formatRectangle.right,formatRectangle.bottom));
SetBkMode(hDC,TRANSPARENT);
//change the text color
SetTextColor(hDC,RGB(240,30,60));
//Get coordinates for the first character
SendMessage(hWnd,EM_POSFROMCHAR,(WPARAM)&formatRectangle,(LPARAM)charIndex);
//Finally, draw the text
DrawTextExW(hDC,tempBuffer,textRange.chrg.cpMax-textRange.chrg.cpMin,&formatRectangle,DT_EXPANDTABS | DT_TABSTOP | DT_WORD_ELLIPSIS,/*&dp*/NULL);
//Release all our resources
SelectObject(hDC,oldRegion);
DeleteObject(oldRegion);
ReleaseDC(hWnd,hDC);
//show the caret
ShowCaret(hWnd);
}
Here is the full code:
HWND hWndEdit; //Handle to RichEdit Control
HWND mhWnd; //Handle to main window
HWND hWndStatus; //Handle to the status bar
OPENFILENAME ofn; //OPENFILENAME structure
WNDPROC OldWindowProc = 0; //The Old window procedure for the rich edit contl
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPWSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wndClass;
MSG msg;
RECT rc;
UNREFERENCED_PARAMETER(lpCmdLine);
UNREFERENCED_PARAMETER(hPrevInstance);
ZeroMemory(&wndClass,sizeof(WNDCLASSEX));
LoadLibrary(TEXT("Msftedit.dll"));
LoadLibrary(TEXT("riched32.dll"));
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.hbrBackground = (HBRUSH)COLOR_WINDOW;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.lpszClassName = "Comment Generator";
wndClass.hInstance = hInstance;
wndClass.lpfnWndProc = WndProc;
wndClass.lpszMenuName=MAKEINTRESOURCE(IDR_MENU1);
if(!RegisterClassEx(&wndClass))
{
MessageBoxA(NULL,"Window Registration Failed","Fatal Application Error",MB_ICONERROR);
return -1;
}
if((mhWnd=CreateWindowEx(NULL,"Comment Generator", "JCGEJava Comment Generator And Editor", WS_OVERLAPPEDWINDOW,150,150,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL))==0)
{
MessageBoxA(NULL,"Window Creation Failed","Fatal Application Error",MB_ICONERROR);
return -1;
}
GetClientRect(mhWnd,&rc);
if((hWndEdit = CreateWindowExW(WS_EX_CLIENTEDGE,L"RICHEDIT50W", NULL,WS_HSCROLL | WS_VSCROLL | ES_MULTILINE | WS_VISIBLE | WS_CHILD | WS_BORDER |ES_NOHIDESEL , 0, 0, rc.right-rc.left, rc.bottom-rc.top-50,mhWnd, (HMENU)ID_EDITCHILD,hInstance, NULL))==0)
{
MessageBoxA(NULL,"Window Edit Control Creation Failed","Fatal Application Error",MB_ICONERROR);
return -1;
}
if((hWndStatus = CreateWindowEx(NULL,STATUSCLASSNAME,NULL, WS_CHILD | WS_VISIBLE | WS_BORDER ,0,0,0,0,mhWnd,(HMENU)ID_STATUS,hInstance,NULL))==0)
{
MessageBoxA(NULL,"Status bar creation failed","Fatal Application Error", MB_ICONERROR);
return -1;
}
OldWindowProc = (WNDPROC)SetWindowLong(hWndEdit,GWL_WNDPROC,(LONG)EditControlProc);
ShowWindow(mhWnd,nCmdShow);
UpdateWindow(mhWnd);
ZeroMemory(&msg, sizeof(msg));
while(msg.message!=WM_QUIT)
{
if(PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return static_cast<int>(msg.wParam);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_CREATE:
break;
case WM_DESTROY:
PostQuitMessage(NULL);
break;
case WM_SIZE:
RECT rbc;
GetClientRect(hwnd,&rbc);
MoveWindow(hWndEdit,0,0,rbc.right-rbc.left-2,rbc.bottom-rbc.left-25,true);
MoveWindow(hWndStatus,0,0,0,0,true);
break;
case WM_COMMAND:
if(HIWORD(wParam)==0)
{
switch(LOWORD(wParam))
{
case ID_FILE_EXIT:
PostQuitMessage(0);
return 0;
case ID_FILE_OPEN40001:
ZeroMemory(&ofn,sizeof(ofn));
char file[MAX_PATH];
ofn.hwndOwner = hwnd;
ofn.lStructSize = sizeof(ofn);
ofn.lpstrFile = file;
ofn.nMaxFile = sizeof(file);
ofn.lpstrFile[0] = '\0';
ofn.lpstrFilter = "All\0*.*\0Text\0*.TXT\0";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
SETTEXTEX st;
ZeroMemory(&st,sizeof(st));
st.flags = ST_KEEPUNDO;
if(GetOpenFileName(&ofn))
{
FileWrapper fWrap(ofn.lpstrFile);
fWrap.init();
fWrap.read();
SendMessageW(hWndEdit,EM_SETTEXTEX,(WPARAM)&st,(LPARAM) fWrap.getBuffer());
}
break;
case ID_ANALYZE_ANALYZEFILE:
MessageBox(NULL,"Analyze this","Success",MB_OK);
break;
}
}
default:
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
return 0;
}
LRESULT CALLBACK EditControlProc(HWND hWnd,UINT uMsg, WPARAM wParam,LPARAM lParam)
{
switch(uMsg)
{
case WM_PAINT:
LRESULT hr;
hr = CallWindowProc(OldWindowProc,hWnd,uMsg,wParam,lParam);
highlightText(hWnd);
return hr; //send the result of the operation
break;
case WM_VSCROLL:
SendMessage(hWnd,WM_ERASEBKGND,(WPARAM)GetDC(hWnd),0);
return CallWindowProc(OldWindowProc,hWnd,uMsg,wParam,lParam);
default:
return CallWindowProc(OldWindowProc,hWnd,uMsg,wParam,lParam);
}
}
void highlightText(HWND hWnd)
{
HDC hDC;
RECT formatRectangle;
DWORD charIndex;
TEXTRANGEW textRange;
//Zero Out textRange
ZeroMemory(&textRange,sizeof(textRange));
//Hide the caret to prevent any unwanted caret issues
HideCaret(hWnd);
//First, we obtain the formatting rectangle
SendMessage(hWnd,EM_GETRECT, NULL, (LPARAM)&formatRectangle); //Get the formatting rectangle for the edit control
//Second, we obtain the character index for the first character
charIndex = SendMessage(hWnd,EM_CHARFROMPOS, NULL, (LPARAM)&formatRectangle); //use the formatting rectangle obtained above to get the index for the first character
//Note that only the first two members of the formatRectangle RECT structure are used,
//since the given message requires a POINTL structure. We reuse our RECT structure to save
//some memory. Also, the contents of formatRectangle are not altered by this call
//this is also the reason why we use charIndex as well
//Now we obtain the line number
charIndex = SendMessage (hWnd,EM_LINEFROMCHAR, charIndex,NULL);
//Finally, we obtaint the index of the first character of the line. We want to perform our syntax highlighting from the first character of the line,
//It is possible that the first visible character may not be the first character of the first visible line
charIndex = SendMessage(hWnd,EM_LINEINDEX,charIndex,NULL);
//now we specify the range for the buffer
textRange.chrg.cpMin = charIndex;
textRange.chrg.cpMax = SendMessage(hWnd,EM_CHARFROMPOS,NULL, (LPARAM)&formatRectangle.right); //We use the same trick as before. Instead of passing a POINTL struct,
//we pass two members of the formatRectngle RECT structure
wchar_t tempBuffer[10000];
textRange.lpstrText = (LPWSTR) tempBuffer;
//Get the Text Range
SendMessageA(hWnd,EM_GETTEXTRANGE,NULL, (LPARAM)&textRange);
//Now, prepare for highlighting
hDC = GetDC(hWnd);
//Set clipping area
HRGN oldRegion = (HRGN) SelectObject(hDC, CreateRectRgn(formatRectangle.left,formatRectangle.top,formatRectangle.right,formatRectangle.bottom));
SetBkMode(hDC,TRANSPARENT);
//change the text color
SetTextColor(hDC,RGB(240,30,60));
//Get coordinates for the first character
SendMessage(hWnd,EM_POSFROMCHAR,(WPARAM)&formatRectangle,(LPARAM)charIndex);
//Finally, draw the text
DrawTextExW(hDC,tempBuffer,textRange.chrg.cpMax-textRange.chrg.cpMin,&formatRectangle,DT_EXPANDTABS | DT_TABSTOP | DT_WORD_ELLIPSIS,/*&dp*/NULL);
//Release all our resources
SelectObject(hDC,oldRegion);
DeleteObject(oldRegion);
ReleaseDC(hWnd,hDC);
//show the caret
ShowCaret(hWnd);
}
Here is an image of the problem:
Screenshot
The text in black is by richedit and the one in red is using the drawtext. As you can see, in the beginning the text is fine, but as soon as i press a tab key it gets messed up
PS. I know that I could do this without subclassing(by sending the formatting message), and that's what i plan to do after this. But for now I'm trying to do it this way, kinda for an educational purpose.
Thanks!
Devjeet