CollectionViewSource не обновляется с помощью PropertyChanged

У меня огромные проблемы с ComboBoxes в сетке данных. И мне действительно нужна помощь, я думаю, я запутался объемом исследований и тем, что я пробовал. Это действительно должно быть просто, поэтому я, должно быть, что-то упускаю.

УПРОЩЕННАЯ ПРОБЛЕМА

Я использую CollectionViewSource в xaml, C # устанавливает источник этого CollectionViewSource на ObservableCollection в классе, который является текстом страницы. Добавление элементов в коллекцию не обновляет столбец DataGridComboBox, в котором отображается источник представления. См. Ниже строку для более подробной информации.


ОБЗОР

У меня есть страница WPF с сеткой данных. Страница имеет контекст данных, установленный для модели представления. ViewModel содержит две наблюдаемые коллекции. Один для оборудования и один для локаций. У каждого снаряжения есть локация. Они заполняются из базы данных EF с первым кодом, но я считаю, что эта проблема выше этого уровня.

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

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

ПРОБЛЕМА

Кажется, что если событие загрузки страницы происходит до того, как ViewModel заполняет ObservableCollection, тогда locationVwSrc будет пустым, и событие изменения свойства не заставит это измениться.

КРАТКАЯ ВЕРСИЯ РЕАЛИЗАЦИИ. На странице есть коллекция viewSource, определенная в xaml.

Loaded="Page_Loaded"
  Title="EquipRegPage">
<Page.Resources>
    <CollectionViewSource x:Key="locationsVwSrc"/>
</Page.Resources>

Сетка данных определяется с помощью xaml.

<DataGrid x:Name="equipsDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" Margin="10,10,-118,59" 
              ItemsSource="{Binding Equips}" EnableRowVirtualization="True" AutoGenerateColumns="False">

Столбец combobox, определенный в xaml

<DataGridComboBoxColumn x:Name="locationColumn" Width="Auto" MaxWidth="200" Header="Location"
                                    ItemsSource="{Binding Source={StaticResource locationsVwSrc}, UpdateSourceTrigger=PropertyChanged}"
                                    DisplayMemberPath="Name"
                                    SelectedValueBinding="{Binding Location}"

Контекст страницы установлен на модель представления

public partial class EquipRegPage : Page
{
    EquipRegVm viewModel = new EquipRegVm();

    public EquipRegPage()
    {
        InitializeComponent();
        this.DataContext = viewModel;
    }

Загруженное событие, устанавливающее контекст

private void Page_Loaded(object sender, RoutedEventArgs e)
    {
        // Locations View Source
        System.Windows.Data.CollectionViewSource locationViewSource =
            ((System.Windows.Data.CollectionViewSource)(this.FindResource("locationsVwSrc")));
        locationViewSource.Source = viewModel.Locations;
        // Above does not work if the viewmodel populates these after this call, only works if its populated prior.
        //TODO inotifypropertychanged not correct? This occurs before the viewmodels loads, and doesn't display.
        // Therefore notify property changes aren't working.

        // Using this as cheat instead instead works, i beleive due to this only setting the source when its full
        //viewModel.Db.Locations.Load();
        //locationViewSource.Source = viewModel.Db.Locations.Local;
        //locationViewSource.View.Refresh();
    }

Класс ViewModel и как он загружается

public class EquipRegVm : DbWrap, INotifyPropertyChanged
{
    /// <summary>
    /// Event triggered by changes to properties. This notifys the WPF UI above which then 
    /// makes a binding to the UI.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Notify Property Changed Event Trigger
    /// </summary>
    /// <param name="propertyName">Name of the property changed. Must match the binding path of the XAML.</param>
    void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }


    public ObservableCollection<Equip> Equips { get; set; }

    public ObservableCollection<Location> Locations { get; set; }

    public EquipRegVm() : base()
    {
        Load();
    }

    /// <summary>
    /// Load the data from the Model.
    /// </summary>
    public async void Load()    //TODO async an issue?
    {
        // EQUIPMENT
        ObservableCollection<Equip> eqList = new ObservableCollection<Equip>();
        var eqs = await (from eq in Db.Equips
                        orderby eq.Tag
                        select eq).ToListAsync();
        foreach(var eq in eqs)
        {
            eqList.Add(eq);
        }
        Equips = eqList;
        RaisePropertyChanged("Equips");


        // LOCATIONS
        ObservableCollection<Location> locList = new ObservableCollection<Location>();
        var locs = await (from l in Db.Locations
                         orderby l.Name
                         select l).ToListAsync();
        foreach (var l in locs)
        {
            locList.Add(l);
        }
        Locations = locList;
        RaisePropertyChanged("Locations");
    }
}

person Asvaldr    schedule 08.10.2016    source источник


Ответы (2)


Установите Binding, как показано ниже:

System.Windows.Data.CollectionViewSource locationViewSource =
           ((System.Windows.Data.CollectionViewSource)(this.FindResource("locationsVwSrc")));
