autocomplete combobox for vb.net word by word or with delimiters - vb.net

I am trying to add word by word (and/or with delimiter support) autocomplete functionality to a combobox (or textbox) in a vb.net application. This is the desired behavior, stripped down to one delimiter for this question:
When the user types 'invoke XX ' in the box, a list of suggested words should pop up. The user should still be able to type through, continually filtering those words, until he/she selects one. For example:
'invoke 121 ' (should prompt a list of words such as Display, DVD, CD, still with typethrough)
I can handle much of the actual logic, but I'm pretty new to vb.net, and I'm unsure of the best way to trigger an autocomplete popup on a space (or period), word by word. (Regular combobox autocomplete doesn't support word by word.) This post looks helpful but I don't know how to convert the accepted answer's developer-express tools into regular .net components: autocomplete text box for .net with support for delimiter. Ideally it'd work a lot like intellisense.
Here is my attempt so far (just a form with a textbox), stripped down. I need help figuring out how to allow typing to filter through the contextmenustrip. I also wonder if I'd be better off with something other than a contextmenustrip. Thanks
Public Class Form1
Private WithEvents firstContextMenu As ContextMenuStrip
' Populate items for the contextmenustrip
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
firstContextMenu = New ContextMenuStrip()
firstContextMenu.Items.Add("Display")
firstContextMenu.Items.Add("DVD")
firstContextMenu.Items.Add("CD")
End Sub
' Bring up the contextmenustrip if typing a space, if after 'invoke XX '
Private Sub TextBox1_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox1.KeyPress
If e.KeyChar = " " And TextBox1.Text.StartsWith("invoke ", StringComparison.CurrentCultureIgnoreCase) And TextBox1.Text.Split().Length = 2 Then
TextBox1.Text = TextBox1.Text + " " ' Add a space
TextBox1.Select(TextBox1.Text.Length, 0) ' To put the cursor at the end of the textbox
Dim point As New System.Drawing.Point(TextBox1.GetPositionFromCharIndex(DirectCast(sender, TextBox).SelectionStart - 1).X, TextBox1.Height - 5)
firstContextMenu.Show(TextBox1, point, ToolStripDropDownDirection.BelowRight)
End If
End Sub
' Adds the clicked item to the textbox
Private Sub firstContextMenu_ItemClicked1(sender As Object, e As ToolStripItemClickedEventArgs) Handles firstContextMenu.ItemClicked
TextBox1.Text = TextBox1.Text + e.ClickedItem.ToString()
TextBox1.Select(TextBox1.Text.Length, 0)
End Sub
' Moves cursor to end of textbox if you 'escape' out of the contextmenustrip
Private Sub firstContextMenu_Closed(sender As Object, e As ToolStripDropDownClosedEventArgs) Handles firstContextMenu.Closed
TextBox1.Select(TextBox1.Text.Length, 0)
End Sub
End Class

Related

Visual Basic - Displaying a combo box entry over a textbox

to keep it short and simple, I am attempting to display a number selected through a combo box on a second form through a label.
Here are the bits that are relevant to the issue I am having:
for i = 1 To 31
cmb_days.Items.Add(i)
next
' Populating combo box
days = cmb_days.text
frm_result.lbl_renting = "Renting for " & days & " Days"
I have tried using other variants such as cmb_days.selecteditem to no avail
I am also kinda having issues with like telling my code to do things with whatever number is actually selected in the combo box, idk I am veryyyyy new
That looks more or less correct.. But the question is where your call to update the label occurs in your code...
This is a minimal working example, with just a ComboBox on a form:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For i = 1 To 31
ComboBox1.Items.Add(i)
Next
End Sub
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
Me.Text = ComboBox1.Text
End Sub
End Class
The update of the label (in this case the text of the form, but it works exactly the same with a label on a different form) occurs in the SelectedIndexChanged event of ComboBox1.
If you copied your code straight from the project, you haven't had time to select anything yet, so the Text property will be empty.

vb.net add values from listbox to textbox via drag&drop AND via double click

I'm struggling with some functionality I want to use on my Windows form.
( Just for info, this is for an AutoDesk Inventor AddIn. )
This is my form layout.
The current workflow
The top 4 list-boxes are filled with available parameter names. The user chooses the parameter(s) he/she wants to use and drags and drops it into one of the driving parameter text-boxes ( marked with the <1> label ).
The code that relates to the drag and drop operations
Private Sub lstTemp_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles lbModelParameters.MouseDown,
lbUserParameters.MouseDown,
lbReferenceParameters.MouseDown,
lbLinkedParameters.MouseDown
' In order to access a specific item in a listbox.itemcollection, you must think of it
' as an array of data or a collection and access it in the same manner by always
' letting VB know which item you intend to use by identifying it with its index location
' within the collection. And this is better than taking up basket weaving :-)
lbModelParameters.DoDragDrop(sender.Items(sender.SelectedIndex()).ToString, DragDropEffects.Move)
End Sub
Private Sub txtTemp_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) _
Handles tbParameter1.DragEnter,
tbParameter2.DragEnter,
tbParameter3.DragEnter,
tbParameter4.DragEnter,
tbParameter5.DragEnter
'Check the format of the incoming data and accept it if the destination control is able to handle
' the data format
'Data verification
If e.Data().GetDataPresent(DataFormats.Text) Then
e.Effect() = DragDropEffects.Move
Else
e.Effect() = DragDropEffects.None
End If
End Sub
Private Sub txtTemp_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) _
Handles tbParameter1.DragDrop,
tbParameter2.DragDrop,
tbParameter3.DragDrop,
tbParameter4.DragDrop,
tbParameter5.DragDrop
'This procedure receives the dragged data once it passes the data verification handled by the DragEnter method
'Drops the data onto the destination control
sender.Text() = e.Data().GetData(DataFormats.Text).ToString()
End Sub
New functionality
Now I would like to decrease the user mouse movement for ergonomic reasons and speed. But I would also like to keep to the drag and drop functionality. As it can overwrite a value that has already been added by the user.
I would like to be able to DoubleClick a item in the listbox, and that item should be added to the first empty textbox. I named my textboxes with a number so it's easy to loop over them all to check if it's empty.
I tried doing it with this code, but my double click event never gets fired. It always goes to the drag and drop. How do you do handle this, that the double click gets fired instead of drag drop?
Private Sub ParameterAddDoubleClick(sender As Object, e As EventArgs) _
Handles lbModelParameters.DoubleClick,
lbUserParameters.DoubleClick,
lbReferenceParameters.DoubleClick,
lbLinkedParameters.DoubleClick
Dim oControl As Windows.Forms.ListBox
oControl = DirectCast(sender, Windows.Forms.ListBox)
' Add line in likedparameters listbox
If oControl.SelectedIndex <> -1 Then
' Loop trough all the controls to see if one is empty
' if it's empty add parameter, else go to next
' if all textboxes are used do nothing.
For i = 1 To 6
Dim oTextbox As Windows.Forms.TextBox =
CType(gbDrivingParameters.Controls("tbParameter" & i),
Windows.Forms.TextBox)
If oTextbox.TextLength = 0 Then
' Add the sender item into the linked listbox
oTextbox.Text = oControl.Items.Item(oControl.SelectedIndex)
End If
Next
End If
End Sub
I hope my question is clear, and well prepared. If there is additional information needed, please let me know in a comment.
Mousedown triggers the DoDragDrop, wich stops the doubleclick-event from firing.
To identify if a user doubleclicks or wants to perform a dragdrop, consider the following:
Private Sub ListBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles ListBox1.MouseDown
' Determine whether we are performing a drag operation OR a double click
If e.Clicks = 1 Then
TextBox1.Text = "mousedown"
Else
TextBox1.Text = "dblclick"
End If
End Sub

