Add DataSource Property to a Custom WinForms Control - c#

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.

Apply one of the following attributes to your custom control, depending on which kind of data binding you need:
For complex data binding: ComplexBindingPropertiesAttribute
For lookup data binding: LookupBindingPropertiesAttribute
(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:
Create a Windows Forms user control that supports complex data binding
Create a Windows Forms user control that supports lookup data binding
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; }
}
}

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

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;
}

Related

Binding with object in Windows forms designer

I am trying to bind a list in my namespace to BindingSource through designer. I know how to bind a list in code behind but I would also like to know if it is possible to do the same in designer.
Using the "Data Source Configuration Wizard", I have selected "Object" but it shows only namespace and classes.
I choose a class with List and clicked Finish.
This is the code generated in designer after choosing the class name
this.bindingSource1.DataSource = typeof(Template.Form3);
It looks like windows forms does not have support for binding a list in designer. I am not sure this is the right method or windows forms does not support it. If windows forms have no support for binding to an object, can anyone explain the reason?
Edit:
I tried the suggestion in answer to choose a data member, but it does not bind the binding source with actual data in List. Now designer code looks like
this.bindingSource1.DataMember = "data";
this.bindingSource1.DataSource = typeof(Template.Form3);
The trick is to rebuild your solution, then any public class will be visible in the dropdown list to choose the datasource type from.
Then from the designer, click the binding source (bottom of screen) => Properties => DataMember => Select Property in your class to bind to (A List or Collection)
Edit:
Binding through the designer allows generation of columns at Design time, but it seems that you need to set the BindingSource's Data at runtime.
Since the bind object can't be static memeber but instance member.
private void bindingForm_Load(object sender, EventArgs e)
{
myDataSourceBindingSource.DataSource = (new myDataSource()).MyDataSourceList;
}
Designer:
//
// colADataGridViewTextBoxColumn
//
this.colADataGridViewTextBoxColumn.DataPropertyName = "ColA";
this.colADataGridViewTextBoxColumn.HeaderText = "ColA";
this.colADataGridViewTextBoxColumn.Name = "colADataGridViewTextBoxColumn";
//
// myDataSourceBindingSource
//
this.myDataSourceBindingSource.DataMember = "MyDataSourceList";
this.myDataSourceBindingSource.DataSource = typeof(myNameSpace.myDataSource);
Class:
public class myDataSource
{
public BindingList<myData> MyDataSourceList
{
get
{
var list = new List<myData>()
{
new myData() { ColA = "A" },
new myData() { ColA = "B" }
};
return new BindingList<myData>(list);
}
}
}
public class myData
{
public string ColA { set; get; }
}
I don't know if this makes sense for you, but this is how it works in Windows Forms.

Using two get and set properties for the same object

I have a DataGridView in UI and my presentation class needs to set and get data to and from the gird. So that I could use a public property in this case as follows.
public DataGridView DeductionDetailsInGrid
{
get { return dgEmployeeDeductions; }
}
public List <Deduction > DeductionDetails
{
set { dgEmployeeDeductions.DataSource = value; }
}
I'm using two properties here as the set property should be able to show the list of objects passed in by the presenter, on the grid and also the presenter should be able to somehow get the data from the grid to supply them for the upper layers.
Is using two get and set properties for the same DataGridView an acceptable solution?
I think that I should change the get property's data type (DataGridView) as exposing the DataGridView breaks encapsulation! How to do that?
EXTRA:
If using ListBoxes we could do something like this...
public List<string> GivenPermission
{
get { return lstGivenPermissions.Items.Cast<string>().ToList(); }
set { lstGivenPermissions.DataSource = value; }
}
As you said returning a DataGridView back to the presenter breaks encapsulation and couples the view and the presenter. The presenter should not be aware of what control the view is using to visualize the model. The presenter simply needs to pass data to the view.
Follow the example from your setter. Return a List<Deduction> in the getter. You can map the list of models in the getter and then return in to the presenter
public List<Deduction> DeductionDetails
{
get
{
List<Deduction> deductionsList = new List<Deduction>();
foreach(var deductionFromGrid in dgEmployeeDeductions.Items)
{
Deduction deduction = new Deduction();
// map properties here
deductionsList.Add(deduction)
}
return deductionsList;
}
}
public List <Deduction > DeductionDetails
{
get { return (List<Deduction>)dgEmployeeDeductions.DataSource; }
set { dgEmployeeDeductions.DataSource = value; }
}

