Wpf Observable集合和DataGrid不更新更改

我在视图模型中有一个可观察的集合,它实现了Bindable Base,如下所示请查看MoveUp和MoveDown方法,它们绑定到视图中的两个按钮。 当按下向上按钮时,我希望数据网格中的选定行在数据库中基于序列列向上移动一步并向下移动一步。两种方法都可以完美地工作。 问题是只有在刷新整个视图时才会在数据网格中显示更改。 我的要求是单击按钮时我希望视图自动刷新。 我为这么长的代码道歉。 请帮忙!!!!。 我有一些cs代码也适用于viewmodel下面指定的向上和向下按钮。 只需要强调的代码中的指针是ObservableCollection JobEntities,MoveUp和MoveDown命令。

ViewModel.cs:

public class JobConfigurationViewModel : BindableBase { public JobConfigurationLogic JobConfigurationLogic = new JobConfigurationLogic(new JobConfigurationResultsRepository()); public SrcDestConfigurationLogic SrcDestConfigurationLogic = new SrcDestConfigurationLogic(new SrcDestCofigurationRepository()); private string _enterprise; public string Enterprise { get { return _enterprise; } set { SetProperty(ref _enterprise, value); } } private int currentJobID; private int currentSequence; private int previousJobID; private int previousSequence; private string _site; public string Site { get { return _site; } set { SetProperty(ref _site, value); } } private int _siteID; public int SiteID { get { return _siteID; } set { SetProperty(ref _siteID, value); } } private ObservableCollection _jobEntities; public ObservableCollection JobEntities { get { return _jobEntities; } set { SetProperty(ref _jobEntities, value); this.OnPropertyChanged("JobEntities"); } } //Source System List for Job private List _lstJobSrcSystems; public List LstJobSrcSystems { get { return _lstJobSrcSystems; } set { //Using bindable base setproperty method instead of older inotify prop changed method SetProperty(ref _lstJobSrcSystems, value); } } //Deestination System List for Job private List _lstJobDestSystems; public List LstJobDestSystems { get { return _lstJobDestSystems; } set { //Using bindable base setproperty method instead of older inotify prop changed method SetProperty(ref _lstJobDestSystems, value); } } //the Selected Source Site system ID private int _selectedSrcSiteSystemId = 0; public int SelectedSrcSiteSystemId { get { return _selectedSrcSiteSystemId; } set { //Using bindable base setproperty method instead of older inotify prop changed method SetProperty(ref _selectedSrcSiteSystemId, value); } } //the Selected Source Site system from the dropdown private SourceSiteSystem _selectedSrcSiteSystem; public SourceSiteSystem SelectedSrcSiteSystem { get { return _selectedSrcSiteSystem; } set { //Using bindable base setproperty method instead of older inotify prop changed method if (value != null) { SetProperty(ref _selectedSrcSiteSystem, value); SelectedSrcSiteSystemId = SelectedSrcSiteSystem.SiteSystemId; } } } //the Selected Destination Site system ID private int _selectedDestSiteSystemId = 0; public int SelectedDestSiteSystemId { get { return _selectedDestSiteSystemId; } set { //Using bindable base setproperty method instead of older inotify prop changed method SetProperty(ref _selectedDestSiteSystemId, value); } } //the Selected Destination Site system from the dropdown private DestinationSiteSystem _selectedDestSiteSystem; public DestinationSiteSystem SelectedDestSiteSystem { get { return _selectedDestSiteSystem; } set { //Using bindable base setproperty method instead of older inotify prop changed method if (value != null) { SetProperty(ref _selectedDestSiteSystem, value); SelectedDestSiteSystemId = SelectedDestSiteSystem.SiteSystemId; } } } private JobConfigurationResults _jeJobConfigurationResults; public JobConfigurationResults JEJobConfigurationResults { get { return _jeJobConfigurationResults; } set { _jeJobConfigurationResults = value; } } private List _taskSelectionList = new List(); private CancellationTokenSource _source; private RelayCommand _commandSaveInstance; private RelayCommand _hyperlinkInstance; private RelayCommand _commandRunJob; private RelayCommand _upCommand; private RelayCommand _downCommand; private IEventAggregator _aggregator; ///  /// This is a Subscriber to the Event published by EnterpriseViewModel ///  ///  public JobConfigurationViewModel(IEventAggregator agg) { _aggregator = agg; PubSubEvent evt = _aggregator.GetEvent<PubSubEvent>(); evt.Subscribe(message => Enterprise = message.Enterprise.ToString(), ThreadOption.BackgroundThread); evt.Subscribe(message => Site = message.Site.ToString(), ThreadOption.BackgroundThread); evt.Subscribe(message => SiteID = message.SiteID, ThreadOption.BackgroundThread); //evt.Unsubscribe(); StartPopulate(); } private async void StartPopulate() { await TaskPopulate(); } //This is to ensure that the publisher has published the data that is needed for display in this workspace private bool TaskProc() { Thread.Sleep(500); PubSubEvent evt = _aggregator.GetEvent<PubSubEvent>(); evt.Subscribe(message => Enterprise = message.Enterprise.ToString(), ThreadOption.BackgroundThread); evt.Subscribe(message => Site = message.Site.ToString(), ThreadOption.BackgroundThread); evt.Subscribe(message => SiteID = message.SiteID, ThreadOption.BackgroundThread); return DoPopulate(); } private Task TaskPopulate() { _source = new CancellationTokenSource(); return Task.Factory.StartNew(TaskProc, _source.Token); } ///  /// This method handles the populating of the Source and Destination Dropdowns and the Job entity and Task Datagrid /// This is mainly driven by the Site selected in the previous workspace ///  ///  private bool DoPopulate() { PopulateSourceDestinations(this.SiteID); return true; } ///  /// this method displays all entities and tasks for the site. /// This is done async so that the Publisher thread is not held up ///  public void GetJobConfigurationResults() { if (SelectedSrcSiteSystem == null) { SelectedSrcSiteSystem = LstJobSrcSystems[0]; } if (SelectedDestSiteSystem == null) { SelectedDestSiteSystem = LstJobDestSystems[0]; } SelectedSrcSiteSystemId = SelectedSrcSiteSystem.SiteSystemId; SelectedDestSiteSystemId = SelectedDestSiteSystem.SiteSystemId; var jobConfigurationResults = new JobConfigurationResults { SourceId = SelectedSrcSiteSystemId, DestinationId = SelectedDestSiteSystemId }; JobEntities = new ObservableCollection(); JobEntities = JobConfigurationLogic.GetResults(jobConfigurationResults.SourceId, jobConfigurationResults.DestinationId); _taskSelectionList = new List(JobEntities.Count * 3); } ///  /// //Adding a method to pupulate the Source and Destination dropdown lists. /// This is done async so that the Publisher thread is not held up ///  /// /// public async void PopulateSourceDestinations(int siteId) { this.LstJobSrcSystems = SrcDestConfigurationLogic.LoadSourceSiteSystems(siteId); this.LstJobDestSystems = SrcDestConfigurationLogic.LoadDestinationSystems(siteId); GetJobConfigurationResults(); } public ICommand HyperlinkCommand { get { if (_hyperlinkInstance == null) _hyperlinkInstance = new RelayCommand(openDialog); return _hyperlinkInstance; } } private void openDialog(object obj) { JobConfigurationResults results = obj as JobConfigurationResults; JEJobConfigurationResults = JobEntities.SingleOrDefault(x => x.JobEntityId == results.JobEntityId); } public ICommand CommandSave { get { if (_commandSaveInstance == null) _commandSaveInstance = new RelayCommand(saveJobConfigurationChanges); return _commandSaveInstance; } } public ICommand CommandRunJob { get { return _commandRunJob ?? (_commandRunJob = new RelayCommand(RunJob)); } } ///  /// this saves all the changes in the selection made by the user ///  ///  public void saveJobConfigurationChanges(object ob) { foreach (var job in JobEntities) { int jobEntityId = job.JobEntityId; foreach (var task in job.TaskDetails) { int id = task.JobTask_ID; bool isSelected = task.IsSelected; _taskSelectionList.Add(task); } } JobConfigurationLogic.UpdateTaskSelection(_taskSelectionList); } public ICommand UpCommand { get { if (_upCommand == null) _upCommand = new RelayCommand(MoveUp); return _upCommand; } } private void MoveUp(object obj) { if (obj != null) { JobConfigurationResults results = obj as JobConfigurationResults; currentJobID = results.JobEntityId; currentSequence = results.SequenceOrder - 1; try { JobConfigurationResults res = _jobEntities.SingleOrDefault(n => n.SequenceOrder == currentSequence); previousJobID = res.JobEntityId; previousSequence = res.SequenceOrder + 1; // JobConfigurationLogic.UpdateSequence(currentJobID, previousSequence, previousJobID, currentSequence); JobConfigurationLogic.UpdateSequence(currentSequence, currentJobID, previousSequence, previousJobID); OnPropertyChanged("JobEntities"); } catch (NullReferenceException) { MessageBox.Show("Can't move the top record"); } } else { MessageBox.Show("Please Select a row that you want to sort"); } } public ICommand DownCommand { get { if (_downCommand == null) _downCommand = new RelayCommand(MoveDown); return _downCommand; } } private void MoveDown(object obj) { if (obj != null) { JobConfigurationResults results = obj as JobConfigurationResults; currentJobID = results.JobEntityId; currentSequence = results.SequenceOrder + 1; try { JobConfigurationResults res = _jobEntities.SingleOrDefault(a => a.SequenceOrder == currentSequence); previousJobID = res.JobEntityId; previousSequence = res.SequenceOrder - 1; JobConfigurationLogic.UpdateSequence(currentSequence, currentJobID, previousSequence, previousJobID); OnPropertyChanged("JobEntities"); } catch (NullReferenceException) { MessageBox.Show("You have reached the end"); } } else { MessageBox.Show("Please Select a row that you want to sort"); } } ///  /// Execute an etl job using the current job id ///  private void RunJob(object obj) { JobEngine jobEngine = new JobEngine(); var jobId = JobEntities[0].JobId; jobEngine.ProcessJob(jobId); } } 

CS代码:

