textbox multiline, length issues - vb.net

I have a textbox with multiline set to true. I want to have max characters set to 50 per line with a total of 3 lines. When they reach the 50 characters, I would like it to jump to the second line.
I am having some issues and have been struggling with this for a while and wanted to know if anyone can help.
MAX_LINE_COUNT = 3
Private Sub txtMsg_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles txtMsg.KeyDown
If e.KeyCode = Keys.Enter Then
e.SuppressKeyPress = (Me.txtMsg.Lines.Length >= MAX_LINE_COUNT)
End If
End Sub

To effectively handle multiple lines of text with a common max characters per line, then you will need to extend the TextBox class and override several items in the TextBox class. Instead of re-inventing the wheel, I am going to redirect you to the code from an answer to Is there a way to catch maximum length PER LINE and not allow user to input more characters if max length PER LINE has been reached?, since it is not the accepted answer, I will paste the VB.NET translation below:
Public Class MaxPerLineTextBox
Inherits TextBox
Public Sub New()
MyBase.Multiline = True
End Sub
Public Overrides Property Multiline() As Boolean
Get
Return True
End Get
Set
Throw New InvalidOperationException("Readonly subclass")
End Set
End Property
Public Property MaxPerLine() As System.Nullable(Of Integer)
Get
Return m_MaxPerLine
End Get
Set
m_MaxPerLine = Value
End Set
End Property
Private m_MaxPerLine As System.Nullable(Of Integer)
Protected Overrides Sub OnKeyPress(e As KeyPressEventArgs)
If Char.IsControl(e.KeyChar) Then
MyBase.OnKeyPress(e)
Return
End If
Dim maxPerLine As Integer
If Me.MaxPerLine.HasValue Then
maxPerLine = Me.MaxPerLine.Value
Else
MyBase.OnKeyPress(e)
Return
End If
Dim activeLine As Integer = Me.GetLineFromCharIndex(Me.SelectionStart)
Dim lineLength As Integer = Me.SelectionStart - Me.GetFirstCharIndexFromLine(activeLine)
If lineLength < maxPerLine Then
MyBase.OnKeyPress(e)
Return
End If
e.Handled = True
End Sub
End Class
To use the above code you will need to do the following:
Create a new project in your solution to hold the code above.
Paste code above into new project and build it.
Ensure that there are no errors and the project compiles successfully.
The MaxPerLineTextBox control should show up in the toolbox. If it does not, then try restarting Visual Studio.
Drag MaxPerLineTextBox onto your form and set the properties.

Related

Check to see if selection/text was changed in form

I have a form with about 20 controls on it (ComboBox, TextBox, etc) that I have pre-loaded with data. This is being displayed to the user and gives them the capability to change any of the fields.
I do not know the best way of recognizing that changes have taken place. After some research, I found TextBox.TextChanged and setting the flag IsDirty = True or something along those lines.
I don't think this will be 100% bulletproof since the user might change the value and then go back and change it to how it was when initially loaded. I've been thinking about saving the current data to .Tag and then comparing it with the .Text that was entered when the user clicks "Cancel" to simply ask them if they'd like to save the changes.
This is my code:
Private Sub Form1_Load(ByVal sender as Object, byVal e as System.EventArgs)Handles MyBase.Load
For Each ctr as Control in me.Controls
if typeof ctr is TextBox then
ctr.tag=ctr.text
end if
Next
End Sub
This is the code for when the user clicks "Cancel":
Private Sub CmdCancel_Click (ByVal sender as Object, ByVal e As System.EventArgs) Handles CmdCancel.Click
For each ctr As Control in Me.Controls
If Typeof ctr is Textbox then
if ctr.tag.tostring <> ctr.text then
MsgBox ("Do you want to save the items", YesNo)
end if
End if
Next
End sub
Is this an effective way to do this? Can it be relied on? If anyone has any better idea, I'd love to hear it.
Have a look at this:
For Each txtBox In Me.Controls.OfType(Of TextBox)()
If txtBox.Modified Then
'Show message
End If
Next
EDIT
Have a look at this. This may be of interest to you if you wanted an alternative way to the .Tag property:
'Declare a dictionary to store your original values
Private _textboxDictionary As New Dictionary(Of TextBox, String)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'You would place this bit of code after you had set the values of the textboxes
For Each txtBox In Me.Controls.OfType(Of TextBox)()
_textboxDictionary.Add(txtBox, txtBox.Text)
Next
End Sub
Then use this to find out the original value and compare to the new value:
For Each txtBox In Me.Controls.OfType(Of TextBox)()
If txtBox.Modified Then
Dim oldValue = (From kp As KeyValuePair(Of TextBox, String) In _textboxDictionary
Where kp.Key Is txtBox
Select kp.Value).First()
If oldValue.ToString() <> txtBox.Text Then
'Show message
End If
End If
Next
I know this already has an accepted answer, but I thought the part about checking if the actual text value has changed should be addressed. Checking modified will reveal if any changes were made to the text, but it will fail if the user, for example, adds a character and then deletes it. I think a good way to do this would be with a custom control, so here's an example of a simple control that stores the textbox's original text whenever it is changed programmatically, and has a textaltered property that can be checked to show whether or not the user's modifications actually resulted in the text being different from its original state. This way, each time you fill the textbox with data yourself, the value you set is saved. Then when you are ready, you just check the TextAltered property:
Public Class myTextBox
Inherits System.Windows.Forms.TextBox
Private Property OriginalText As String
Public ReadOnly Property TextAltered As Boolean
Get
If OriginalText.Equals(MyBase.Text) Then
Return False
Else
Return True
End If
End Get
End Property
Public Overrides Property Text As String
Get
Return MyBase.Text
End Get
Set(value As String)
Me.OriginalText = value
MyBase.Text = value
End Set
End Property
End Class

