CoreSuite.AsyncLookupBox
1.0.0
dotnet add package CoreSuite.AsyncLookupBox --version 1.0.0
NuGet\Install-Package CoreSuite.AsyncLookupBox -Version 1.0.0
<PackageReference Include="CoreSuite.AsyncLookupBox" Version="1.0.0" />
<PackageVersion Include="CoreSuite.AsyncLookupBox" Version="1.0.0" />
<PackageReference Include="CoreSuite.AsyncLookupBox" />
paket add CoreSuite.AsyncLookupBox --version 1.0.0
#r "nuget: CoreSuite.AsyncLookupBox, 1.0.0"
#:package CoreSuite.AsyncLookupBox@1.0.0
#addin nuget:?package=CoreSuite.AsyncLookupBox&version=1.0.0
#tool nuget:?package=CoreSuite.AsyncLookupBox&version=1.0.0
AsyncLookupBox
A data-source-independent asynchronous lookup and selection control for .NET 8 Windows Forms, included in CoreSuite.
AsyncLookupBox is one of the independent projects that make up the CoreSuite solution. The package contains the control, result-column configuration, event data, drop-down interface, and Windows Forms designer support.
Overview
AsyncLookupBox is a text box that waits while the user types, requests results from application code, and displays selectable objects in a tabular drop-down. It does not create SQL and does not depend on a database, HTTP client, ORM, or business-object library.
The application supplies a Task in the SearchRequested event. The control manages the rest of the interaction:
- Waits for
MinimumCharactersand the debounce interval. - Cancels the preceding request when the text changes.
- Displays loading, empty-result, and error states.
- Ignores an old result if a newer request has already started.
- Builds the result grid from
Columns. - Supports mouse and keyboard selection.
- Exposes the selected object and selected value.
This makes the same control suitable for REST APIs, Entity Framework, Dapper, ADO.NET, local services, files, caches, and in-memory collections.
Key features
- Inherits from the standard WinForms
TextBox. - Accepts any application-defined enumerable result type.
- Uses real asynchronous tasks without blocking the UI thread.
- Debounces searches with
SearchInterval. - Cancels replaced requests through
CancellationToken. - Prevents late results from overwriting newer searches.
- Supports properties,
DataRow,DataRowView, and dictionaries. - Supports nested property paths such as
Category.Name. - Provides configurable result columns, formatting, widths, and fill modes.
- Tracks
SelectedItem,SelectedValue, andHasSelection. - Clearly distinguishes selected and editable states through configurable colors and a built-in check mark.
- Supports automatic selection of a single result.
- Includes loading animation and a combined cancel/clear button.
- Supports
Up,Down,Enter,Escape,F4, andAlt+Down. - Includes localized status-message properties.
- Includes a smart tag and collection editor for the modern .NET WinForms designer.
- Has no runtime dependency on another CoreSuite package.
Requirements
- Windows Forms
- .NET 8 for Windows (
net8.0-windows) - A reference to
CoreSuite.AsyncLookupBox
Installation
dotnet add package CoreSuite.AsyncLookupBox
Or add AsyncLookupBox/AsyncLookupBox.vbproj as a project reference when working directly with the CoreSuite solution.
Quick start
Assume that the application searches this business class:
Public Class Product
Public Property Id As Integer
Public Property Code As String
Public Property Name As String
Public Property Category As String
Public Property Price As Decimal
End Class
Configure the lookup once, usually in the Designer or the form's Load event:
Imports CoreSuite.Controls
Private Sub ConfigureProductLookup()
ProductLookup.DisplayMember = NameOf(Product.Name)
ProductLookup.ValueMember = NameOf(Product.Id)
ProductLookup.MinimumCharacters = 2
ProductLookup.SearchInterval = 300
ProductLookup.DropDownWidth = 520
ProductLookup.PlaceholderText = "Search by code, name, or category..."
ProductLookup.Columns.Add(NameOf(Product.Code)).HeaderText = "Code"
ProductLookup.Columns.Add(NameOf(Product.Name)).HeaderText = "Product"
ProductLookup.Columns.Add(NameOf(Product.Category)).HeaderText = "Category"
Dim PriceColumn As AsyncLookupColumn = ProductLookup.Columns.Add(NameOf(Product.Price))
PriceColumn.HeaderText = "Price"
PriceColumn.Format = "C2"
End Sub
Handle SearchRequested and give the event arguments the task returned by your service:
Private Sub ProductLookup_SearchRequested(Sender As Object, E As AsyncLookupSearchRequestedEventArgs) Handles ProductLookup.SearchRequested
E.SetSearchTask(ProductService.SearchAsync(E.SearchText, E.CancellationToken))
End Sub
The handler itself is intentionally not Async. SetSearchTask registers the running operation, and the control awaits it internally. The service can return Task(Of List(Of Product)), Task(Of Product()), or another task whose result implements IEnumerable.
Read the selected values in SelectionChanged or when saving the form:
Private Sub ProductLookup_SelectionChanged(Sender As Object, E As AsyncLookupSelectionChangedEventArgs) Handles ProductLookup.SelectionChanged
If Not ProductLookup.HasSelection Then
SelectedProductLabel.Text = "No product selected"
Return
End If
Dim Product As Product = DirectCast(ProductLookup.SelectedItem, Product)
SelectedProductLabel.Text = $"{Product.Code} - {Product.Name}"
Dim ProductId As Integer = CInt(ProductLookup.SelectedValue)
End Sub
Complete test without a database
The CoreSuite Test project included with the source contains a ready-to-run example with 50 products. The following is the essential search method used by that example:
Private Async Function SearchProductsAsync(SearchText As String, CancellationToken As CancellationToken) As Task(Of List(Of Product))
Await Task.Delay(700, CancellationToken)
Return Products.Where(Function(Product) Product.Code.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase) OrElse Product.Name.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase) OrElse Product.Category.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase)).ToList()
End Function
The delay makes the loading indicator and cancellation behavior visible. Type one term and immediately replace it with another; the first token is canceled and only the newest result can appear.
Why SetSearchTask is used
A normal .NET event is raised synchronously. If the event handler were an Async Sub, the control could not reliably know when that handler had completed or catch its exceptions.
This pattern keeps event handling safe:
Private Sub CustomerLookup_SearchRequested(Sender As Object, E As AsyncLookupSearchRequestedEventArgs) Handles CustomerLookup.SearchRequested
E.SetSearchTask(SearchCustomersAsync(E.SearchText, E.CancellationToken))
End Sub
SetSearchTask accepts the task immediately. AsyncLookupBox then awaits it, catches failures, checks cancellation, updates its state, and displays the results.
Call SetResults instead when the objects are already available synchronously:
Private Sub CityLookup_SearchRequested(Sender As Object, E As AsyncLookupSearchRequestedEventArgs) Handles CityLookup.SearchRequested
Dim Matches = Cities.Where(Function(City) City.Name.Contains(E.SearchText, StringComparison.CurrentCultureIgnoreCase)).ToList()
E.SetResults(Matches)
End Sub
Only one of SetSearchTask or SetResults may be called for each request.
Search cancellation
Every request receives its own token:
E.CancellationToken
The token is canceled when:
- a newer search starts;
- the text is cleared;
- the clear/cancel button is clicked while searching;
CancelSearch()is called;SearchEnabledbecomesFalse;- the control is disabled or disposed.
Pass the token through every operation that supports it:
Private Async Function SearchCustomersAsync(SearchText As String, CancellationToken As CancellationToken) As Task(Of List(Of Customer))
Return Await CustomerApi.SearchAsync(SearchText, CancellationToken)
End Function
Even when an external service ignores cancellation, AsyncLookupBox compares request versions and refuses to display a result belonging to an older search.
Configuring result columns
When Columns is empty, the drop-down displays one fill-width column containing DisplayMember. If DisplayMember is empty, the control displays each object's ToString() result.
Each configured AsyncLookupColumn supports:
| Property | Default | Description |
|---|---|---|
PropertyName |
Empty | Property, data-column, dictionary-key, or nested path to display. |
HeaderText |
Empty | Column header; empty uses PropertyName. |
Width |
120 |
Width used when automatic sizing is disabled. |
MinimumWidth |
5 |
Smallest permitted width. |
AutoSizeMode |
None |
Standard DataGridViewAutoSizeColumnMode. |
FillWeight |
100 |
Relative width when AutoSizeMode is Fill. |
Format |
Empty | Standard or custom value format, such as C2 or dd/MM/yyyy. |
NullValue |
Empty | Text shown for Nothing or DBNull.Value. |
Visible |
True |
Determines whether the column is displayed. |
Example with a nested object:
Dim CategoryColumn As AsyncLookupColumn = ProductLookup.Columns.Add("Category.Name")
CategoryColumn.HeaderText = "Category"
CategoryColumn.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
The collection can also be edited through the Columns property in the Windows Forms Designer.
DataTable and dictionary results
The same property names work with DataRow and DataRowView results:
Private Sub SupplierLookup_SearchRequested(Sender As Object, E As AsyncLookupSearchRequestedEventArgs) Handles SupplierLookup.SearchRequested
E.SetSearchTask(SupplierRepository.SearchRowsAsync(E.SearchText, E.CancellationToken))
End Sub
Configure columns using the DataColumn.ColumnName values. For dictionary results, use dictionary keys. String dictionary keys are resolved case-insensitively when an exact key is unavailable.
Selection behavior
Selecting a row performs these operations:
- Stores the complete object in
SelectedItem. - Resolves
SelectedValuefromValueMember, or uses the complete object whenValueMemberis empty. - Resolves the text from
DisplayMember, or uses the object's text representation when it is empty. - Cancels pending searching.
- Closes the result drop-down.
- Raises
SelectionChanged.
If the user edits the selected text, the stale selection is cleared before a new search begins. SelectionChanged then contains the old item and Nothing as the new item.
The selected state is visible by default:
- the text box uses
SelectedItemBackColorandSelectedItemForeColor; - the embedded action button displays a check mark;
- changing or deleting any character immediately restores the normal
BackColorandForeColor, replaces the check mark with the clear glyph, clearsSelectedItemandSelectedValue, and starts a new search.
The color change is only visual. It does not set ReadOnly and does not prevent the user from editing the selected text.
Customize the selected state in the Designer or in code:
ProductLookup.HighlightSelectedItem = True
ProductLookup.SelectedItemBackColor = Color.Honeydew
ProductLookup.SelectedItemForeColor = Color.DarkGreen
Set HighlightSelectedItem to False to retain the normal text-box colors. The check mark remains available as long as ShowClearButton is enabled. Assign SelectedButtonImage to replace the built-in check mark with a custom image.
Select an object programmatically:
ProductLookup.SelectItem(Product)
Clear everything:
ProductLookup.ClearSelection()
Localizing status text
All user-facing drop-down messages are configurable:
ProductLookup.LoadingText = "Pesquisando..."
ProductLookup.NoResultsText = "Nenhum produto encontrado."
ProductLookup.SearchErrorText = "Não foi possível carregar os produtos."
ProductLookup.SearchNotConfiguredText = "A pesquisa não foi configurada."
ProductLookup.CharactersRemainingText = "Digite mais {0} caractere(s)."
ProductLookup.ResultColumnHeaderText = "Resultado"
CharactersRemainingText receives the missing character count as format argument {0}.
Keyboard and mouse behavior
| Input | Behavior |
|---|---|
| Typing | Clears a stale selection and restarts the debounce interval. |
Down / Up |
Moves through visible results. |
Enter |
Selects the current row or starts an immediate search. |
Escape |
Closes the drop-down; when already closed, clears the lookup. |
F4 / Alt+Down |
Reopens retained results. |
| Result-row click | Selects that result. |
| Action button while searching | Cancels the active search. |
| Action button while idle | Clears text and selection. |
Main properties
| Property | Default | Description |
|---|---|---|
DisplayMember |
Empty | Property path used as selected text. |
ValueMember |
Empty | Property path used for SelectedValue. |
Columns |
Empty | Columns displayed by the result grid. |
SearchInterval |
300 |
Debounce interval in milliseconds. |
MinimumCharacters |
2 |
Character count required before searching. |
MaximumResults |
100 |
Maximum retained results; 0 removes this limit. |
SearchEnabled |
True |
Enables or disables search requests. |
AutoSelectSingleResult |
False |
Automatically selects exactly one result. |
DropDownWidth |
0 |
Width; 0 uses at least the control width. |
DropDownHeight |
220 |
Drop-down height in pixels. |
ShowColumnHeaders |
True |
Displays result headers. |
ShowClearButton |
True |
Displays the embedded clear/cancel button. |
HighlightSelectedItem |
True |
Uses distinct text-box colors while an item is selected. |
SelectedItemBackColor |
AliceBlue |
Background color used while an item is selected. |
SelectedItemForeColor |
RoyalBlue |
Text color used while an item is selected. |
SelectedButtonImage |
Nothing |
Image used for the selected state; Nothing draws a check mark. |
SelectedItem |
Read-only | Complete selected result object. |
SelectedValue |
Read-only | Value resolved through ValueMember. |
HasSelection |
Read-only | Indicates whether an object is selected. |
IsSearching |
Read-only | Indicates whether a current request is running. |
IsDropDownOpen |
Read-only | Indicates whether the result list is open. |
Results |
Read-only | Results retained from the latest successful search. |
Inherited TextBox properties such as PlaceholderText, CharacterCasing, MaxLength, ReadOnly, Font, BackColor, ForeColor, and data binding remain available.
Methods
| Method | Description |
|---|---|
RefreshResultsAsync() |
Immediately searches the current text and returns retained results. |
CancelSearch() |
Cancels the active request and closes the drop-down. |
SelectItem(item) |
Selects an application object programmatically. |
ClearSelection() |
Clears text, selection, results, and pending searching. |
CloseDropDown() |
Closes only the result drop-down. |
Events
| Event | Description |
|---|---|
SearchRequested |
Requests a task or immediate collection from the application. |
SearchCompleted |
Reports a successful current search, results, duration, and truncation. |
SearchFailed |
Reports an exception from the event handler or supplied task. |
IsSearchingChanged |
Reports transitions into and out of active searching. |
SelectionChanged |
Reports selected and cleared items and values. |
DropDownOpened |
Reports that the result interface opened. |
DropDownClosed |
Reports that the result interface closed. |
Do not use an Async Sub handler for SearchRequested. Supply the task synchronously with SetSearchTask, as shown throughout this README.
AsyncLookupBox and QueriedBox
| Requirement | Use |
|---|---|
| Build SQL from tables, joins, conditions, parameters, and supported dialects | QueriedBox |
| Search any application-defined service or source asynchronously | AsyncLookupBox |
| Configure database querying inside the control | QueriedBox |
| Keep data access completely outside the control | AsyncLookupBox |
| Require cancellation and protection against late results | AsyncLookupBox |
Both controls provide a lookup experience, but they solve different integration problems and can coexist in the same application.
License
CoreSuite is licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0-windows7.0 is compatible. net9.0-windows was computed. net10.0-windows was computed. |
-
net8.0-windows7.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 129 | 8/2/2026 |
Initial release of AsyncLookupBox with application-defined asynchronous searches, request cancellation, configurable result columns, selection tracking, clear selected-state feedback, debounce, status messages, and designer support.