std::ostream Implementation for Text Output with CWnd-derived Controls

I needed this class for keeping some other parts of a larger application portable. These other classes just require ostream & parameters for doing message and error text output (adapted lex and yacc generated classes actually).

The basic ideas for the implementation came from a sample implementation for a X-Motif widget ostream by Dietmar Kuehl, see this URL to read more about it, and compare what i have been changing:

I added extra handling for special characters '\n' and '\a':
  • '\n' needs to have a '\r' prefixed for proper output of a CR-LF sequence (linebreak) in a CWnd
  • '\a' is commonly defined as alert character, i have implemented this as an 'optical bell' (flashing of top window title)

The implementation is intended for use with a STATIC or EDIT control window class and actually uses the CWnd::GetWindowText() and CWnd::SetWindowText() methods for doing the text rendering. To get a CDC * with CWnd::GetWindowDC() and to draw directly in the windows' client area would be more efficient and flexible (concerning text colouring and formatting). But if you use an EDIT control in conjunction with winostream you'll have advantage of all the standard control features as 'copy & paste' for instance.

There's another article to be found on CodeGuru, mentioning to send a WM_SETSEL message to append text to the actual window text (thus you can omit the CWnd::GetWindowText() calls). But this can be realized with EDIT control window classes only; my solution will be slower but more general.

Implementation of special stream manipulators in the same style as 'std::endl(ostream &)' will reqire to reimplement std::ostream overloadings of the operator<<() method to return a winostream & actually. Otherwise the manipulator function will receive an ostream & when a standard operator<<() overloading is seen at the left side.

The methods overloaded and declared with winstrbuf should represent a sufficient abstraction for implementing an ostream/streambuf pair. Overload or rewrite the methods winstrbuf::TranslateWinChar(), winstrbuf::put_buffer() and winstrbuf::put_char() to adapt character rendering to your needs.

//////////////////////////////////////////////////////////////////////
// winostream.h: interface of class winostream
// author: g. makulik
// purpose: implementing std::ostream for text output on
// a MFC-CWnd. This is primarily intended to use
// with CStatic/CEdit controls.
//
//////////////////////////////////////////////////////////////////////

// ... some includes etc.

class winostream : public ostream
{
public:
 winostream ( CWnd * pWnd_
 , int nBufSize_ = 0
 );
 virtual ~winostream ();

 // It's not essential to reimplement all the operator<< overloads
 // of ostream. But this will be useful if you like to implement
 // special manipulator methods and their global pendants in the
 // same style as 'endl'.
 winostream & operator<< ( winostream & (*fmanip)(winostream &)
 );
 winostream & operator<< ( ostream & (*fmanip)(ostream &)
 )
 { fmanip(*this); return *this; };
 winostream & operator<< ( ios & (*fmanip)(ios &)
 )
 { fmanip(static_cast<ios>(*this)); return *this; };
 winostream & operator<< ( ios_base & (*fmanip)(ios_base &)
 )
 { fmanip(static_cast<ios_base>(*this)); return *this; };
 winostream & operator<< ( bool x_
 )
 { ostream::operator <<(x_); return *this; };
 winostream & operator<< ( short x_
 )
 { ostream::operator <<(x_); return *this; };
 // ... put more << operator overloadings here, see ostream for
 // their signature

 // declare special winostream manipulators here
 //====================================================================

protected:
 virtual CWnd * getwnd ()
 { return m_pWinstrbuf->getwnd(); };
private:
 winstrbuf * m_pWinstrbuf;
};

//////////////////////////////////////////////////////////////////////
// winstrbuf.h: interface of class winstrbuf.
// author: g. makulik
// purpose: see winostream.h
//////////////////////////////////////////////////////////////////////

// ... some includes etc.

class winstrbuf : public streambuf
{
public:
 winstrbuf ( CWnd * pWnd_
 , int nBufSize_ = 0
 );
 virtual ~winstrbuf ();

 CWnd * getwnd ()
 { return m_pWnd; };

protected:
 virtual int overflow ( int c_
 );
 virtual int sync ();
 virtual CString TranslateWinChar( int c_
 );
 virtual CString TranslateWinChar( CString const & cs_
 );
 virtual void PutAlertChar ();
private:
 CWnd * m_pWnd;
 int m_nBufSize;

