![]() |
Desktop Development »
Edit Controls »
General
Intermediate
License: The Code Project Open License (CPOL)
A chat control based on the Rich Edit controlBy Rob MandersonUsing the Rich Edit control as a chat window. |
VC6Win2K, WinXP, Win2003, MFC, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Chat clients also typically allow for varied formatting of the contents based on the message type. Was this a whisper? Was it an action? Or just normal 'said' text. (If you have no idea what I'm talking about you haven't experienced a chat room). The need to differentiate different message types makes the Rich Edit control seem a 'natural' fit for the display. But it's not as easy as that!
class CChatRichEd : public CRichEditCtrl
{
DECLARE_DYNAMIC(CChatRichEd);
// Construction
public:
CChatRichEd();
virtual ~CChatRichEd();
// Attributes
public:
CHARFORMAT& CharFormat() { return m_cfDefault; }
// Operations
public:
BOOL Create(DWORD dwStyle, const RECT& rcRect,
CWnd* pParentWnd, UINT nID);
void AppendText(LPCTSTR szText);
BOOL SaveToFile(CFile *pFile);
void Freeze();
void Thaw();
void Clear();
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CRichEd)
//}}AFX_VIRTUAL
// Generated message map functions
protected:
void InternalAppendText(LPCTSTR szText);
static DWORD CALLBACK StreamCallback(DWORD dwCookie, LPBYTE pbBuff,
LONG cb, LONG *pcb);
int m_iLineCount,
m_iLastLineCount;
CStringList m_cslDeferredText;
BOOL m_bFrozen;
CHARFORMAT m_cfDefault;
//{{AFX_MSG(CRichEd)
afx_msg void OnSize(UINT nType, int cx, int cy);
//}}AFX_MSG
afx_msg void OnLink(NMHDR *in_pNotifyHeader, LRESULT *out_pResult);
DECLARE_MESSAGE_MAP()
};
The constructor does little of interest save for initialising a CHARFORMAT structure. This structure is used by the Rich Edit control to set formatting
for inserted text. The formatting you can set includes font settings and text colour.
The Create() function ensures that we've done the appropriate initialisation of the Rich Edit control DLL before we try and create it.
BOOL CChatRichEd::Create(DWORD dwStyle, const RECT& rcRect,
CWnd* pParentWnd, UINT nID)
{
if (!::AfxInitRichEditEx())
return FALSE ;
CWnd* l_pWnd = this;
return l_pWnd->Create(_T("RichEdit20A"), NULL, dwStyle, rcRect,
pParentWnd, nID);
}
Hmmm, we've got a function there that's not part of the windows API. AfxInitRichEditEx() is defined here (in the RichEd.cpp file).
_AFX_RICHEDITEX_STATE::_AFX_RICHEDITEX_STATE()
{
m_hInstRichEdit20 = NULL ;
}
_AFX_RICHEDITEX_STATE::~_AFX_RICHEDITEX_STATE()
{
if (m_hInstRichEdit20 != NULL)
::FreeLibrary(m_hInstRichEdit20) ;
}
_AFX_RICHEDITEX_STATE _afxRichEditStateEx;
BOOL PASCAL AfxInitRichEditEx()
{
_AFX_RICHEDITEX_STATE *l_pState = &_afxRichEditStateEx;
if (l_pState->m_hInstRichEdit20 == NULL)
l_pState->m_hInstRichEdit20 = LoadLibraryA(_T("RICHED20.DLL"));
return l_pState->m_hInstRichEdit20 != NULL ;
}
Well that's pretty simple. The RichEd.cpp file defines a global variable of type _AFX_RICHEDITEX_STATE. Because it's a global the
compiler dutifully runs the constructor before our program's WinMain is called. The constructor loads the RICHED20.DLL library,
ensuring it's in our process space when we come to run the CChatRichEd constructor. Just to be sure, the CChatRichEd::Create() function
checks that the library has been loaded. (this code, _AFX_RICHEDITEX_STATE, was written by Andrew Forget and the article I found it on is at
CodeGuru).
So now we've created our control. As I said before, typically it's a readonly control, so the only way text can get to it is by calling the AppendText()
function. So let's have a look at that.
// This is the public interface for appending text to the control. It
// either appends it directly to the control or adds it to a string
// list if the control is frozen. When the control is thawed the
// strings are taken off the list and added to the control.
void CChatRichEd::AppendText(LPCTSTR szText)
{
if (m_bFrozen)
m_cslDeferredText.AddHead(szText);
else
InternalAppendText(szText);
}
Hmmmmm so now we have to know if the control is thawed or frozen? Yes we do. Picture the use of this control in a chat client. You have maybe a dozen people connected
to the chat server from half a dozen different countries. Depending on what's being said one or more users may want to scroll back and see previous history. That's
very difficult to do without telling the chat server to shut up. Since the IRC protocol doesn't support such a message we need to do it ourselves. We do this by
'freezing' the control (under user control). When the control is 'frozen' it doesn't display new messages. But we don't want to lose new
messages, just defer them. When we 'thaw' the control we want any messages recieved since we 'froze' it to be displayed. That's the purpose of the
m_cslDeferredText string array.
The control doesn't 'freeze' or 'thaw' itself. That's the responsibility of your application.
Most of the time the m_cslDeferredText array is empty and messages are added directly to the chat control.
So our control isn't frozen. What does InternalAppendText() do?
void CChatRichEd::InternalAppendText(LPCTSTR szText)
{
int len;
ASSERT(szText);
ASSERT(AfxIsValidString(szText));
int iTotalTextLength = GetWindowTextLength();
CWnd *focusWnd = GetFocus();
// Hide any selection and select the end of text marker.
HideSelection(TRUE, TRUE);
SetSel(iTotalTextLength, iTotalTextLength);
// Now set the character format
SetSelectionCharFormat(m_cfDefault);
// And put the text into the selection
ReplaceSel(szText);
len = GetWindowTextLength();
// Now select the end of text marker again
SetSel(len, len);
if (iTotalTextLength > 125000)
{
// The control's starting to get full so trim off the first
// 50,000 bytes....
SetSel(0, 50000);
ReplaceSel(_T(""));
SetSel(iTotalTextLength, iTotalTextLength);
}
HideSelection(FALSE, TRUE);
SendMessage(EM_SCROLLCARET, 0, 0);
if (focusWnd != (CWnd *) NULL)
focusWnd->SetFocus();
}
This is also pretty simple. We need to hide the current selection (else the control does amazing things visually), select the end of the text, set the character
formatting and insert the new text. When we've inserted the text we scroll the cursor to the end of the control.
This code also sets a hard limit of 125000 bytes on the text length. If the control contains more than 125000 bytes it will delete the first 50000 bytes. Hmmmm this is Win32 and we have gigabytes of virtual memory space available right? Right. But empirical experience (which is admittedly two years old) tells me that trying to save more than 125000 bytes will cause some users of your chat client major problems. Tune those numbers for yourself :)
EN_LINK message which
the class intercepts and turns into a ShellExecute "open" action. ShellExecute in turn does the 'right' thing with the URL.
CFile using whatever method is
appropriate and pass a pointer to it to the SaveToFile() function. That function in turn sets up the Rich Edit control stream conditions needed and saves
the contents as an RTF file.
I was that close to including my entire object heirarchy for text objects. I decided not to because it has too many dependencies on other parts of my chat client. How are colours specified? How are fonts specified? Suddenly a simple article becomes a treatise on my way of doing things. This is not my intention :)
ChatRichEd.cpp/h files to your project. When you want to use the chat control in a dialog (or in a view) add a custom control whose class is
RichEdit20A. Set the control styles as required. The sample uses 0x50a10844 which translates into
WS_CHILDWINDOW | WS_VISIBLE | WS_OVERLAPPED | WS_BORDER | WS_VSCROLL | WS_MAXIMIZEBOX | ES_READONLY | ES_AUTOVSCROLL | ES_MULTILINE.
You then need to create an instance of CChatRichEd and subclass the Rich Edit control to that instance. In the sample project I did this by adding
CChatRichEd m_richEdit;
to the dialog header and
DDX_Control(pDX, IDC_RICHEDIT, m_richEdit);
to the DoDataExchange() function.
First version - December 7 2003
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 6 Dec 2003 Editor: Rob Manderson |
Copyright 2003 by Rob Manderson Everything else Copyright © CodeProject, 1999-2009 Web21 | Advertise on the Code Project |