How do I create a user control with 3 combo boxes and a different data binding for each?

I've done some extensive searching before finally deciding to ask this question. I've followed the MSDN tutorials on creating User Controls that use Simple, Complex and Lookup Data Binding.
Walkthrough: Creating a User Control that Supports Simple Data Binding
Walkthrough: Creating a User Control that Supports Complex Data Binding
Walkthrough: Creating a User Control that Supports Lookup Data Binding
And they work great...for a User Control that only uses a single Combobox or Gridview.
Now I want to create a User Control with three different Comboboxes. I want to bind each one to a different Table. The tables are 'Names', 'Types', and 'Products'.
The MSDN tutorials involve creating DataBindingProperties for a single Combobox, but do not show how to do the same for a user control that contains more than one.
using System.Windows.Forms;
namespace CS
{
[System.ComponentModel.LookupBindingProperties(
"DataSource", "DisplayMember", "ValueMember", "LookupMember")]
public partial class LookupBox : UserControl
{
public object DataSource
{
get{ return comboBox1.DataSource; }
set{ comboBox1.DataSource = value; }
}
public string DisplayMember
{
get{ return comboBox1.DisplayMember; }
set{ comboBox1.DisplayMember = value; }
}
public string ValueMember
{
get{ return comboBox1.ValueMember; }
set{ comboBox1.ValueMember = value; }
}
public string LookupMember
{
get{ return comboBox1.SelectedValue.ToString(); }
set{ comboBox1.SelectedValue = value; }
}
public LookupBox()
{
InitializeComponent();
}
}
}
Now, as you can see, there is only one Combobox mentioned in the code. I need to have three Comboboxes, each bound to a different table as mentioned above.
Please, I'm banging my head against the wall. I'm not too well versed in User Controls (although I have used them in ASP.NET), but it seems like a good idea make one since I'm going to be using these three Comboboxes together quite a lot in different places in my application.
You can simply extrapolate what you know with what you need:
public object DataSource1
{
get{ return comboBox1.DataSource; }
set{ comboBox1.DataSource = value; }
}
public object DataSource2
{
get{ return comboBox2.DataSource; }
set{ comboBox2.DataSource = value; }
}
public object DataSource3
{
get{ return comboBox3.DataSource; }
set{ comboBox3.DataSource = value; }
}
Though you probably want to make better descriptive names than ..1, ..2, ..3.
I would create a UserControl that contained three of your LookupBox's. For example:
public partial class MyLookupBoxes : UserControl
{
public LookupBox()
{
// Add the 3 LookupBox to this UserControl using the designer
InitializeComponent();
SetupDataSources();
}
private void SetupDataSources()
{
namesLookupBox1.DataSource = names_data_source_1;
// ...
typesLookupBox2.DataSource = types_data_srouce_2;
// ...
productsLookupBox3.DataSource = products_data_srouce_2;
// ...
}
}

Is there such built-in control in silverlight?