Cannot set focus to textbox

I am using VB and trying to select a portion of the text in a textbox of a separate form. However, I can't seem to find a good way to access the textbox from the other form, although the textbox is public (I am new to VB).
Currently, I'm trying to do this by calling a function located in the form (the form with the textbox), and then focusing on the textbox and selecting/highlighting the text. But it still doesn't work:
Public Sub GetFindLoc(ByVal lngStart As Long, ByVal intLen As Integer)
frmFind.Hide()
MessageBox.Show(ActiveForm.Name)
MessageBox.Show(txtNotes.CanFocus())
txtNotes.Focus()
txtNotes.Select(lngStart, intLen)
frmFind.Show()
End Sub
With this, I first hide the original form, and then try to select the text, and bring back the form. It shows that the active form is the one which I'm trying to select the text on, but it returns false on CanFocus().
Any help would be appreciated, thank you!
Hmm. This was more fiddly than I thought. You need to pass a reference to the other form:
Main form:
Public Class frmNotes
'This is the main form
'This form has a textbox named txtNotes and a button called btnShowFind
'txtNotes has .MultiLine=True
Private mfrmFind As frmFind
Private Sub btnShowFind_Click(sender As Object, e As EventArgs) Handles btnShowFind.Click
If mfrmFind Is Nothing OrElse mfrmFind.IsDisposed Then
mfrmFind = New frmFind(Me)
mfrmFind.Show()
Else
mfrmFind.BringToFront()
End If
End Sub
End Class
Finder form:
Public Class frmFind
'This form has a textbox called txtFind and a button called btnFind
Private mfrmParent As frmNotes
Sub New(parent As frmNotes)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
mfrmParent = parent
End Sub
Private Sub btnFind_Click(sender As Object, e As EventArgs) Handles btnFind.Click
If txtFind.Text = "" Then
MsgBox("Please enter text to find", MsgBoxStyle.Exclamation)
Exit Sub
End If
Dim intSearchBegin As Integer = mfrmParent.txtNotes.SelectionStart + 1
Dim intStart As Integer = mfrmParent.txtNotes.Text.IndexOf(txtFind.Text, intSearchBegin)
If intStart > -1 Then
mfrmParent.txtNotes.Select(intStart, txtFind.Text.Length)
mfrmParent.txtNotes.Focus()
mfrmParent.BringToFront()
Else
mfrmParent.txtNotes.Select(0, 0)
MsgBox("No more matches")
End If
End Sub
End Class
Public Class frmFind
Private Sub btnFind_Click(sender As Object, e As EventArgs) Handles btnFind.Click
Dim search As String = TextBox1.Text.Trim
Dim pos As Integer = frmNotes.txtNotes.Text.IndexOf(search)
If pos > 0 Then
frmNotes.txtNotes.Focus()
frmNotes.txtNotes.Select(pos, search.Length)
End If
End Sub
End Class
This is just a "find" form with 1 textbox and 1 button which will highlight the first occurrence of the string in TextBox1 that it finds in txtNotes on the other form. If you want it to find whitespace as well, then remove the Trim function. You can add code to find other occurrences or go forward/backward.

Most efficient way to refer to multiple controls. IE: the multi-WITH

