Search listbox for string, DataSource property set - vb.net

I'm trying to make TextBox1 a search bar, to search for specific strings in ListBox1.
I want it to remove other items that don't have the string I searched. The list shows all files in a specific directory, so if I search "icon_" it would only show files with icon_ in the name. Is this possible?
I've asked this question a while ago, but I can't use any of the answers because the listbox is being populated by filenames from a specific directory, which gives me this error:
Items collection cannot be modified when the DataSource property is set.

Lots of different ways to do this.
This method puts your list of files into a DataTable and using a BindingSource, you can use it's Filter property to filter the list.
Here is a form with a ListBox and a TextBox:
Public Class Form1
Dim bs As New BindingSource
Public Sub New()
InitializeComponent()
End Sub
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
MyBase.OnLoad(e)
Dim testPath As String = "c:\MyPath"
Dim dt As New DataTable
dt.Columns.Add("File", GetType(String))
For Each f As String In Directory.GetFiles(testPath)
Dim row As DataRow = dt.NewRow
row("File") = Path.GetFileName(f)
dt.Rows.Add(row)
Next
bs.DataSource = dt
ListBox1.DisplayMember = "File"
ListBox1.ValueMember = "File"
ListBox1.DataSource = bs
End Sub
Private Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As EventArgs) Handles TextBox1.TextChanged
bs.Filter = String.Format("File LIKE '*{0}*'", TextBox1.Text)
End Sub
End Class

