LookUpEdit Class

The editor that provides lookup functionality using a lightweight grid in a drop-down window.

Namespace: DevExpress.XtraEditors

Assembly: DevExpress.XtraEditors.v21.2.dll

Declaration

[DefaultBindingProperty("EditValue")]
public class LookUpEdit :
    LookUpEditBase

The following members accept/return LookUpEdit objects:

Remarks

Demo: LookUp Edit module in the XtraEditors MainDemo

A lookup editor is an editor with an embedded dropdown window, which displays lookup records (the records from which a user can select). When a lookup record is selected, the editor’s value (the BaseEdit.EditValue bindable property) and display text are modified accordingly.

The LookUpEdit displays lookup records as a lightweight grid.

LookUpEditor

To display lookup records with a feature-rich GridControl, use the GridLookUpEdit or SearchLookUpEdit control instead.

See the following topics to learn how to set up lookup editors in different binding modes:

You may want to filter the popup data source of one (secondary) lookup editor based on the value of another (primary) lookup editor. This scenario is covered in the following topic:

Additional Customization

The following list shows some of the members that help you perform the additional customization of the LookUpEdit control.

To embed a LookUpEdit in a cell within a container control (XtraGrid, XtraTreeList, etc.), use the RepositoryItemLookUpEdit component. See the “Repository items” section in the Editors and Simple Controls topic for more information.

If you want users to be able to search for lookup items, change the RepositoryItemLookUpEdit.SearchMode property to either AutoSuggest or AutoSearch. The RepositoryItemLookUpEdit.TextEditStyle property automatically switches to Standard when you select either of these modes.

Difference between AutoSearch and AutoSuggest modes:

  • AutoSuggest runs a custom Task to return a collection of items for the drop-down panel. As a result, the editor has a virtual data source that changes every time a user’s input changes. This mode is primarily used with unbound editors.

  • AutoSearch uses the default search logic to find required items in the data source to which this lookup is bound.

  • AutoSearch mode is not currently available with server-mode editor sources.

Both modes are available for stand-alone editors only.

AutoSuggest Mode

When a user types text, the editor fires the AutoSuggest event (LookUpEdit.AutoSuggest / GridLookUpEdit.AutoSuggest) that runs a custom asynchronous task (a System.Threading.Tasks.Task object). This task performs a search against the given data set, and returns the ICollection object with records that match the entered text. This collection is automatically assigned to the editor’s DataSource and its entries appear in the editor drop-down menu.

demo-autosuggest

This feature is in effect only for stand-alone editors.

AutoSuggest event parameters provide the following API.

  • QuerySuggestions - assign a Task that performs the search and returns the suggestions collection.

  • Text - returns the currently entered user text.

  • SetMinimumAnimationDuration - while a Task is searching for suggestions, the editor displays a loading indicator. To prevent this indicator from flickering when the search completes too fast, the SetMinimumAnimationDuration method allows you to set the minimum on-screen duration.

  • SetHighlightRanges - allows you to manually choose which parts of found items should be highlighted.

To test the AutoSuggest mode, run the AutoSuggest Lookup Demo Center module. You can click the “Open Solution” Ribbon button to view the complete demo code.

In this demo, suggestions (drop-down menu items) are retrieved on the QueryAsync task. The SetMinimumAnimationDuration method ensures the loading indicator is visible for at least one second.

lookUpEdit1.AutoSuggest += OnAutoSuggest;

void OnAutoSuggest(object sender, LookUpEditAutoSuggestEventArgs e) {
    // Set delay (if needed)
    e.SetMinimumAnimationDuration(TimeSpan.FromMilliseconds(1000));
    // Assign a Task that returns suggestions
    e.QuerySuggestions = WorldCities.QueryAsync(e.Text, e.CancellationToken);
}

The code below demonstrates how this QueryAsync Task is implemented. The EnsureAllEntries method retrieves all locations from the source. If a user opens the editor drop-down menu, all these entries are visible. Otherwise, the method compares user text with data source records.

public static Task<ICollection> QueryAsync(string text, CancellationToken cancellation) {
    var contains = IgnoreCaseComparisonFunctions.GetContains(
        CultureInfo.CurrentCulture.CompareInfo, CompareOptions.IgnoreCase);
    return Task.Run(() => {
        var entries = CsvDataHelper.EnsureAllEntries();
        var parts = text.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
        if(parts.Length == 0)
            return entries as ICollection;
        else
            return entries.Where(e => {
                // stop task if needed
                cancellation.ThrowIfCancellationRequested();
                return parts.All(x => e.Tags.Any(tag => contains(tag, x)));
            }).ToList() as ICollection;
    });
}

