Каков лучший способ заставить WPF DataGrid добавить конкретный новый элемент? - PullRequest
2 голосов
/ 31 марта 2012

У меня есть DataGrid в приложении WPF, которое имеет для ItemsSource пользовательскую коллекцию, которую я написал.Коллекция обеспечивает, чтобы все ее элементы удовлетворяли определенному требованию (а именно, они должны быть между некоторыми минимальными и максимальными значениями).

Подпись класса коллекции:

   public class CheckedObservableCollection<T> : IList<T>, ICollection<T>, IList, ICollection,
                                            INotifyCollectionChanged
                                             where T : IComparable<T>, IEditableObject, ICloneable, INotifyPropertyChanged

Я хочу иметь возможностьиспользовать функцию DataGrid, в которой при редактировании последней строки в DataGrid новый элемент добавляется в конец ItemsSource.

К сожалению, DataGrid просто добавляет новый элемент, созданный с использованием конструктора по умолчанию.Таким образом, при добавлении нового элемента, DataGrid косвенно (через его ItemCollection, который является запечатанным классом) объявляет:

ItemsSource.Add(new T())

, где T - тип элементов в CheckedObservableCollection.Я хотел бы, чтобы сетка вместо этого добавила другой T, который удовлетворяет ограничениям, наложенным на коллекцию.

Мои вопросы: есть ли способ сделать это?Кто-нибудь уже сделал это?Каков наилучший (самый простой, самый быстрый для кода; производительность не проблема) способ сделать это?

В настоящее время я только что получил DataGrid, чтобы переопределить функцию OnExecutedBeginEdit своей собственной следующим образом:

public class CheckedDataGrid<T> : DataGrid where T : IEditableObject, IComparable<T>, INotifyPropertyChanged, ICloneable
{
  public CheckedDataGrid() : base() { }

  private IEditableCollectionView EditableItems {
     get { return (IEditableCollectionView)Items; }
  }

  protected override void OnExecutedBeginEdit(ExecutedRoutedEventArgs e) {
     try {
        base.OnExecutedBeginEdit(e);
     } catch (ArgumentException) {
        var source = ItemsSource as CheckedObservableCollection<T>;
        source.Add((T)source.MinValue.Clone());
        this.Focus();
     }
  }
}

Где MinValue - наименьший допустимый элемент в коллекции.

Мне не нравится это решение.Если у кого-нибудь из вас есть совет, я буду очень признателен!

Спасибо

Ответы [ 2 ]

3 голосов
/ 13 сентября 2013

Эта проблема теперь частично разрешима при 4.5 с использованием события AddingNewItem DataGrid. Вот мой ответ на аналогичный вопрос .

Я решил проблему с помощью события AddingNewItem DataGrid. Это почти полностью недокументированное событие не только сообщает вам, что добавляется новый элемент, но также [ позволяет вам выбирать, какой элемент добавляется ] [2]. AddingNewItem стреляет раньше всего; NewItem свойство EventArgs просто null.

Даже если вы предоставите обработчик для события, DataGrid откажет пользователю добавить строки, если у класса нет конструктора по умолчанию. Тем не менее, как ни странно (но, к счастью), если у вас есть и установлено свойство NewItem для AddingNewItemEventArgs, оно никогда не будет вызвано.

Если вы решите сделать это, вы можете использовать такие атрибуты, как [Obsolete("Error", true)] и [EditorBrowsable(EditorBrowsableState.Never)], чтобы убедиться, что никто никогда не вызовет конструктор. Вы также можете сделать так, чтобы тело конструктора выдало исключение

Декомпиляция элемента управления позволяет нам видеть, что там происходит ...

1 голос
/ 05 апреля 2012

Для всех, кто заинтересовался, я решил проблему, просто выведя из BindingList<T> вместо ObservableCollection<T>, используя мой производный класс в качестве ItemsSource в обычном DataGrid:

   public class CheckedBindingList<T> : BindingList<T>, INotifyPropertyChanged where T : IEditableObject, INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  private Predicate<T> _check;
  private DefaultProvider<T> _defaultProvider;

  public CheckedBindingList(Predicate<T> check, DefaultProvider<T> defaultProvider) {
     if (check == null)
        throw new ArgumentNullException("check cannot be null");
     if (defaultProvider != null && !check(defaultProvider()))
        throw new ArgumentException("defaultProvider does not pass the check");

     _check = check;
     _defaultProvider = defaultProvider;
  }

  /// <summary>
  /// Predicate the check item in the list against.
  /// All items in the list must satisfy Check(item) == true
  /// </summary>
  public Predicate<T> Check {
     get { return _check; }

     set {
        if (value != _check) {
           RaiseListChangedEvents = false;

           int i = 0;
           while (i < Items.Count)
              if (!value(Items[i]))
                 ++i;
              else
                 RemoveAt(i);

           RaiseListChangedEvents = true;
           SetProperty(ref _check, value, "Check");

           ResetBindings();
        }
     }
  }

  public DefaultProvider<T> DefaultProvider {
     get { return _defaultProvider; }
     set {
        if (!_check(value()))
           throw new ArgumentException("value does not pass the check");
     }
  }

  protected override void OnAddingNew(AddingNewEventArgs e) {
     if (e.NewObject != null)
        if (!_check((T)e.NewObject)) {
           if (_defaultProvider != null)
              e.NewObject = _defaultProvider();
           else
              e.NewObject = default(T);
        }

     base.OnAddingNew(e);
  }

  protected override void OnListChanged(ListChangedEventArgs e) {
     switch (e.ListChangedType) {
        case (ListChangedType.ItemAdded):
           if (!_check(Items[e.NewIndex])) {
              RaiseListChangedEvents = false;
              RemoveItem(e.NewIndex);
              if (_defaultProvider != null)
                 InsertItem(e.NewIndex, _defaultProvider());
              else
                 InsertItem(e.NewIndex, default(T));
              RaiseListChangedEvents = true;
           }
           break;
        case (ListChangedType.ItemChanged):
           if (e.NewIndex >= 0 && e.NewIndex < Items.Count) {
              if (!_check(Items[e.NewIndex])) {
                 Items[e.NewIndex].CancelEdit();
                 throw new ArgumentException("item did not pass the check");
              }
           }
           break;
        default:
           break;
     }

     base.OnListChanged(e);
  }

  protected void SetProperty<K>(ref K field, K value, string name) {
     if (!EqualityComparer<K>.Default.Equals(field, value)) {
        field = value;
        if (PropertyChanged != null)
           PropertyChanged(this, new PropertyChangedEventArgs(name));
     }
  }
}

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

...