defining on which line the cursor is in a textbox

  • Thread starter Thread starter DraguVaso
  • Start date Start date
D

DraguVaso

Hi,

For my application I need the following behavior: When I press F4 the cursor
has to move to the next line in my multiline textbox which begins with "0".

Finding lines starting with 0 isn't that difficult, but to find the next
line is more difficult. For exemple: if my cursor is on line 200, it has to
start searching on line 201, and not on line 1.

Anybody has any ideas?

I guess that posotioning the cursor on the lien I foudn is also something I
will need, so if somebody knows how to do that... :-)

Thansk a lot in advance,

Pieter
 
Hi,

As far as I remember, this can be done at the API level. Take a look at the
text box messages (starting with the EM_ prefix), especially EM_LINEFROMCHAR
and EM_LINEINDEX.
 
Hi Pieter,

Did you look for
textbox.lines

Something as (roughly written here not tested and never tried)
\\\\
for i as integer = 200 to textbox1.lines.length-1
if x................
next
////
I hope this helps?

Cor
 
* "DraguVaso said:
For my application I need the following behavior: When I press F4 the cursor
has to move to the next line in my multiline textbox which begins with "0".

Finding lines starting with 0 isn't that difficult, but to find the next
line is more difficult. For exemple: if my cursor is on line 200, it has to
start searching on line 201, and not on line 1.

Quick and dirty: Use a loop and loop through the textbox's 'Lines'
property and sum up the length of the lines (+ the length of the line
separator). Then set the position by using the overloaded version of
the textbox's 'Select' method.
 
I found this VB functions:

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA"
(ByVal _
hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _
lParam As Any) As Long
Const EM_LINEFROMCHAR = &HC9

Function GetTextBoxCurrentLine(TB As TextBox) As Long
GetTextBoxCurrentLine = SendMessage(TB.hWnd, EM_LINEFROMCHAR, -1, _
ByVal 0&) + 1
End Function


But I'm not able to 'translate' them in VB.NET. Anybody can help with this?

Dmitriy Lapshin said:
Hi,

As far as I remember, this can be done at the API level. Take a look at the
text box messages (starting with the EM_ prefix), especially EM_LINEFROMCHAR
and EM_LINEINDEX.

--
Dmitriy Lapshin [C# / .NET MVP]
X-Unity Test Studio
http://x-unity.miik.com.ua/teststudio.aspx
Bring the power of unit testing to VS .NET IDE


DraguVaso said:
Hi,

For my application I need the following behavior: When I press F4 the cursor
has to move to the next line in my multiline textbox which begins with "0".

Finding lines starting with 0 isn't that difficult, but to find the next
line is more difficult. For exemple: if my cursor is on line 200, it has to
start searching on line 201, and not on line 1.

Anybody has any ideas?

I guess that posotioning the cursor on the lien I foudn is also
something
I
will need, so if somebody knows how to do that... :-)

Thansk a lot in advance,

Pieter
 
Indeed dirty, but not quick I'm affraid. Somewhere else I have to work
alreaddy with ther Lines-property and loop through all of them, but it takes
a lot of time! I sometimes have really big amounts of text! It can take
easily 10 seconds.
 
DraguVaso said:
Indeed dirty, but not quick I'm affraid. Somewhere else I have to work
alreaddy with ther Lines-property and loop through all of them, but it takes
a lot of time! I sometimes have really big amounts of text! It can take
easily 10 seconds.

That sounds pretty excessive - how are you doing it? If you're doing it
in a way that repeatedly uses the Lines property, that would be pretty
slow. If you fetch the Lines property value once and then repeatedly
use that "cached" value it shouldn't take long at all.

You'll need to be very careful in case you've got some \n and some \r\n
separators in there though - you should definitely check whether or not
that confuses things.
 
Hi Pieter,

What are you storing in that textbox, The bible?

But to be serious, I saw that the lines is not an implementation of Ilist,
so maybe you can copy it first to an Ilist array to get it faster.

Just an Idea.

Cor
 
Hehe,

