Corin Blaikie
Corin Blaikie

Reputation: 18138

Add DataSource Property to a Custom WinForms Control

I want to add complex databinding to my custom winforms control, so I can do the following:

myControl.DisplayMember = "Name";
myControl.ValueMember = "Name";
myControl.DataSource = new List<someObject>();

Does anyone know what interfaces, etc. have to be implemented to achieve this?

I have had a look into it and all I found is IBindableComponent, but that seems to be for Simple Binding rather than Complex Binding.

Upvotes: 6

Views: 6559

Answers (4)

gcode
gcode

Reputation: 2989

I want to add an answer to this question which may not address the OP's particular user case in the body, but seemed to at least answer the subject: Add DataSource Property to a Custom WinForms Control

When working with stock Controls, I would usually create a custom class that represents the data I want to bind, then use the Control's DataSource property to link the instance of my custom class to the Control, completing the data bind. However, when it comes to creating custom controls, there is no DataSource property like I was used to before. Using Visual Studio's designer, here's how I have data binding working in custom controls:

I select my custom control, and in the Properties dialog, I bind the control property to the property in my custom class. The designer creates a new BindingSource object based on my custom class, and adds a new DataBinding element in the DataBindings list property that is included by inheriting the Control class. In the constructor for the Form that contains the instance of my custom control, all I need to do is create a new instance of my custom class, then assign the DataSource property of the BindingSource object, to the instance of my custom class.

For example, in the .Designer code file for my Form:

private System.Windows.Forms.BindingSource customControlBusinessObjectBindingSource;

private void InitializeComponent()
{
  this.customControlBusinessObjectBindingSource = new System.Windows.Forms.BindingSource(this.components);

...

// Support PropertyChanged events
this.customControlInstance.DataBindings.Add(new System.Windows.Forms.Binding("MyProperty", this.customControlBusinessObjectBindingSource, "MyProperty", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
}

With the rest of your Form designer code, this is enough to prepare data binding. Then, in the Form's main code file, you would have:

private CustomControlBusinessObject customControlBO = new CustomControlBusinessObject();

public MyForm()
{
  customControlBusinessObjectBindingSource.DataSource = customControlBO;
}

And that will complete the connection of your custom business object (class) to your custom control. As long as you have PropertyChanged event notifications implemented, your control's bound value will be updating.

Upvotes: 0

Chris Tollefson
Chris Tollefson

Reputation: 455

Apply one of the following attributes to your custom control, depending on which kind of data binding you need:

(The question specifically mentions complex data binding, but the given code example looks like lookup data binding to me, so I have included both.)

For example implementations, look at the .NET Framework source code:

  • ComplexBindindPropertiesAttribute implementation in DataGridView
  • LookupBindingPropertiesAttribute implementation in ListControl

But those implementations look very complicated to me, so it might be easier to embed an existing control (such as a DataGridView, ListBox or ComboBox) within your own custom control to take advantage of its existing data binding implementation, rather than writing your own. (You could make the embedded control invisible if necessary.) That is the approach demonstrated by Microsoft in the following guides:

In those guides, they create a data source to bind the custom control to an external database, but it looks like you're simply trying to bind your custom control to an internal collection such as a List<T>. In that case, the adapted code below might work for you.


In a Windows Forms project in Visual Studio, add a new UserControl.

For complex data binding, apply the ComplexBindingPropertiesAttribute to the custom control. Add a DataGridView control to it. Add DataSource and DataMember properties, and hook them into the DataGridView's own properties.

// ComplexBindingControl.cs
// Adapted from https://learn.microsoft.com/visualstudio/data-tools/create-a-windows-forms-user-control-that-supports-complex-data-binding

using System.ComponentModel;
using System.Windows.Forms;

namespace BindingDemo
{
    [ComplexBindingProperties("DataSource", "DataMember")]
    public partial class ComplexBindingControl : UserControl
    {
        public ComplexBindingControl()
        {
            InitializeComponent();
        }

        // Use a DataGridView for its complex data binding implementation.

        public object DataSource
        {
            get => dataGridView1.DataSource;
            set => dataGridView1.DataSource = value;
        }

        public string DataMember
        {
            get => dataGridView1.DataMember;
            set => dataGridView1.DataMember = value;
        }
    }
}

For lookup data binding, apply the LookupBindingPropertiesAttribute to the custom control. Add a ListBox or ComboBox control to it. Add DataSource, DisplayMember, ValueMember and LookupMember properties, and hook them into the ListBox's or ComboBox's own properties.

// LookupBindingControl.cs
// Adapted from https://learn.microsoft.com/visualstudio/data-tools/create-a-windows-forms-user-control-that-supports-lookup-data-binding

using System.ComponentModel;
using System.Windows.Forms;

namespace BindingDemo
{
    [LookupBindingProperties("DataSource", "DisplayMember", "ValueMember", "LookupMember")]
    public partial class LookupBindingControl : UserControl
    {
        public LookupBindingControl()
        {
            InitializeComponent();
        }

        // Use a ListBox or ComboBox for its lookup data binding implementation.

        public object DataSource
        {
            get => listBox1.DataSource;
            set => listBox1.DataSource = value;
        }

        public string DisplayMember
        {
            get => listBox1.DisplayMember;
            set => listBox1.DisplayMember = value;
        }

        public string ValueMember
        {
            get => listBox1.ValueMember;
            set => listBox1.ValueMember = value;
        }

        public string LookupMember
        {
            get => listBox1.SelectedValue?.ToString();
            set => listBox1.SelectedValue = value;
        }
    }
}

(Edit: thanks to Frank's answer for reminding me that listBox1.SelectedValue could be null.)

To test it, build the project in Visual Studio, then add an instance of the custom control to a Form. Create some sample data, and bind it to the custom control using its relevant properties.

// Form1.cs

using System.Collections.Generic;
using System.Windows.Forms;

namespace BindingDemo
{
    public partial class Form1 : Form
    {
        private readonly List<SomeObject> data;

        public Form1()
        {
            InitializeComponent();

            // Prepare some sample data.
            data = new List<SomeObject>
            {
                new SomeObject("Alice"),
                new SomeObject("Bob"),
                new SomeObject("Carol"),
            };

            // Bind the data to your custom control...

            // ...for "complex" data binding:
            complexBindingControl1.DataSource = data;

            // ...for "lookup" data binding:
            lookupBindingControl1.DataSource = data;
            lookupBindingControl1.DisplayMember = "Name";
            lookupBindingControl1.ValueMember = "Name";
        }
    }

    internal class SomeObject
    {
        public SomeObject(string name)
        {
            Name = name;
        }

        public string Name { get; set; }
    }
}

Upvotes: 5

Frank
Frank

Reputation: 39

To run the very helpfull example of Chris Tollefson BindingDemo without problems put a try/catch Block around the LookupMember getter like this:

public string LookupMember {
       get {
            try {
                return listBox1.SelectedValue.ToString();
            }
            catch { return null; }
        }
        set => listBox1.SelectedValue = value;
    }

Upvotes: 0

2ndstep
2ndstep

Reputation: 1

Your class needs to inherit the DataBoundControl class instead of UserControl.

Upvotes: 0

Related Questions