 private void btnup_Click(object sender, RoutedEventArgs e) { dgEntities.Items.Refresh(); //dgEntities.GetBindingExpression(DataGrid.ItemsSourceProperty).UpdateTarget(); } private void btndown_Click(object sender, RoutedEventArgs e) { dgEntities.GetBindingExpression(DataGrid.ItemsSourceProperty).UpdateTarget(); } 

ObservableCollection将通知更改。 没有理由手动执行此操作,因此您可以删除所有OnPropertyChanged("JobEntities"); 。 这将使您获得更清洁的解决方案。

MSDN

WPF提供了ObservableCollection类,它是实现INotifyCollectionChanged接口的数据集合的内置实现。

下一部分是ObservableCollection只会通知集合本身的更改(添加/删除)。 对列表中元素的任何修改都不会发送通知消息。 为此,最简单的方法是将INotifyPropertyChanged实现为Observable Collection中使用的元素

我在示例中使用PRISM 5,因此它应该与您正在做的事情完全相同。 你的代码有几个主要的设计变化。 首先,我正在为我的Observable Collection使用直接属性。 我们知道框架将处理对此集合的任何添加/删除操作。 然后,当我在可观察集合中更改实体内的属性时,我已经在TestEntity类本身中使用了notify属性。

 public class MainWindowViewModel : BindableBase { //Notice no OnPropertyChange, just a property public ObservableCollection TestEntities { get; set; } public MainWindowViewModel() : base() { this.TestEntities = new ObservableCollection { new TestEntity { Name = "Test", Count=0}, new TestEntity { Name = "Test1", Count=1}, new TestEntity { Name = "Test2", Count=2}, new TestEntity { Name = "Test3", Count=3} }; this.UpCommand = new DelegateCommand(this.MoveUp); } public ICommand UpCommand { get; private set; } private void MoveUp() { //Here is a dummy edit to show the modification of a element within the observable collection var i = this.TestEntities.FirstOrDefault(); i.Count = 5; } } 

这是我的实体,请注意BindableBase以及我在更改时通知的事实。 这允许DataGrid或您正在使用的任何内容通知属性已更改。

 public class TestEntity : BindableBase { private String _name; public String Name { get { return _name; } set { SetProperty(ref _name, value); } } //Notice I've implemented the OnPropertyNotify (Prism uses SetProperty, but it's the same thing) private Int32 _count; public Int32 Count { get { return _count; } set { SetProperty(ref _count, value); } } } 

现在真的所有TestEntity需要实现INotifyPropertyChanged才能工作,但我使用的是PRISM BindableBase作为示例。

编辑

我在SO上发现了类似的问题。 我认为你的略有不同,但它们在概念上重叠。 它可能有助于审视它。

可观察集合在MVVM中更改属性时通知

编辑

如果数据网格已排序,则以前的方法不会更新网格。 要处理此问题,您需要刷新网格视图,但无法使用MVVM直接访问它。 因此,为了处理这个问题,您需要使用CollectionViewSource

 public class MainWindowViewModel : BindableBase { //This will bind to the DataGrid instead of the TestEntities public CollectionViewSource ViewSource { get; set; } //Notice no OnPropertyChange, just a property public ObservableCollection TestEntities { get; set; } public MainWindowViewModel() : base() { this.TestEntities = new ObservableCollection { new TestEntity { Name = "Test", Count=0}, new TestEntity { Name = "Test1", Count=1}, new TestEntity { Name = "Test2", Count=2}, new TestEntity { Name = "Test3", Count=3} }; this.UpCommand = new DelegateCommand(this.MoveUp); //Initialize the view source and set the source to your observable collection this.ViewSource = new CollectionViewSource(); ViewSource.Source = this.TestEntities; } public ICommand UpCommand { get; private set; } private void MoveUp() { //Here is a dummy edit to show the modification of a element within the observable collection var i = this.TestEntities.FirstOrDefault(); i.Count = 5; //Now anytime you want the datagrid to refresh you can call this. ViewSource.View.Refresh(); } } 

TestEntity类没有改变,但这里又是类:

 public class TestEntity : BindableBase { private String _name; public String Name { get { return _name; } set { SetProperty(ref _name, value); } } //Notice I've implemented the OnPropertyNotify (Prism uses SetProperty, but it's the same thing) private Int32 _count; public Int32 Count { get { return _count; } set { SetProperty(ref _count, value); } } } 

为了澄清,这是我的XAML显示绑定到新的CollectionViewSource

  

有关进一步阅读,请参阅MSDN上的文章。

这是另一个相关的问题/答案 – 在有界数据发生变化后重新排序WPF DataGrid