WPF связывает 3 коллекции вместе

#c# #wpf #xaml #mvvm #binding

#c# #wpf #xaml #mvvm #привязка

Вопрос:

У меня есть приложение MVVM, в котором я хотел бы связать 3 коллекции вместе. В поле зрения у меня есть ItemsControl с временными блоками (просто текстовые поля со свойством зависимости Time).

 <Window x:Class="Scoreboard.View.MainWindow"
    ...
    <ItemsControl ItemsSource="{Binding TimeBoxes}"/>
    ...
</Window>
  

С кодом за

 public class Mainwindow
{
    //...
    var Timeboxes = new ObservableCollection<TimeBox>();
}
  

В модели я хотел бы иметь коллекцию времени.

 public class GameModel
{
    var Times = new ObservableCollection<Time>();
    // Don't know if this is how it should be
}
  

И тогда у меня есть другое окно с выводом, которое похоже на Views MainWindow, но ItemsControl содержит границы вместо временных рамок.

 <Window x:Class="Scoreboard.Display.DisplayWindow"
    ...
    <ItemsControl ItemsSource="{Binding Borders}"/>
    ...
</Window>
  

Что он должен делать: При нажатии на кнопку в представлении ( MainWindow ) в TimeBox коллекции TimeBoxes создается. Time из этого TimeBox привязывается к новому Time в Times GameModel . И это Time также привязано к содержимому (метке) нового Border (у меня есть TimeToStringConverter для этого) в Borders выводе (дисплее) Window . Когда Time in GameModel достигает нуля, его экземпляры удаляются из всех коллекций. Моя проблема в том, что я не знаю, как привязать элемент в коллекции к элементу другой коллекции. ViewModel опущен для простоты.

Подводя итог, я хочу динамически привязывать содержимое TimeBox к Time и это Time к Border в соотношении 1: 1: 1.

Ответ №1:

Здесь у вас есть проверенное решение для a, CollectionHelper которое связывает 2 ObservableCollection коллекции вместе. Когда элемент добавляется или удаляется из одной коллекции, другая обновляется. Bind Метод возвращает IDisposable , поэтому, когда вы удаляете его, автоматическое обновление прекращается. Это работает с 2 коллекциями одного и того же универсального типа. Если вам нужен метод для обработки коллекций разных типов, вам следует реализовать метод с сигнатурой, подобной методу commented:

 [TestClass]
public class BindTwoObservableCollections_test
{
    [TestMethod]
    public void BindTwoObservableCollections()
    {
        var c1 = new ObservableCollection<int>();
        var c2 = new ObservableCollection<int>();

        c1.Add(1);
        Assert.AreEqual(0, c2.Count);

        var subscription = CollectionHelper.Bind(c1, c2);

        c1.Add(2);
        Assert.AreEqual(1, c2.Count);
        Assert.AreEqual(2, c2[0]);

        c2.Add(3);
        Assert.AreEqual(3, c1.Count);
        Assert.AreEqual(3, c1[2]);

        c2.Remove(2);
        Assert.AreEqual(2, c1.Count);

        subscription.Dispose();

        c2.Remove(3);
        Assert.AreEqual(2, c1.Count);
    }
}

public static class CollectionHelper
{
    public static IDisposable Bind<T>(
        ObservableCollection<T> c1,
        ObservableCollection<T> c2)
    {
        var fromC1Subscription = InternalBind(c1, c2);
        var fromC2Subscription = InternalBind(c2, c1);

        return new Disposable(() =>
        {
            fromC1Subscription?.Dispose();
            fromC2Subscription?.Dispose();
        });
    }

    private static IDisposable InternalBind<T>(
        ObservableCollection<T> from,
        ObservableCollection<T> to)
    {
        NotifyCollectionChangedEventHandler onFromChanged =
            (s, e) =>
            {
                switch (e.Action)
                {
                    case NotifyCollectionChangedAction.Add:
                        foreach (T added in e.NewItems)
                            if (!to.Contains(added))
                                to.Add(added);
                        break;

                    case NotifyCollectionChangedAction.Remove:
                        foreach (T removed in e.OldItems)
                            to.Remove(removed);
                        break;

                    //other cases...

                    default:
                        break;
                }
            };

        from.CollectionChanged  = onFromChanged;

        return new Disposable(() => { from.CollectionChanged -= onFromChanged; });
    }

    //public static IDisposable Bind<T1, T2>(
    //    ObservableCollection<T1> c1,
    //    ObservableCollection<T2> c2,
    //    Func<T1, T2> converter1,
    //    Func<T2, T1> converter2)
    //{
    //    todo...
    //}
}

public class Disposable : IDisposable
{
    public Disposable(Action onDispose)
    {
        _onDispose = onDispose;
    }

    public void Dispose()
    {
        _onDispose?.Invoke();
    }

    private Action _onDispose;
}
  

Очевидно, что если вам нужно связать c1, c2 и c3, вы пишете:

 CollectionHelper.Bind(c1, c2);
CollectionHelper.Bind(c2, c3);
  

И этого достаточно.

Комментарии:

1. Спасибо, чувак, это отличное решение. Я был настолько сосредоточен на привязке, что забыл об уведомлении: D

2. «Я знаю это чувство, братан» 😉