Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Measuring height of a wordwrapped string

Status
Not open for further replies.

awaresoft

Programmer
Feb 16, 2002
373
DE
Any ideas on how I can measure the height of a word wrapped string (> 255 chars) that is wrapped within a given width? (For strings < 255 chars, one can measure the height of a label control with its .AutoSize property set to True).

I can use a form's .TextWidth() method to determine the width required to display a single line of text of a given font.

A form's .TextHeight() has no way to specify the width to use when wrapping text and so appears to be basically worthless.

Is there a Windows API, FoxTools function or some other technique I can use to determine the height of a word wrapped string displayed within a given width?

Thanks,
Malcolm
 

lcString = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"

*// Maximum character width in pixels =
lcFontName = ThisForm.Text1.FontName
lnFontSize = ThisForm.Text1.FontSize
lnTotalWidth = ThisForm.Text1.Width

nACW = FONTMETRIC(6,lcFontName , lnFontSize )

*// Total width of the string =
nStringLen = nACW * Len(lcString)

*// How many lines this string will need?
nLines = nStringLen / lnTotalWidth

X1 = FONTMETRIC(1, lcFontName , lnFontSize ) &&Character height in pixels
X2 = FONTMETRIC(2, lcFontName , lnFontSize ) &&Character ascent (units above baseline) in pixels
X3 = FONTMETRIC(3, lcFontName , lnFontSize ) &&Character descent (units below baseline) in pixels

*// The Height for each line
nLineH = X1 + X2 + X3
*// The Height of all lines =
nLinesH = nLines * nLineH

*// Calculate the Leading (space between lines) in pixels

nLeading = FONTMETRIC(4, lcFontName , lnFontSize )
nTotalLeading = nLeading * nLines
*// the final answer
nTotalH = nLinesH + nTotalLeading

ThisForm.Text1.Height = nTotalH
ThisForm.Text1.Value = lcString

This will answer your question but you should use Edit Box instead of Text Box and make it scrolable
ThisForm.EditBoxl.ScrollBars = 2 && Vertical

This will make all of these calculations automatically for you.

Good Luck

Walid Magd
Engwam@Hotmail.com
 
Walid,

Thanks for your help! Your code is cool and I will definitely find use for it, but it doesn't help me solve my problem.

I need to find the height of a string when its word wrapped within a specific width container. Your code doesn't take into account the effect of word wrapping and carriage return/linefeeds.

Another way to phrase my original problem would be to come up with a vertically autosizing editbox control (working just like an autosizing label) and the ability to determine when an editbox has more content than can be displayed in the editbox's visibile region.

I'm working on an dashboard type user interface where scrollbars in an editbox are distracting (and are turned off), but I still need to know if the editbox's content is completely displayed or not so I can provide another type of visual clue to the user that more info is present.

I also have a form where I would like to present "autosized" editboxes in a specific vertical order without having users have to scroll editboxes and without having these editboxes have unused vertical space at the end of their content.

Malcolm
 
Hi Malcolm,

the easiest would be to use a label with wordwrap =.t., autosize = .t. and then simply read out the height.

Something like that...

But it's rather limited to short texts.

Bye, Olaf.
 
Olaf,

Thanks - I can use an autosized label with word wrap to determine the veritical size of a word wrapped string.

My problem is calculating text height when a string is > 255 chars.

My thinking is that there should be some way to use an editbox to determine the vertical height of a large word wrapped string, but how? In other words, what tells me the total number of lines in an editbox?

Malcolm
 
For those of you following this thread there IS a Windows API function (DrawText) that provides this info.

The "magic" call courtesy of Anatoliy Mogylevet's news2news.com site (great site - highly recommended). This particular link is part of the publicly posted content - no passwords or subscription info required to access.


Missed it during my initial research. Thanks for everyone's help,

Malcolm
 
This is exactly what my code does. The code accounted for the word wrapping when I divided the string length (In Pixels) by the width of the text box itself.

