-
May 14th, 2021, 10:08 AM
#1
Thread Starter
Hyperactive Member
[RESOLVED] DataGridView Autocomplete TextBox
I have a DataGridView that is ReadOnly. I would like to implement "type ahead" functionality for a column (DataGridViewTextBoxColumn) of the grid.
I have seen examples for how this is done. For example:
http://vb.net-informations.com/datagridview/vbauto.htm
The example in the above link works for a grid that is not read-only, but it doesn't work for a read-only grid because the EditingControlShowing event is never raised.
This example makes use of the DataGridViewEditingControlShowingEventArgs to cast the selected cell as a TextBox.
Code:
TryCast(e.Control, TextBox)
Is there a way to do this for a read-only grid?
Last edited by Mark@SF; May 14th, 2021 at 10:14 AM.
-
May 14th, 2021, 12:20 PM
#2
Re: DataGridView Autocomplete TextBox
Sorry, don't have an answer but I'm confused about wanting a textbox autocomplete when the grid is read only. What are you trying to accomplish?
-
May 14th, 2021, 02:14 PM
#3
Thread Starter
Hyperactive Member
Re: DataGridView Autocomplete TextBox
Originally Posted by wes4dbt
Sorry, don't have an answer but I'm confused about wanting a textbox autocomplete when the grid is read only. What are you trying to accomplish?
This application is a password manager. It has a TableLayoutPanel with a DataGridView on the left-hand side of the panel (dgvAccounts). The grid is a listing of all accounts. The user (me) clicks an account in the grid and the account details are shown in the right-hand side of the panel (password, security questions, notes, etc.).
I am interested in doing the following:
1. Give the Focus to the dgvAccounts grid.
2. Press a key (e.g., "b") and have the dgvAccounts selected item change to the first account in the grid that begins with the pressed key ("b").
3. Press a another key (e.g., "e") and now the selected item changes to the first account in the grid that begins with these 2 keys ("be").
4. Repeat #2 and #3 until the the user (me) finds the account of interest.
I can do this with the dgvAccounts.KeyPress method and a class-level Timer and a class-level Stopwatch...
Code:
Private WithEvents autotypeTimer As New Timer
Private WithEvents autotypeStopWatch As New Stopwatch
Dim autotypeText As String
Code:
Private Sub timerAutoType_Tick(sender As Object, e As EventArgs) Handles autotypeTimer.Tick
Dim strMethodName = New System.Diagnostics.StackTrace().GetFrame(0).GetMethod().Name '...this procedure's name
Dim flgDebug As Boolean = False '...debug/test purposes only
Try
If autotypeStopWatch.IsRunning Then
autotypeStopWatch.Stop()
autotypeStopWatch.Reset()
autotypeTimer.Stop()
autotypeText = ""
End If
Catch ex As Exception
MessageBox.Show(ex.GetType.ToString & vbCrLf & vbCrLf & ex.Message & " (err=" & Err.Number & ")", strMethodName, MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
Code:
Private Sub dgvAccounts_KeyPress(sender As Object, e As KeyPressEventArgs) Handles dgvAccounts.KeyPress
Dim strMethodName = New System.Diagnostics.StackTrace().GetFrame(0).GetMethod().Name '...this procedure's name
Dim flgDebug As Boolean = True '...debug/test purposes only
Dim strFilter As String
Dim index As Integer
Dim rows() As DataRow
Try
If Not autotypeStopWatch.IsRunning Then
autotypeStopWatch.Start()
autotypeTimer.Start()
End If
autotypeText += e.KeyChar
strFilter = "Account LIKE '" & autotypeText & "%'"
If rdoActive.Checked Then
strFilter += " AND Status = True"
ElseIf rdoInactive.Checked Then
strFilter += " AND Status = False"
ElseIf rdoBoth.Checked Then
'...returns active and inactive accounts
End If
If cbxFavorites.Checked Then
strFilter += " AND IsFavorite = True"
End If
rows = dsMain.Tables("Accounts").Select(filterExpression:=strFilter, sort:="Account")
If flgDebug Then Debug.WriteLine("{0}, {1}, Count = {2}", autotypeText, strFilter, rows.Length)
If rows.Length > 0 Then
If flgDebug Then
For i As Integer = 0 To rows.Count - 1
Debug.WriteLine("{0}, {1}", i, rows(i)("Account"))
Next
End If
index = bsAccounts.Find("Account", rows(0).Item("Account").ToString)
bsAccounts.Position = index
End If
Catch ex As Exception
MessageBox.Show(ex.GetType.ToString & vbCrLf & vbCrLf & ex.Message & " (err=" & Err.Number & ")", strMethodName, MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
rows = Nothing
End Try
End Sub
When I came across the example for how to use the DataGridViewEditingControlShowingEventArgs to cast the selected cell as a TextBox, it peaked my curiosity as to whether or not I could do the same for my read-only grid.
Last edited by Mark@SF; May 14th, 2021 at 02:18 PM.
-
May 14th, 2021, 09:07 PM
#4
Re: DataGridView Autocomplete TextBox
Forget about auto-complete. In order to complete something automatically, there has to be something to complete. If your grid is read-only then there's no data being entered so there's nothing to complete. Forget about auto-complete, unless you want to use a TextBox control that is completely separate to the grid.
Handling a keyboard event is the right way to go but it seems to me that you're complicating things terribly. What exactly is the point of the Stopwatch and Timer? It seems to me that you're clearing the stored text if the user doesn't type anything for a period of time. In that case, you don't need both a Stopwatch and a Timer and you could actually do without either. Here's a simple demo:
vb.net Code:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim table As New DataTable
table.Columns.Add("Name", GetType(String))
With table.Rows
.Add("Peter")
.Add("Paul")
.Add("Mary")
.Add("John")
.Add("James")
.Add("Janet")
End With
BindingSource1.DataSource = table
DataGridView1.DataSource = BindingSource1
End Sub
Private Sub DataGridView1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles DataGridView1.KeyPress
Console.WriteLine("DataGridView1_KeyPress")
'The search text will be reset if nothing is typed for 3 seconds.
Dim timeThreshold = TimeSpan.FromSeconds(3)
Static lastEventTime As Date = Date.MinValue
Static searchText As String = String.Empty
If Date.Now - lastEventTime > timeThreshold Then
'The time limit since the last key press has expired so reset the search text.
searchText = e.KeyChar
Else
'Append to the current search text.
searchText += e.KeyChar
End If
lastEventTime = Date.Now
For i = 0 To BindingSource1.Count - 1
Dim row = DirectCast(BindingSource1(i), DataRowView)
Dim name = CStr(row("Name"))
If name.StartsWith(searchText, StringComparison.CurrentCultureIgnoreCase) Then
BindingSource1.Position = i
Exit For
End If
Next
End Sub
That would need a bit more work to be production-ready, e.g. if there's no match then you have to wait three seconds before trying again, but it demonstrates the principle. You could use a Timer or a Stopwatch to control when the search text gets reset but you definitely don't need both. Maybe I'm misinterpreting something but, with no explanatory comments in your code, that can happen.
-
May 15th, 2021, 04:28 AM
#5
Thread Starter
Hyperactive Member
Re: DataGridView Autocomplete TextBox
jmc -
Forget about auto-complete
Roger that.
Thanks for the demo code. I agree, your approach is much cleaner. My initial effort was a trial-and-error approach to just get something working. I'll rework it to make it cleaner.
I've never used the StartsWith method and, once again, you've shown me something new. Thanks for that.
I am going to take a look at whether or not using a For...End For loop to iterate the rows of the BindingSource is faster than calling the Select method on the bound DataSet.
Thanks again for all your help.
-
May 15th, 2021, 04:48 AM
#6
Re: DataGridView Autocomplete TextBox
Originally Posted by Mark@SF
I am going to take a look at whether or not using a For...End For loop to iterate the rows of the BindingSource is faster than calling the Select method on the bound DataSet.
One issue I can see with Select is that it will always check every row, because it returns an array that can contain multiple matches, while my code stops searching when it finds the first match. If you wanted to highlight all matches then Select might be a better option. Also, working with the DataTable means that it will ignore any filtering and sorting. You may not be using any but using the BindingSource will respect either or both.
-
May 15th, 2021, 05:29 AM
#7
Thread Starter
Hyperactive Member
Re: DataGridView Autocomplete TextBox
jmc -
It looks like calling the Select method is usually faster than iterating the rows of the BindingSource. Here's some of the results (the first number after "search: " is the ElapsedTicks of a Timer and the second number is either the BindingSource rows examined or the DataTable selected rows count).
Code:
5/15/2021 6:39:24 AM, a
bsAccounts search: 147537, 3
DataTable search: 9031, 29
5/15/2021 6:39:25 AM, al
bsAccounts search: 123834, 9
DataTable search: 7584, 5
5/15/2021 6:39:35 AM, 2
bsAccounts search: 146151, 0
DataTable search: 8202, 1
5/15/2021 6:39:42 AM, y
bsAccounts search: 318427, 262
DataTable search: 9644, 2
5/15/2021 6:40:44 AM, b
bsAccounts search: 293516, 32
DataTable search: 8652, 17
5/15/2021 6:40:44 AM, bl
bsAccounts search: 82666, 36
DataTable search: 7678, 2
The BindingSource has 268 rows.
I am filtering the DataTable with the Select method. You can see that in the above results. For example, looking at the last 6 lines (KeyPress "b" followed by KeyPress "l"). The first KeyPress returned 17 rows from the Select method and the second KeyPress returned 2 rows. When iterating the BindingSource, the first KeyPress exited the loop at row #32 and the second KeyPress exited the loop at row #36.
Thanks again for your help!
Last edited by Mark@SF; May 15th, 2021 at 05:44 AM.
-
May 15th, 2021, 03:44 PM
#8
Re: DataGridView Autocomplete TextBox
Originally Posted by jmcilhinney
One issue I can see with Select is that it will always check every row, because it returns an array that can contain multiple matches,
I always thought it stops as soon as it finds the first matching case and exit the select afterward. I tested (VB 2010, FW 4.0) it with this simple code :
Code:
Dim var = 3
Dim result = ""
Select Case var
Case 3
result += " =3"
Case Is > 2
result += " >2"
End Select
MsgBox(result)
and it never goes to the case >2.
Did I miss something?
Last edited by Delaney; May 15th, 2021 at 03:55 PM.
The best friend of any programmer is a search engine
"Don't wish it was easier, wish you were better. Don't wish for less problems, wish for more skills. Don't wish for less challenges, wish for more wisdom" (J. Rohn)
“They did not know it was impossible so they did it” (Mark Twain)
-
May 15th, 2021, 08:47 PM
#9
Re: DataGridView Autocomplete TextBox
Originally Posted by Delaney
I always thought it stops as soon as it finds the first matching case and exit the select afterward. I tested (VB 2010, FW 4.0) it with this simple code :
Code:
Dim var = 3
Dim result = ""
Select Case var
Case 3
result += " =3"
Case Is > 2
result += " >2"
End Select
MsgBox(result)
and it never goes to the case >2.
Did I miss something?
My comment was nothing to do with the Select Case statement. It was about the DataTable.Select method.
-
May 16th, 2021, 01:58 AM
#10
Re: DataGridView Autocomplete TextBox
Ah, Ok, misreading, Sorry
The best friend of any programmer is a search engine
"Don't wish it was easier, wish you were better. Don't wish for less problems, wish for more skills. Don't wish for less challenges, wish for more wisdom" (J. Rohn)
“They did not know it was impossible so they did it” (Mark Twain)
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|