Part of an application I'm building in VB has terminal functionality, and I'd like to add functionality to it so it remembers past commands in chronological order, similarly to how a windosw terminal works.
In short I'd like you to be able to press the up arrow when the text area is focused and be able to cycle through a list of commands that were entered previously.
I had two ideas for how to do this:
A combobox that, when you hit enter, reads whatever is in combobox.text, whether that be a newly entered command or an old one that was selected. Then add that command to the items of the combobox so you could scroll up and find it again.
Simply a textbox that, when the up arrow is pressed, cycles through some stored queue and sets the text accordingly. This would requires a second queue to remember the cycled through commands and replace them, correct?
Are there any built-in structures of Visual Basic that would be better for this, or is there some better way of doing it?
Thanks for your help.
It sounds like you are looking for something like a Most Recently Used List.
You idea for the ComboBox
is probably generally the right thing to do. To do what you suggest with a TextBox
would largely result in...a ComboBox
.
Considerations:
- Is it is case sensitive? Does
FooBar
match fooBar
? - A Stack (or Queue) is not the right tool for this because if they use something from index 4 in the list, there is no easy way to move that item from #4 to #1.
- To use this with a
ComboBox
as the UI picker, you want to use something that will work as a binding source.
Here is a nascent MRU class:
Public Class MRUList Private myList As BindingList(Of String) Public ReadOnly Property DataList As BindingList(Of String) Get Return myList '.Select(Function(f) f.Value).ToList End Get End Property Private myCapacity As Integer Public Sub New(capacity As Integer) myCapacity = capacity myList = New BindingList(Of String) End Sub Public Overloads Sub Add(item As String) Dim ndx As Integer = IndexOfKey(item) If ndx >= 0 Then myList.RemoveAt(ndx) End If myList.Insert(0, item) If myList.Count > myCapacity Then myList.RemoveAt(myList.Count - 1) End If End Sub ' case insensitive search Private Function IndexOfKey(s As String) As Integer Return myList.ToList.FindIndex(Function(f) f.Equals(s, StringComparison.InvariantCultureIgnoreCase)) End Function End Class
- When they pick an existing item, it moves from whence it was to the top of the list.
- It is case insensitive, "Able" matches "ABLE". But it is case-aware: if/when they type an item again, it uses the new casing. So if "ZIggy" is in the list at slot 3, if they type in "Ziggy" correctly, they old one is removed and the new one used.
- There is a capacity limiter so you wont get absurdly long lists. When the lists gets too long, old items are dropped.
- It is built from a
BindingList(Of String)
so that you can bind it to a Listbox
or ComboBox
.
The List management is pretty wasteful under the hood. Each time we insert a new item at myList(0)
.NET has to shift and jigger the underlying array around. The ideal collection type would be a LinkedList
, but that wont work as a binding source and I dont suspect you will have 1000s of items being stored.
Usage:
Private myMRU As New MRUList(8) ... ' bind to CBO in form_load: cboMRU.DataSource = myMRU.DataList
As things are added to the list they will automatically appear in the list. When the user makes a selection
Private Sub cboMRU_Leave(sender As Object, e As EventArgs) Handles cboMRU.Leave If cboMRU.Text.Length = 0 Then Exit Sub Dim thisCmd As String = cboMRU.Text myMRU.Add(thisCmd) cboMRU.Text = "" End Sub
I used the Leave
event because they can pick from the list or select an item from the list. You code doesn't need to do anything as far as checking for new vs existing items, the Add
method does that for you.
On the left, I entered 4 items, Delta
being the last one. Next, I typed in Able
correctly. The class removed the old one and floated the new one to the top as the MRU with the new spelling.
Since these mean something to your code, use thisCmd
in the event for whatever they are. For more complex things, perhaps where what they type is just a key or token for something else, use a BindingList(of TokenItem)
in the MRU
Class
Better than a queue would be an array. The queue allow only sequential access, and after you dequeue it, the object you dequeue get "lost". If you want save it and have also in future session, you can use a file, such cookie or what I prefere, also because prepare the ground for future expansions, an embedded database, such sqlite or firebird. The second one is an incredible powerful database, that allow if you want to get a server, a powerful server