lnTotalWidth = ThisForm.Text1.Width
*// How many lines this string will need?
nLines = nStringLen / lnTotalWidth

Just drop a text box control on a form (don’t rename it) and also drop a button on the form then
Copy and past the code in the click event of this button. Once you click this button, your text box will be resized vertically to fit the string. Just try the code.

If it is still don’t address your problem, I am so sorry that I missed the whole point.


Walid Magd
Engwam@Hotmail.com
 
Hi Walid,

Your code works - but only with short text values < 255 chars in length. Try it - your textbox automatically truncates values > 254 chars to 254 chars. Have you figured out a way to get textboxes (or labels) to work with captions/values > 254 chars?

I'm looking for a similar solution that works with Editbox, hence my comment about an "autosizing" editbox.

The DrawText() API call shows promise - but setting the font used for the DrawText() calculations is a complicated, multi-step process, and I'm not gettng consistent results.

So close yet so far!

Thanks again for your help!

Malcolm
 
For those who have been following this thread, I found the problem:

The VFP 8 (and 9) editbox does _NOT_ use the same word wrapping algorthm as:

- DrawText()/CreateFont() Win32 API's

- VFP's old "?" (output) command

- Using Fontmetric/Textwidth to manually calculate wordwrap and line breaks

All of the above produce _IDENTICAL_ output and text height results.

UNFORTUNATELY, when I compare the output produced by the above 3 techniques to the editbox's display of the same text/font constrained by the same display width, the VFP editbox word breaks SLIGHTLY differently, often times resulting in different overall text height requirements.

I posted my results to Calvin Hsia using his blog's contact form.

Conclusion: I can't see any way to implement an autosizing editbox given we have no way to predict how the VFP editbox will wrap a given string of text.

Any other ideas short of using an ActiveX component? (RTF, webbrowser control solutions not appropriate for my situation).

Thanks,
Malcolm
 
If you want an 'auto-sizing' text, I may just have the right stuff for you.
What I have used is the .Textwidth- and .Textheight-methods of the FORM (See the Help-file for more information). These functions use the FontName and FontSize of the form itself, so you will have to change the form-fontname and fontsize to that of the editbox in question. The trick to do this will follow below.

I also want to know the width of the textbox, which auto-adjusts to the length of the text. A longer text will result in a slightly wider text, but not too wide, otherwise you cannot read the text (there is an optimal nr of chars that you can read easyly).
Of course, you can change this to your liking.
Here's what you do :

1. Add an EditBox to the form with the following properties :
Borderstyle None
Scrollbars None
Enabled .F.
DisabledForeColor 0,0,0
Readonly .T.
Name EdtMessage

2. In the INIT, you write:
Code:
edtMessage.Value = tctext    && this is the text that you pass to the form; fill the editbox with the text
lnTxtHeight  = thisform.TxtDimensions(thisform.edtMessage, @lnTxtWidth)
*.. (perform calculations, and if necessary, adjust the memvars lnTxtHeight and / or TxtWidth)
thisform.edtMessage.Height = lnTxtHeight
thisform.edtMessage.Widtht = lnTxtWidth

2. Add a method to the form named TxtDimensions.
Add the following code :
Code:
* pass toEditBox as object !
* pass tnBoxWidth by reference !
LPARAMETERS toEditBox AS Object, tnBoxWidth AS Number
LOCAL lcTxt, lcCur_FontName, lnCur_FontSize, lnBoxHeight, a_linetxt, a_linewidth, lnNr, lnLineCnt, lnWrapCnt

WITH thisform
	lcCur_FontName = .FontName      && save current form-fontname and -size
	lnCur_FontSize = .Fontsize
	.FontName = toEditBox.FontName   && set the form-fontname to that of the editbox
	.FontSize = toEditBox.FontSize
ENDWITH

tnBoxWidth  = 0                                              && is passed back by reference
lnBoxHeight = 0
lcTxt       = toEditBox.Value      && the text in the editbox