 // helper class for representing installed
 // WIN32 timer ressources
 struct CFlashTimer {
 CFlashTimer ( UINT nId_
 , CWnd * pWnd_
 , UINT nTimes_
 )
 : m_nId(nId_)
 , m_pWnd(pWnd_)

 // Assure to have an odd value for m_nTimes, to
 // get the same window state as before, when the
 // flashing is over
 , m_nTimes((nTimes_%2)?nTimes_+1:nTimes_)
 , m_nCount(0)
 {};
 UINT m_nId;
 CWnd * m_pWnd;
 UINT m_nTimes;
 UINT m_nCount;
 BOOL m_bNextFlash;

 static void CALLBACK EXPORT
 TimerProc ( HWND hWnd_
 , UINT nMsg_
 , UINT nId_
 , DWORD dwTime_
 );
 };
 friend struct CFlashTimer;

 CFlashTimer * InstallTimer ( CWnd * pWnd_
 , DWORD dwFreq_
 , UINT nTimes_
 );
 static CFlashTimer * GetTimer ( UINT nId_
 );
 static BOOL KillTimer ( UINT nId_
 );

 static UINT sm_nextTimerId;
 static map<UINT,CFlashTimer*> sm_mapFlshTimers;

 void put_buffer ();
 void put_char ( int c_
 );

};

//////////////////////////////////////////////////////////////////////
// winstrbuf.cpp: implementation of class winstrbuf.
// author: g. makulik
//
//////////////////////////////////////////////////////////////////////

// ... some includes etc.

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

winstrbuf::winstrbuf ( CWnd * pWnd_
 , int nBufSize_
 )
 : streambuf()
 , m_pWnd(pWnd_)
 , m_nBufSize(nBufSize_)
{
 ASSERT_VALID(pWnd_);
 if (m_nBufSize)
 {
 // if the requested buffer size is greater than 0,
 // allocate the buffer memory and tell streambuf
 // where it is allocated
 char * ptr = new char[m_nBufSize];
 setp(ptr, ptr + m_nBufSize);
 }
 else
 setp(0, 0);

 setg(0, 0, 0);
}

winstrbuf::~winstrbuf ()
{
 sync();
 if (m_nBufSize)
 {
 // free any buffer memory which was
 // allocated on construction
 delete [] pbase();
 }
}

// overflow is called when the buffer is full
int winstrbuf::overflow ( int c_
 )
{
 // render textoutput and clear the buffer
 put_buffer();

 // check for end of file character
 if (c_ != EOF)
 if (pbase() == epptr())
 // if there's no space left in the buffer
 // send the character directly to the device
 put_char(c_);
 else
 // otherwise append it to the buffer
 sputc(c_);

 return 0;
}

// called to syncronize contents of buffer and device
int winstrbuf::sync ()
{
 put_buffer();
 return 0;
}

// called to render a single character on the device
void winstrbuf::put_char( int c_
 )
{
 // extract actual window text
 ASSERT_VALID(m_pWnd);
 CString csBuf;
 m_pWnd->GetWindowText(csBuf);

 // append character to extracted text
 csBuf += TranslateWinChar(c_);

 // write back text to the window (render)
 m_pWnd->SetWindowText(csBuf);
}

// called to render the actual content of the buffer on the device
void winstrbuf::put_buffer()
{
 if (pbase() != pptr())
 {
 int nBufLength = (pptr() - pbase());

 // extract actual window text
 ASSERT_VALID(m_pWnd);
 CString csBuf;
 m_pWnd->GetWindowText(csBuf);

 // append buffer to extracted text
 CString csHelp(pbase(),nBufLength);
 csBuf += TranslateWinChar(csHelp);

 // write back text to the window (render)
 m_pWnd->SetWindowText(csBuf);

 setp(pbase(), epptr());
 }
}

// used to implement rendering of special characters
// on the specific device (CWnd* actually)
CString winstrbuf::TranslateWinChar ( int c_
 )
{
 CString csRes;

 csRes = "";
 switch((char)c_)
 {
 // prefix 'LF' with 'CR'
 case '\n':
 csRes += '\r';
 break;
 // implement alert character as 'optical bell'. Just override if
 // you want to do anything other.
 case '\a':
 PutAlertChar();
 return csRes;
 break;
 }

 csRes += (char)c_;
 return csRes;
}

// respects rendering of special characters
// on the specific device for a sequence of
// characters
CString winstrbuf::TranslateWinChar ( CString const & cs_
 )
{
 CString csRes;

 csRes = "";
 for(int i = 0;
 i < cs_.GetLength();
 i++)
 {
 csRes += TranslateWinChar(cs_[i]);
 }

 return csRes;
}