No bible, but coda-files :-) Files with bankmovements etc. According to the
format it has for every record 1-3 lines with a lenght of 128 or 360, and
can eaisliy have 2000 records in it, so a total of 5000 lines isn't an
exception. For that I need to get a key to go quick to some special marks in
it.

I guess that's a good idea to put it first in an other kind of list,
although I don't know the 'IList", but I'm gonna try all these usggestions
out now.

Thansk a lot, I let you guys know something.

Pieter
 
* "DraguVaso said:
I found this VB functions:

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA"
(ByVal _
hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _
lParam As Any) As Long
Const EM_LINEFROMCHAR = &HC9

Untested:

\\\
Private Declare Auto Function SendMessage Lib "user32.dll" ( _
ByVal hWnd As IntPtr, _
ByVal wMsg As Int32, _
ByVal wParam As Int32, _
ByVal lParam As Int32 _
)

Private Const EM_LINEFROMCHAR As Int32 = &HC9
..
..
..
Public Function GetTextBoxCurrentLine(ByVal TB As TextBox) As Integer
Return SendMessage(TB.Handle, EM_LINEFROMCHAR, -1, 0)
End Function
///
 
For example, a large log-file?

You knew it and I remembered it me also when I saw the answer from Pieter.

Cor
 
DraguVaso said:
No bible, but coda-files :-) Files with bankmovements etc. According to the
format it has for every record 1-3 lines with a lenght of 128 or 360, and
can eaisliy have 2000 records in it, so a total of 5000 lines isn't an
exception. For that I need to get a key to go quick to some special marks in
it.

5000 lines shouldn't take long to parse at all - so long as you only
need to parse them *once* rather than once (or more) per line.
 
Hi DraguVaso,

If it is possible and if you don't like using P\Invole my suggestion is to
go with RichEditBox control.
RichEditBox supports methods like GetCharIndexFromPosition and
GetLineFromCharIndex. Combining these methods will give you what you need.
I believe changing EditBox with RichEditBox won't be a pain.

B\rgds
100
 
Hi DraguVaso,
I misundertood your question. Sorry about that.
I take that when you say cursor you mean the caret not the mouse cursor.
Right?
If it is the caret you can go again with RichTextBox.
The position of the carret you can get from SelectionStart property. This is
position is in terms of char index.
Then you can get the line where the caret currently is by using
GetLineFromCharIndex.
However, this will work only if the WordWrap is turned off.
GetLineFormCharIndex returns the physical line on the screen. If a line has
been splited by the WordWrap RichTextBox considers this line as two or more
physical lines.

My second idea:

You can consider using RichTextBox' Find method. It has whole bunch of
overloads.
You can look for '0' this is what your lines begin with. When you find it
you have to make sure it is at the begining of the line. This is not easy.
Unfortunately Find method cannot search for new-line characters.
Thus, you cannot do Find("\n0") it won't find anything. So the first
solution I came up with is to check whether the '0' character you have found
and the character before it are on the same physical line. If they are not
'0' is at the begining of the line.
If they are, though, the '0' may be or may be not what you are looking for.
This will work only of '0' is the very first character in the line; no
whitspaces or stuff.

You can test the following code and see of it works for you. Just create a
form with one button and one RichTextBox on it and paste the following
button's event handler.
BTW the example is in c#, but the translation should be a piece of cake.

private void button1_Click(object sender, System.EventArgs e)
{

int index = richTextBox1.Find(new char[]{'0'},
richTextBox1.SelectionStart + 1);
if(index >= 0)
{
int foundTextLine = richTextBox1.GetLineFromCharIndex(index);
if(foundTextLine > richTextBox1.GetLineFromCharIndex(index) -1)
{
//Begining of the line found
richTextBox1.SelectionStart = index;
}

}
//Just to show the caret.
richTextBox1.Focus();

}


BTW, is your TextBox readonly or it can be change by the user? If it is
readonly you can cache the text and do the searching in the copy. Reading
Lines property is slow operation because it envolves sending the
underlaying windows control messages and then split the text into lines.
 