I have a list of items with id and description(i can introduce key-value collection instead if needed). What i need is control that binded to viewmodel id property, but shows description of corresponding item/pair on it. Closest example i know is combobox, where i set DisplayMemberPath and SelectedValue/SelectedValuePath, but i don't need dropdown. So is there any in-built control in Silverlight for this?
(of course i can code one myself, it's easy and I can even just put some logic for viewmodel to get pair i need and bind it's description to simple textblock)
Edit: To illustrate what funcionality i need i coded simple example class. It actually satisfies my needs, but i still want to know if i can use built-in control.
public class CollectionItemDisplayControl:TextBox
{
public CollectionItemDisplayControl()
{
IsReadOnly = true;
}
public string SelectedID
{
get { return (string)GetValue(SelectedIDProperty); }
set { SetValue(SelectedIDProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedID. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedIDProperty =
DependencyProperty.Register("SelectedID", typeof(string), typeof(CollectionItemDisplayControl), new PropertyMetadata(new PropertyChangedCallback(OnSelectedIDChangedStatic)));
private static void OnSelectedIDChangedStatic(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CollectionItemDisplayControl originator = d as CollectionItemDisplayControl;
if (originator != null)
{
originator.OnSelectedIDChanged(e);
}
}
private void OnSelectedIDChanged(DependencyPropertyChangedEventArgs e)
{
string description = String.Empty;
string value = e.NewValue as string;
if (value != null)
{
foreach (var item in _items)
{
if (item.UniqueID == value)
{
description = item.Description;
break;
}
}
}
Text = description;
}
private IDataCollection _viewModel;
public IDataCollection ViewModel
{
get { return _viewModel; }
set
{
_viewModel = value;
if (_viewModel != null)
{
_items = _viewModel.Items;
}
}
}
private ObservableCollection<IUnique> _items = new ObservableCollection<IUnique>();
}
ItemClass contains two properties: ID and Description. I can place this control on the page, bind Items, and one-way bind SelectedID.
Edit 2: well i didn't make SelectedID DependencyProperty so binding won't work, but i will fix it right away
Edit 3: first snippet was sloppy and didn't work properly, so i fixed it.
If I understood properly,
You just need the right binding implemented.
(you do need a list? not just a single item, even if single it's similar just any control)
Bind the list to e.g. ItemsControl.
Set ItemsSource to your list of items
Then override ToString on your Item providing it's 'yours' really. If not you can make your own wrapper.
Within ToString output whatever is presenting your item, e.g. description.
That's a quickest solution, you can also make item template as you want.
EDIT:
well just put everything in the view model and bind to it - the TextBox, i.e.
Text={Binding SelectedText}
e.g.
...in your view model add SelectedText and SelectedID (and Items if needed) - properly do OnPropertyChanged.
Set SelectedID from view model or if 'bound' from another control that may change it.
Within set for SelectedID set the SelectedText.
No need for a control for things like that, it's all data binding really.

Usercontrol Datasource?

In my current windows application I am looking on how to give a datasource to my usercontrol.
On my page I add my own usercontrol in a flowlayoutpanel, the usercontrol has 3 textboxes in it which I want to fill with data from a datasource.
usercontrol uc = new usercontrol();
flowlayoutpanel.Controls.Add(uc);
uc.DataSource?
I know that in silverlight and ASP.NET you can add a datasource to a usercontrol. In the usercontrol you get the data into the textboxes by using {Binding fieldname} as their content. I can't find any information on this for Windows Forms.
Thanks for the help.
Thomas
There's an article on MSDN which might help you implement this - Walkthrough: Creating a User Control that Supports Simple Data Binding (see also Complex and Lookup Data binding walkthroughs).
I solved this (thanks to the link of Stuart) by putting setters and getters in my usercontrol.
public partial class ucOpleiding : UserControl
{
public string Datum
{
get { return txtDatum.Text; }
set { txtDatum.Text = value; }
}
public string Plaats
{
get { return txtPlaats.Text; }
set { txtPlaats.Text = value; }
}
public string Omschrijving
{
get { return txtOmschrijving.Text; }
set { txtOmschrijving.Text = value; }
}
public ucOpleiding()
{
InitializeComponent();
}
And in my main form I will then call those setters and getters.
foreach (opleiding opl in ChauffeurManagement.getOpleidingen(Int32.Parse(cbbID.SelectedValue.ToString())))
{
ucOpleiding uc = new ucOpleiding();
uc.Datum = opl.datum.ToString();
uc.Plaats = opl.plaats_instantie;
uc.Omschrijving = opl.omschrijving;
flpOpleidingen.Controls.Add(uc);
}

Categories

Resources