IF NOT EMPTY(lcTxt)
	DIMENSION a_linetxt[1]                                     && necessary for local's
	lnLineCnt = ALINES(a_linetxt, lcTxt, CHR(13))              && nr of lines
	DIMENSION a_linewidth[lnLineCnt]                           && .textwidth of each text-segment

	lnTxtWidth = 100                                           && minimum textwidth (pixels)
	FOR lnNr = 1 TO lnLineCnt                                  && note length of each text-part
		a_linewidth[lnNr] = thisform.TextWidth(a_linetxt[lnNr])  && text-width of form-font  (=== box-font)
		lnTxtWidth = MAX(lnTxtWidth, a_linewidth[lnNr])          && maximum textwidth
	ENDFOR

	* determine the optimum box-width
	DO CASE
	CASE lnTxtWidth < 200
		tnBoxWidth  = 200                                        && minimum boxwidth (of editbox)
	CASE lnTxtWidth < 400
		tnBoxWidth = 250
	CASE lnTxtWidth < 700
		tnBoxWidth = 300
	CASE lnTxtWidth < 1400
		tnBoxWidth = 350
	CASE lnTxtWidth < 2100
		tnBoxWidth = 400
	CASE lnTxtWidth < 3500
		tnBoxWidth = 450
	OTHERWISE
		tnBoxWidth = 500                                       && maximum boxwidth
	ENDCASE

	lnWrapCnt = 0                                              && nr of word-wrap lines
	FOR lnNr = 1 TO lnLineCnt                                  && check the lines
		IF a_linewidth[lnNr] > tnBoxWidth                      && then wrap
			lnWrapCnt = lnWrapCnt + INT(a_linewidth[lnNr] / tnBoxWidth)  && 1 line was already there, so use INT()
		ENDIF
	ENDFOR
	lnBoxHeight = (lnLineCnt + lnWrapCnt) * (thisform.TextHeight('FOO') + 2) + 16      && this memvar will be returned via the reference-parameter passing
ENDIF
WITH thisform                                                  && reset font name and size
	.FontName = lcCur_FontName
	.FontSize = lnCur_FontSize
ENDWITH
RETURN lnBoxHeight

Note: it works, and you may use this part in one of your classes, but you may need to test and adjust a bit more when you use the bigger fonts. The method TextHeight does not always seem to be too reliable with larger fonts, so go ahead and check it out.

 
DoctorNix,

Wow! Thanks for that code. I implemented a similar technique mentioned earlier in this thread where I manually calculated word breaks using .TextWidth(). When I displayed my manually calculated wrapped text, it matched the same output generated by Windows DrawText()/CreateFont() API calls.

But when I compared these results to the way the editbox wraps text, I found that the editbox was word breaking slightly differently (oh, ever so slightly). In many font/fontsize/width scenarios the results were close enough that similar heights were generated. However, with other font/fontsize/width scenarios, the different wordwrapping strategies resulted in an extra +/- 2 or 3 lines difference in display height. (I took into consideration border widths and margins when calculating display width)

Mind you, this was all done using standard MS XP SP 2 OpenType and TrueType fonts (Arial, Times New Roman, Verdana) on a small font, 1024x767 generic display - with VFP themes support enabled and disabled.

Thanks again for your code. I'll give it a shot anyway!

Malcolm
 
Have you considered the number of carriage returns in the text ?
Some functions (.TextWidth including) just calculate the width of the text, without keeping track of the CR's.
That's why I intrioduced the ALINES, and the lnWrapCnt

Good luck!
 
DoctorNix,

Yes, I wrote my own parser that extracted space delimited words (preserving duplicate spaces), built each line of text using Textwidth, and treated CRLF's as blank lines.

This works in most cases - but not all. The longer your text value is in terms of word wrapped lines, the more likely your calculations (which will match those of the Windows API DrawText()/CreateFont() functions) will differ ever so slightly from the mysterious calculations used by the VFP editbox.

I believe the issue is with the VFP editbox, not your word wrap calculations.

Malcolm
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top