I'm building a text editor program, and on the left-hand side of the main RichTextBox, is a line numbering scheme that consists of a PictureBox, and a routine uses the Graphics method to draw numbers. As you scroll down the RichTextBox, the line numbers equally adjust.
I found some code online and made a few adjustments, but I'm struggling with one of them.
I have a Zoom In/Out feature so the user can adjust the size of the text in the RichTextBox, this is done by adding/subtracting 0.5 to/from the .ZoomFactor property of the RichTextBox. That part works perfectly and is a great, simple solution. However; If I adjust the zoom on the RichTextBox, the text is now larger than the line numbers, so they don't line up. My idea was to just increase the font size of the line numbering scheme and make small adjustments until they line up perfectly with each other.
Example:
This is it at normal size (the numbers are coloured because I've told the program to do that on purpose)
This is what happens when I adjust the RichTextBox zoom factor, without adjusting font size of line number
The code for drawing the line numbers is:
Private Sub DrawRichTextBoxLineNumbers(ByRef g As Graphics)
With TextEditBox
Dim font_height As Single
font_height = .GetPositionFromCharIndex(.GetFirstCharIndexFromLine(2)).Y _
- .GetPositionFromCharIndex(.GetFirstCharIndexFromLine(1)).Y
If font_height = 0 Then Exit Sub
'Get the first line index and location
Dim first_index As Integer
Dim first_line As Integer
Dim first_line_y As Integer
first_index = .GetCharIndexFromPosition(New _
Point(0, g.VisibleClipBounds.Y + font_height / 3))
first_line = .GetLineFromCharIndex(first_index)
first_line_y = .GetPositionFromCharIndex(first_index).Y
'Print on the PictureBox the visible line numbers of the RichTextBox
g.Clear(Control.DefaultBackColor)
Dim i As Integer = first_line
Dim y As Single
Do While y < g.VisibleClipBounds.Y + g.VisibleClipBounds.Height
y = first_line_y + 2 + font_height * (i - first_line - 1)
g.DrawString((i).ToString, .Font, Brushes.Gray, LineNumber.Width _
- g.MeasureString((i).ToString, .Font).Width, y)
i += 1
Loop
'Debug.WriteLine("Finished: " & firstLine + 1 & " " & i - 1)
End With
End Sub
What adjustment would I make to this section of the code to increase the size?
Dim font_height As Single
font_height = .GetPositionFromCharIndex(.GetFirstCharIndexFromLine(2)).Y _
- .GetPositionFromCharIndex(.GetFirstCharIndexFromLine(1)).Y
If font_height = 0 Then Exit Sub
Any help is greatly appreciated :)
Turns out, the drawing routine already handles this.
I've made it so that when the user moves the mouse on the form, resizes the form or adjusts the zoom, it refreshes.
I simply wrote the following code which immediately solved the problem.
LineNumber.Invalidate()
Also this was a great Line Numbers for RichTextBox with many Features
You can download the Source Code at this Link: https://drive.google.com/file/d/1aMVts_pkok_56DRvCdTkYCGywzjsRqS6/view?usp=sharing
Related
In my a form, I have a rich text box with vertical scrollbars. I have set it so that a line is read from a local text file, and added to the existing text in the text box. The following code makes it so that a line is read from the text file, there is 3 seconds of pause, and then another line is read and added to the existing text. This is done the emulate the feeling of a messenger application, so the 3 seconds of pause is absolutely necessary.
However, I have found that whenever a new line is added and there is enough text in the rich text box for the scrollbar to appear, the scrollbar will jump to the top. Every new line is added to the bottom, so this is extremely annoying, because it means the user is sent all the way up to the top of the text every single time there is a new line added.
Is there anything I can do to prevent this? i.e: locking the scrollbar to the bottom? Or having the scrollbar automatically scroll down to the bottom whenever there is a new line added?
If you need any pictures or further code, please let me know.
Dim i As Integer = 0
Call Pause(3)
RichTextBox1.Text = R.ReadLine()
Do Until i = lineCount
Call Pause(3)
RichTextBox1.Text = RichTextBox1.Text + vbCrLf + vbCrLf + R.ReadLine()
i = i + 1
Loop
The pause subroutine:
Public Sub Pause(ByVal seconds As Single)
Dim newDate As Date
newDate = DateAndTime.Now.AddSeconds(seconds)
While DateAndTime.Now.Second <> newDate.Second
Application.DoEvents()
End While
End Sub
After the TextChangedEvent you can try to set the caret to the last postiton and then use ScrollToCaret
Let me be very clear - I am not looking for a static character limit here (Textbox.MaxLength just doesn't cut it.)
I'm making a simple messaging program and I'd like to implement a character limit. The messages are displayed inside a listbox and can't be horizontally scrolled/wrapped. The solution: impose a limit on every message so that users don't accidentally cut off their own messages.
The problem is that 10 small characters are a lot smaller than 10 full width characters. - E.G. i and W:
iiiiiiiiii
WWWWWWWWWW
I'd like to find a way to limit the characters entered into the text box by the actual amount of pixels the string is wide.
so that:
nobody can use all capitals and get cut off, and
nobody can type normally and be stopped by the character limit far earlier than neccesary.
For reference, I'm using Verdana 8.25pt for the listbox.
Any suggestions would be appreciated,
Thanks.
This should do the trick for you. I've deliberately chosen the keyUp event because the user will see the letter typed and it disappearing.
Private Sub TextBox1_TextChanged(sender As Object, e As KeyEventArgs) Handles TextBox1.KeyUp
Dim text1 As String = TextBox1.Text
Dim textboxFont As Font = TextBox1.Font
Dim textSize As Size = TextRenderer.MeasureText(text1, textboxFont)
If textSize.Width > TextBox1.ClientRectangle.Width Then
Dim cursorLocation As Integer = TextBox1.SelectionStart
TextBox1.Text = TextBox1.Text.Remove(cursorLocation - 1, 1)
If cursorLocation > TextBox1.Text.Length Then
TextBox1.SelectionStart = TextBox1.Text.Length
Else
TextBox1.SelectionStart = cursorLocation
End If
End If
End Sub
Basically what is happening is that the text is rendered (not displayed) using the font of the textbox and measured. If the width of the rendered text is greater than the client area of the textbox, the letter is removed at the point it was typed. This can be anywhere in the text box.
If the cursor is at the end of the text when the letter is removed, .net automatically sets the cursor position to the beginning is a letter is removed. So this sub checks if the initial cursor position was a bigger index than the length of the new textbox contents. If so, the cursor is set to the end again. Otherwise it is moved back 1 because a character was deleted.
I've got a WinForms Panel control which holds a large number of child controls. Each child is left docked, causing the horizontal width of the contents to grow. The containing Panel has its AutoScroll property set so that you can get to all the contents.
I'm running into a problem when the total width of the contents gets too large. Once you've hit this maximum width, additional content elements are placed on top of existing contents instead of being placed to the right. But, if I resize the Panel after it has done its initial layout, it corrects itself by expanding its logical width and placing each content element in the correct location. How do I get it to layout correctly before the user resizes the window?
Here's a simple example:
Form1.vb
Public Class Form1
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
For i As Integer = 1 To 200
Dim gb As New GroupBox
gb.Text = "Box " & i.ToString
gb.Width = 250
gb.Dock = DockStyle.Left
Panel1.Controls.Add(gb)
gb.BringToFront()
Next
End Sub
End Class
Form1.Designer.vb
Partial Class Form1
Inherits System.Windows.Forms.Form
Private Sub InitializeComponent()
Me.Panel1 = New System.Windows.Forms.Panel()
Me.SuspendLayout()
'
'Panel1
'
Me.Panel1.AutoScroll = True
Me.Panel1.Dock = System.Windows.Forms.DockStyle.Fill
Me.Panel1.Location = New System.Drawing.Point(0, 0)
Me.Panel1.Name = "Panel1"
Me.Panel1.Size = New System.Drawing.Size(284, 262)
Me.Panel1.TabIndex = 0
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(284, 262)
Me.Controls.Add(Me.Panel1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub
Friend WithEvents Panel1 As System.Windows.Forms.Panel
End Class
This is what the window looks like when it first comes up, scrolled nearly to the end so you can see the problem area. Notice that Box 183 to 199 are missing because they are placed on top of each other. This is not right.
This is what the window looks like after you manually resize it, scrolled nearly to the end. The panel fixed itself in response to the resize; the total logical width of the panel was automatically extended enough to hold all the contents. This is what I want it to look like when it first comes up.
I've tried manually setting the location of each box, and I've tried calling PerformLayout() and several other functions. Nothing seems to work. So far I haven't found the magic combination to get the good layout. Does anyone know how to fix this?
Edit:
Here's a screenshot that might make the issue more obvious. I adjusted the box widths and the number of boxes to show the problem better. See how the last box overlaps box 656? Every box from 657 to 700 has the same incorrect location. Turning off docking and setting the location myself doesn't help.
Looks like a bug with the scrolling information. If you call PerformLayout when the Panel is scrolled all the way to the right, it correctly places the controls in the proper place. That requires some code in the OnShown method:
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
Panel1.AutoScroll = True
Panel1.SuspendLayout()
For i As Integer = 1 To 200
Dim gb As New GroupBox
gb.Text = "Box " & i.ToString
gb.Width = 250
gb.Dock = DockStyle.Left
Panel1.Controls.Add(gb)
gb.BringToFront()
Next
Panel1.ResumeLayout(False)
End Sub
Protected Overrides Sub OnShown(e As EventArgs)
MyBase.OnShown(e)
Panel1.AutoScrollPosition = New Point(Panel1.HorizontalScroll.Maximum - _
Panel1.HorizontalScroll.LargeChange, 0)
Panel1.PerformLayout()
Panel1.AutoScrollPosition = Point.Empty
End Sub
Of course, having over 200 container controls on the form is never recommended.
AutoScroll is not AutoPositionMyChildren. From MSDN:
When adding controls programmatically to a form, use the AutoScrollPosition property to position the control either inside or outside of the current viewable scroll area.
If you looped thru the controls, to print their location, you's see at some point (probably around 130) that Location.Y becomes fixed at 32767 probably some default unscrolled max. This is also the point they start stacking because they in fact have the same initial location. Some of the code you have makes up for that but it isnt quite right. Once you scroll it, the panel fixes the coords on the child controls.
First, I would suggest that you set Panel1.AutoScrollMinSize to something like {480, 0} so that the HScroll bar appears at design time; this allows you to calc a good height for the boxes which wont cause a VScroll as you add controls.
Dim gb As GroupBox
' only 150 because problem is when (i * width) > 32k
For i As Integer = 0 To 150
gb = New GroupBox
gb.Name = i.ToString ' added
gb.Text = "Box " & i.ToString
gb.Width = 250
' no docking so set the height
gb.Height = Panel1.Bounds.Height - 30 ' trying to avoid the VSCroll
' set location explicitly
gb.Location = NewCtlLocation(Panel1.Controls.Count,
Panel1.AutoScrollPosition.X)
' Dock and Anchor mess up the AutoScroll
'gb.Dock = DockStyle.Left
Panel1.Controls.Add(gb)
' allow panel to update its scroll positions
Panel1.ScrollControlIntoView(gb)
' not needed; seems to offset something with Dock
' changing ZOrder may not always be desirable
'gb.BringToFront()
' debug illumination
Console.WriteLine("{0} {1} {2}", i.ToString,
Panel1.AutoScrollPosition.X.ToString,
gb.Location.X.ToString)
Next
'Go back to start
Panel1.ScrollControlIntoView(Panel1.Controls("0"))
Location helper so you can tweak gutters or margins (dock replacement):
Friend Function NewCtlLocation(ByVal n As Integer,
ByVal ScrollPosX As Integer) As Point
Const TopMargin As Integer = 5
Const LeftMargin As Integer = 5
Return New Point((n * 250) + ScrollPosX, 0)
End Function
Notes:
I have a vertical scroller which repeatedly adds up to 120 user controls which works well but it does not need/use ScrollControlIntoView and they never stack up like yours do. I suspect maybe because they are smaller. There is also at least a second or two before the next one can be added, which may matter. But, good to know.
It might be possible to use the ControlAdded event of the panel to do something, but it would likely amount to ScrollControlIntoView. Doing it once at the end only doesnt work, so using it as they are added is allowing something to get updated as you go.
With the right fiddling, you might be able to get Dock to work, but I suspect it may be part of the problem such as Height and Left set this way dont update the panel's internal scroll map.
Your boxes actually look narrower than 250 - is autosize on?
Suspend/Resume Layout hurt rather than help - they prevent the control from doing anything about the virtual area being populated. It should happen fast enough that no one will see anything. Results:
Works on My SystemTM
I have to create dynamic table layout panel with some controls with auto sized rows and and fixed columns size.
My problem is that i want to show whole checkbox text .
Any help
My code is
Dim textBox2 As New CheckBox()
textBox2.Text = "You forgot to add the ColumnStyles. Do this on a sample form first with the designer. Click the Show All Files icon in the Solution Explorer window. Open the node next to the form and double-click the Designer.vb file. "
textBox2.AutoSize = True
textBox2.Dock = DockStyle.Top
'' textBox2.Size = New Point(200, 90)
Dim lbl1 As New Label()
lbl1.Location = New Point(10, 10)
lbl1.Text = "Yoer.vb"
lbl1.AutoSize = True
lbl1.Location = New Point(120, 50)
lbl1.Dock = DockStyle.Top
'' dynamicTableLayoutPanel.Padding = New Padding(2, 17, 4, 5)
dynamicTableLayoutPanel.Controls.Add(lbl1, 0, 0)
dynamicTableLayoutPanel.Controls.Add(textBox2, 1, 0)
Me.dynamicTableLayoutPanel.SetColumnSpan(textBox2, 5)
If you mean you want the table to size to the controls within it, then:
dynamicTableLayoutPanel.AutoSize = True
I know this is old, but I stumbled across it and figured I'd throw my 2 cents in in case someone else comes along.
Note: I'm using Visual Studio 2015 with .NET 4.6. Functionality may differ between versions.
The problem is that the really long text is not word-wrapping to fit within the table or form. Instead, it is set to Dock = DockStyle.Top. This will cause it to make a single line that continues on and gets clipped, similar to a single-line textbox.
If you want it to automatically word wrap, you'll need to use Dock = DockStyle.Fill. Now, this doesn't completely resolve the problem if your row or table isn't large enough to display the text. Since all of the rows are set to AutoSize, it will only do the bare minimum to fit the control vertically. It doesn't care if text gets clipped off. The end result, using your example code against a 6-column, 10-row table, is this:
Since there isn't a word wrap property, you'll need to manually fit it. Now, to do this, you'll need to change the row to be Absolute instead of AutoSize. To figure out how big to make it, you can pretty much rely on PreferredSize. This reveals a much wider Width than the existing regular Width. From that, we can determine how many lines it would take if we wrap it.
This is what my code ended up looking like:
Dim h As Single = 0
Dim chk As New CheckBox()
chk.Text = "You forgot to add the ColumnStyles. Do this on a sample form first with the designer. Click the Show All Files icon in the Solution Explorer window. Open the node next to the form and double-click the Designer.vb file. "
chk.AutoSize = True
chk.Dock = DockStyle.Fill
Dim lbl1 As New Label()
lbl1.Text = "Yoer.vb"
lbl1.AutoSize = True
lbl1.Dock = DockStyle.Top
dynamicTableLayoutPanel.Controls.Add(lbl1, 0, 0)
dynamicTableLayoutPanel.Controls.Add(chk, 1, 0)
dynamicTableLayoutPanel.SetColumnSpan(chk, 5)
' Find the preferred width, divide by actual, and round up.
' This will be how many lines it should take.
h = Math.Ceiling(chk.PreferredSize.Width / chk.Width)
' Multiply the number of lines by the current height.
h = (h * chk.PreferredSize.Height)
' Absolute size the parent row to match this new height.
dynamicTableLayoutPanel.RowStyles.Item(0) = New RowStyle(SizeType.Absolute, h)
The changes included delaring a height variable, renaming the CheckBox variable, setting its Dock to Fill, removing the Location from lbl1, and adding in size calculation. The output:
This isn't perfect since the height includes the checkbox itself, and the checkbox takes up padding, so there can be too much or too little height calculated. There are other calculations that may need to be considered. But, this is a starting point.
Problem
In a VB label, if there are more lines than the fixed height can support, then the additional lines get cut off and the user only sees the first couple of lines.
I need it to be completely opposite. I want to see the latest 5 or 6 lines. What that means is that if there is more lines than the fixed height of the label can show, then instead of simply cutting them off, all the lines should move up with the latest one at the bottom. The top lines can be cut off, but the latest one needs to be in the bottom.
Example of what I am trying to do
If you look at a console and enter a command like dir, then it lists the latest directories, but you see latest read directory at the bottom. Basically, you see the latest directory it read. You only see the latest 5 or 6 directories it read instead of seeing every printed line.
Another Example: Look a textbox. If you type in more text than the height, then you see that the textbox autoscrolls with you on the text and shows the latest lines while the older ones keep moving up and eventually get cut-off until you move the scroll bar up. I need it to be exactly the same, except without scroll bars.
One more example: If you set the TextAlign property of the label to Bottom Center, then you see the text move up as you add more lines. The problem occurs when the label is filled with the lines and the text exceeds the height and gets cut off. That shouldn't happen. The text at top should get cut off, but the latest line should keep coming from the bottom.
Solutions recommended by others
The only solution that I have been given is to create a custom control derived from the label.
Is there any other way that this can be done?
Thank You for your help.
Drop a button and a label on a NEW form (so as not to mess up your existing code) and copy and paste the code below and click the button repeatedly and see if this solves your issue.
obviously if it does you still have to mess with the code so it suits your particular needs.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Static TextLines As Generic.List(Of String) = Nothing
Static SingleLineHeight As Integer = Nothing
Static maxrows As Integer
Static qty As Integer = Nothing
Dim text As String = Nothing
Dim counta As Integer = Nothing
'
'set MAX ROWS
maxrows = 6
' Initalise
If TextLines Is Nothing Then TextLines = New Generic.List(Of String)
If SingleLineHeight = 0 Then
Label1.Text = "Test Line"
SingleLineHeight = Label1.Font.Height
Label1.Text = ""
End If
'
'process
qty = qty + 1
text = "Line Number " & qty
TextLines.Add(text)
Label1.Text = ""
If TextLines.Count > maxrows - 1 Then TextLines.RemoveAt(0)
For counta = 0 To TextLines.Count - 1
Label1.Text = Label1.Text & TextLines(counta) & vbCrLf
Next
End Sub