Wpf可观察集合和DataGrid未更新更改

11
我有一个在视图模型中实现Bindable Base的可观察集合,请查看MoveUp和MoveDown方法,它们绑定到视图中的两个按钮。每当按下向上按钮时,我希望数据表格中选定的行根据数据库中的顺序列向上移动一步,向下则向下移动一步...这两种方法都完美地工作。问题是仅当刷新整个视图时才会在数据表格中显示更改。我的要求是当单击按钮时,我希望视图自动刷新。非常抱歉代码如此冗长。请帮忙!!!我还有一些cs代码,用于指定视图模型下的上下按钮。需要强调的代码指针是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<JobConfigurationResults> _jobEntities;

    public ObservableCollection<JobConfigurationResults> JobEntities
    {
        get { return _jobEntities; }
        set
        {
            SetProperty(ref _jobEntities, value); 
            this.OnPropertyChanged("JobEntities");
        }
    }

    //Source System List for Job
    private List<SourceSiteSystem> _lstJobSrcSystems;

    public List<SourceSiteSystem> 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<DestinationSiteSystem> _lstJobDestSystems;

    public List<DestinationSiteSystem> 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<JobTaskConfiguration> _taskSelectionList = new List<JobTaskConfiguration>();

    private CancellationTokenSource _source;

    private RelayCommand<object> _commandSaveInstance;
    private RelayCommand<object> _hyperlinkInstance;
    private RelayCommand<object> _commandRunJob;
    private RelayCommand<object> _upCommand;
    private RelayCommand<object> _downCommand;
    private IEventAggregator _aggregator;


    /// <summary>
    /// This is a Subscriber to the Event published by EnterpriseViewModel
    /// </summary>
    /// <param name="agg"></param>
    public JobConfigurationViewModel(IEventAggregator agg)
    {
        _aggregator = agg;
        PubSubEvent<Message> evt = _aggregator.GetEvent<PubSubEvent<Message>>();
        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<Message> evt = _aggregator.GetEvent<PubSubEvent<Message>>();
        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<bool> TaskPopulate()
    {
        _source = new CancellationTokenSource();
        return Task.Factory.StartNew<bool>(TaskProc, _source.Token);
    }

    /// <summary>
    /// 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
    /// </summary>
    /// <returns></returns>

    private bool DoPopulate()
    {
        PopulateSourceDestinations(this.SiteID);

        return true;
    }

    /// <summary>
    /// this method displays all entities and tasks for the site.
    /// This is done async so that the Publisher thread is not held up
    /// </summary>
    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<JobConfigurationResults>();
        JobEntities = JobConfigurationLogic.GetResults(jobConfigurationResults.SourceId,
            jobConfigurationResults.DestinationId);
        _taskSelectionList = new List<JobTaskConfiguration>(JobEntities.Count * 3);
    }

    /// <summary>
    /// //Adding a method to pupulate the Source and Destination dropdown lists. 
    /// This is done async so that the Publisher thread is not held up
    /// </summary>
    /// 
    /// 
    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<object>(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<object>(saveJobConfigurationChanges);
            return _commandSaveInstance;
        }
    }

    public ICommand CommandRunJob
    {
        get { return _commandRunJob ?? (_commandRunJob = new RelayCommand<object>(RunJob)); }
    }

    /// <summary>
    /// this saves all the changes in the selection made by the user 
    /// </summary>
    /// <param name="ob"></param>
    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<object>(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<object>(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");
        }
    }

    /// <summary>
    /// Execute an etl job using the current job id
    /// </summary>
    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吗?另外,你确定每次点击Up/Down时命令都会被执行吗? - Bhupendra
Yup for datagrid itemsource=JobEntities.. 所有命令都能正常工作,只是数据网格没有刷新。 - nikhil
2个回答

15

ObservableCollection(可观察集合)将在更改时进行通知。没有必要手动执行此操作,因此您可以删除所有的 OnPropertyChanged("JobEntities");。这将使您获得更简洁的解决方案。

MSDN

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

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

我在示例中使用的是PRISM 5,所以它应该与您正在做的相当。有几个重大的设计更改需要进行。首先,我对我的Observable Collection使用了一个直接属性。我们知道框架将处理此集合中的任何添加/删除操作。然后,在可观察集合中更改实体中的属性时,我在TestEntity类本身中使用了一个通知属性来通知。
public class MainWindowViewModel : BindableBase
{
    //Notice no OnPropertyChange, just a property
    public ObservableCollection<TestEntity> TestEntities { get; set; }

    public MainWindowViewModel()
        : base()
    {
        this.TestEntities = new ObservableCollection<TestEntity> {
            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); }
    }
}

现在,为了使其正常工作,真正需要实现INotifyPropertyChanged的只有TestEntity,但我将使用PRISM的BindableBase作为示例。 编辑 我在SO上找到了一个类似的问题。我认为你的问题略有不同,但它们重叠在概念上。看看可能会有所帮助。 Observable Collection Notify when property changed in 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<TestEntity> TestEntities { get; set; }

    public MainWindowViewModel()
        : base()
    {
        this.TestEntities = new ObservableCollection<TestEntity> {
        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进行绑定。

<DataGrid  Grid.Row="1" ItemsSource="{Binding ViewSource.View}"></DataGrid>

如需进一步阅读,您可以参考关于此的MSDN文章。

这里还有一个相关的问题/答案 - 重新排序WPF DataGrid在绑定数据更改后


1
谢谢Nathan,我会尝试一下。非常感谢您提供的代码和解释。 - nikhil
Nathan,我已经尝试过了,但是它仍然无法刷新DataGrid。只有在整个视图刷新时才有效。是否有一种方法可以通过按钮点击来刷新视图... - nikhil
抱歉,Nathan,问题是由于导致问题的线程引起的。发布-订阅模型完全启动了一个新线程,这会阻止视图被通知。非常感谢您的帮助。 - nikhil
@nikhil:不用担心,很高兴你解决了它。 - Nathan
嘿,Nathan,我有一个小问题。在ViewModel中,有没有办法检查Observable Collection是否有任何更改? - nikhil
显示剩余6条评论

0
我知道这是很久以前的问题,但可能对正在寻找答案的人有所帮助。
在我的情况下,我将ObservableCollection设置为我的数据网格的项源,并且在另一个线程中,我试图将另一个集合对象分配给源集合对象:
var _events = new ObservableCollection<Events>();
myDatagrid.ItemsSource = _events;

Task.Factory.StartNew(LoadEvents);

private void LoadEvents()
{
   var _tempData = GetDataFromEvtLogFile();
   Dispatcher.Invoke(()=> {
      _events = _tempData;
   });

}

单独的任务似乎无法触发INotifyPropertyChange通知。所以我必须将每个单独的条目添加到_events集合中,然后我的集合开始刷新。
变更:
foreach(var entry in _tempData)
{
    _events.Add(entry);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接