I have had a little look around and cannot seem to find an easy way to refer to multiple controls in stuff like an IF statement, or to set a property to multiple controls etc. (IF exists = true!)
The 'shortcut'if you like; would best be described in illegal code such as:
Public Sub BreakCompiler()
if string.IsNullOrEmpty(Textbox1.text, textbox2.text, textbox3.text) Then .....
'As opposed to
If String.IsNullOrEmpty(PartNumTextBox.Text) Or _
String.IsNullOrEmpty(PartNameTextBox.Text) Or _
String.IsNullOrEmpty(PartGRNTextBox.Text) Or_
String.IsNullOrEmpty(SerialNumTextBox.Text) Then
'Warn user
Else
'do nofin.
End If
Or even more outlandish:
WITH Textbox1.text, textbox2.text, textbox3.text
.ReadOnly = true
END WITH
End sub
The idea is to prevent having to run 3 if statements, or whatever, that basicaly do the same thing to 3 different objects...etc.. etc. Similar to a handler for multiple events that can be separated by a ','.
Im aware of looping through controls (IE for each control in groupbox.controls for eg) but that wouldn't quite achieve what I'm after here. (Say you wanted to skip a couple?)
Just thought id check the collective wisdom.
As far as I know, there isn't anything similar to a handler for control properties in if statements.
You either have to do them separately (or add them all into an array), or, as you mentioned, go through a subset within another control.
However, one thing you can do (say you only wanted to change a certain type of control), is this:
For Each tb As TextBox In Me.Controls.OfType(Of TextBox)
'do stuff here
Next
Another option is to create a Sub that will make the changes for you, and then pass each into the Sub.
Private Sub changeTextBox(tBox as TextBox)
'make changes here
End Sub
You want to process a variable length collection of some type of object. One way to do this is to write helper methods that have an argument decorated with ParamArray keyword.
Public Shared Function AnyIsNullOrEmpty(ParamArray controls As Control()) As Boolean
Dim ret As Boolean
For i As Int32 = 0 to controls.GetUpperBound(0)
ret = String.IsNullOrEmpty(controls(i).Text)
If ret then Exit for
Next
Return ret
End Function
Public Shared Sub SetTBReadOnlyProperty(value As Boolean,ParamArray textboxes As TextBox())
For i As Int32 = 0 to textboxes.GetUpperBound(0)
textboxes(i).ReadOnly=value
Next
End Sub
Possible usage:
Private Sub DemoUsage
If AnyIsNullOrEmpty(TextBox1,TextBox3,TextBox4) then
' at least one is empty
Else
' all have value
End If
SetTBReadOnlyProperty(True,TextBox1,TextBox3,TextBox4)
End Sub
The newer versions of .NET allow you to use the .ForEach Linq extension on a List object. If you build a e.g. a List(Of TextBox) you can use .ForEach with an anonymous method to quickly iterate the controls in the List and manipulate properties and so forth. It's still a loop - but in a much more compact form. You can be selective about which controls are in a List, and have multiple Lists etc.
Here's an example:
Public Class Form1
Private _BoxList As New List(Of TextBox)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
_BoxList.ForEach(Sub(tb As TextBox) tb.Enabled = Not (tb.Text = String.Empty))
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
_BoxList.Add(Me.TextBox1)
_BoxList.Add(Me.TextBox2)
_BoxList.Add(Me.TextBox3)
End Sub
End Class
That's just 3 TextBoxs and a Button on a vanilla form:
For the particular example I was working on when I thought "There must be a better way" Something like this works perfectly.
It might be easier if trying to do multiple non type specific operations on different things. (Thanks #Sastreen and #the_lotus)
Private Sub Validate_PartDetails() Handles PartNumTextBox.TextChanged, PartNameTextBox.TextChanged, PartGRNTextBox.TextChanged, SerialNumTextBox.TextChanged
For Each tb As TextBox In BasicDetailsCustomGroupBox.Controls.OfType(Of TextBox)
If tb.Tag = "notnull" Then
If String.IsNullOrEmpty(tb.Text) Then
tb.BackColor = Color.MistyRose
Else
tb.BackColor = Control.DefaultBackColor
End If
End If
If tb.Tag = "notnumeric" Then
If not(isnumeric(tb.Text)) Then
tb.BackColor = Color.MistyRose
Else
tb.BackColor = Control.DefaultBackColor
End If
End If
Next
End Sub
Thanks guys.

Visual Basic Help - Text limitations in a visual basic text box to prevent certain characters being entered

This is a simple thing that I have been trying to figure out for a while now and its starting to annoy. All I want to is when a button is pressed only certain values are allowed to appear in the textbox. What is meant by this is for example only allow "abc123!" in the textbox and if say a value such as "w" then clear the textbox.
I have tried things such as 'If Not Regex.Match' but it is just causing me errors.
Please help ;)
You would want to use a white list. Your allowed characters would be a much smaller list than every other character in existence. You can do this a few ways. You can handle the key press event on the text box and if that value is whatever, then you execute your code. The other way you can do this (say if it was a winforms app) would be to inherit from the textbox and put your code there (you could re-use this control then). Here is an example of a TextBox that only allows numeric input:
''' <summary>
''' Text box that only accepts numeric values.
''' </summary>
''' <remarks></remarks>
Public Class NumericTextBox
Inherits TextBox
Private Const ES_NUMBER As Integer = &H2000
Protected Overrides ReadOnly Property CreateParams() As System.Windows.Forms.CreateParams
Get
Dim params As CreateParams = MyBase.CreateParams
params.Style = params.Style Or ES_NUMBER
Return params
End Get
End Property
Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean
'Prevent pasting of non-numeric characters
If keyData = (Keys.Shift Or Keys.Insert) OrElse keyData = (Keys.Control Or Keys.V) Then
Dim data As IDataObject = Clipboard.GetDataObject
If data Is Nothing Then
Return MyBase.ProcessCmdKey(msg, keyData)
Else
Dim text As String = CStr(data.GetData(DataFormats.StringFormat, True))
If text = String.Empty Then
Return MyBase.ProcessCmdKey(msg, keyData)
Else
For Each ch As Char In text.ToCharArray
If Not Char.IsNumber(ch) Then
Return True
End If
Next
Return MyBase.ProcessCmdKey(msg, keyData)
End If
End If
ElseIf keyData = (Keys.Control Or Keys.A) Then
' Process the select all
Me.SelectAll()
Else
Return MyBase.ProcessCmdKey(msg, keyData)
End If
End Function
End Class
If you just want to use a TextBox and a KeyPress event you can do something like this. I only have two characters in my white list, you'd want to include the characters for everything you'd want included:
Private Sub TextBox1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles TextBox1.KeyPress
' Test white list, this is only 0 and 1 which are ASCII 48 and 49
Dim allowedChars() As String = {Chr(48), Chr(49)}
If allowedChars.Contains(e.KeyChar) Then
' Setting handled to true stops the character from being entered, remove this or execute your code
' here that you want
e.Handled = True
End If
End Sub
If you want a list of the char codes you can get them here:
http://www.asciitable.com/
Hope this helps. ;-)

