Binding Controls to Data Created at Runtime

  • 9 minutes to read

DevExpress data-aware .NET WinForms controls can be bound to databases, XML files and lists of data created at runtime. This topic shows you how to bind controls to lists of data.

Concepts

Suppose you have an object that represents a data record and a list of these records should be displayed in a data-aware control. To allow this list to be bound to a control, you can do one of the following:

  • Use the System.ComponentModel.BindingList<> or System.Collections.Generic.List<> generic types to create a list. Unlike the List<> class, the BindingList<> class supports change notifications. When bound to this data source, a data-aware control will update itself when the underlying data changes.
  • Create a class encapsulating a list of records and implement the IList, IListSource, ITypedList or IBindingList interface for this class. The differences between these interfaces is described in the following section.
  • Create an IEnumerable<T> collection that stores your records. Note that the IEnumerable interface allows users to view collection records, not modify them or add\remove collection items.

The simplest way to create a list is to use the BindingList<> generic type.

Consider a sample MyRecord class encapsulating a data record:


public class MyRecord {
    public int ID { get; set; }
    public string Country { get; set;  }
    public string Name { get; set; }
    public MyRecord(int id, string name, string country) {
        ID = id;
        Name = name;
        Country = country;
    }
}

A list of MyRecord objects can be created and bound to a data-aware control as follows:


BindingList<MyRecord> list = new BindingList<MyRecord>();
myDataAwareControl.DataSource = list;

To populate the list with data, you can use the following code:


list.Add(new MyRecord(0, "Steven Baum", "USA"));
list.Add(new MyRecord(1, "Robert McKinsey", "USA"));
list.Add(new MyRecord(2, "Robert McKinsey", "UK"));
list.Add(new MyRecord(3, "Daniella Lloyd", "UK"));

When bound to a BindingList<> object, the data-aware control automatically subscribes to the BindingList<>.ListChanged event. This allows it to receive notifications and update itself when the underlying data changes.

Instead of creating custom lists, you can use a DataTable object and fill it with data at runtime, if this better suits your requirements.

IList vs ITypedList vs IBindingList

As stated above, the control's data source can implement one of three interfaces. The following describes the difference between the data sources that implement each interface type.

  • Objects implementing the IList interface - Such data sources must have at least one "record". Otherwise, bound controls will not be able to create any rows. Controls bound to such data sources will not be notified of any data changes and thus must be updated manually.
  • Objects implementing the ITypedList interface - In this case, it is not necessary to have any "records" for rows to be created. Data change notifications are not supported.
  • Objects implementing the IBindingList interface (derived from the IList) - This data source type does not have the shortcomings of the other interfaces. The interface declares the ListChanged event, which is used by the control to update itself when bound data is changed.

Example

This example binds a GridControl to a collection of custom Record objects and demonstrates the following features:

  • Assigning an inplace editor (combo box) to a column

  • Specifying a column's display name and data format by applying DataAnnotation attributes to Record class properties

  • Two ways of changing cell values - at the data source and grid level.

  • Highlighting cell values that meet a condition