In this demo, the data source is the “Data\cities.csv” Excel file that stores approximately 13,000 city names. Each record also contains country and state names, country UN code, and Unicode city name. All records are extracted inside the custom Entry class.

public sealed class Entry {
    readonly static List<Entry> allEntries = new List<Entry>();
    public static IEnumerable<Entry> EnsureAllEntries() {
        lock(allEntries) {
            if(allEntries.Count == 0 && File.Exists("Data\cities.csv")) {
                var lines = File.ReadLines("Data\cities.csv").Skip(1);
                var entries = lines.Select(x => new Entry(x))
                        .OrderBy(e => e.Country)
                        .ThenBy(e => e.City);
                foreach(Entry entry in entries)
                    allEntries.Add(entry);
            }
            return allEntries;
        }
    }
//. . .
}

The Entry class contains data fields for Excel file columns, plus additional data fields for values combined from standard fields.

  • Tags - a portion of a record to be checked against an entered user text: City, Unicode City Name, and State Name;
  • Text - full location address in the “CountryName, CityName, StateName” format.
  • HtmlText - same as Text, but country name is displayed with grayed-out text. This field is used to custom draw lookup items (see the “Additional Customization” section below).
public sealed class Entry {
    //standard fields
    public string Country { get; private set; }
    public string City { get; private set; }
    public string City_Unicode { get; private set; }
    public string Admin_Name_Unicode { get; private set; }
    public string Country_UN { get; private set; }
    //additional fields
    public string[] Tags { get; private set; }
    public string Text { get; private set; }
    public string HtmlText { get; private set; }
}

You can call the LookUpEditBase.StartAutoSuggest method to manually trigger the AutoSuggest event.

Custom Item Draw

The HtmlText property decalred in the Entry class is optional - it is not required to implement the auto-suggest functionality. This property is used to manually re-paint lookup items so that country names appear washed out. To do that, handle the RepositoryItemLookUpEdit.CustomDrawCell event.

htmltext

lookUpEdit1.CustomDrawCell += OnCustomDrawCell;

void OnCustomDrawCell(object sender, LookUpCustomDrawCellArgs e) {
    e.DrawHtmlText(WorldCities.GetHtmlText(e.Row));
    e.Handled = true;
}

Custom Highlight Ranges

The QueryAsync Task compares user text with the entry Tags property. This property includes city name, city unicode name, and state name - country names are ignored. For example, the figure below illustrates that the demo database contains many Mexican cities.

mexican_cities

If a user enters “ex”, from all Mexican cities only “Mexicali” and “Mexico City” remain visible - the Task behaves correctly and filters out non-matching records. However, although the Task ignores countries, the editor highlights “ex” in country names as well.

mexican_cities-filtered

This happens because there are two separate processes that take place in a lookup editor.

  1. A Task assigned to the QuerySuggestions property scans the data source and chooses records to show in a drop-down menu.
  2. A lookup editor scans items returned by the Task, and looks for user text matches.

If these two processes use different algorithms, a misleading highlight behavior similar to that mentioned above may happen. If this happens, define custom highlight range(s) with the SetHighlightRange / SetHighlightRanges methods. The demo code below uses the same logic as in the QueryAsync task to display the correct ranges.

void OnAutoSuggest(object sender, LookUpEditAutoSuggestEventArgs e) {
    //...
    // Set Custom Highlight Strategy
    e.SetHighlightRanges(HighlightTags(e.Text));
}

static Func<string, string, DisplayTextHighlightRange[]> HighlightTags(string pattern) {
    var indexOf = IgnoreCaseComparisonFunctions.GetIndexOf(
        CultureInfo.CurrentCulture.CompareInfo, CompareOptions.IgnoreCase);
    var parts = pattern.Split(new char[] { ' ' },
        StringSplitOptions.RemoveEmptyEntries);
    return (displayText, fieldName) => {
        var tags = displayText.Split(new string[] { ", " },
            StringSplitOptions.RemoveEmptyEntries);
        var ranges = new List<DisplayTextHighlightRange>();
        for(int i = 1/*skip country tag*/; i < tags.Length; i++) {
            int tagStart = displayText.IndexOf(tags[i]);
            for(int j = 0; j < parts.Length; j++) {
                int index = indexOf(tags[i], parts[j]);
                if(index != -1)
                    ranges.Add(new DisplayTextHighlightRange(tagStart + index, parts[j].Length));
            }
        }
        return ranges.ToArray();
    };
}

The AutoSuggest event parameter offers a number of methods that you can use to define custom highlight ranges.

AutoSearch Mode

