Textbox line property

  • Thread starter Thread starter Miro
  • Start date Start date
M

Miro

I am looking for something similar to the textbox "lines" property... (
http://msdn.microsoft.com/en-us/library/system.windows.forms.textboxbase.lines(VS.85).aspx
)

but what I am looking to do is to be able to grab each "Line" out of a
multiline as it is "displayed".

Example:
Create a textbox multiline option true, and set it to like 25 characters
wide...
This whole line will print on one line <enter key>
This line will print on the other line but as i am continueing to type it
will automatically word wrap it on the screen.


That second line, I want to grab as 2 lines out.

Basically if you have a "super long string" and would like to parse it out
to word wrap just like the multiline text box.
Or do i have to write my own little for next loop to grab the words and
parse my new string?

I do not have a problem in setting my texbox.enabled = false, throw my long
string into it, as long as i can loop through each line and pull it out 1 by
one. ( as it is displayed ).



Thanks,

Miro
 
Miro said:
I am looking for something similar to the textbox "lines" property...
(
http://msdn.microsoft.com/en-us/library/system.windows.forms.textboxbase.lines(VS.85).aspx
)
but what I am looking to do is to be able to grab each "Line" out of a
multiline as it is "displayed".

Example:
Create a textbox multiline option true, and set it to like 25
characters wide...
This whole line will print on one line <enter key>
This line will print on the other line but as i am continueing to
type it will automatically word wrap it on the screen.


That second line, I want to grab as 2 lines out.

Basically if you have a "super long string" and would like to parse
it out to word wrap just like the multiline text box.
Or do i have to write my own little for next loop to grab the words
and parse my new string?

I do not have a problem in setting my texbox.enabled = false, throw
my long string into it, as long as i can loop through each line and
pull it out 1 by one. ( as it is displayed ).


Well, what you have after displaying a String are not lines but an image. So
you can only get the size (in pixels) of the text, for example by using
graphics.measurestring and derive the number of lines from the height. You'd
have to immitate the Textbox' behavior exactly. However, then you only have
the number of lines but you don't have a list of strings as displayed in the
textbox. Therefore the approach you mentioned is probably the only way to
go; maybe optimized by using a binary approach (like the binary search
algorithm).


Armin
 
I think you will need to write your own separate function to do this.
Because the place where a line breaks is based on the font and/or browser,
your function will need some way to get the current font info from the
browser. This is not always possible, since JavaScript cannot always tell
you which font is actually being used, so you may need to look into this. By
best suggestion for your situation would probably be to use a standard
fixed-width that all browsers support and then specify the width in chars.
This will allow you to determine the breaks by looking at where characters
such as spaces and other characters that might be wrapping points. Idon't
know what else to say, except that it sounds like a very strange task you
are taking on.
 
Well, what you have after displaying a String are not lines
but an image. So you can only get the size (in pixels) of the
text, for example by using graphics.measurestring and
derive the number of lines from the height. You'd have to
immitate the Textbox' behavior exactly . . .

What's wrong with simply using SendMessage to send an EM_GETLINECOUNT
message to the TextBox to get the total number of lines (as wrapped by the
TextBox) and follow that with the appropriate number of EM_GETLINE messages
to get the individual lines into a buffer. That should get the lines of text
exactly as the TextBox has wrapped them, regardless of the facename or size
of the font used. It's what I would do in VB6. Or have I misunderstood the
question?

Mike
 
Hi Mike,

I vb.net skill is above a newbie, and would probably put myself into a 'good
beginner' but I am unfamiliar with EM_GETLINECOUNT

I went a different route and created my own function to parse out the text
and word wrap based on an X amount of characters per line, incorporating
into it the enterkey, linefeeds, and tabs.

Miro
 
Mike said:
What's wrong with simply using SendMessage to send an EM_GETLINECOUNT
message to the TextBox to get the total number of lines (as wrapped
by the TextBox) and follow that with the appropriate number of
EM_GETLINE messages to get the individual lines into a buffer. That
should get the lines of text exactly as the TextBox has wrapped them,
regardless of the facename or size of the font used. It's what I
would do in VB6. Or have I misunderstood the question?

Nothing wrong. Just didn't know it. But is there also a way to get each of
these lines? EM_GETLINE?


Armin
 
Nothing wrong. Just didn't know it. But is there also a
way to get each of these lines? EM_GETLINE?

Yes. Sending the EM_GETLINECOUNT message to the TextBox returns the total
number of lines of text as they have been wrapped by the TextBox. Then if,
for example, the returned value is 15 you would send the EM_GETLINE message
15 times in a loop, which will cause each EM_GETLINE message to return one
of the 15 lines into a buffer. Typically you would save each returned string
into an element of a String array (or whatever) and then get the next line
into the same buffer, or alternatively you could get each of the 15 lines
into 15 separate buffers. Here's the MSDN stuff relating to the two
messages:

http://msdn.microsoft.com/en-us/library/bb761586(VS.85).aspx
http://msdn.microsoft.com/en-us/library/bb761584(VS.85).aspx

Mike
 
What's wrong with simply using SendMessage to send an EM_GETLINECOUNT
message to the TextBox to get the total number of lines (as wrapped by the
TextBox) and follow that with the appropriate number of EM_GETLINE messages
to get the individual lines into a buffer. That should get the lines of text
exactly as the TextBox has wrapped them, regardless of the facename or size
of the font used. It's what I would do in VB6. Or have I misunderstood the
question?

Mike

Good suggestion. Not quite as easy as that - since you want to know the
line length but close. Here it is in VB.NET:

Public Class Form1

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If (TextBox1.Text.Length > 0) Then
Dim lineCount As Integer = SendMessage(TextBox1.Handle, EM_GETLINECOUNT, 0, 0)
For i As Integer = 1 To lineCount
Dim firstCharIndex As Integer = SendMessage(TextBox1.Handle, EM_LINEINDEX, i - 1, 0)
Dim lineLength = SendMessage(TextBox1.Handle, EM_LINELENGTH, firstCharIndex, 0)
Dim chars(lineLength) As Char

' set first char to the line length (for unicode)
' it will be overwritten by the function
chars(0) = Convert.ToChar(lineLength)
SendMessage(TextBox1.Handle, EM_GETLINE, i - 1, chars)
MessageBox.Show(New String(chars))
Next
Else
MessageBox.Show("No Text")
End If
End Sub

Private Const EM_GETLINECOUNT As Integer = &HBA
Private Const EM_LINEINDEX As Integer = &HBB
Private Const EM_LINELENGTH As Integer = &HC1
Private Const EM_GETLINE As Integer = &HC4

Private Declare Auto Function SendMessage Lib "user32" ( _
ByVal hWnd As IntPtr, _
ByVal m As Integer, _
ByVal wParam As Integer, _
ByVal lParam As Integer) As Integer

Private Declare Auto Function SendMessage Lib "user32" ( _
ByVal hWnd As IntPtr, _
ByVal m As Integer, _
ByVal wParam As Integer, _
ByVal lParam As Char()) As Integer
End Class
 
I've followed this ng closely for quite a while and I don't recall every
seeing before this concept of sending a message to a control. Can you say
more about it? Where are the messages supported by a .NET control
documented?

Thanks, Bob
 
eBob.com said:
I've followed this ng closely for quite a while and I don't recall
every seeing before this concept of sending a message to a control. Can
you say more about it? Where are the messages supported by a
.NET control documented?

http://msdn.microsoft.com/en-us/library/bb761586(VS.85).aspx

You see the other messages next to it and other controls if you look at the
TOC some nodes above ("Control library").

One way to find out the window class[1] is to use spy++ (included with VS).


[1] http://msdn.microsoft.com/en-us/library/ms632596(VS.85).aspx


Armin
 
Armin,

I see in your link that it is Win32 and Com development, can you assure us
that it will work in all future Microsoft OS systems the next 10 years?

Cor

Armin Zingler said:
eBob.com said:
I've followed this ng closely for quite a while and I don't recall
every seeing before this concept of sending a message to a control. Can
you say more about it? Where are the messages supported by a
.NET control documented?

http://msdn.microsoft.com/en-us/library/bb761586(VS.85).aspx

You see the other messages next to it and other controls if you look at
the TOC some nodes above ("Control library").

One way to find out the window class[1] is to use spy++ (included with
VS).


[1] http://msdn.microsoft.com/en-us/library/ms632596(VS.85).aspx


Armin
 
Good suggestion. Not quite as easy as that - since
you want to know the line length but close.

Actually I don't usually bother with EM_LINELENGTH, although I suppose that
I really should. The maximum possible number of characters in the line can
be roughly guessed from the width of the TextBox and the type and size font
used, and since an exceedingly long buffer is still quite small anyway, I
just read each line into the same "overly long" buffer, each time pulling
the appropriate number of line characters (in accordance with the line
length returned by the EM_GETLINE when it fills the buffer) into my array of
strings and reusing the same buffer for every line. Now that you've
mentioned it though I think it might be a better idea for me to use
EM_LINELENGTH in the future and do the job properly :-)

Mike
 