Search ListBox elements in VB.Net

I'm migrating an application from VB6 to VB.Net and I found a change in the behavior of the ListBox and I'm not sure of how to make it equal to VB6.
The problem is this:
In the VB6 app, when the ListBox is focused and I type into it, the list selects the element that matches what I type. e.g. If the list contains a list of countries and I type "ita", "Italy" will be selected in the listbox.
The problem is that with the .Net version of the control if I type "ita" it will select the first element that starts with i, then the first element that starts with "t" and finally the first element that starts with "a".
So, any idea on how to get the original behavior? (I'm thinking in some property that I'm not seeing by some reason or something like that)
I really don't want to write an event handler for this (which btw, wouldn't be trivial).
Thanks a lot!
I shared willw's frustration. This is what I came up with. Add a class called ListBoxTypeAhead to your project and include this code. Then use this class as a control on your form. It traps keyboard input and moves the selected item they way the old VB6 listbox did. You can take out the timer if you wish. It mimics the behavior of keyboard input in Windows explorer.
Public Class ListBoxTypeAhead
Inherits ListBox
Dim Buffer As String
Dim WithEvents Timer1 As New Timer
Private Sub ListBoxTypeAhead_KeyDown(sender As Object, _
e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
Select Case e.KeyCode
Case Keys.A To Keys.Z, Keys.NumPad0 To Keys.NumPad9
e.SuppressKeyPress = True
Buffer &= Chr(e.KeyValue)
Me.SelectedIndex = Me.FindString(Buffer)
Timer1.Start()
Case Else
Timer1.Stop()
Buffer = ""
End Select
End Sub
Private Sub ListBoxTypeAhead_LostFocus(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.LostFocus
Timer1.Stop()
Buffer = ""
End Sub
Public Sub New()
Timer1.Interval = 2000
End Sub
Private Sub Timer1_Tick(sender As Object, e As System.EventArgs) Handles Timer1.Tick
Timer1.Stop()
Buffer = ""
End Sub
End Class
As you probably know, this feature is called 'type ahead,' and it's not built into the Winform ListBox (so you're not missing a property).
You can get the type-ahead functionality on the ListView control if you set its View property to List.
Public Function CheckIfExistInCombo(ByVal objCombo As Object, ByVal TextToFind As String) As Boolean
Dim NumOfItems As Object 'The Number Of Items In ComboBox
Dim IndexNum As Integer 'Index
NumOfItems = objCombo.ListCount
For IndexNum = 0 To NumOfItems - 1
If objCombo.List(IndexNum) = TextToFind Then
CheckIfExistInCombo = True
Exit Function
End If
Next IndexNum
CheckIfExistInCombo = False
End Function