When the DataSource property is set, you cannot modify the list. While it is convenient to populate a ListBox control using the DataSource property, it is certainly not necessary. You can add the items to the control using its Items.Add method, instead. For instance, borrowing from my answer to your previous question:
Public Class FileSearchTool
Public Sub New(ByVal listBox As ListBox, ByVal textBox As TextBox)
_listBox = listBox
_textBox = textBox
End Sub
Private _listBox As ListBox
Private WithEvents _textBox As TextBox
Private _fileNames As New List(Of String)()
Private _folderPath As String
Public Property FolderPath() As String
Get
Return _folderPath
End Get
Set(ByVal value As String)
_folderPath = value
loadFilePaths()
End Set
End Property
Private Sub loadFilePaths()
_fileNames = New List(Of String)(Directory.GetFiles(_folderPath))
refreshList()
End Sub
Private Sub _textBox_TextChanged(ByVal sender As Object, ByVal e As EventArgs) Handles _textBox.TextChanged
refreshList()
End Sub
Private Sub refreshList()
_listBox.SuspendLayout()
_listBox.Items.Clear()
For Each item As String In _fileNames
If item.StartsWith(_textBox.Text, StringComparison.CurrentCultureIgnoreCase) Then
_listBox.Items.Add(item)
End If
Next
_listBox.ResumeLayout()
End Sub
End Class
Then in any form you could use it like this:
Public Class Form1
Private _tool As FileSearchTool
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
_tool = New FileSearchTool(ListBox1, TextBox1)
_tool.FolderPath = "C:\"
End Sub
End Class
But, at that point, you might as well just encapsulate it further by creating a FileSearch user control.
Or, As I said in my answer to your previous question, if all you want is an auto complete box, you can just use a text box without a listbox at all as such:
Dim source As New AutoCompleteStringCollection()
source.AddRange(Directory.GetFiles("C:\"))
TextBox1.AutoCompleteCustomSource = source
TextBox1.AutoCompleteMode = AutoCompleteMode.Suggest
TextBox1.AutoCompleteSource = AutoCompleteSource.CustomSource
In fact, another interesting option is that you can set AutoCompleteSource to FileSystem, which you may want to play with.

Related

ContextMenuStrip Requires Two Right Clicks to Display

I like to create my contextmenu's programmatically. I generally do not add the items to the contextmenustrip until it is opening as the items that get displayed are dependent on other aspects of the design that are variable.
I have found that the contextmenustrips seem to require two right clicks to display. I've tried adding the menu items in different events (opening, opened, etc) and also manually setting the contextmenustrip's visibility to true to no avail.
I can't for the life of me figure out why two right clicks are necessary. If you create a blank winforms project and then replace all the code with this, it'll demonstrate the issue.
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim currContextMenuStrip As New ContextMenuStrip
Me.ContextMenuStrip = currContextMenuStrip
AddHandler currContextMenuStrip.Opening, AddressOf ContextMenuStrip1_Opening
End Sub
Private Sub ContextMenuStrip1_Opening(sender As Object, e As CancelEventArgs)
Dim currContextMenuStrip As ContextMenuStrip = sender
Dim menuTxt As String = "&Find"
'only add the menu if it doesn't already exist
If (From f In currContextMenuStrip.Items Where f.text = menuTxt).Count = 0 Then
Dim newMenuItem As New ToolStripMenuItem
newMenuItem.Text = menuTxt
currContextMenuStrip.Items.Add(newMenuItem)
End If
End Sub
End Class
EDIT: Just figured out it seems to be connected to the fact that the contextmenustrip doesn't have any items on the first right click. If I add a dummy item, then hide it once other items are added, it works on the first right click. So confused!
This works:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim currContextMenuStrip As New ContextMenuStrip
Me.ContextMenuStrip = currContextMenuStrip
AddHandler currContextMenuStrip.Opening, AddressOf ContextMenuStrip1_Opening
'add a dummy item
Dim newMenuItem As New ToolStripMenuItem
newMenuItem.Text = "dummy"
currContextMenuStrip.Items.Add(newMenuItem)
End Sub
Private Sub ContextMenuStrip1_Opening(sender As Object, e As CancelEventArgs)
Dim currContextMenuStrip As ContextMenuStrip = sender
Dim menuTxt As String = "&Find"
'only add the menu if it doesn't already exist
If (From f In currContextMenuStrip.Items Where f.text = menuTxt).Count = 0 Then
Dim newMenuItem As New ToolStripMenuItem
newMenuItem.Text = menuTxt
currContextMenuStrip.Items.Add(newMenuItem)
End If
'hide the dummy item
Dim items As List(Of ToolStripMenuItem) = (From f As ToolStripMenuItem In currContextMenuStrip.Items Where f.Text = "dummy").ToList
items.First.visible = False
End Sub
End Class
If you really need to do things this way, one option is to create your own custom ContextMenuStrip that accounts for the behaviour when there are no items and the requirement for a dummy item. I used this code:
Imports System.ComponentModel
Public Class Form1
Private WithEvents menu As New ContextMenuStrip
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ContextMenuStrip = menu
End Sub
Private Sub menu_Opening(sender As Object, e As CancelEventArgs) Handles menu.Opening
If menu.Items.Count = 0 Then
menu.Items.AddRange({New ToolStripMenuItem("First"),
New ToolStripMenuItem("Second"),
New ToolStripMenuItem("Third")})
End If
End Sub
Private Sub menu_ItemClicked(sender As Object, e As ToolStripItemClickedEventArgs) Handles menu.ItemClicked
MessageBox.Show(e.ClickedItem.Text)
End Sub
End Class
and saw the same behaviour you describe. I then defined this class:
Public Class ContextMenuStripEx
Inherits ContextMenuStrip
Private dummyItem As ToolStripItem
Public ReadOnly Property IsInitialised As Boolean
Get
Return dummyItem Is Nothing
End Get
End Property
Public Sub New()
dummyItem = Items.Add(CStr(Nothing))
End Sub
''' <inheritdoc />
Protected Overrides Sub OnItemAdded(e As ToolStripItemEventArgs)
If Not IsInitialised Then
Items.Remove(dummyItem)
dummyItem = Nothing
End If
MyBase.OnItemAdded(e)
End Sub
End Class
and changed my form code to this:
Imports System.ComponentModel
Public Class Form1
Private WithEvents menu As New ContextMenuStripEx
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ContextMenuStrip = menu
End Sub
Private Sub menu_Opening(sender As Object, e As CancelEventArgs) Handles menu.Opening
If Not menu.IsInitialised Then
menu.Items.AddRange({New ToolStripMenuItem("First"),
New ToolStripMenuItem("Second"),
New ToolStripMenuItem("Third")})
End If
End Sub
Private Sub menu_ItemClicked(sender As Object, e As ToolStripItemClickedEventArgs) Handles menu.ItemClicked
MessageBox.Show(e.ClickedItem.Text)
End Sub
End Class
and it worked as desired. Note that the last code snippet uses the custom type and its custom property.
Thanks for all the help and suggestions! I ultimately decided to build the superset of menus in the Designer and then just show/hide at run time. That's probably faster on each right click then rebuilding the menu each time.
Microsoft has old style and new style context menus. The Popup event was used for the old style context menus and it received a plain EventArgs object. The new context menus use the Opening event which receives a CancelEventArgs object. If currContextMenuStrip.Items doesn't contain any items, e.Cancel will be set to True when the event handler is called (which caused the problem you encountered). The fix is to add the menu items and then set e.Cancel to False. It should display fine after that. To make sure items were actually added, the assignment of e.Cancel can be guarded with an if statement as follows:
If currContextMenuStrip.Items.Count <> 0 Then
e.Cancel = False
End If

Try to read text , put on list , then compare on list and finally replace txt file

Trying to read a .txt file , put items to list, then on textbox change compare if the string exists in the list. Finally write the new list on the same .txt file.
Public Class Form1
Dim stockList As New List(Of String)
private sub
ListBox1.Items.AddRange(IO.File.ReadAllLines("C:\Users\path\file.txt"))
end sub
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles
TextBox1.ReadOnlyChanged
Dim text As String = TextBox1.Text
If TextBox1.Text <> "TRAINING" Then
For Each item As Object In ListBox1.Items
If item.ToString = text Then
MsgBox("This code has already been used.", 0, "cheat attempt violation") ' Else alert the user that item already exist
Else
ListBox1.Items.Add(TextBox1.Text)
End If
Next
End If
IO.File.WriteAllLines("C:\Users\path\file.txt", ListBox1.Items.Cast(Of String).ToArray)
End Sub
Instead of using a UI control to store data, you should store the data in a variable. I'm not sure if you really need to show the data in a ListBox, so in the following example code I didn't.
If you use a List(Of String) instead of an array of strings, it is simpler to add another item before saving it.
The Contains method I used in the example can take a second parameter which does the comparison of the item - I guessed that you might want to ignore the case (e.g. "ABC" is the same as "aBc") so I used StringComparer.CurrentCultureIgnoreCase.
I suspect that you want to use the Control.Validating Event instead of the TextChanged event. When the data has been validated, the Control.Validated event handler is used to save it.
I put one TextBox and one Button on the form, so that the focus could change away from the TextBox, e.g. when pressing tab, to fire the validating event.
Imports System.IO
Public Class Form1
Dim dataFile As String = "C:\temp\grains.txt"
Dim alreadyUsed As List(Of String) = Nothing
Sub LoadAlreadyUsed(filename As String)
'TODO: Add exception checking, e.g., the file might not exist.
alreadyUsed = File.ReadAllLines(filename).ToList()
End Sub
Sub SaveAlreadyUsed(filename As String)
File.WriteAllLines(dataFile, alreadyUsed)
End Sub
Function CodeIsAlreadyUsed(newCode As String) As Boolean
Return alreadyUsed.Contains(newCode, StringComparer.CurrentCultureIgnoreCase)
End Function
Private Sub TextBox1_Validating(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles TextBox1.Validating
' Do nothing if the user has clicked the form close button
' See https://stackoverflow.com/questions/15920770/closing-the-c-sharp-windows-form-by-avoiding-textbox-validation
If Me.ActiveControl.Equals(sender) Then
Exit Sub
End If
Dim txt = DirectCast(sender, TextBox).Text
If CodeIsAlreadyUsed(txt) Then
MessageBox.Show("This code has already been used.", "Cheat attempt violation", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
e.Cancel = True
End If
End Sub
Private Sub TextBox1_Validated(sender As Object, e As EventArgs) Handles TextBox1.Validated
' The Validated event is raised before the FormClosing event.
' We do not want to save the data when the form is closing.
If Me.ActiveControl.Equals(sender) Then
Exit Sub
End If
Dim txt = DirectCast(sender, TextBox).Text
alreadyUsed.Add(txt)
SaveAlreadyUsed(dataFile)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
LoadAlreadyUsed(dataFile)
End Sub
End Class

ListChanged Event not firing?

I have a class with a bindinglist(of T) in it. The bindinglist is bound to a datagridview on my form. When items are added to the bindinglist, they show up in the datagridview however the scrollbar never changes to accommodate for the new data. I am starting to think this is because the Listchanged event isn't being fired (or properly captured by my form). I have my code set up like this:
Data Class:
Public Class data
Implements INotifyPropertyChanged
Public Sub new(byVal att1 as string, ByVal att2 as string)
Attribute1 = att1
Attribute2 = att2
End sub
Private mAttribute1 as string
Public Property Attribute1 as string
Get
return mAttribute1
End get
Set(ByVal value as string)
mAttribute1 = value
OnPropertyChanged("Attribute1")
End Set
End Property
Private mAttribute2 as string
Public Property Attribute2 as string
Get
return mAttribute2
End Get
Set(ByVal value as string)
mAttribute2 = value
OnPropertyChanged("Attribute2")
End Set
End Property
Public Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
Public Sub ChangeDataFormat()
'change from one format to the other
End Sub
End Class
Data Generator Class:
Public Class dataGenerator()
private myThread as New System.Theading.Thread(address of StartDataGeneration)
Public Sub new()
mDataList = new bindingList(of Data)
mDataList.RaiseListChangedEvents = True
Private WithEvents mDataList as bindingList(Of Data)
Public readonly DataList as bindingList(of Data)
Get
Return mDataList
End Get
End property
Private Sub StartDataGeneration()
dim att1 as integer = 1
dim att2 as integer = 2
for i as Integer = 0 to 1000
mDataList.Insert(0,New Data(att1.ToString,att2.ToString)
att1 *= 2
att2 *=3
next
End Sub
Public Sub StartDataThread()
myThread.Start()
End Sub
Public Sub ChangeDataFormat()
for each d as data in mDataList
d.ChangeDataFormat()
next
End Sub
End Class
Form:
Public class Form1
Private myGenerators as new BindingList(of dataGenerator)
Private myDataGrids as new BindingList(of DataGridView)
Private Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Mybase.Load
dim NumberOfGenerators as integer = Convert.ToInt32(My.Settings.CraneCount)
for i as integer = 1 to NumberOfGenerators
Dim newGenerator As New DataGenerator()
Dim newTab as Ne tabPage(i.ToString)
Dim NewGrid as New DataGridView
newTab.Controls.Add(newGrid)
newGrid.DataSource = newGenerator.DataList
myGenerators.Add(newGrid)
next
End Sub
Private Sub ButtonStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonStart.Click
for each generator as dataGenerator in myGenerators
generator.StartDataThread()
next
End Sub
Private Sub ButtonChangeFormat_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonChangeFormat.Click
for each generator as dataGenerator in myGenerators
generator.ChangeDataFormat()
next
End Sub
End Class
I know that there is a lot of code but I wanted to be clear. So when I click the start button the new items start appearing, however, once they get to the bottom of the grid the scroll bar doesn't appear. If I click the Change Format button the data changes format and updates in the grid properly. I was under the impression that the ListChanged event would automatically work with a bindinglist and datagridview. I tried calling update and refresh on myDataGridView and setting datagridview.datasource to nothing and then back to DataList.
Am I missing something?

how to populate combobox in a usercontrol from an array in a class

I have a program in vb.net with a class and an usercontrol with a combobox. I want to populate the combobox with an array in the class. If user selects a value in combobox in usercontrol and return that value into class. I tried lot but there is no use.
Is it possible? If yes, then please guide me how to do it.
I have written the code for usercontrol as container1 as follows:
Private Sub container1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
For Each i As Double In yl
Me.ComboBox1.Items.Add(i)
Next
End Sub
ERROR: Name yl is not declared.
yl is an array in my main class in the program.
thanks in advance
gvg
Ok you can do this in four steps:
Create a custom event in the usercontrol that fires when the selectedindex in the combobox is changed
Create a function that populates the combobox
Create a readonly property in your class that returns the items for the combobox
Mix this all together in the base form
First add some functionality to your userform
Public Class CtrlBox
'This is a usercontrol, created in VS
'The designer is not shown in this example
'This event is used to relay changes to the combobox to the outside world
Public Event ComboboxSelectionChanged(ByVal NewIndex As Integer, ByVal NewText As String)
'This Sub sets the combobox items
Public Sub SetComboboxItems(Newitems() As String)
Me.ComboBox1.Items.Clear()
Me.ComboBox1.Items.AddRange(Newitems)
End Sub
'Here the custom event is raised
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
RaiseEvent ComboboxSelectionChanged(Me.ComboBox1.SelectedIndex, Me.ComboBox1.Items(Me.ComboBox1.SelectedIndex).ToString)
End Sub
End Class
The class that will contain the selected index is called clsStuff. Notice the readonly property I use to return the desired items in the combobox
Public Class clsStuff
'This is like a constant that returns the items you
'want to add to the combobox
Public ReadOnly Property CustomComboItems As String()
Get
Return {"Test 1", "Test 2", "Test 3", "Test 4"}
End Get
End Property
'This variable shall store the selected string
Public SomeString As String = ""
End Class
Then bring it all together in your main form
Public Class Form1
'Drag the usercontrol containing the combobox onto your form
'This object is the one used to populate the combobox
'and store the selected item
Dim StuffObj As clsStuff
'Here the object is initialized and the CB-Items are set
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
StuffObj = New clsStuff
Me.CtrlBox1.SetComboboxItems(StuffObj.CustomComboItems)
End Sub
'Here the custom event is handled and used to change the
'string in the object
Private Sub CtrlBox1_ComboboxSelectionChanged(NewIndex As Integer, NewText As String) Handles CtrlBox1.ComboboxSelectionChanged
If StuffObj IsNot Nothing Then
StuffObj.SomeString = NewText
MsgBox("Updated property in object: " & StuffObj.SomeString)
End If
End Sub
End Class
This scheme should work well for your needs.
It is not a good way to access main program variable from the UserControl, although there is Parent property.
(1), you should create a AddItems() method to add the items as follow in your UserControl.
Public Sub AddItems(ByVal yl As String())
ComboBox1.Items.Clear()
For Each i As String In yl
ComboBox1.Items.Add(i)
Next
End Sub
(2), create a SelectedText() read-only property as follow
Public ReadOnly Property SelectedText()
Get
Return ComboBox1.Text
End Get
End Property
(3), create an event SelectedIndexChanged
Public Event SelectedIndexChanged()
(4), fire the event
Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
RaiseEvent SelectedIndexChanged()
End Sub
(5), you can add items in your main form like this
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim yl() As String = {"Aa", "Bb", "Cc"}
Container1.AddItems(yl)
End Sub
(6), you can catch SelectedIndexChanged() event in your main form
Private Sub Container1_SelectedIndexChanged() Handles Container1.SelectedIndexChanged
Me.Text = Container1.SelectedText
End Sub

Creating a Combobox from a Class - In VB.net

im new to vb.net, and my query is:
file: read.vb
Friend Function Nav()
Dim NavBox As New ComboBox()
NavBox.Size = New System.Drawing.Size(44, 21)
NavBox.Location = New System.Drawing.Point(135, 305)
NavBox.DropDownStyle = ComboBoxStyle.DropDownList
NavBox.Items.Add("1")
NavBox.Items.Add("2")
NavBox.Items.Add("3")
NavBox.Items.Add("4")
NavBox.Items.Add("5")
NavBox.Items.Add("6")
NavBox.Items.Add("7")
NavBox.Items.Add("8")
NavBox.Items.Add("9")
NavBox.Items.Add("10")
NavBox.Items.Add("11")
AddHandler (NavBox.SelectionChangeCommitted), AddressOf MSGB
NavBox.Show()
Return NavBox
End Function
Public Sub MSGB(ByVal sender As Object, ByVal e As System.EventArgs)
Dim cb As ComboBox = DirectCast(sender, ComboBox)
MsgBox(cb.SelectedItem)
End Sub
And im calling this function or displaying this combobox in mainFrom.vb
file: mainFrom.vb
Dim l As New read
Me.Controls.Add(CType(l.Nav(), Control))
Now what i need is, a access to its control so i can parss value from mainFrom.vb to read.vb to manipulation the SelectionChangeCommitted activity.
I hope my question is clear....
Since you control is a simple ComboBox, I don't see the point of having common code to create this control. You can have a common function to populate it's contents though.
If you want to add functionality to the ComboBox, just create your own combo box class, let's say ExtendedComboBox, which inherits from ComboBox, and use it in your forms.
If you don't want to change your existing code, you can assign your ComboBox returned from Nav to a variable, and then hook into events from this ComboBox:
Public Class MainForm
'Declare NavBox as a form member
Private NavBox As ComboBox
Public Sub MainForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'Create NavBox
Dim read As New read
NavBox = read.Nav()
'Hook NavBox event so MainForm knows when selection is changed
AddHandler NavBox.SelectionChangeCommitted, AddressOf NavBox_SelectionChangedCommited
'Add NavBox to MainForm
Me.Controls.Add(NavBox)
End Sub
Public Sub NavBox_SelectionChangedCommited(ByVal sender As Object, ByVal e As System.EventArgs)
Dim cb As ComboBox = DirectCast(sender, ComboBox)
'Do something when selection changes.
End Sub
End Class
You should also remove the useless AddHandler line inside the Nav function.