Example - RuntimeBinding - Result

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

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

        private void Form1_Load(object sender, EventArgs e) {
            gridControl1.DataSource = DataHelper.GetData(10);
            // The grid automatically creates columns for the public fields found in the data source. 
            // Calling the gridView1.PopulateColumns method is not required unless the gridView1.OptionsBehavior.AutoPopulateColumns is disabled

            // Create a ComboBox editor that shows available companies in the Company column
            RepositoryItemComboBox riComboBox = new RepositoryItemComboBox();
            riComboBox.Items.AddRange(DataHelper.companies);
            gridControl1.RepositoryItems.Add(riComboBox);
            gridView1.Columns["CompanyName"].ColumnEdit = riComboBox;

            // Specify a different null value text presentation for the Image column
            gridView1.Columns["Image"].RealColumnEdit.NullText = "[load image]";

            //Highlight the RequiredDate cells that match a certain condition.
            GridFormatRule gridFormatRule = new GridFormatRule();
            FormatConditionRuleValue formatConditionRuleValue = new FormatConditionRuleValue();
            gridFormatRule.Column = gridView1.Columns["RequiredDate"];
            formatConditionRuleValue.PredefinedName = "Red Bold Text";
            formatConditionRuleValue.Condition = FormatCondition.Greater;
            formatConditionRuleValue.Value1 = DateTime.Today;
            gridFormatRule.Rule = formatConditionRuleValue;
            gridFormatRule.ApplyToRow = false;
            gridView1.FormatRules.Add(gridFormatRule);

            gridView1.BestFitColumns();
        }

        private void btnClearPayment_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) {
            //Change a cell value at the data source level to see the INotifyPropertyChanged interface in action.
            gridView1.CloseEditor();
            Record rec = gridView1.GetFocusedRow() as Record;
            if (rec == null) return;
            rec.Value = 0;
        }

        private void btnSetPayment_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) {
            //Change a cell value at the grid level
            gridView1.SetFocusedRowCellValue("Value", 999);
        }
    }

    public class Record : INotifyPropertyChanged {
        public Record() {
        }
        int id;
        public int ID {
            get { return id; }
            set {
                if (id != value) {
                    id = value;
                    OnPropertyChanged();
                }
            }
        }

        string text;
        [DisplayName("Company")]
        public string CompanyName {
            get { return text; }
            set {
                if (text != value) {
                    if (string.IsNullOrEmpty(value))
                        throw new Exception();
                    text = value;
                    OnPropertyChanged();
                }
            }
        }
        Nullable<decimal> val;
        [DataType(DataType.Currency)]
        [DisplayName("Payment")]
        public Nullable<decimal> Value {
            get { return val; }
            set {
                if (val != value) {
                    val = value;
                    OnPropertyChanged();
                }
            }
        }
        DateTime dt;
        [DisplayFormat(DataFormatString = "d")]
        public DateTime RequiredDate {
            get { return dt; }
            set {
                if (dt != value) {
                    dt = value;
                    OnPropertyChanged();
                }
            }
        }
        bool state;
        public bool Processed {
            get { return state; }
            set {
                if (state != value) {
                    state = value;
                    OnPropertyChanged();
                }
            }
        }
        Image image;
        public Image Image {
            get { return image; }
            set {
                if (image != value) {
                    image = value;
                    OnPropertyChanged();
                }
            }
        }
        public override string ToString() {
            return string.Format("ID = {0}, Text = {1}", ID, CompanyName);
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "") {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class DataHelper {

        public static string[] companies = new string[] { "Hanari Carnes", "Que Delícia", "Romero y tomillo", "Mère Paillarde",
            "Comércio Mineiro", "Reggiani Caseifici", "Maison Dewey" };

        public static Image[] images = new Image[] {
            global::GridBoundToRuntimeCreatedData.Properties.Resources.palette_16x16,
            global::GridBoundToRuntimeCreatedData.Properties.Resources.viewonweb_16x16,
            global::GridBoundToRuntimeCreatedData.Properties.Resources.design_16x16,
            global::GridBoundToRuntimeCreatedData.Properties.Resources.piestylepie_16x16,
            global::GridBoundToRuntimeCreatedData.Properties.Resources.alignhorizontaltop2_16x16,
            null
        };

        public static BindingList<Record> GetData(int count) {
            BindingList<Record> records = new BindingList<Record>();
            Random rnd = new Random();
            for (int i = 0; i < count; i++) {
                int n = rnd.Next(10);
                records.Add(new Record() {
                    ID = i + 100,
                    CompanyName = companies[i % companies.Length],
                    RequiredDate = DateTime.Today.AddDays(n - 5),
                    Value = i % 2 == 0 ? (i + 1) * 123 : i * 231,
                    Processed = i % 2 == 0,
                    Image = images[i % images.Length],
                });
            };
            return records;
        }
    }
}
See Also