Ok, I have a solution that works allmost fine, hehe :-)
Copyng the Textbox.Lines to a string() worked great! It improved speed
amazingly.

Some little remarks that I didn't expect: to synchronize the
Textbox.SelectionStart with the sum of the lenghts of all the lines I had to
add 2 to eacht line. I expected 1 for the carriage return, and still don't
really know why it was 2, but whatever, hehe.

Another thing: it works really fine with small files, but with big files
(5000 lines, more than three 0-records in it) it has some weird behaviour:
Somewhere it doesn't calculate enough positions to the total lenght
(lngLengte in the routine). I tested it on a file whith records which have
all a lenght of 128. For the first 0-record it found it putted the cursor at
the end of the file (perfect), the second 0-record was alreaddy the
beginning. The third it was 2 records before the 0-record (so like +- 250
positions before it, and it was like record 2000), and the fourth 0-record
it foudn it putted the position of the cursor +- 10 lines before it (or 1200
positions, on +- record 5000).

Anybody got any idea what causes this?

The full code you find underneath:

Private Sub ZoekVolgendBestand()
Dim lngX As Long
Dim lngBeginPos, lngBeginL As Long
Dim lngFound As Long
Dim lngLengte As Long

lngBeginPos = txtText.SelectionStart()
lngLengte = 0
lngBeginL = 0
Dim strT As String()
'store the textbox in a string()
strT = txtText.Lines

'search the current line number + position
Do Until lngLengte > lngBeginPos
lngLengte = lngLengte + strT(lngBeginL).Length
If lngBeginL > UBound(strT) Then
'end of file: no line with a 0 found
Exit Sub
End If
lngBeginL = lngBeginL + 1
Loop

lngFound = -1

'search next 0-line
For lngX = lngBeginL To UBound(strT)
lngLengte = lngLengte + strT(lngX).Length + 2 '2 = carriage
return
If Microsoft.VisualBasic.Left(strT(lngX), 1) = "0" Then
lngFound = lngX
Exit For
End If
Next

'end of file but not yet found one: go to begin of file and keep on
searching
If (lngFound < 0) Then
lngLengte = 0
For lngX = 0 To lngBeginL
lngLengte = lngLengte + strT(lngX).Length + 2 '2 =
carriage return
If Microsoft.VisualBasic.Left(strT(lngX), 1) = "0" Then
lngFound = lngX
Exit For
End If
Next
End If