Eliminate user input of single quote for every field in every form in a project

using VB.net and Visual studio is there a way to globally (across all fields in all forms in a project) prevent a user from entering a single quote character into a field. I'm thinking some sort of modification of the keypress event, but I'm not sure how to go about it. Currently I am currently using the ADDHANDLER, ADDRESSOF technique to assign this special code to a particular field, and I guess I could do a loop over all my contained controls on a particular form and assign the keypress code, but if there is a way to do it once without repeating the code on each form that would be great. Any hints graciously accepted. Thanks.
What about a regex? Stripping or converting to escape character on submit?
Would it be possible to pass an array where TextBox1 is TextBox1,Textbox2, etc. Obviously you would need to edit the expression if you only need to strip the single quote. This for all non alphabet characters. I don't do much with vb.net but thought this might be helpful.
Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
StripNonAlphabetCharacters(TextBox1)
End Sub
Public Sub StripNonAlphabetCharacters(ByVal input As TextBox)
' pattern matches any character that is NOT A-Z (allows upper and lower case alphabets)
Dim rx As New Regex("[^a-zA-Z]")
If (rx.IsMatch(input.Text)) Then
Dim startPosition As Integer = input.SelectionStart - 1
input.Text = rx.Replace(input.Text, "")
input.SelectionStart = startPosition
End If
End Sub
The easiest solution is to create a derived TextBox and use that instead of the standard textbox.
Public Class NoQuoteTextBox
Inherits TextBox
Public Sub New()
AddHandler MyBase.KeyPress, AddressOf Handler
End Sub
Public Sub Handler(ByVal sender As Object, ByVal e As KeyPressEventArgs)
If e.KeyChar = "'" Then
e.Handled = True
End If
End Sub
End Class
Build your project then the NoQuoteTextBox (or whatever you decide to call it) will appear in the Toolbox in the Designer:
Use this new control instead of the standard textbox and it will handle the single quote for you.