// locationViewSource.Source = viewModel.Locations;

Binding b = new Binding("Locations");
b.Source = viewModel;
b.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(locationViewSource, CollectionViewSource.SourceProperty, b);

Это все, что вам нужно.

person AnjumSKhan    schedule 08.10.2016
comment
Это определенно устранило непосредственную проблему. Спасибо @AnjumSKhan. Ли Кэмпбелл делает несколько хороших замечаний, и мне, безусловно, интересно узнать от него больше. - person Asvaldr; 08.10.2016
comment
Да, это решит проблему, но добавит еще больше ненужного кода. - person Lee Campbell; 08.10.2016
comment
Я согласен, Ли, если у тебя есть еще какие-то идеи, я очень хочу научиться. Пожалуйста, посмотрите мой последний комментарий к вашему решению. - person Asvaldr; 08.10.2016

Похоже, вы не смогли разбить проблему на достаточно мелкие проблемы. Вопрос, похоже, представляет собой сочетание ComboBoxes в Datagrid, асинхронно устанавливая источник CollectionViewSource, загружая данные из базы данных. Я предполагаю, что было бы полезно рассмотреть либо

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

  2. или разделение существующего кода. Похоже, что Пейдж явно знает о вашей ViewModel (EquipRegVm viewModel = new EquipRegVm();), а вы ViewModel явно знает о базах данных и о том, как загружать себя. Ах да, теперь наши представления связаны с вашей базой данных? Разве не в этом суть паттернов вроде MVVM, чтобы мы не были связаны?

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

  • Настраиваемые свойства коллекции
  • код для страницы (все могут жить в XAML)

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

Изменить 1

    /*foreach(var eq in eqs)
    {
        eqList.Add(eq);
    }
    Equips = eqList;
    RaisePropertyChanged("Equips");*/
    foreach(var eq in eqs)
    {
        Equips.Add(eq);
    }

Изменить 2

    /*foreach (var l in locs)
    {
        locList.Add(l);
    }
    Locations = locList;
    RaisePropertyChanged("Locations");*/
    foreach (var l in locs)
    {
        Locations.Add(l);
    }

Изменить 3

Либо просто удалите использование CollectionViewSource (что он вам предлагает?), Либо используйте привязку для установки источника. Поскольку в настоящее время вы вручную устанавливаете Source (т.е. locationViewSource.Source = viewModel.Locations;), вы отказались от обновления этого значения при возникновении события PropertyChanged.

Поэтому, если вы просто удалите CollectionViewSource, вам просто нужно выполнить привязку к свойству Locations. Если вы решите сохранить CollectionViewSource, я бы предложил удалить код страницы и просто изменить XAML на

<CollectionViewSource x:Key="locationsVwSrc" Source="{Binding Locations}" />
person Lee Campbell    schedule 08.10.2016
comment
Спасибо за ответ. Не могли бы вы подробнее рассказать о том, как выполнить привязку к свойству location. Я пробовал много способов сделать это, и мне никогда не удалось понять это правильно. CollectionViewSource был решением этой проблемы. - person Asvaldr; 08.10.2016
comment
Хорошо, я предполагаю, что ваш текст данных в строке относится к типу Equip, а не к родительскому EquipRegVm. Если вы хотите вернуться к родительскому типу, вам нужно будет использовать RelativeSource. Так что-то вроде <ComboBox ItemsSource="{Binding Path=DataContext.Locations, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/> - person Lee Campbell; 08.10.2016
comment
Как показано в почтовом коде, DataGrid привязан к Equips, а не к виртуальной машине. Внесение предложенного вами изменения вернуло меня к ошибке System.Windows.Data. Ошибка: 4: Не удается найти источник для привязки со ссылкой 'RelativeSource FindAncestor, AncestorType =' System.Windows.Controls.DataGrid ', AncestorLevel =' 1 ''. BindingExpression: Path = DataContext.Locations; DataItem = null; целевой элемент - DataGridComboBoxColumn (HashCode = 57273980); целевым свойством является ItemsSource (тип IEnumerable) - person Asvaldr; 08.10.2016
comment
Извините, что просто дал общее руководство. Поскольку вы не опубликовали MVCE, трудно говорить конкретно. Несмотря на это, в опубликованном вами коде DataGrid привязан ItemSource к Equips. Поскольку свойство Equips относится к типу EquipRegVm, это означает, что DataContext также должен иметь доступ к родственному свойству Location. В вашем случае, возможно, придерживайтесь CVS, - person Lee Campbell; 08.10.2016
comment
Спасибо. Ваши ответы помогли мне лучше понять, насколько я далеко от MVVM. Изначально я пытался изучить паттерн MVVM, но, как говорят многие, он был чрезвычайно громоздким при первом введении. Из-за моего расписания, которое я сейчас сгорел, я стараюсь следовать концепциям MVVM, где я могу, чтобы в будущем его можно было исправить, если потребуется. - person Asvaldr; 08.10.2016