Hello Folks:
Developing in Win 8.1 Pro with Visual Studio 2010, WIN32, no MFC. The application uses multi-byte code, not UNICODE.
I want to be able to print rich text inside an arbitrary rectangle.
The text will come from a database, and not be associated with any rich text control.
I believe the first step is to get the text printed to an arbitrary rectangle in an arbitrary HDC.
So the sample I'm presenting attempts to display on the dialog's HDC when the user presses the "OK" button.
The code I've written to do this has a problem. A white block is displayed to the right of any text I attempt to display:
![]()
The top box is a rich text edit control, the bottom box is an arbitrary rectangle defined in the dialog's HDC after receiving the rich text.
I apologize for the amount of code that follows, but this will reproduce the problem, and might help you assist me:
Header file trailing_white_line_problem.h:
#ifndef TRAILING_WHITE_LINE_PROBLEM_H
#define TRAILING_WHITE_LINE_PROBLEM_H
#define DEFAULT_RICHEDIT_CLASS MSFTEDIT_CLASS
const int TWIPS_TO_POINTS = 20;
const COLORREF BACKGROUND_COLOR = RGB(0xD0, 0xD0, 0xD0);
const COLORREF FOREGROUND_COLOR = RGB(0x00, 0x00, 0x00);
bool text_has_a_graphic_char(const std::string *test_string_ptr);
bool text_has_a_graphic_char(const char *test_text);
void copy_rtf_to_hdc(HWND hdlg);
void setup_dialog(HWND hdlg);
INT_PTR CALLBACK trailing_white_dialog_box(HWND hdlg,
UINT message,
WPARAM wParam,
LPARAM lParam);
void rtf_to_hdc(HDC dest_hdc, std::string *rtf_string_ptr, RECT *destination_rect_ptr,
COLORREF background_color);
class RICH_TEXT_STREAM_STRING_REC
{
public:
RICH_TEXT_STREAM_STRING_REC(std::string *string_ptr_arg):
string_ptr(string_ptr_arg),
position(0)
{
}
RICH_TEXT_STREAM_STRING_REC(HWND rich_text_control_arg,
std::string *string_ptr_arg):
rich_text_control(rich_text_control_arg),
string_ptr(string_ptr_arg),
position(0)
{
}
void increment_position(LONG increment_arg)
{
if (this != NULL)
{
position += increment_arg;
}
}
void read(std::string *input_string_ptr)
{
EDITSTREAM edit_stream_struct;
if(this != NULL)
{
if(rich_text_control != NULL)
{
memset(&edit_stream_struct, 0x00, sizeof edit_stream_struct);
position = 0;
string_ptr = input_string_ptr;
if(string_ptr != NULL)
{
string_ptr->erase();
// edit_stream_struct.pfnCallback = rich_text_control_stream_write_callback;
edit_stream_struct.dwCookie = DWORD_PTR(this);
edit_stream_struct.pfnCallback =
&RICH_TEXT_STREAM_STRING_REC::rich_text_control_stream_read_callback;
SendMessage(rich_text_control, EM_STREAMOUT, SF_RTF, LPARAM(&edit_stream_struct));
}
}
}
}
void write(HWND rich_text_control)
{
EDITSTREAM edit_stream_struct;
if(this != NULL)
{
memset(&edit_stream_struct, 0x00, sizeof edit_stream_struct);
position = 0;
if(string_ptr != NULL)
{
// edit_stream_struct.pfnCallback = rich_text_control_stream_write_callback;
edit_stream_struct.dwCookie = DWORD_PTR(this);
edit_stream_struct.pfnCallback =
&RICH_TEXT_STREAM_STRING_REC::rich_text_control_stream_write_callback;
SendMessage(rich_text_control, EM_STREAMIN, SF_RTF, LPARAM(&edit_stream_struct));
}
}
}
static DWORD CALLBACK rich_text_control_stream_read_callback(DWORD_PTR dwCookie,
LPBYTE pbBuff,
LONG cb,
LONG *pcb)
{
DWORD return_val = 0; // Zero indicates success.
RICH_TEXT_STREAM_STRING_REC *stream_rec_ptr = (RICH_TEXT_STREAM_STRING_REC *)dwCookie;
std::string *local_string_ptr = stream_rec_ptr->string_ptr;
if(stream_rec_ptr != NULL)
{
local_string_ptr->append((const char *)pbBuff, cb);
stream_rec_ptr->increment_position(cb);
if(*pcb != NULL)
{
*pcb = cb;
}
}
return return_val;
}
// Make this function static, to allow assignment to a pointer.
static DWORD CALLBACK rich_text_control_stream_write_callback(DWORD_PTR dwCookie,
LPBYTE pbBuff,
LONG cb,
LONG *pcb)
{
DWORD return_val = 0; // Zero indicates success.
RICH_TEXT_STREAM_STRING_REC *stream_rec_ptr = (RICH_TEXT_STREAM_STRING_REC *)dwCookie;
std::string *local_string_ptr = stream_rec_ptr->string_ptr;
int local_position = stream_rec_ptr->position;
int char_to_write_count = 0;
if(stream_rec_ptr != NULL)
{
char_to_write_count = std::min(int(local_string_ptr->size() - local_position), int(cb));
strncpy((char*)pbBuff,
local_string_ptr->substr(local_position, char_to_write_count).c_str(),
char_to_write_count);
stream_rec_ptr->increment_position(char_to_write_count);
if(*pcb != NULL)
{
*pcb = char_to_write_count;
}
}
return return_val;
}
private:
std::string *string_ptr;
HWND rich_text_control;
LONG position;
};
#endif
Here is the C++ file:
#define NOMINMAX
#include <windows.h>
#include <Windowsx.h>
#include <Richedit.h>
#include "resource.h"
#include <iostream>
#include <string>
#include <algorithm>
#include "trailing_white_line_problem.h"
int WINAPI WinMain (HINSTANCE hinstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
DWORD error_code = 0;
LoadLibrary(TEXT("Msftedit.dll"));
SetLastError(0);
INT_PTR dialogbox_return = DialogBox(hinstance,
"IDD_SOURCE_RTF_DIALOG", NULL,
(DLGPROC)trailing_white_dialog_box);
if(dialogbox_return != 0)
{
error_code = GetLastError();
}
}
INT_PTR CALLBACK trailing_white_dialog_box(HWND hdlg,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
int test_x = 0;
switch (message)
{
case WM_INITDIALOG:
setup_dialog(hdlg);
return 0;
case WM_COMMAND:
switch(LOWORD (wParam))
{
case IDOK:
copy_rtf_to_hdc(hdlg);
return 0;
case IDCANCEL: // Needed for upper right "X" to function.
switch(HIWORD(wParam))
{
case BN_CLICKED:
SetLastError(0);
EndDialog(hdlg, true);
break;
}
return 0;
}
}
return 0;
}
void setup_dialog(HWND hdlg)
{
HWND edit_box_handle = GetDlgItem(hdlg, IDC_RTF_EDIT);
CHARFORMAT2 charformat_record;
memset(&charformat_record, 0x00, sizeof charformat_record);
charformat_record.cbSize = sizeof charformat_record;
LPARAM rich_text_edit_event_mask = ENM_CHANGE |
ENM_UPDATE |
ENM_SCROLL |
ENM_DRAGDROPDONE |
ENM_PARAGRAPHEXPANDED |
ENM_PAGECHANGE |
ENM_KEYEVENTS |
ENM_REQUESTRESIZE |
ENM_SELCHANGE |
ENM_DROPFILES |
ENM_PROTECTED |
ENM_CORRECTTEXT |
ENM_IMECHANGE |
ENM_LANGCHANGE |
ENM_OBJECTPOSITIONS |
ENM_LINK |
ENM_LOWFIRTF;
SendMessage(edit_box_handle, EM_SETBKGNDCOLOR, 0, BACKGROUND_COLOR);
// Enable the edit control to send notifications to parent dialog.
SendMessage(edit_box_handle, EM_SETEVENTMASK, 0, rich_text_edit_event_mask);
SendMessage(edit_box_handle, EM_SETBKGNDCOLOR, 0, BACKGROUND_COLOR);
// Enable the edit control to send notifications to parent dialog.
SendMessage(edit_box_handle, EM_SETEVENTMASK, 0, rich_text_edit_event_mask);
// From setup_coach_observation_char_format:
charformat_record.dwMask = CFM_ALL2;
charformat_record.yHeight = 10 * TWIPS_TO_POINTS;
charformat_record.crBackColor = BACKGROUND_COLOR;
charformat_record.crTextColor = FOREGROUND_COLOR;
SendMessage(edit_box_handle, EM_SETCHARFORMAT, SCF_ALL, LPARAM(&charformat_record));
}
void copy_rtf_to_hdc(HWND hdlg)
{
RECT rich_text_rect;
RECT button_rect;
std::string rtf_string;
HWND control_handle = GetDlgItem(hdlg, IDC_RTF_EDIT);;
RICH_TEXT_STREAM_STRING_REC stream_rec(control_handle, &rtf_string);
TEXTMETRIC tm;
HDC hdc = GetDC(hdlg);;
static int char_height;
int display_rect_height = 0;
if(char_height == 0)
{
GetTextMetrics(hdc, &tm);
char_height = tm.tmHeight;
}
// Define the rectangle that will receive the rich text.
// Make this the same width as the edit rectangle, and
// fit it between the edit rectangle and the "OK" button.
// control_handle = GetDlgItem(hdlg, IDC_RTF_EDIT);
GetWindowRect(control_handle, &rich_text_rect);
control_handle = GetDlgItem(hdlg, IDOK);
GetWindowRect(control_handle, &button_rect);
display_rect_height =
(button_rect.top - rich_text_rect.bottom) - char_height;
MapWindowRect(NULL, hdlg, &rich_text_rect);
// Move the rect.
rich_text_rect.top = rich_text_rect.bottom + (char_height >> 1);
rich_text_rect.bottom = rich_text_rect.top + display_rect_height;
stream_rec.read(&rtf_string);
rtf_to_hdc(hdc, &rtf_string, &rich_text_rect, BACKGROUND_COLOR);
ReleaseDC(hdlg, hdc);
}
void rtf_to_hdc(HDC dest_hdc, std::string *rtf_string_ptr, RECT *destination_rect_ptr,
COLORREF background_color)
{
HWND hdc_window = WindowFromDC(dest_hdc);
HINSTANCE hinstance = HINSTANCE(GetWindowLongPtr(hdc_window, GWLP_HINSTANCE));
HWND invisible_rich_text_control = NULL;
int cxPhys = GetDeviceCaps(dest_hdc, PHYSICALWIDTH);
int cyPhys = GetDeviceCaps(dest_hdc, PHYSICALHEIGHT);
int invisible_control_width = 0;
int invisible_control_height = 0;
FORMATRANGE format_range;
double pixels_to_twips_x = 0.0;
double pixels_to_twips_y = 0.0;
RICH_TEXT_STREAM_STRING_REC stream_rec(rtf_string_ptr);
HFONT current_font = HFONT(GetCurrentObject(dest_hdc, OBJ_FONT));
HDC invisible_control_hdc = NULL;
RECT invisible_control_rect;
HBRUSH background_brush = CreateSolidBrush(background_color);
SelectObject(dest_hdc, background_brush);
if((cxPhys == 0) || (cyPhys == 0))
{
// PHYSICALWIDTH and PHYSICALHEIGHT arguments are for printing devices.
cxPhys = GetDeviceCaps(dest_hdc, HORZRES);
cyPhys = GetDeviceCaps(dest_hdc, VERTRES);
}
if(text_has_a_graphic_char(rtf_string_ptr) && (dest_hdc != NULL) && (destination_rect_ptr != NULL))
{
invisible_control_width = destination_rect_ptr->right - destination_rect_ptr->left;
invisible_control_height = destination_rect_ptr->bottom - destination_rect_ptr->top;
if((invisible_control_width > 0) && (invisible_control_height > 0))
{
memset(&format_range, 0x00, sizeof format_range);
format_range.hdc = dest_hdc;
format_range.hdcTarget = dest_hdc;
// Set page rect to physical page size in twips.
format_range.rcPage.top = 0;
format_range.rcPage.left = 0;
format_range.rcPage.right = MulDiv(cxPhys, 1440, GetDeviceCaps(dest_hdc, LOGPIXELSX));
format_range.rcPage.bottom = MulDiv(cyPhys, 1440, GetDeviceCaps(dest_hdc, LOGPIXELSY));
// Set the rendering rectangle to the pintable area of the page.
format_range.rc = format_range.rcPage;
// This is going to be a bit clumsy until I understand what's going on.
// I'm going to caculate the multiplier to convert pixels to twips.
pixels_to_twips_x = double(format_range.rcPage.right) / double(cxPhys);
pixels_to_twips_y = double(format_range.rcPage.bottom) / double(cyPhys);
format_range.rc.left = MulDiv(destination_rect_ptr->left, 1440, GetDeviceCaps(dest_hdc, LOGPIXELSX));
format_range.rc.top = MulDiv(destination_rect_ptr->top, 1440, GetDeviceCaps(dest_hdc, LOGPIXELSY));
format_range.rc.right = MulDiv(destination_rect_ptr->right, 1440, GetDeviceCaps(dest_hdc, LOGPIXELSX));
format_range.rc.bottom = MulDiv(destination_rect_ptr->bottom, 1440, GetDeviceCaps(dest_hdc, LOGPIXELSY));
invisible_rich_text_control = CreateWindowExW(0,
DEFAULT_RICHEDIT_CLASS,
L"",
ES_MULTILINE |
ES_READONLY,
destination_rect_ptr->left,
destination_rect_ptr->top,
invisible_control_width,
invisible_control_height,
NULL, // hdlg,
NULL, // Menu
hinstance,
0);
// The invisible conrol has the same width as the destination rectangle.
SendMessage(invisible_rich_text_control, EM_SETTARGETDEVICE, (WPARAM)dest_hdc,
invisible_control_width);
invisible_control_rect.left = destination_rect_ptr->left;
invisible_control_rect.top = destination_rect_ptr->top;
invisible_control_rect.right = destination_rect_ptr->left + invisible_control_width;
invisible_control_rect.bottom = destination_rect_ptr->top + invisible_control_height;
// This is new code to try to force the background color.
invisible_control_hdc = GetDC(invisible_rich_text_control);
SetBkMode(invisible_control_hdc, TRANSPARENT);
ReleaseDC(invisible_rich_text_control, invisible_control_hdc);
stream_rec.write(invisible_rich_text_control);
// Select the entire contents.
SendMessage(invisible_rich_text_control, EM_SETSEL, 0, (LPARAM)-1);
// Get the selection into a CHARRANGE.
SendMessage(invisible_rich_text_control, EM_EXGETSEL, 0, (LPARAM)&format_range.chrg);
FillRect(dest_hdc, destination_rect_ptr, background_brush);
int cpMin = SendMessage(invisible_rich_text_control, EM_FORMATRANGE, TRUE, (LPARAM)&format_range);
if (cpMin <= format_range.chrg.cpMin)
{
// fSuccess = FALSE;
}
// This must be called to prevent a memory leak.
SendMessage(invisible_rich_text_control, EM_FORMATRANGE, FALSE, 0);
DeleteObject(background_brush);
DestroyWindow(invisible_rich_text_control);
// Restore the HDC's font.
SelectObject(dest_hdc, current_font);
}
}
}
bool text_has_a_graphic_char(const std::string *test_string_ptr)
{
bool return_val = false;
if(test_string_ptr != NULL)
{
return_val = text_has_a_graphic_char(test_string_ptr->c_str());
}
return return_val;
}
bool text_has_a_graphic_char(const char *test_text)
{
bool graphic_char_found = false;
int index = 0;
int text_length = 0;
if(test_text != NULL)
{
text_length = (int)strlen(test_text);
while((index < text_length) && !graphic_char_found)
{
graphic_char_found |= (isascii(test_text[index]) && (isgraph(test_text[index]) != 0));++index;
}
}
return graphic_char_found;
}
Here is the .rc file
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""afxres.h""\r\n""\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n""\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_SOURCE_RTF_DIALOG DIALOGEX 0, 0, 314, 271
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,205,250,50,14
PUSHBUTTON "Cancel",IDCANCEL,257,250,50,14
CONTROL "Here is some sample\r\ntext",IDC_RTF_EDIT,"RICHEDIT50W",WS_VSCROLL | 0x9144,7,7,300,116
END
/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
"IDD_SOURCE_RTF_DIALOG", DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 307
TOPMARGIN, 7
BOTTOMMARGIN, 264
END
END
#endif // APSTUDIO_INVOKED
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
I'm sorry to dump almost 500 lines of C++ on you, and I understand if nobody responds. But this is actually the smallest I was able to distill the code that demonstrates the problem.
In addition to just getting this code to work, if you see other faults with this code, or ways that it can be made more efficient, please let me know.
Thanks to anybody who decides to help me.
Larry