The AutoSearch mode allows users to search for required data source items.

search

Change Search Logic

Handle the AutoSearch event (LookUpEdit.AutoSearch \ GridLookUpEdit.AutoSearch) and use a SetParameters method overload to specify how the editor should compare user text with editor items.

  • parserKind parameter - specifies how user text parts divided by a space character should be combined (see the ColumnViewOptionsFind.ParserKind property for examples).
  • condition parameter - specifies which function should be used to build filter expressions (see the ColumnViewOptionsFind.Condition Data Grid property for examples).
  • field or fields[] parameter - allows you to limit the search by specific data fields only.
  • text parameter - allows you to replace the user text.

The example below illustrates how to make the editor look for items whose text starts with the user text.

search

lookUpEdit1.AutoSearch += OnAutoSearch;

void OnAutoSearch(object sender, LookUpEditAutoSearchEventArgs e) {
    string[] fields = new string[] { "ShipCity", "ShipCountry" };
    e.SetParameters(fields, e.Text, FindPanelParserKind.And, FilterCondition.StartsWith);
}

You can call the LookUpEditBase.StartAutoSearch method to manually trigger the AutoSearch event.

Custom Text Highlight Ranges

Lookup editors highlight text portions that match user text. Handle the AutoSearch event to implement custom highlight ranges. This mode uses same highlight API as the AutoSuggest mode does.

The example below illustrates how to make a lookup editor highlight the entire field value if it starts with the specified user text.

highlight

private void LookUpEdit1_AutoSearch(object sender, LookUpEditAutoSearchEventArgs e)
{
    e.SetAutoSearchParameters(FindPanelParserKind.And, FilterCondition.StartsWith);
    e.SetHighlightRanges(CustomHightlight(e.Text));
}

static Func<string, string, DisplayTextHighlightRange[]> CustomHightlight(string userText)
{
    return (displayText, fieldName) =>
    {
        if (fieldName == "ShipCity" || fieldName == "ShipCountry")
        {
            if (displayText.StartsWith(userText))
                return new DisplayTextHighlightRange[] {
                    new DisplayTextHighlightRange(0, displayText.Length) };
        }
        return null;
    };
}

Dictionary as a Data Source

If you use a Dictionary<TKey,TValue> as a data source for a lookup editor, the editor uses keys and values as follows:

  • the TKey values — as actual values. These values are assigned to the editor when a user selects an item in the drop-down box.
  • the TValue values — as string representations of the actual values. These strings are displayed in the edit box and drop-down window.

There is no need to specify the ValueMember and DisplayMember properties.

Note

The TKey and TValue parameters should be of the String type or a value type. For example, keys are integers and values are strings.

See the following topic for more information: Dictionary as a Data Source for a Lookup Editor.

Examples

How to Set Up a Standalone LookUpEdit Editor

The following example shows how to use a standalone LookUp editor to select a city from a dropdown list.

Lookup-standalone-example.gif

The example performs the following:

  • creates two lists (Persons and Cities) that contain Person and CityInfo objects. Each Person object is linked to a CityInfo object using the Person.CityID and the matching CityInfo.ID field.
  • uses the TextEdit and LookUpEdit controls to edit the Person.Name and Person.CityID properties, respectively.
  • uses a DataNavigator control to navigate between the Persons list items.
  • sets up the lookup data source to display the Cities list
  • handles the RepositoryItem.CustomDisplayText event to display the CityInfo.City and CityInfo.Country field values in the edit box. If this event is not handled, the edit box displays the RepositoryItemLookUpEditBase.DisplayMember property’s value (CityInfo.City).