If lngFound >= 0 Then
'found one
txtText.SelectionStart = lngLengte
txtText.ScrollToCaret()
Else
MessageBox.Show("Cannot find a file", "Find file",
MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
End Sub


I'm gonna try the other solutiosn here also, already big thanks for all the
help I got here!

Pieter
 
Thanks, but unfortunately it doesn't work, it gave me an error:
Run-time exception thrown :
System.Runtime.InteropServices.MarshalDirectiveException - PInvoke
restriction: can not return variants.

I didn't manage to find the solution for that error :-(
 
Hi,

Your second idea takes to much time, hehe. There are too many 0's in the
files. After 10 seconds it has only looked into 1500 records.

Regarding your first solution: that works ideal to find the current line,
but unfortunately there doesn't seem to be a functionthat does the reverse:
when I found my 0-line: giving me the position so I can set the
SelectionStart to it. In case you would know a way to get aroudn this
problem, it woudl be nice if you could tell me.

Thanks a lot!

Pieter

100 said:
Hi DraguVaso,
I misundertood your question. Sorry about that.
I take that when you say cursor you mean the caret not the mouse cursor.
Right?
If it is the caret you can go again with RichTextBox.
The position of the carret you can get from SelectionStart property. This is
position is in terms of char index.
Then you can get the line where the caret currently is by using
GetLineFromCharIndex.
However, this will work only if the WordWrap is turned off.
GetLineFormCharIndex returns the physical line on the screen. If a line has
been splited by the WordWrap RichTextBox considers this line as two or more
physical lines.

My second idea:

You can consider using RichTextBox' Find method. It has whole bunch of
overloads.
You can look for '0' this is what your lines begin with. When you find it
you have to make sure it is at the begining of the line. This is not easy.
Unfortunately Find method cannot search for new-line characters.
Thus, you cannot do Find("\n0") it won't find anything. So the first
solution I came up with is to check whether the '0' character you have found
and the character before it are on the same physical line. If they are not
'0' is at the begining of the line.
If they are, though, the '0' may be or may be not what you are looking for.
This will work only of '0' is the very first character in the line; no
whitspaces or stuff.

You can test the following code and see of it works for you. Just create a
form with one button and one RichTextBox on it and paste the following
button's event handler.
BTW the example is in c#, but the translation should be a piece of cake.

private void button1_Click(object sender, System.EventArgs e)
{

int index = richTextBox1.Find(new char[]{'0'},
richTextBox1.SelectionStart + 1);
if(index >= 0)
{
int foundTextLine = richTextBox1.GetLineFromCharIndex(index);
if(foundTextLine > richTextBox1.GetLineFromCharIndex(index) -1)
{
file://Begining of the line found
richTextBox1.SelectionStart = index;
}

}
file://Just to show the caret.
richTextBox1.Focus();

}


BTW, is your TextBox readonly or it can be change by the user? If it is
readonly you can cache the text and do the searching in the copy. Reading
Lines property is slow operation because it envolves sending the
underlaying windows control messages and then split the text into lines.


100 said:
Hi DraguVaso,

If it is possible and if you don't like using P\Invole my suggestion is to
go with RichEditBox control.
RichEditBox supports methods like GetCharIndexFromPosition and
GetLineFromCharIndex. Combining these methods will give you what you need.
I believe changing EditBox with RichEditBox won't be a pain.

B\rgds
100


has
to something
 
Hehe, I found the reason why: I forgot in the first loop to add the extra 2
to my line-lenght :-)

So this is the working code, which also goes the fastest.

Private Sub ZoekVolgendBestand()
Dim lngX As Long
Dim lngBeginPos, lngBeginL As Long
Dim lngFound As Long
Dim lngLengte As Long

lngBeginPos = txtText.SelectionStart()
lngLengte = 0
lngBeginL = 0
Dim strT As String()
'store the textbox in a string()
strT = txtText.Lines

'search the current line number + position
Do Until lngLengte > lngBeginPos
lngLengte = lngLengte + strT(lngBeginL).Length + 2 '2 =
carriage return
If lngBeginL > UBound(strT) Then
'end of file: no line with a 0 found
Exit Sub
End If
lngBeginL = lngBeginL + 1
Loop

lngFound = -1
Dim lngLast As Long

'search next 0-line
For lngX = lngBeginL To UBound(strT)
lngLast = strT(lngX).Length + 2
lngLengte = lngLengte + lngLast '2 = carriage return
If Microsoft.VisualBasic.Left(strT(lngX), 1) = "0" Then
lngFound = lngX
Exit For
End If
Next

'end of file but not yet found one: go to begin of file and keep on
searching
If (lngFound < 0) Then
lngLengte = 0
For lngX = 0 To lngBeginL
lngLast = strT(lngX).Length + 2
lngLengte = lngLengte + lngLast '2 = carriage return
If Microsoft.VisualBasic.Left(strT(lngX), 1) = "0" Then
lngFound = lngX
Exit For
End If
Next
End If

If lngFound >= 0 Then
'found one
txtText.SelectionStart = lngLengte - lngLast
txtText.SelectionLength = lngLast - 1
txtText.ScrollToCaret()
Else
MessageBox.Show("Cannot find a file", "Find file",
MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
End Sub



Thanks a lot everybody, especially Cor, who came with the ward-winning tip
of storing everything in an other list! hahaha :-)

See you later guys!

Pieter
 
Ensure the API function declared as one returning Long value. One of the
declarations given by Herfried misses the return value which is assumed
Variant by default. You should specify System.Int32 or its VB.NET equivalent
(most likely Integer).
 
Back
Top