Actually I don't usually bother with EM_LINELENGTH, although I suppose that
I really should. The maximum possible number of characters in the line can
be roughly guessed from the width of the TextBox and the type and size font
used, and since an exceedingly long buffer is still quite small anyway, I
just read each line into the same "overly long" buffer, each time pulling
the appropriate number of line characters (in accordance with the line
length returned by the EM_GETLINE when it fills the buffer) into my array of
strings and reusing the same buffer for every line. Now that you've
mentioned it though I think it might be a better idea for me to use
EM_LINELENGTH in the future and do the job properly :-)

Yeah, I thought about just using a long buffer myself - but I decided it was
better to just get the line length in the first place.
 
Armin, I see in your link that it is Win32 and Com development,
can you assure us that it will work in all future Microsoft OS
systems the next 10 years?

That's a damn stupid question Ligthert. Nobody can say for certain whether
anything will still be here in the next ten years. A lot of people hope you
will not be, that's for sure!

Mike
 
Cor said:
Armin,

I see in your link that it is Win32 and Com development, can you
assure us that it will work in all future Microsoft OS systems the
next 10 years?

No. Like with everything else that is not marked as "unsupported" or
"obsolete". You know that this is basic Windows stuff, even more basic than
the Framework placed upon. And the "32" in "Win32" says nothing about 64-bit
compatibility (if that's the point). You must know the history to interpret
the Win32, 0x86, Win64, etc terms and where and how they are used. In this
case I don't see a problem. This
http://msdn.microsoft.com/en-us/library/bb427430(VS.85).aspx is a sub
node of the "Win32" chapter, so...


Armin
 
I wouldn't recommend diving in Win32 internals if you are not
familiar with them. Look here for a good description of how
this can be done using the existing .Net functionality:
http://social.msdn.microsoft.com/Forums/en-US/vbgeneral/thread/353d7c46-7000-4859-8f16-2d099b4a04f7
It is probably similar to what you have done, except that it uses
graphical measures to get the exact wrap point - it might end up
with results not greatly different than what you are already getting.

Actually the code at the link you posted will produce unreliable result. I'm
sure that it works using the specific textbox settings and string contents
as shown in the example, otherwise the author would not have posted it, but
it will often fail to work with different settings and different strings. As
an example try using different strings for the textbox contents, or even
using the same string and setting the texbox size to different values, for
example a width of 95 instead of 100. The longer the string the more likely
you are to see the problems. And of course as soon as one line fails to
produce the correct result, which under many conditions it surely will, then
the next line will also be wrong and it is highly likely that many of the
following lines be wrong as well. You may be able to improve things by
changing the code so that it takes into account the current border thickness
of the textbox, but that on its own is not likely to solve the whole problem
because a textbox does not always use the full available client area and
there may be various other things concerning the wrap settings it is using
which also might make the simple string measurements in the code at your
link fail to produce the correct results. You might be able to get the code
working properly by adjusting it to take into account all the things I have
mentioned, but it will need a bit of work to ensure that it is totally
reliable. Personally I would prefer to use the SendMessage method myself.

Mike
 
Hi Mike, I vb.net skill is above a newbie, and would probably
put myself into a 'good beginner' but I am unfamiliar with
EM_GETLINECOUNT. I went a different route and created
my own function to parse out the text and word wrap based
on an X amount of characters per line, incorporating into it the
enterkey, linefeeds, and tabs.

The simple method of detecting the word wrap points using an X amount of
characters per line, as you have suggested, will work only if you are using
a fixed width font in the TextBox, such as Courier New for example. To
correctly deduce the TextBox's word wrap positions when using a
proportionally spaced font, such as the default Microsoft Sans Serif or
Arial or Times New Roman or whatever in which the width of the character is
different for different characters, you will need to acurately take into
account the pixel width of the characters as drawn. Measuring character and
string pixel widths is of course easy to do using the VB functions provided,
but when doing so you must ensure that you are mimicing the behaviour of the
TextBox /exactly/ which again is possible but is not as straight forward as
it might at first seem and there are a number of factors to take into
account as mentioned briefly in my other response.

If you don't want to use the SendMessage method, which is very easy and
which does produce the correct result, then you could instead use the
straight VB.Net code at the link posted by James Hahn (which does not
currently work correctly) as a basis and you could build on it by adding
extra code to take into account the things I have mentioned, but personally,
if I were doing this job myself I would simply send the TextBox the
appropriate messages and effectively ask the TextBox itself where it wrapped
the lines.

By the way, you haven't said exactly why you are performing this task but if
it is so that you can print a block of text on another screen device exactly
as it is displayed in the TextBox then breaking up the block into its
displayed lines (as you are attempting to do) will work whichever method you
use to deduce the correct line wrap positions, or even by instructing the
Control when wrapping the text to measure its output on a different device
than the device on which it is rendering it (some Controls are capable of
performing this function). But if you intend to produce the output on a
device other than the screen (a printer for example) then it will work only
insofar as the individual lines themselves will contain the same words. This
of course may be suitable for your needs. However, the lines of text
themselves will not be exactly the same logical width on the screen as they
are on the printer. If this isn't a problem for you for the job you are
performing then that's okay of course. However, if you do see it as a
problem then you will need to add code to take account of it and to adjust
things so that everything does line up correctly.

The failure of the width of the same line of characters to be the same on
different devices is due to a number of factors. One problem is the fact
that the font size is usually limited to a point size (a vertical
measurement) that produces a whole number of pixels on the specific device
you are using it with, and so in many cases you cannot actually achieve the
same font point size on the printer as you can on the screen since their
device pixels have a different size. Another problem is that even when it is
possible to achieve the same font point size on both devices there is still
the fact that all individual characters (most of which have different widths
in a proportionally spaced font) are also usually restricted to a character
cell width that equates to a whole number of device pixels, and so the
actual logical width of a line of characters is extremely unlikely to be the
same on the printer as it is on the display, even in cases where the font
face and point size is exactly the same.

These problems can of course be overcome in all sorts of different ways by
altering the spacing between individual characters and words to achieve the
same overall printed width on both devices, but if it isn't a problem for
the job you are actually doing then it might not be worth going to the extra
trouble to do it.

Mike
 
It works OK here for any text of 3 or 4 lines, which is what OP wanted it
for. It can go wrong after that in some cases. You might like to replace
the new rectangle in the MeasureCharacterRanges argument list with
tb.ClientRectangle, but that's only for neatness. However you should use
fonts with an integer font size, and larger fonts are more reliable than
smaller ones. If it continues to give trouble, try adding options to the
StringFormat - I think the one to do with trimming spaces might be relevant.
The method already takes account of the formatting factors that you have
mentioned.
 
It works OK here for any text of 3 or 4 lines, which
is what OP wanted it for.

Well it very definitely does NOT work here for any text of 3 or 4 lines, and
neither does it work even with the actual small piece of text in the
specific posted example if the width of the TextBox is changed to various
values other than the 100 specified in the example. And things sometimes go
spectacularly wrong when you change the font as well.
It can go wrong after that in some cases.

It can wrong even /before/ that, and in many cases. Here for example is the
result produced by your posted code of simply changing the TextBox width
from 100 to 95:

TEXTBOX DISPLAY (TB WIDTH 95):
Now is the time for
all good men to
come to the aid of
their countrymen.
STRINGS RETURNED BY YOUR CODE:
Now is the time
for all good men
to come to the aid
of their countrymen.

As you can see, the output is clearly wrong. And here is another example,
again using exactly the same string but with the TextBox width set to 106:

TEXTBOX DISPLAY (TB WIDTH 106):
Now is the time for all
good men tt come to
the aid of their
countrymen.
STRINGS RETURNED BY YOUR CODE:
Now is the time for
all good men to
come to the aid of
their countrymen.

Again, the output from the code you posted is clearly wrong. And here is the
result of leaving the size of the TextBox at the 100 specified in the code
but changing the font to Arial size 9.75, which is the nearest you can get
to 10 on most systems:

TEXTBOX DISPLAY (TB WIDTH 100 : FONT ARIAL 9.75):
Now is the
time for all
good men to
come to the
aid of their
countrymen.
STRINGS RETURNED BY YOUR CODE:
Now is the time
for all good
en to come to
he aid of their
ountrymen

Clearly something has gone very wrong there. Not only are the lines
themselves incorrect but also in some cases the leading character of a word
is missing. So, contrary to what you have said, it does /not/ work okay,
even for a small number of lines, and it will get worse the more text you
use.
However you should use fonts with an integer font
size, and larger fonts are more reliable than smaller
ones.

Well those are very restrictive conditions, and in any case it very often
does not work even when you do that. Here is the result of using Artial 18pt
(which is definitely available as an integer point size on my system):

TEXTBOX DISPLAY (TB WIDTH 100 : FONT ARIAL 18):
Now is
the
time for
.. . . etc, etc
STRINGS RETURNED BY YOUR CODE:
Now is
the time
for all
.. . . etc, etc

As you can see, it clearly does not work even under the very restrictive
conditions you have stipulated. Personally I'm not really botherd whether
the code works or not, because iut is not my code and I had already
suggested an alternative method that definitely does work reliably and I
certainly would not switch to an alternative method just to avoid a simple
SendMessage call unless the alternative was just as reliable as SendMessage,
which clearly the code you posted is not, at least not as it stands.

Mike
 
Back
Top