Привязка значений пользовательских свойств экземпляра ExpandoObject к элементам управления .NET Windows Forms

Контекст: Visual Studio 2012/C# 5.0

У меня есть форма .NET Windows Form с тремя элементами управления текстовыми полями: firstNameTextBox, lastNameTextBox и ageTextBox, а также простой пользовательский класс

public class Customer
{
 public string FirstName { get; set; }
 public string LastName { get; set; }
 public int Age { get; set; }
}

и я хочу связать свойства экземпляра пользовательского класса Customer с моими элементами управления Windows Forms. Поэтому я пишу:

private dynamic _customer;
private void Form_Load(object sender, EventArgs e)
{
    _customer = new Customer()
    {
        FirstName = "Andrew",
        LastName = "Chandler",
        Age = 23
    };
    this.firstNameTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text",     _customer, "FirstName"));
    this.lastNameTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", _customer, "LastName"));
    this.ageTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", _customer, "Age"));
}

и это хорошо работает. Затем я немного изменяю код, используя анонимный тип:

private dynamic _customer;
private void Form_Load(object sender, EventArgs e)
{
    _customer = new 
    {
        FirstName = "Andrew",
        LastName = "Chandler",
        Age = 23
    };
    this.firstNameTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", _customer, "FirstName"));
    this.lastNameTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", _customer, "LastName"));
    this.ageTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", _customer, "Age"));

}

и это тоже неплохо работает, хотя в данном случае у меня только односторонняя привязка. Затем я еще больше меняю свой код:

private dynamic _customer;
private void Form_Load(object sender, EventArgs e)
{   
    _customer = new ExpandoObject();
    _customer.FirstName = "Andrew";
    _customer.LastName = "Chandler";
    _customer.Age = 23;

     this.firstNameTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", _customer, "FirstName"));
     this.lastNameTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", _customer, "LastName"));
     this.ageTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", _customer, "Age"));
}

и я получаю ошибку времени выполнения:

'Невозможно выполнить привязку к свойству или столбцу FirstName в источнике данных. Имя параметра: dataMember'

Вопрос. Как связать экземпляр System.Dynamic.ExpandoObject (или System.Dynamic.DynamicObject), имеющий набор динамических настраиваемых свойств, с набором элементов управления Windows Forms (текстовое поле)?

Примечание 1. Решение с вспомогательным классом агрегации/контейнера меня вполне устроит.

Примечание 2: я потратил несколько часов на гугление и попытки применить различные методы (в том числе те, которые я нашел здесь, в StackOverflow), но потерпел неудачу.

Вот «Элегантное» решение, основанное на подсказке Ханса Пассана:

private dynamic _customer;
private void Form_Load(object sender, EventArgs e)
{
    _customer = new ExpandoObject();
    _customer.FirstName = "Andrew";
    _customer.LastName = "Chandler";
    _customer.Age = 23;

    bindExpandoField(this, "FirstNameTextBox", "Text", _customer, "FirstName");
    bindExpandoField(this, "lastNameTextBox", "Text", _customer, "LastName");
    bindExpandoField(this, "ageTextBox", "Text", _customer, "Age");
}


private void bindExpandoField(
      Control hostControl,
      string targetControlName,
      string targetPropertyName,
      dynamic expandoObject,
      string sourcePropertyName)
{
    Control targetControl = hostControl.Controls[targetControlName];
    var IDict = (IDictionary<string, object>)expandoObject;
    var bind = new Binding(targetPropertyName, expandoObject, null);
    bind.Format += (o, c) => c.Value = IDict[sourcePropertyName];
    bind.Parse += (o, c) => IDict[sourcePropertyName] = c.Value;
    targetControl.DataBindings.Add(bind);
}

person ShamilS    schedule 13.03.2013    source источник


Ответы (1)


Привязка данных использует Reflection, который плохо работает с ExpandoObject, поскольку его «свойства» на самом деле не являются свойствами базового объекта. ExpandoObject реализует IDictionary, но легко привязывается только к элементу управления списком, например ListBox.

Это не совсем невозможно, вы должны явно реализовать события Binding.Format и Parse. Как это:

dynamic bag = new ExpandoObject();
bag.foo = "bar";
var bind = new Binding("Text", bag, null);
bind.Format += (o, c) => c.Value = bag.foo;
bind.Parse += (o, c) => bag.foo = c.Value;
textBox1.DataBindings.Add(bind);

Это работает, но, конечно, не приносит очков элегантности.

person Hans Passant    schedule 13.03.2013
comment
На самом деле ваше решение хорошо работает для меня, и его можно сделать элегантным. Большое спасибо! - person ShamilS; 13.03.2013
comment
Ганс, я только что опубликовал решение, основанное на вашей подсказке выше, в нижней части основного текста моего вопроса. Спасибо. - person ShamilS; 13.03.2013
comment
Ганс, пожалуйста, посмотри, я использовал твою подсказку в C# 5.0 'Универсальный' WinForms TextBox Text 'Двусторонний' Binder 'Примечание StackOverflow'. Пожалуйста, прокомментируйте это, если вы находите это интересным. Спасибо. - person ShamilS; 13.03.2013