BindingList vs List - привязка данных WinForms - PullRequest
0 голосов
/ 21 января 2020

Чтобы привязка данных в WinForms (например, к DataGridView) работала так, как вы надеетесь, и добавляла / удаляла строки по мере изменения коллекции, вы должны использовать BindingList (или DataTable) вместо Список c. Проблема в том, что почти никто не имеет первого инстинкта для кодирования с BindingList вместо List в своих библиотеках.

BindingList реализует два события, которых нет в List, и это должно быть различием в привязке данных действие (также свойство для подавления второго события):

AddingNew
ListChanged
RaiseListChangedEvents

Аналогично, в DataTable есть два события, которые, вероятно, включают аналогичную функциональность:

RowDeleted
TableNewRow

EDIT : Как указало полезное SO-сообщество здесь и в другой статье , список можно преобразовать (может быть, более точно инкапсулировать?), Вызвав правильный конструктор BindingList:

BindingList<MyType> MyBL = new BindingList<MyType>();
MyList.ForEach(x => MyBL.Add(x));

My Ситуация немного сложнее, как показано в коде ниже. РЕДАКТИРОВАТЬ Добавлен материал INotifyPropertyChanged, который должен существовать в реальной библиотеке.

public class RealString : INotifyPropertyChanged
{
  private int _KnotCount = 0;
  private List<KnotSpace> _KnotSpacings = new List<KnotSpace>();

  public RealString()
  {
    KnotSpacings.Add(new KnotSpace());
  }

  public int KnotCount
  {
    get { return _KnotCount; }

    set
    {
      int requiredSpacings = 0;

      _KnotCount = value;
      // Always one more space than knots
      requiredSpacings = _KnotCount + 1;

      if (requiredSpacings < KnotSpacings.Count)
      {
        while (requiredSpacings < KnotSpacings.Count)
        {
          KnotSpacings.Add(new KnotSpace());
        }
      }
      else if (requiredSpacings > KnotSpacings.Count)
      {
        while (requiredSpacings > KnotSpacings.Count)
        {
          KnotSpacings.Remove(KnotSpacings.Last());
        }
      }
      this.OnPropertyChanged(this, "KnotCount");
    }

  }

  public List<KnotSpace> KnotSpacings { get => _KnotSpacings; }

  public event PropertyChangedEventHandler PropertyChanged;

  protected virtual void OnPropertyChanged(object sender, string PropertyName)
  {
    if (this.PropertyChanged == null) return;
    this.PropertyChanged(sender, new PropertyChangedEventArgs(PropertyName));
  }

}

public class KnotSpace
{
  private double _Spacing = 10;

  public double Spacing { get => _Spacing; set => _Spacing = value; }

}

Элементы в списке отображаются в пользовательском интерфейсе, а свойства элементов в списке изменяются в пользовательский интерфейс, но пользовательский интерфейс не добавляет / удаляет вещи напрямую из списка, за исключением изменения свойства KnotCount. Обтекание свойства KnotSpacings в BindingList не приводит к обновлению BindingList при обновлении KnotSpacings путем изменения свойства KnotCount.

EDIT ОК, дополнительные пояснения ...

BindingList BL = new BindingList<KnotSpace>(MyRealString.KnotSpacings);

DataGridView1.AutoGenerateColumns = true;
DataGridView1.DataSource = BL;
NumericUpDown1.DataBindings.Add("Value", MyRealString, "KnotCount", false, DataSourceUpdateMode.OnPropertyChanged);

BindingList не более успешно отслеживает изменения в базовом свойстве List (KnotSpacings), чем элементы управления Windows. Таким образом, данные, привязывающие элементы управления к BindingList, не достигают sh. BindingList прекрасно работает, если пользовательский интерфейс добавляет / удаляет элементы из BindingList, поскольку он выполняет те же операции в базовом списке. Но тогда мне нужно будет повторить действие добавления / удаления и логики c библиотеки в моем пользовательском интерфейсе, и это серьезное изменение в ожидании.

РЕДАКТИРОВАТЬ Основные изменения, внесенные в мой оригинал пост пытается: (1) уточнить проблему. (2) Distin guish это не дублирующий вопрос (хотя один из нескольких вопросов был дублирующим). (3) Признать полезные усилия других, которые были бы потеряны, если бы я удалил сообщение.

Ответы [ 2 ]

1 голос
/ 23 января 2020

Если вы используете конструктор BindingList<T>, который принимает экземпляр IList<T>, то этот экземпляр используется для поддержки BindingList<T>, а изменения в IList<T> отражаются в BindingList.

Однако это не конец истории. Привязка данных в WinForms структурирована таким образом, что чем дальше вы получаете от простого двухстороннего связывания с одним свойством, тем больше вещей вы должны охватить сами.

Например, интерфейс INotifyPropertyChanged реализуется классами, которые используются в качестве источника данных для уведомления об изменении дочернего свойства (например, вашего свойства KnotCount).

Для более сложных сценариев ios нельзя использовать BindingList<T>, но может извлечь из него класс и переопределить один или несколько механизмов привязки данных. То же самое для класса BindingSource.

За механизмом привязки данных стоит партия образца, но почти каждая его часть открыта для деривации для настройки поведения. Иногда полезно нарисовать граф объектов из классов и интерфейсов, используемых при связывании данных (много читая соответствующую документацию), чтобы дать себе хороший обзор всего процесса.

1 голос
/ 21 января 2020

Прежде всего, есть лучший способ передать List<T> в BindingList<T>. У BindingList<T> есть конструктор, который принимает List<T>, который копирует элементы List в BindingList, вот так:

List<int> myList = new List<int>();
BindingList<int> myBindingList = new BindingList<int>(myList);

Но на самом деле это не ваш вопрос. Чтобы ответить на ваш вопрос просто - Правильно, List<T> не является хорошим выбором для двусторонней привязки в WinForms . Поскольку List<T> не имеет никаких событий, уведомляющих о добавленных элементах, вы действительно можете гарантировать только одностороннюю привязку - ввод данных может сработать, но все не получается при попытке обновить sh, скажем, при добавлении элементов в List.

Тем не менее, вы упомянули, что эти библиотеки модифицируют List<T>, к которому у вас есть доступ во время изменений. Я бы сказал, что хорошая библиотека будет использовать шаблон интерфейса для использования, изменения и передачи коллекций. Хотя List<T> и BindingList<T> очень разные классы, они оба реализуют IList<T>, ICollection<T> и IEnumerable<T>. Таким образом, любая функция, которая принимает любой из этих интерфейсов в качестве параметра, может принять List<T> или BindingList<T> (например: public void DoSomethingWithCollection(IEnumerable<int> collection) может принять List<int>, BindingList<int>, или любую другую коллекцию, которая реализует IEnumerable<int> ). Шаблон Интерфейса является хорошо известным стандартом на данный момент в жизни C#, и хотя никто не хотел бы использовать BindingList<T> вместо List<T>, их первым инстинктом должно быть использование IEnumerable<T> (или IList<T> или ICollection<T>) через List<T>.

Там, где это возможно, было бы лучше для привязки передать ваш List конструктору BindingList, затем никогда не использовать снова List - вместо этого используйте методы Add и Remove BindingList для управления своей внутренней коллекцией.

...