我在实现 Bindable Base 的 View 模型中有一个可观察集合,如下所示请查看 MoveUp 和 MoveDown 方法,它们绑定(bind)到 View 中的两个按钮。当按下向上按钮时,我希望数据网格中的选定行根据数据库中的序列列向上移动一步,向下移动一步。这两种方法都可以正常工作。问题是仅当刷新整个 View 时更改才会显示在数据网格中。我的要求是单击按钮时我希望自动刷新 View 。我为这么长的代码道歉。请帮忙!!!!。对于 View 模型下方指定的向上和向下按钮,我也有一些 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
将在更改时发出通知。没有理由手动执行此操作,因此您可以删除所有 OnPropertyChanged("JobEntities");
。这将使您获得更清洁的解决方案。
WPF provides the ObservableCollection class, which is a built-in implementation of a data collection that implements the INotifyCollectionChanged interface.
下一部分是 ObservableCollection 只会通知集合本身的更改(添加/删除)。对列表中元素的任何修改都不会发送通知消息。为此,最简单的方法是对 Observable 集合中使用的元素实现 INotifyPropertyChanged
我在示例中使用的是 PRISM 5,因此它应该与您正在执行的操作相当。您的代码有几个主要的设计更改。首先,我为我的 Observable 集合使用了一个直接的属性。我们知道框架将处理对该集合的任何添加/删除操作。然后,为了在我更改可观察集合中的实体内的属性时发出通知,我在 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); }
}
}
现在所有的 TestEntity
都需要实现 INotifyPropertyChanged
才能工作,但我使用 PRISM BindableBase
作为示例。
编辑
我在 SO 上发现了类似的问题。我认为你的略有不同,但它们在概念上有所重叠。看看它可能会有所帮助。
Observable Collection Notify when property changed in MVVM
编辑
如果数据网格已排序,之前的方法将不会更新网格。要处理此问题,您需要刷新网格的 View ,但无法使用 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
的绑定(bind)。
<DataGrid Grid.Row="1" ItemsSource="{Binding ViewSource.View}"></DataGrid>
要进一步阅读,您可以引用 MSDN关于此的文章。
这是另一个相关问题/答案 - Re-sort WPF DataGrid after bounded Data has changed
关于c# - Wpf Observable 集合和 DataGrid 不更新更改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26921537/