Проекционный класс bindinglist фантик


Есть ли простой способ создать оболочку BindingList (с проекцией), которая будет обновляться по мере обновления исходного списка?

Например, предположим, что у меня есть изменяемый список чисел, и я хочу представить их в виде шестнадцатеричных строк в ComboBox. Используя эту обертку, я мог бы сделать что-то вроде этого:
BindingList<int> numbers = data.GetNumbers();
comboBox.DataSource = Project(numbers, i => string.Format("{0:x}", i));
Я мог бы обернуть список в Новый BindingList, обработать все исходные события, обновить список и снова запустить эти события, но я чувствую, что уже есть более простой способ.
1 2

1 ответ:

Я только что наткнулся на этот вопрос, и я понял, что могу опубликовать код, с которым я закончил.

Так как я хотел быстрого решения, я сделал своего рода реализацию бедного человека. Он работает как оболочка вокруг существующего исходного списка, но создает полный проецируемый список элементов и обновляет его по мере необходимости. Сначала я надеялся, что смогу сделать проекцию на лету, поскольку элементы доступны, но для этого потребуется реализовать весь интерфейс IBindingList с нуля.

Что is does: любые обновления исходного списка также обновят целевой список, поэтому связанные элементы управления будут должным образом обновлены.

Что он не делает: он не обновляет исходный список при изменении целевого списка. Для этого потребовалась бы функция инвертированной проекции, а мне эта функция все равно не нужна. Таким образом, элементы всегда должны быть добавлены, изменены или удалены в списке источников.

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

// simple list of numbers
List<int> numbers = new List<int>(new[] { 1, 2, 3, 4, 5 });

// wrap it in a binding list
BindingList<int> sourceList = new BindingList<int>(numbers);

// project each item to a squared item
BindingList<int> squaredList = new ProjectedBindingList<int, int>
    (sourceList, i => i*i);

// whenever the source list is changed, target list will change
sourceList.Add(6);
Debug.Assert(squaredList[5] == 36);

И вот исходный код:

public class ProjectedBindingList<Tsrc, Tdest> 
    : BindingList<Tdest>
{
    private readonly BindingList<Tsrc> _src;
    private readonly Func<Tsrc, Tdest> _projection;

    public ProjectedBindingList(
        BindingList<Tsrc> source, 
        Func<Tsrc, Tdest> projection)
    {
        _projection = projection;
        _src = source;
        RecreateList();
        _src.ListChanged += new ListChangedEventHandler(_src_ListChanged);
    }

    private void RecreateList()
    {
        RaiseListChangedEvents = false;
        Clear();

        foreach (Tsrc item in _src)
            this.Add(_projection(item));

        RaiseListChangedEvents = true;
    }

    void _src_ListChanged(object sender, ListChangedEventArgs e)
    {
        switch (e.ListChangedType)
        {
            case ListChangedType.ItemAdded:
                this.InsertItem(e.NewIndex, Proj(e.NewIndex));
                break;

            case ListChangedType.ItemChanged:
                this.Items[e.NewIndex] = Proj(e.NewIndex);
                break;

            case ListChangedType.ItemDeleted:
                this.RemoveAt(e.NewIndex);
                break;

            case ListChangedType.ItemMoved:
                Tdest movedItem = this[e.OldIndex];
                this.RemoveAt(e.OldIndex);
                this.InsertItem(e.NewIndex, movedItem);
                break;

            case ListChangedType.Reset:
                // regenerate list
                RecreateList();
                OnListChanged(e);
                break;

            default:
                OnListChanged(e);
                break;
        }
    }

    Tdest Proj(int index)
    {
        return _projection(_src[index]);
    }
}
Я надеюсь, что кто-то найдет это полезным.