using DevExpress.XtraEditors;
using DevExpress.XtraEditors.Controls;
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace WindowsFormsApplication9 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
            List<Person> Persons = new List<Person>();
            Persons.Add(new Person() { Name = "Carlos Gonzalez", CityID = 0 });
            Persons.Add(new Person() { Name = "Mario Pontes", CityID = 5 });
            Persons.Add(new Person() { Name = "Horst Kloss", CityID = 2 });
            Persons.Add(new Person() { Name = "Martin Sommer", CityID = 3 });
            Persons.Add(new Person() { Name = "Diego Roel", CityID = 3 });
            Persons.Add(new Person() { Name = "Catherine Dewey", CityID = 4 });
            Persons.Add(new Person() { Name = "Pascale Cartrain", CityID = 4 });
            Persons.Add(new Person() { Name = "Bernardo Batista", CityID = 1 });

            List<CityInfo> Cities = new List<CityInfo>();
            Cities.Add(new CityInfo() { ID = 0, City= "Barquisimeto", Country= "Venezuela", Region="Lara" });
            Cities.Add(new CityInfo() { ID = 1, City = "Rio de Janeiro", Country = "Brazil", Region = "RJ" });
            Cities.Add(new CityInfo() { ID = 2, City = "Cunewalde", Country = "Germany", Region = "" });
            Cities.Add(new CityInfo() { ID = 3, City = "Madrid", Country = "Spain", Region = "" });
            Cities.Add(new CityInfo() { ID = 4, City = "Charleroi", Country = "Belgium", Region = "" });
            Cities.Add(new CityInfo() { ID = 5, City = "Sao Paulo", Country = "Brazil", Region = "SP" });


            DataNavigator dataNavigator1 = new DataNavigator();
            dataNavigator1.Parent = this;
            dataNavigator1.Location = new System.Drawing.Point(12, 12);
            dataNavigator1.Size = new System.Drawing.Size(385, 25);
            TextEdit textEdit1 = new TextEdit();
            textEdit1.Parent = this;
            textEdit1.Location = new System.Drawing.Point(198, 52);
            textEdit1.Size = new System.Drawing.Size(199, 20);
            LookUpEdit lookUpEdit1 = new LookUpEdit();
            lookUpEdit1.Parent = this;
            lookUpEdit1.Location = new System.Drawing.Point(198, 78);
            lookUpEdit1.Size = new System.Drawing.Size(199, 20);


            dataNavigator1.DataSource = Persons;
            textEdit1.DataBindings.Add(new Binding("EditValue", Persons, "Name"));
            lookUpEdit1.DataBindings.Add(new Binding("EditValue", Persons, "CityID"));
            lookUpEdit1.Properties.DataSource = Cities;
            lookUpEdit1.Properties.ValueMember = "ID";
            lookUpEdit1.Properties.DisplayMember = "City";
            lookUpEdit1.Properties.PopulateColumns();
            lookUpEdit1.Properties.Columns["ID"].Visible = false;

            lookUpEdit1.CustomDisplayText += LookUpEdit1_CustomDisplayText;
        }

        // Display the city and country in the edit box
        private void LookUpEdit1_CustomDisplayText(object sender, CustomDisplayTextEventArgs e) {
            LookUpEdit lookUpEdit = sender as LookUpEdit;
            CityInfo city = lookUpEdit.Properties.GetDataSourceRowByKeyValue(e.Value) as CityInfo;
            if(city!=null) {
                e.DisplayText = city.City + ", " + city.Country;
            }
        }
    }

    public class Person {
        public string Name { get; set; }
        public int CityID { get; set; }
    }

    public class CityInfo {
        public int ID { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
        public string Region { get; set; }
    }
}

How to Use a LookUpEdit as an In-Place Editor for Data Grid

This example shows how to use an in-place LookUpEdit control (RepositoryItemLookUpEdit) for editing cells in a grid column.The lookup editor in the example is used to edit the CategoryID field values from the Products list. However, instead of displaying category IDs, the editor will display corresponding category names in the edit box.The main properties used to set up the LookUpEdit control are:- DataSource - Specifies the lookup data source.- ValueMember - Specifies the field from the lookup data source whose values match the editor’s edit value.- DisplayMember - Identifies the field from the lookup data source whose values match the editor’s display text.

The following image shows the result.

lookup-standardmode-example-result.gif

View Example