How to create Control Arrays in VB .NET

In VB6 there is a feature called Control Arrays, where you name controls the same name and provide them an index value. This allows you to set a value by looping through the controls and setting each value. In VB .NET I can't create a control array could someone provide me with a similar solution.
Here is a sample I wrote for something else that shows how to do something similar and shows how to do the handler as well. This makes a 10x10 grid of buttons that turn red when you click them.
Dim IsCreated(99) As Boolean
Dim Buttons As New Dictionary(Of String, Button)
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
For i As Integer = 0 To 99
Dim B As New Button
Me.Controls.Add(B)
B.Height = 30
B.Width = 40
B.Left = (i Mod 10) * 41
B.Top = (i \ 10) * 31
B.Text = Chr((i \ 10) + Asc("A")) & i Mod 10 + 1
Buttons.Add(B.Text, B)
B.Tag = i
AddHandler B.Click, AddressOf Button_Click
Next
End Sub
Private Sub Button_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim B As Button = sender
IsCreated(B.Tag) = True
B.BackColor = Color.Red
End Sub
Avoid using the proposed iteration approaches, you'll get a fairly random collection of controls unless your form is very simple. Simply declare the control array in your code and initialize it in the form constructor. Like this:
Public Class Form1
Private OrderNumbers() As TextBox
Public Sub New()
InitializeComponent()
OrderNumbers = New TextBox() {TextBox1, TextBox2}
End Sub
End Class
You can now treat OrderNumbers just like you could in VB6.
Maybe this is simpler. To create a control array, I put the control array declaration in a module. For example, if I have a Form with three TextBoxes and I want the TextBoxes to be part of a control array called 'mytext', I declare my control array in a module as follows:
Module Module1
Public mytext() As TextBox = {Form1.TextBox1, Form1.TextBox2, Form1.TextBox3}
End Module
And, I use the TextBoxes from the control array as follows:
Public Class Form1
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
mytext(0).Text = "Hello"
mytext(1).Text = "Hi"
mytext(2).Text = "There"
End Sub
End Class
You can even loop through the control array, like you could in VB6:
Public Class Form1
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
For i As Integer = 0 To 2
mytext(i).Text = i + 1
Next
End Sub
End Class
The beauty of using a module is that the TextBoxes do not even need to be in the same form.
With Winforms, you could do this:
myForm.Controls _
.OfType(Of TextBox) _
.OrderBy(Function(c) c.Name) _
.Where(Function(c) c.Name.StartsWith("somePrefix")) _
.ToArray()
On your form you would name your textboxes somePrefix1, somePrefix2, etc.
Here is an old article but it could give you more information. The top method is super easy.
Your Form, or PanelControl, or anything else that can contain child controls will have a Property called Controls.
You can loop through all of the text boxes in a control by using
'Create a List of TextBoxes, like an Array but better
Dim myTextBoxControls As New List
For Each uxControl As UserControl in MyFormName.Controls
If TypeOf(uControl) is TextBox
myTextBoxControls.Add(uControl)
End IF
Next
Now you have your iterate-able collection you can work with.
You can access a TextBoxes value with the EditValue property.
After looking at what you're trying to do a little further.
You probably want to name all of your controls with a Prefix, let's say abc for now.
For Each uxControl As UserControl in MyFormName.Controls
If TypeOf(uControl) is TextBox Then
Dim tbControl As TextBox = DirectCast(uControl, TextBox)
If tbControl.Name.StartsWith("abc") Then
tbControl.EditValue = "the Value you want to initialize"
End If
End If
Next
So this is one of the features that did not make the transition to VB.NET -- exactly :-( However, you can accomplish much of what you would have done in VB6 with two different mechanisms in .NET: Looping through the controls collection and handling control events.
Looping Through the Controls Collection
In VB.NET every form and control container has a controls collection. This is a collection that you can loop through and then do an operation on the control like set the value.
Dim myTxt As TextBox
For Each ctl As Control In Me.Controls
If TypeOf ctl Is TextBox Then
myTxt = CType(ctl, TextBox)
myTxt.Text = "something"
End If
Next
In this code sample you iterate over the controls collection testing the type of the returned object. If you find a textbox, cast it to a textbox and then do something with it.
Handling Control Events
You can also handle events over multiple controls with one event handler like you would have using the control array in VB6. To do this you will use the Handles keyword.
Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged, TextBox2.TextChanged, TextBox3.TextChanged
Dim myTxt As TextBox = CType(sender, TextBox)
MessageBox.Show(myTxt.Text)
End Sub
The key here is the Handles keyword on the end of the event handler. You separate out the various controls that you want to handle and the event by using a comma. Make sure that you are handling controls that have the same event declaration. If you ever wondered what sender was for on every event well here's one of the uses for it. Cast the sender argument to the type of control that you are working with and assign it to a local variable. You will then be able to access and manipulate the control that fired the event just like you would have in VB6 if you specified and index to the array.
Using these two techniques you can replicate the functionality of control arrays in VB6. Good luck.
Private Sub Button3_Click(sender As System.Object, e As System.EventArgs) Handles Button3.Click
Dim a() As Control = GetControls("textbox")
For Each c As TextBox In a
c.Text = c.Name
Next
End Sub
Private Function GetControls(typeOfControl As String) As Control()
Dim allControls As New List(Of Control)
'this loop will get all the controls on the form
'no matter what the level of container nesting
'thanks to jmcilhinney at vbforums
Dim ctl As Control = Me.GetNextControl(Me, True)
Do Until ctl Is Nothing
allControls.Add(ctl)
ctl = Me.GetNextControl(ctl, True)
Loop
'now return the controls you want
Return allControls.OrderBy(Function(c) c.Name). _
Where( _
Function(c) (c.GetType.ToString.ToLower.Contains(typeOfControl.ToLower) AndAlso _
c.Name.Contains("Box")) _
).ToArray()
End Function

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