// The following functions implement an 'optical bell' for
// a MFC standard window
void winstrbuf::PutAlertChar()
{
 ASSERT(m_pWnd);
 // flashing is always done with the top level parent window
 // (the one with the title bar usually)
 CWnd * pFlashWin = m_pWnd->GetTopLevelParent();

 // Install a timer activated every 100 ms and
 // killed after 5th activation
 InstallTimer(pFlashWin,100,5);

}

// the static timer callback function, it is called when the
// timer is activated. Note since this is a static method, a
// pseudo-this pointer is externalized in a static map of
// this class. It can be uniquely identified with the associated
// timer's ID. Any access to the static map will be thread critical;
// use critical sections for map access, to make this class thread
// safe.
void CALLBACK EXPORT
 winstrbuf::CFlashTimer::TimerProc ( HWND hWnd_
 , UINT nMsg_
 , UINT nId_
 , DWORD dwTime_
 )
{
 CFlashTimer * pThis;
 // Thread critical
 //---------------------------------------------------------
 // get pseudo-this pointer
 if((pThis = winstrbuf::GetTimer(nId_)) != NULL)
 {
 // Thread critical end
 //---------------------------------------------------------

 // increment call counter first
 if(++pThis->m_nCount <= pThis->m_nTimes)
 {
 // change flash state of the window to former state
 pThis->m_bNextFlash = ::FlashWindow(hWnd_,pThis->m_bNextFlash);
 }
 else
 {
 // kill the timer if the specified number
 // of activations was done
 winstrbuf::KillTimer(nId_);
 }
 }
}


// initialize static members
UINT winstrbuf::sm_nextTimerId = 0;
map<UINT,CFlashTimer*> winstrbuf::sm_mapFlshTimers;

// this method installs a timer. The this-pointer of a CFlashTimer
// is stored in the global timer map
winstrbuf::CFlashTimer *
 winstrbuf::InstallTimer ( CWnd * pWnd_
 , DWORD dwFreq_
 , UINT nTimes_
 )
{
 CFlashTimer * pRes = NULL;
 // Thread critical from here to return
 //---------------------------------------------------------
 if(pWnd_->SetTimer(++sm_nextTimerId,dwFreq_,CFlashTimer::TimerProc) 
 != sm_nextTimerId)
 {
 return NULL;
 }
 pRes = new CFlashTimer(sm_nextTimerId,pWnd_,nTimes_);
 sm_mapFlshTimers[sm_nextTimerId] = pRes;
 return pRes;
}

// finds a specific CFlashTimer-pointer identified by
// an associated timer ID
winstrbuf::CFlashTimer * winstrbuf::GetTimer ( UINT nId_
 )
{
 CFlashTimer * pRes = NULL;
 // Thread critical from here to return
 //---------------------------------------------------------
 if(sm_mapFlshTimers.find(nId_) != sm_mapFlshTimers.end())
 {
 pRes = sm_mapFlshTimers[nId_];
 }
 return pRes;
}

// releases the WIN32 timer resource and removes the associated
// CFlashTimer object from the global timer map
BOOL winstrbuf::KillTimer ( UINT nId_
 )
{
 BOOL bRes = FALSE;
 // Thread critical from here to return
 //---------------------------------------------------------
 CFlashTimer * pFlashTimer = GetTimer(nId_);
 if(pFlashTimer)
 {
 bRes = pFlashTimer->m_pWnd->KillTimer(pFlashTimer->m_nId);
 delete pFlashTimer;
 sm_mapFlshTimers.erase(nId_);
 }
 return bRes;
}

Downloads

Download demo project - 24 Kb
Download source - 4 Kb


Comments

  • Tanx

    Posted by Legacy on 01/26/2004 12:00am

    Originally posted by: Teldin Moore

    Thank you for showing me how to redirect streams to a MFC editbox.

    kind regards,

    Mr. Moore.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Agile methodologies give development and test teams the ability to build software at a faster rate than ever before. Combining DevOps with hybrid cloud architectures give teams not just the principles, but also the technology necessary to achieve their goals. By combining hybrid cloud and DevOps: IT departments maintain control, visibility, and security Dev/test teams remain agile and collaborative Organizational barriers are broken down Innovation and automation can thrive Download this white paper to …

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds