I have a TForm and TRichEdit inside it (alClient, no borders).
I need to adjust the width & height of the TForm depending on
the TRichEdit contents.
Ok. I have the code which calculates the accurate WIDTH and
also gets into account the possibility of TRichEdit to have different
formating, like BOLD, etc.
But I made some overlook in the part which calculates the
HEIGHT...
In fact I used a *constant* 14, for Arial of size 8. (Yes, I know
that's a bad idea :) That worked fine untill I tried the program on
a PC on which some standard Windows view settings were changed.
I don't know what was exactly changed but I noticed HIGH screen
resolution with BIG caption bar, and BIG minimize, maximize, close
gadgets.
Can you please tell me:
1) What can I do to avoid that Windows settings affect my app?
(What is the property Scaled really for?)
2) Which of the three routines below should I use to calculate
the hight in my case, and why the Button1 & Button3 returns
the result of 14, and the Button2 the result of 13 (the both
TForm & TRichEdit hah the Arial size 10).
// 14
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ShowMessage( Canvas->TextHeight( "Wg" ) );
}
// 13
void __fastcall TForm1::Button2Click(TObject *Sender)
{
TControlCanvas *pCanvas = NULL;
int result;
try
{
pCanvas = new TControlCanvas();
pCanvas->Control = RichEdit1;
result = pCanvas->TextHeight( "Wg" );
}
catch ( const Exception &e )
{
// ...
}
if ( pCanvas )
delete pCanvas;
ShowMessage( result );
}
// 14
void __fastcall TForm1::Button3Click(TObject *Sender)
{
HFONT OldFont;
HDC DC;
TTextMetric* Tm = new TTextMetric;
DC = GetDC( RichEdit1->Handle );
OldFont = SelectObject( DC, RichEdit1->Font->Handle );
GetTextMetrics( DC, Tm );
int result = Tm->tmHeight;
SelectObject( DC, OldFont );
ReleaseDC( RichEdit1->Handle, DC );
delete Tm;
ShowMessage( result );
}
--
Best regards,
Vladimir Stefanovic
> I don't know what was exactly changed but I noticed HIGH
> screen resolution with BIG caption bar, and BIG minimize,
> maximize, close gadgets.
The changed *thing* was DPI resolution, which was changed
from 96 to 120.
An now, I'm more confused than before:
When I change the resolution from 96 to 120, and reopen
my project, I can see that TRichEdit::Font::Size was changed in
my IDE from 8 to 7 (?!).
Then, when I change the resolution again from 120 to 96, and
reopen my project, I can see that TRichEdit::Font::Size was changed
in my IDE from 7 to 8 (?!).
The TRichEdit::Font::Height is the same: -11.
In the BCB Help, I was found this formula:
Font.Size = - Font.Height * 72 / Font.PixelsPerInch
...which can explane how FontSize changed:
96 DPI: ( -11 * 72 ) / 96 = 8.25
120 DPI: ( -11 * 72 ) / 96 = 6.6
Does that mean i have to calculate the desired font height based
on DPI to fin the font size?
120 DPI: ( -11 * 72 ) / 120 = 6.6
> Ok. I have the code which calculates the accurate WIDTH
> and also gets into account the possibility of TRichEdit to have
> different formating, like BOLD, etc.
>
> But I made some overlook in the part which calculates the
> HEIGHT...
An alternative way to calculate the dimensions of the TRichEdit content is
to not involve any Canvas at all. Use the EM_GETLINECOUNT, EM_LINEINDEX,
EM_LINELENGTH, and EM_POSFROMCHAR messages instead. Given particular
character indexes, you can calculate absolute pixel offsets for any
character in the content, regardless of the font, style, dpi, etc.
For example (untested):
int lines = RichEdit1->Perform(EM_GETLINECOUNT, 0, 0);
if( lines > 0 )
{
POINTL pt;
int idx, len;
int width = 0;
int height = 0;
for(int x = 0; x < lines; ++x)
{
idx = RichEdit1->Perform(EM_LINEINDEX, x, 0);
len = RichEdit1->Perform(EM_LINELENGTH, idx, 0);
RichEdit1->Perform(EM_POSFROMCHAR, (WPARAM)&pt; idx+len);
if( width < pt.x )
width = pt.x;
}
for(int x = 0; x < len; ++x)
{
RichEdit1->Perform(EM_POSFROMCHAR, (WPARAM)&pt; idx+x);
if( height < pt.y )
height = pt.y;
}
RichEdit1->Width = (width + 5);
RichEdit1->Height = (height + 5);
}
Gambit
> [CODE]
Thank you... I'll try the code...
> for(int x = 0; x < len; ++x)
I'v chaged the existing part of code like this, and I hope
it's OK now... (there were also some typos ; -> ,)
for(int x = 0; x < lines; ++x)
Thanks...
> The code finds width OK, but the height remains 5.
I forgot that EM_POSFROMCHAR returns the coordinates of the top-level
corner of a character. Use EM_GETCHARFORMAT to get the width and height of
a character as well. For example (again, untested):
int num_lines = RichEdit1->Perform(EM_GETLINECOUNT, 0, 0);
if( num_lines > 0 )
{
POINTL pt;
int line_idx, line_len;
int width = 0;
int height = 0;
CHARFORMAT fmt;
CHARRANGE cr;
// these are so that changes to the current selection later
// on will not be displayed onscreen while looping...
RichEdit1->Perform(WM_SETREDRAW, FALSE, 0);
int oldmask = RichEdit1->Perform(EM_SETEVENTMASK, 0, 0);
try
{
for(int x = 0; x < num_lines; ++x)
{
line_idx = RichEdit1->Perform(EM_LINEINDEX, x, 0);
line_len = RichEdit1->Perform(EM_LINELENGTH, line_idx, 0);
RichEdit1->Perform(EM_POSFROMCHAR, (int)&pt; line_idx +
line_len);
if( width < pt.x )
width = pt.x;
}
// line_idx remains pointing to the last line at this point...
for(int x = 0; x < line_len; ++x)
{
memset(&fmt, 0, sizeof(fmt);
fmt.cbSize = sizeof(fmt);
fmt.dwMask = CFM_SIZE;
cr.cpMin = (line_idx + x);
cr.cpMax = (cr.cpMin + 1);
RichEdit1->Perform(EM_EXSETSEL, 0, (int)&cr);
RichEdit1->Perform(EM_GETCHARFORMAT, TRUE, (int)&fmt);
RichEdit1->Perform(EM_POSFROMCHAR, (int)&pt; cr.cpMin);
if( height < (pt.y + fmt.yHeight) )
height = (pt.y + fmt.yHeight);
}
RichEdit1->Width = (width + 5);
RichEdit1->Height = (height + 5);
}
__finally
{
RichEdit1->Perform(EM_SETEVENTMASK, 0, oldmask);
RichEdit1->Perform(WM_SETREDRAW, TRUE, 0);
RichEdit1->Invalidate();
}
}
> I'v chaged the existing part of code like this, and I
> hope it's OK now... (there were also some typos ; -> ,)
>
> for(int x = 0; x < lines; ++x)
That was not the correct thing to do with that loop. I was intentionally
looping through the characters of the last line only, not looping through
all of the lines a second time. I was attempting to find the largest
character in the last line, as that would dicate the height of the RichEdit.
Gambit
I tried everything but still couldn't get the proper HEIGHT.
> // line_idx remains pointing to the last line at this point...
There is a (possible) bug here, because 'line_len' is always 0
after last iteration (?!), and so the loop is unreachable.
> for(int x = 0; x < line_len; ++x)
> {
> memset(&fmt, 0, sizeof(fmt);
> fmt.cbSize = sizeof(fmt);
> fmt.dwMask = CFM_SIZE;
>
I also tried to hardoce the line_len, but still there is something
wrong in the code in this (second) loop.
// qstxt is a RichEdit control
int heti; TControlCanvas *cCanvas; RECT r;
cCanvas=new TControlCanvas(); cCanvas->Font->Assign(qstxt->Font);
cCanvas->Control=qstxt; r.right=qstxt->Width-25; r.left=2;
heti=DrawText(cCanvas->Handle,qstxt->Text.c_str(),-1,&r,
DT_CALCRECT|DT_WORDBREAK);
delete cCanvas;
// heti now contains the height
--
Mark Jacobs
http://www.dkcomputing.co.uk
> I would use the DrawText method like this :-
That won't take the RichEdit's formatting into account at all.
Gambit
> There is a (possible) bug here, because 'line_len' is always 0 after last
iteration (?!)
The only way that can happen is if there is a blank line at the end of your
text.
> I also tried to hardoce the line_len, but still there is something wrong
> in the code in this (second) loop.
Can you be a little more specific?
Gambit
One of the problems is:
RichEdit1->Perform(EM_GETLINECOUNT, 0, 0);
which returns the undesired lines count. I've elaborated
that in the code below.
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
// make it small because of testing
RichEdit1->Width=60;
RichEdit1->Height=60;
// clear the default (RichEdit1) contents
RichEdit1->Lines->Clear();
// remarks "x : y" are "line_idx : line_len" by the ShowMessage() in the
loop
// 0:14
RichEdit1->Lines->Add("1234 567890123");
// 16:32
RichEdit1->Lines->Add("1234 567890123 45678901234567890");
// 50:14, 66:14, 82:14, 98:14, 114:14
for(int x = 0; x<5; ++x)
RichEdit1->Lines->Add("1234 567890123");
//130:18
RichEdit1->Lines->Add("1234 567890123 456");
// 150:0 (???)
// This is because RichEdit1->Perform(EM_GETLINECOUNT, 0, 0);
// returns 9 !
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
int num_lines = RichEdit1->Perform(EM_GETLINECOUNT, 0, 0);
// 9 (???)
ShowMessage ( num_lines );
// 8
ShowMessage ( RichEdit1->Lines->Count );
// num_lines = 8;
// even if I hardoce this to 8
// the HEIGHT is not correct. It has much bigger value (maybe doubled)
if( num_lines > 0 )
{
POINTL pt;
int line_idx, line_len;
int width = 0;
int height = 0;
CHARFORMAT fmt;
CHARRANGE cr;
for(int x = 0; x < num_lines; ++x)
{
line_idx = RichEdit1->Perform(EM_LINEINDEX, x, 0);
line_len = RichEdit1->Perform(EM_LINELENGTH, line_idx, 0);
RichEdit1->Perform(EM_POSFROMCHAR, (int)&pt, line_idx +
line_len);
if( width < pt.x )
width = pt.x;
// test
ShowMessage( "line_idx="+IntToStr(line_idx)+",
line_len="+IntToStr(line_len) );
}
// Gambit: line_idx remains pointing to the last line at this
point...
for(int x = 0; x < line_len; ++x)
{
memset(&fmt, 0, sizeof(fmt) );
fmt.cbSize = sizeof(fmt);
fmt.dwMask = CFM_SIZE;
cr.cpMin = (line_idx + x);
cr.cpMax = (cr.cpMin + 1);
RichEdit1->Perform(EM_EXSETSEL, 0, (int)&cr);
RichEdit1->Perform(EM_GETCHARFORMAT, TRUE, (int)&fmt);
RichEdit1->Perform(EM_POSFROMCHAR, (int)&pt, cr.cpMin);
if( height < (pt.y + fmt.yHeight) )
height = (pt.y + fmt.yHeight);
}
RichEdit1->Width = (width + 5);
RichEdit1->Height = (height + 5);
}
}
Also, why did you abandon the approach:
for(int x = 0; x < len; ++x)
{
RichEdit1->Perform(EM_POSFROMCHAR, (WPARAM)&pt; idx+x);
if( height < pt.y )
height = pt.y;
}
which seem to worked fine?
Sorry for bothering you with this so muh time.
> RichEdit1->Perform(EM_GETLINECOUNT, 0, 0);
> which returns the undesired lines count.
The VCL gets around that issue by decrementing the line count if the last
line has a length of 0, ie:
int line_count = RichEdit1->Perform(EM_GETLINECOUNT, 0, 0);
if( RichEdit1->Perform(EM_LINELENGTH, RichEdit1->Perform(EM_LINEINDEX,
line_count-1, 0), 0) == 0 )
--line_count;
> Also, why did you abandon the approach:
>
> for(int x = 0; x < len; ++x)
> {
> RichEdit1->Perform(EM_POSFROMCHAR, (WPARAM)&pt; idx+x);
> if( height < pt.y )
> height = pt.y;
> }
>
> which seem to worked fine?
Because it was not accurate to begin with. EM_POSFROMCHAR returns the pixel
offset of the top-left corner of a character. I incorrectly assumed that
the y offset would include the actual height of the character, which it does
not. EM_GETCHARFORMAT, on the other hand, can return the actual height of a
character, which I then add to the top-left offset of the character.
Gambit
The last block of code which calculates the height still does
not work accutare. Do you see what could be wrong with it?
With the changes I made to determine num_lines, the code
calculates the height in much size more than it should...
for(int x = 0; x < line_len; ++x)
{
memset(&fmt, 0, sizeof(fmt) );
fmt.cbSize = sizeof(fmt);
fmt.dwMask = CFM_SIZE;
cr.cpMin = (line_idx + x);
cr.cpMax = (cr.cpMin + 1);
RichEdit1->Perform(EM_EXSETSEL, 0, (int)&cr);
RichEdit1->Perform(EM_GETCHARFORMAT, TRUE, (int)&fmt);
RichEdit1->Perform(EM_POSFROMCHAR, (int)&pt, cr.cpMin);
if( height < (pt.y + fmt.yHeight) )
height = (pt.y + fmt.yHeight);
}
--
Best regards,
Vladimir Stefanovic
"Vladimir Stefanovic" <anti...@po.sbb.co.yu> wrote in message
news:4396...@newsgroups.borland.com...
> The last block of code which calculates the height still does
> not work accutare. Do you see what could be wrong with it?
It does not convert the yHeight value. The documentation for the CHARFORMAT
structure does not state the unit of measurement that is used. It turns out
that the yHeight (and yOffset) value is in twips, not pixels. So you have
to convert the value to pixels manually. With that in mind, the following
code gets much closer to the truer sizes for me:
int num_lines = RichEdit1->Perform(EM_GETLINECOUNT, 0, 0);
if( num_lines > 0 )
{
if( RichEdit1->Perform(EM_LINELENGTH,
RichEdit1->Perform(EM_LINEINDEX, num_lines-1, 0), 0) == 0 )
--num_lines;
}
if( num_lines > 0 )
{
// this is so that changes to the current selection later
// on will not be displayed onscreen while looping...
RichEdit1->Perform(WM_SETREDRAW, FALSE, 0);
try
{
// this is to speed up access to the RichEdit by disabling
notifications...
int oldmask = RichEdit1->Perform(EM_SETEVENTMASK, 0, 0);
try
{
// this is to preserve the current selection so that it can
be restored later...
CHARRANGE oldrange = {0};
RichEdit1->Perform(EM_EXGETSEL, 0, (int)&oldrange);
try
{
POINTL pt;
int line_idx, line_len;
int width = 0;
int height = 0;
int char_height;
CHARFORMAT fmt;
CHARRANGE cr;
// this is used in the twips->pixels conversion
HDC hdc = GetDC(RichEdit1->Handle);
int LogicalPixelsY = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(RichEdit1->Handle, hdc);
for(int x = 0; x < num_lines; ++x)
{
line_idx = RichEdit1->Perform(EM_LINEINDEX, x, 0);
line_len = RichEdit1->Perform(EM_LINELENGTH,
line_idx, 0);
RichEdit1->Perform(EM_POSFROMCHAR, (int)&pt,
line_idx + line_len);
if( width < pt.x )
width = pt.x;
}
// line_idx remains pointing to the last line at this
point...
for(int x = 0; x < line_len; ++x)
{
memset(&fmt, 0, sizeof(fmt));
fmt.cbSize = sizeof(fmt);
fmt.dwMask = CFM_SIZE;
cr.cpMin = (line_idx + x);
cr.cpMax = (cr.cpMin + 1);
RichEdit1->Perform(EM_EXSETSEL, 0, (int)&cr);
RichEdit1->Perform(EM_GETCHARFORMAT, SCF_SELECTION,
(int)&fmt);
RichEdit1->Perform(EM_POSFROMCHAR, (int)&pt,
cr.cpMin);
// divide by 20 to convert twips to points, and
// then use MulDiv() to convert points to pixels...
char_height = MulDiv(fmt.yHeight / 20,
LogicalPixelsY, 72);
if( height < (pt.y + char_height) )
height = (pt.y + char_height);
}
// RichEdit1->Width = (width + 5);
// RichEdit1->Height = (height + 5);
ShowMessage(AnsiString(width) + "," +
AnsiString(height));
}
__finally
{
RichEdit1->Perform(EM_EXSETSEL, 0, (int)&oldrange);
}
}
__finally
{
RichEdit1->Perform(EM_SETEVENTMASK, 0, oldmask);
}
}
__finally
{
RichEdit1->Perform(WM_SETREDRAW, TRUE, 0);
RichEdit1->Invalidate();
}
}
The only problem left is that the lowercase 'g' and 'y' characters are still
not calculated properly. The pixel offset of the top-left corner is correct
but the height reported by EM_GETCHARFORMAT seems to be missing some extra
spacing that actually exists in those characters. I don't have a solution
for that.
Gambit
I'm not so affected by problems with y&g&j characters because
I'm not going to use LARGE fonts where such deviations can be
too obvious.
Thanks again.