Prev: Task
Next: compile error note
From: Hector Santos on 27 Apr 2010 10:26 Joe, IMV, this is a MAJOR step back in the programming solid code in order to support this UNICODE stuff. Wow!! Are you kidding me? Why is UNICODE such an on-going issue with so many people even after all these years. Its terrible. Wow! Personally, in this topic, I think understanding more what the CRichEditCtrl wants for UNICODE support is whats important here. I don't see any consistent information about this. -- HLS Joseph M. Newcomer wrote: > See below... > On Tue, 27 Apr 2010 01:18:08 -0700, JY <sd(a)nospamgroup.com> wrote: > >> Hi, >> >> I have a Wizard based application, in which I have some property pages. In >> one of the pages, I have a CRichEditCtrl, and I try to populate its contents >>from a RTF file. The porblem is, it works correctly if the RTF file is small >> (about 4-5 KB), but when I use a larger file, its contents either don't >> display at all, or gets truncated. >> >> The code is shown below. What can I do to show the entire contents of the >> RTF file in the control? Also, it should work for all languages - I have >> UNICODE defined in the project. m_RECtrl is the rich edit control. >> >> static DWORD CALLBACK MyStreamInCallback(DWORD dwCookie, LPBYTE pbBuff, LONG > **** > Why did you declare the cookie as a DWORD and not a DWORD_PTR? This is incorrect, > although in 32-bit Windows it would not have any impact > **** >> cb, LONG *pcb) >> { >> CFile* pFile = (CFile*)(DWORD_PTR)dwCookie; >> *pcb = pFile->Read(pbBuff, cb); > **** > Note that the Read should be contained in a try/catch(CFileException * e) structure if you > plan to detect errors correctly > **** >> return 0; >> } >> >> BOOL CREPage::OnInitDialog() >> { >> CBasePage::OnInitDialog(); >> >> if (m_strRTFFilePath.GetLength()) >> { >> m_RECtrl.ShowScrollBar(SB_VERT, TRUE); >> CFile eulaFile(m_strRTFFilePath, CFile::modeRead); > **** > Note that this constructor must be in a try/catch(CFileException * e) block, since if > there is a problem (such as the file does not exist along the path) then it will throw an > exception > **** >> EDITSTREAM es; >> >> es.dwCookie = (DWORD_PTR)&eulaFile; >> es.pfnCallback = (EDITSTREAMCALLBACK)MyStreamInCallback; >> m_RECtrl.StreamIn(SF_RTF, es); >> } > >> return TRUE; >> } >> >> int CREPage::OnCreate(LPCREATESTRUCT lpCreateStruct) >> { >> if (CBasePage::OnCreate(lpCreateStruct) == -1) >> return -1; >> >> CRect rect; >> GetClientRect(&rect); >> rect.bottom -= 25; >> >> m_RECtrl.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE, rect, >> this, IDC_RICHEDIT_EULA); >> m_RECtrl.SetOptions(ECOOP_OR, ECO_AUTOVSCROLL | ECO_AUTOHSCROLL | >> ECO_READONLY); >> m_RECtrl.ModifyStyleEx(0, WS_EX_CLIENTEDGE, SWP_FRAMECHANGED); >> >> return 0; >> } >> >> TIA, >> JY > **** > Here's the stream callback from my handler: > > class StreamInCookie { > public: > CFile * file; > DWORD_PTR remaining; > DWORD err; > }; > **** > This is retrieving data from a precomputed cache which is in rtf format, and if there is > any timestamp change on an input, it resets all the content and will recompute it from > scratch, ignoring the cache. The cache starts with a binary timestamp and a binary length > value. It also marks the hyperlinks. If you want to see the hyperlink code, I've > attached it > ***** > BOOL CIndex::RetrieveFromCache() > { > CString filename; > if(!GetCacheFile(filename)) > return FALSE; > > CFile f; > > if(!f.Open(filename, CFile::modeRead)) > return FALSE; // open failed > > try { /* try read */ > StreamInCookie streamIn; // > streamIn.file = &f; > > //**************************************************************** > // [Timestamp] > //**************************************************************** > > TimeStamp ft; > if(f.Read(&ft, sizeof(TimeStamp)) == 0) > { /* failed */ > ResetAllContent(); > f.Close(); > return FALSE; > } /* failed */ > > //**************************************************************** > // [eod] End of data position for RTF data > //**************************************************************** > > if(f.Read(&streamIn.remaining, sizeof(DWORD)) == 0) > { /* failed */ > ResetAllContent(); > f.Close(); > return FALSE; > } /* failed */ > > // Now convert from offset to length > streamIn.remaining -= f.GetPosition(); > > //**************************************************************** > // <...> RTF data > //**************************************************************** > EDITSTREAM es; > es.dwCookie = (DWORD_PTR)&streamIn; > es.pfnCallback = StreamInCallback; > c_Index.StreamIn(SF_RTF, es); > > if(streamIn.err != ERROR_SUCCESS) > { /* failed */ > f.Close(); > ResetAllContent(); > return FALSE; > } /* failed */ > > //**************************************************************** > // Read the hyperlink length > //**************************************************************** > DWORD len; > if(f.Read(&len, sizeof(DWORD)) == 0) > { /* failed to get links */ > f.Close(); > ResetAllContent(); > return FALSE; > } /* failed to get links */ > > hyperlinks.SetSize(len); > > //**************************************************************** > // Read the hyperlink data > //**************************************************************** > > for(DWORD i = 0; i < len; i++) > { /* read each */ > hyperlinks[i] = new Reference; > if(!hyperlinks[i]->Read(f)) > { /* failed */ > if(::GetLastError() == ERROR_HANDLE_EOF) > break; // "failure" is EOF (see spec on Reference::Read) > f.Close(); > ResetAllContent(); > return FALSE; > } /* failed */ > MarkLink(c_Index, hyperlinks[i]->range); > } /* read each */ > > //**************************************************************** > // [eod] Read EOD for Fastlink RTF data > //**************************************************************** > > if(f.Read(&streamIn.remaining, sizeof(DWORD)) == 0) > { /* failed */ > ResetAllContent(); > f.Close(); > return FALSE; > } /* failed */ > > streamIn.remaining -= f.GetPosition(); // convert from offset to length > > //**************************************************************** > // <...> RTF data > //**************************************************************** > c_FastIndex.StreamIn(SF_RTF, es); > > if(streamIn.err != ERROR_SUCCESS) > { /* failed */ > ResetAllContent(); > f.Close(); > return FALSE; > } /* failed */ > > //**************************************************************** > // Read the hyperlink length > //**************************************************************** > > if(f.Read(&len, sizeof(DWORD)) == 0) > { /* failed to get links */ > ResetAllContent(); > f.Close(); > return FALSE; > } /* failed to get links */ > > fastlinks.SetSize(len); > > //**************************************************************** > // Read the hyperlink data > //**************************************************************** > > for(i = 0; i < len; i++) > { /* read each */ > if(!fastlinks[i].Read(f)) > { /* failed */ > if(::GetLastError() == ERROR_HANDLE_EOF) > break; > f.Close(); > ResetAllContent(); > return FALSE; > } /* failed */ > MarkLink(c_FastIndex, fastlinks[i].range); > } /* read each */ > //**************************************************************** > } /* try read */ > catch(CFileException * e) > { /* read error */ > e->Delete(); > ResetAllContent(); > f.Close(); > return FALSE; > } /* read error */ > f.Close(); > c_Index.SetSel(0,0); > c_FastIndex.SetSel(0,0); > return TRUE; > } // CIndex::RetrieveFromCache > > void CIndex::MarkLink(CRichEditCtrlEx & ctl, CHARRANGE & range) > { > CHARFORMAT2 linkfmt; > > ctl.SetSel(range); > > linkfmt.cbSize = sizeof(CHARFORMAT2); > linkfmt.dwMask = CFM_LINK; > linkfmt.dwEffects = CFE_LINK; > linkfmt.dwMask = CFM_LINK; > ctl.SetSelectionCharFormat(linkfmt); > } // CIndex::MarkLink > > /* static */ DWORD CALLBACK CIndex::StreamInCallback(DWORD_PTR cookie, LPBYTE buffer, LONG > count, LONG * pcb) > { > StreamInCookie * streamIn = (StreamInCookie *)cookie; > if(streamIn->remaining == 0) > { /* all done */ > *pcb = 0; > streamIn->err = ERROR_SUCCESS; > return 1; // nonzero value terminates read > } /* all done */ > > DWORD bytesToRead = min(streamIn->remaining, (DWORD)count); > > UINT bytesRead; > try { > bytesRead = streamIn->file->Read(buffer, bytesToRead); > } > catch(CFileException * e) > { /* catch */ > streamIn->err = e->m_lOsError; > e->Delete(); > streamIn->remaining = 0; > return 1; > } /* catch */ > > if(bytesRead == 0) > { /* read error */ > streamIn->err = ::GetLastError(); > *pcb = 0; > return 1; // return nonzero to stop operation > } /* read error */ > > streamIn->remaining -= bytesRead; > *pcb = bytesRead; > streamIn->err = ERROR_SUCCESS; > return 0; > } // CIndex::StreamInCallback > Joseph M. Newcomer [MVP] > email: newcomer(a)flounder.com > Web: http://www.flounder.com > MVP Tips: http://www.flounder.com/mvp_tips.htm -- HLS
From: Goran on 27 Apr 2010 10:30 On Apr 27, 4:20 pm, Hector Santos <sant9...(a)nospam.gmail.com> wrote: > The problem with the particular code you have is that it doesn't > support reading a RTF file saved as UNI-GARBAGE A.K.A UNICODE. > > It appears you need to convert each block read in into multi-byte > array. It works find when the RTF file is not stored in unicode. Indeed, my file was not stored as unicode. But how do I save it so? (Wordpad or swriter do not offer an easy option, e.g. something on the menu, or in "save as" dialog). Goran.
From: Hector Santos on 27 Apr 2010 10:56 Goran, I couldn't see how this resolves reading in an RTF unicode? I am not yet an UNI-GARBAGE expert, but I got it to work doing this (excluding the finer details of passing an object): In InitDialog() #define BOM L'\xFEFF' .... EDITSTREAM es = {0}; WCHAR ch; int n = eulaFile2.Read(&ch, sizeof(WCHAR)); if(n == 2 && ch == BOM) { es.pfnCallback = (EDITSTREAMCALLBACK)MyStreamInCallbackW; m_RECtrlW.StreamIn(SF_RTF | SF_UNICODE, es); } else { eulaFile2.SeekToBegin(); es.pfnCallback = (EDITSTREAMCALLBACK)MyStreamInCallback; m_RECtrlW.StreamIn(SF_RTF, es); } And the MyStreamInCallbackW() reads in cb amount and converts it a multi-byte array. Of course, this can all be done cleanly passing the cookie struct like you have it to 1 callback and have it do the initial BOM test, but I would like to know if converting it to a multi-byte array in the callback was correct or it is redundant based on the StreamIn() parameters (SF_RTF | SF_UNICODE), etc. -- Goran wrote: > Whoops! That went out sooner than I wanted... The above worked for > me, regardless of the file size, using unicode in project settings, > and using rtf in two rather distinctive languages. > > I don't know why you use OnCreate. It should work, your code, but... > DDX_Control in DoDataExchange is easier. > > Other than that, note a canonical form for any non-trivial > OnInitDialog is e.g: > > bool CXDialog::OnInitDialog() > { > CDialog::OnInitDialog(); > TRY > { > InitBabyInit(); > } > CATCH(CException* pe) > { > ReportErrorSomehow(pe); > EndDialog(PROBABLY_ID_CANCEL); // But it's up to you. > } > } > > It's a bad idea to let exception escape your callback (possible due to > use of MFC in it), so you should e.g. do: > > struct RichEditStreamInContext > { > RichEditStreamInContext(const CString& csFileName) : > m_File(csFileName, CFile::modeRead), m_pError(NULL) {} > > CFile m_File; > CException* m_pError; > }; > > static DWORD CALLBACK MyStreamInCallback(DWORD_PTR dwCookie, LPBYTE > pbBuff, LONG cb, LONG *pcb) > { > RichEditStreamInContext& Ctx = > *reinterpret_cast<RichEditStreamInContext*>(dwCookie); > try > { > *pcb = Ctx.m_File.Read(pbBuff, cb); > return 0; > } > catch (CException* p) > { > ASSERT(FALSE); > Ctx.m_pError = p; > return -1; > } > } > > > and in OnInitDialog: > > ... > RichEditStreamInContext Ctx(RTF_PATH_HERE); > EDITSTREAM es = { (DWORD_PTR)&Ctx, 0, &MyStreamInCallback }; > MCALLBACK)MyStreamInCallback; > m_Edit.StreamIn(SF_RTF, es); > if (es.dwError) > HandleError(Ctx); // Including looking at Ctx.m_pError; > > HTH, > > Goran. -- HLS
From: Hector Santos on 27 Apr 2010 11:08 Goran wrote: > On Apr 27, 4:20 pm, Hector Santos <sant9...(a)nospam.gmail.com> wrote: >> The problem with the particular code you have is that it doesn't >> support reading a RTF file saved as UNI-GARBAGE A.K.A UNICODE. >> >> It appears you need to convert each block read in into multi-byte >> array. It works find when the RTF file is not stored in unicode. > > Indeed, my file was not stored as unicode. But how do I save it so? > (Wordpad or swriter do not offer an easy option, e.g. something on the > menu, or in "save as" dialog). > > Goran. I tried Write.exe "save as" but it saves it as Unicode Code text removing the RTF stuff. I tried NotePad++ utility and it wasn't reading it right to begin with so I wrote a quicky "ConvertToUnicode.cpp" // File: G:\wc7beta\convert2unicode.cpp #include <stdio.h> #include <afx.h> BYTE bom[2] = {0xFF,0xFE}; void main(char argc, char *argv[]) { FILE *fvi = fopen("active-api.rtf","rt"); // ansi original FILE *fvo = fopen("active-api-unc.rtf","wb"); fwrite(bom,sizeof(bom),1,fvo); char sz[1024] = {0}; while (fgets(sz,sizeof(sz),fvi)) { for (int i = 0; i < strlen(sz); i++) { char szo[2] = {0}; szo[0] = sz[i]; fwrite(szo,sizeof(szo),1,fvo); } } fclose(fvo); fclose(fvi); } With this I was able to correctly read in active-api-unc.rtf but the callback has to convert each block it reads into a multi-byte array. I am not an expert yet with this unicrap but for the proof of concept: static DWORD CALLBACK MyStreamInCallbackW(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) { CFile* pFile = (CFile*)(DWORD_PTR)dwCookie; BYTE *p = new BYTE[cb]; ZeroMemory(p,cb); *pcb = pFile->Read(p, cb); int n = 0; for (int w = 0; w < *pcb;) { pbBuff[n] = p[w]; w += 2; n++; } delete p; TRACE("MyStreamInCallback: cb: %d | pcb: %d | n: %d\n", cb, *pcb, n); *pcb = n; return 0; } What I would like to figure out is what CRichEditCtrl expects for UNICODE with the SF_XXXXXX StreamIn() option. I also found a reference that about changing the control type in the resource file to "RichEdit20W" CONTROL "",IDC_RICHEDIT23,"RichEdit20W",ES_MULTILINE | ES_AUTOHSCROLL | WS_BORDER | WS_VSCROLL | WS_TABSTOP,7,161,456,95 But that didn't work to read it in without having to convert it myself. -- HLS
From: JY on 27 Apr 2010 11:20
Hi Joe, I've taken some snippets from what you'd posted and I still have the same problem as earlier. Only a truncated portion of the file appears in the CRichEditCtrl. Here's my latest code: class StreamInCookie { public: CFile *file; DWORD_PTR remaining; DWORD err; }; static DWORD CALLBACK EULAStreamInCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) { StreamInCookie * streamIn = (StreamInCookie *)dwCookie; if(streamIn->remaining == 0) { *pcb = 0; streamIn->err = ERROR_SUCCESS; return 1; // nonzero value terminates read } DWORD bytesToRead = min(streamIn->remaining, (DWORD)cb); UINT bytesRead; try { bytesRead = streamIn->file->Read(pbBuff, bytesToRead); } catch(CFileException * e) { streamIn->err = e->m_lOsError; e->Delete(); streamIn->remaining = 0; return 1; } if(bytesRead == 0) { streamIn->err = ::GetLastError(); *pcb = 0; return 1; // return nonzero to stop operation } streamIn->remaining -= bytesRead; *pcb = bytesRead; streamIn->err = ERROR_SUCCESS; return 0; } BOOL CREPage::OnInitDialog() { CBasePage::OnInitDialog(); if (m_strRTFFilePath.GetLength()) { m_RECtrl.ShowScrollBar(SB_VERT, TRUE); try { StreamInCookie streamIn; CFile eulaFile(m_strRTFFilePath, CFile::modeRead|CFile::shareExclusive); streamIn.file = &eulaFile; EDITSTREAM es; es.dwCookie = (DWORD_PTR)&streamIn; es.pfnCallback = (EDITSTREAMCALLBACK)EULAStreamInCallback; m_RECtrl.StreamIn(SF_RTF, es); } catch (CFileException *pEx) { //not shown for brevity } } return TRUE; } Thanks, JY |