using DevExpress.XtraEditors.Repository;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LookupEdit_StandardBinding {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
            InitData();

            gridControl1.DataSource = Products;
            gridView1.Columns["UnitPrice"].DisplayFormat.FormatType = DevExpress.Utils.FormatType.Numeric;
            gridView1.Columns["UnitPrice"].DisplayFormat.FormatString = "c2";

            // Create an in-place LookupEdit control.
            RepositoryItemLookUpEdit riLookup = new RepositoryItemLookUpEdit();
            riLookup.DataSource = Categories;
            riLookup.ValueMember = "ID";
            riLookup.DisplayMember = "CategoryName";

            // Enable the "best-fit" functionality mode in which columns have proportional widths and the popup window is resized to fit all the columns.
            riLookup.BestFitMode = DevExpress.XtraEditors.Controls.BestFitMode.BestFitResizePopup;
            // Specify the dropdown height.
            riLookup.DropDownRows = Categories.Count;

            // Enable the automatic completion feature. In this mode, when the dropdown is closed, 
            // the text in the edit box is automatically completed if it matches a DisplayMember field value of one of dropdown rows. 
            riLookup.SearchMode = DevExpress.XtraEditors.Controls.SearchMode.AutoComplete;
            // Specify the column against which an incremental search is performed in SearchMode.AutoComplete and SearchMode.OnlyInPopup modes
            riLookup.AutoSearchColumnIndex = 1;

            // Optionally hide the Description column in the dropdown.
            // riLookup.PopulateColumns();
            // riLookup.Columns["Description"].Visible = false;

            gridControl1.RepositoryItems.Add(riLookup);

            // Assign the in-place LookupEdit control to the grid's CategoryID column.
            // Note that the data types of the "ID" and "CategoryID" fields match.
            gridView1.Columns["CategoryID"].ColumnEdit = riLookup;
            gridView1.BestFitColumns();
        }

        List<Product> Products = new List<Product>();
        List<Category> Categories = new List<Category>();

        private void InitData() {
            Products.Add(new Product() { ProductName = "Sir Rodney's Scones", CategoryID = 3, UnitPrice = 10 });
            Products.Add(new Product() { ProductName = "Gustaf's Knäckebröd", CategoryID = 5, UnitPrice = 21 });
            Products.Add(new Product() { ProductName = "Tunnbröd", CategoryID = 5, UnitPrice = 9 });
            Products.Add(new Product() { ProductName = "Guaraná Fantástica", CategoryID = 1, UnitPrice = 4.5m });
            Products.Add(new Product() { ProductName = "NuNuCa Nuß-Nougat-Creme", CategoryID = 3, UnitPrice = 14 });
            Products.Add(new Product() { ProductName = "Gumbär Gummibärchen", CategoryID = 3, UnitPrice = 31.23m });
            Products.Add(new Product() { ProductName = "Rössle Sauerkraut", CategoryID = 7, UnitPrice = 45.6m });
            Products.Add(new Product() { ProductName = "Thüringer Rostbratwurst", CategoryID = 6, UnitPrice = 123.79m });
            Products.Add(new Product() { ProductName = "Nord-Ost Matjeshering", CategoryID = 8, UnitPrice = 25.89m });
            Products.Add(new Product() { ProductName = "Gorgonzola Telino", CategoryID = 4, UnitPrice = 12.5m });

            Categories.Add(new Category() { ID = 1, CategoryName = "Beverages", Description = "Soft drinks, coffees, teas, beers, and ales" });
            Categories.Add(new Category() { ID = 2, CategoryName = "Condiments", Description = "Sweet and savory sauces, relishes, spreads, and seasonings" });
            Categories.Add(new Category() { ID = 3, CategoryName = "Confections", Description = "Desserts, candies, and sweet breads" });
            Categories.Add(new Category() { ID = 4, CategoryName = "Dairy Products", Description = "Cheeses" });
            Categories.Add(new Category() { ID = 5, CategoryName = "Grains/Cereals", Description = "Breads, crackers, pasta, and cereal" });
            Categories.Add(new Category() { ID = 6, CategoryName = "Meat/Poultry", Description = "Prepared meats" });
            Categories.Add(new Category() { ID = 7, CategoryName = "Produce", Description = "Dried fruit and bean curd" });
            Categories.Add(new Category() { ID = 8, CategoryName = "Seafood", Description = "Seaweed and fish" });
        }
    }

    public class Product {
        public string ProductName { get; set; }
        public decimal UnitPrice { get; set; }
        public int CategoryID { get; set; }
    }

    public class Category {
        public int ID { get; set; }
        public string CategoryName { get; set; }
        public string Description { get; set; }
    }
}

How to Obtain a Value of a Cell Inside a Focused Row

When a LookUpEdit’s EditValue property changes, you may need to read the value of a cell located at the intersection of a selected row and a specific column. To do this, handle the LookUpEdit.EditValueChanged event and call the GetColumnValue(String) method.

private void lookUpEdit1_EditValueChanged(object sender, EventArgs e) {
    DevExpress.XtraEditors.LookUpEdit editor = sender as DevExpress.XtraEditors.LookUpEdit;
    object value = editor.GetColumnValue("aFieldName");
}

You may also call the GetDataSourceRowByKeyValue(Object) method instead of GetColumnValue. This method retrieves row objects of a data source assigned to the editor’s Properties.DataSource property. For example, if your editor is bound to a DataTable object, use the code below.

private void lookUpEdit1_EditValueChanged(object sender, System.EventArgs e) {
    DevExpress.XtraEditors.LookUpEdit editor = sender as DevExpress.XtraEditors.LookUpEdit;
    DataRowView row = editor.Properties.GetDataSourceRowByKeyValue(editor.EditValue) as DataRowView;
    object value = row["aFieldName"];
}

Knowledge Base Articles:

See Also