我有一个窗口与TabControl在它。TabControl包含5个不同的tabitem。每个TabItem都有自己的ViewModel作为它的DataContext,而Window有一个DataContext,它拥有所有5个TabItem的视图模型作为属性。我遇到的问题是设置。当我(从我的主窗口)启动窗口时,有一个明显的延迟,我花了大量的时间重构我的代码,通过并行运行来提高速度,减少对数据库的调用,并在半昂贵的操作上运行任务。除了一个TabItem和它的视图模型之外,一切都很好。由于某些原因,视图不能正确刷新自身。
例如,我有一个名为DiaryDescriptionViewModel
的视图模型,它接受List<SectionViewModel>
并使用它做东西,视图绑定到结果集合。它工作得很好。我麻烦的视图模型被称为DiaryPayItemEditorViewModel
,它也采取了List<SectionViewModel>
和做的东西,与视图绑定到一个结果集合。视图模型都不会在List<SectionViewModel>
的工作线程上执行工作。然而,两个视图模型都是并行实例化和设置的,我认为这不是问题的根源。
在我的DiaryPayItemEditorViewModel
中,我有一个ObservableCollection<DiaryPayItemDetailViewModel>
, ListView是数据绑定到。ListView从不显示数据,即使它存在。如果我从Parallel.Invoke
调用中取出所有视图模型初始化代码,那么它将绑定并显示数据。
我在这里的假设是,视图在DiaryPayItemEditorViewModel
完全设置之前被初始化(this.InitializeComponents
),这应该没问题。由于我的视图模型都实现了INotifyPropertyChanged
,因此视图应该被通知发生了更改。我怎么也想不明白这个问题。
以下是视图窗口视图模型(DiaryEditorViewModel
)的适用源,视图模型使用相同的集合并使用绑定(DiaryDescriptionViewModel
及其子DiaryDescriptionDetailsViewModel
),然后是我麻烦的视图模型(DiaryPayItemEditorViewModel
及其子DiaryPayItemDetailViewModel
)。
DiaryEditorViewModel.cs
public class DiaryEditorViewModel : BaseChangeNotify
{
private DiaryViewModel diary;
private Project project;
private DiaryDetailsViewModel diaryDetailsViewModel;
private DiaryDescriptionViewModel diaryDescriptionViewModel;
private DiaryPayItemEditorViewModel diaryPayItemsViewModel;
private DiaryEquipmentEditorViewModel diaryEquipmentEditorViewModel;
private DiaryLaborViewModel diaryLaborViewModel;
// This is the designated constructor used by the app.
public DiaryEditorViewModel(Project project, Diary diary, UserViewModel user)
: base(user)
{
// Instance a new diary view model using the provided diary.
this.diary = new DiaryViewModel(diary, user);
this.project = project;
// Setup the repositories we will use.
var repository = new ProjectRepository();
var contractorRepository = new ContractorRepository();
// Setup the temporary collections used by the repositories.
var contractors = new List<Contractor>();
var contractorViewModels = new List<ContractorViewModel>();
var projectSections = new List<Section>();
var bidItemCollection = new List<BidItem>();
var subItemCollection = new List<SubItem>();
var sectionViewModels = new List<SectionViewModel>();
var equipmentCategories = new List<EquipmentCategory>();
var equipmentFuelTypes = new List<EquipmentFuelType>();
var equipmentList = new List<Equipment>();
var equipmentViewModels = new List<EquipmentViewModel>();
Task.Run(() =>
{
Parallel.Invoke(
// Fetch contractors for selected project.
() =>
{
contractors.AddRange(contractorRepository.GetContractorsByProjectId(diary.ProjectId));
equipmentCategories.AddRange(contractorRepository.GetEquipmentCategories());
equipmentFuelTypes.AddRange(contractorRepository.GetEquipmentFuelTypes());
equipmentList.AddRange(contractorRepository.GetEquipmentByProjectId(this.Project.ProjectId));
// Reconstruct the contractor->Equipment->FuelType & Category relationship.
contractorViewModels.AddRange(
contractors.Select(contractor =>
new ContractorViewModel(
contractor,
equipmentList.Where(equipment =>
equipment.ContractorId == contractor.ContractorId).Select(e =>
new EquipmentViewModel(
e,
contractor,
equipmentCategories.FirstOrDefault(cat =>
cat.EquipmentCategoryId == e.EquipmentCategoryId),
equipmentFuelTypes.FirstOrDefault(f =>
f.EquipmentFuelTypeId == e.EquipmentFuelTypeId))))));
},
() =>
{
// Fetch all of the Sections, Bid-Items and Sub-items for the project
projectSections.AddRange(repository.GetSectionsByProjectId(project.ProjectId));
bidItemCollection.AddRange(repository.GetBidItemsByProjectId(project.ProjectId));
subItemCollection.AddRange(repository.GetSubItemsByProjectId(project.ProjectId));
// Reconstruct the Section->BidItem->SubItem hierarchy.
sectionViewModels.AddRange(
projectSections.Select(s =>
new SectionViewModel(project, s,
bidItemCollection.Where(b => b.SectionId == s.SectionId).Select(b =>
new BidItemViewModel(project, b,
subItemCollection.Where(si => si.BidItemId == b.BidItemId))))));
}
);
// Once the parallel invocations are completed, instance all of the children view models
// using the view model collections we just set up.
Parallel.Invoke(
// Fetch contractors for selected project.
() =>
this.DiaryDetailsViewModel = new DiaryDetailsViewModel(
project,
diary,
user),
() => // This view model works just fine, with same constructor signature.
this.DiaryDescriptionViewModel = new DiaryDescriptionViewModel(
project,
diary,
user,
sectionViewModels),
() =>
this.DiaryPayItemEditorViewModel = new DiaryPayItemEditorViewModel(
project,
diary,
user,
sectionViewModels),
() => // This view model does not notify the UI of changes to its collection.
this.DiaryEquipmentEditorViewModel = new DiaryEquipmentEditorViewModel(
project,
diary,
user,
contractorViewModels),
() =>
// For the Labor view, we just pass the Contractor model collection rather than the view model collection
// since the Labor view does not need any of the additional equipment information.
this.DiaryLaborViewModel = new DiaryLaborViewModel(
project,
diary,
user,
contractors));
});
}
public Project Project
{
get
{
return this.project;
}
set
{
this.project = value;
this.OnPropertyChanged();
}
}
public DiaryViewModel Diary
{
get
{
return this.diary;
}
set
{
this.diary = value;
this.OnPropertyChanged();
}
}
public DiaryDetailsViewModel DiaryDetailsViewModel
{
get
{
return this.diaryDetailsViewModel;
}
set
{
this.diaryDetailsViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryDescriptionViewModel DiaryDescriptionViewModel
{
get
{
return this.diaryDescriptionViewModel;
}
set
{
this.diaryDescriptionViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryPayItemEditorViewModel DiaryPayItemEditorViewModel
{
get
{
return this.diaryPayItemsViewModel;
}
set
{
this.diaryPayItemsViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryLaborViewModel DiaryLaborViewModel
{
get
{
return this.diaryLaborViewModel;
}
set
{
this.diaryLaborViewModel = value;
this.OnPropertyChanged();
}
}
public DiaryEquipmentEditorViewModel DiaryEquipmentEditorViewModel
{
get
{
return this.diaryEquipmentEditorViewModel;
}
set
{
this.diaryEquipmentEditorViewModel = value;
this.OnPropertyChanged();
}
}
}
DiaryDescriptionViewModel
这个视图模型工作得很好,它的this.DiaryDescriptions
集合被正确地绑定并显示在ListView
public class DiaryDescriptionViewModel : BaseDiaryViewModel, IDataErrorInfo
{
private ObservableCollection<DiaryDescriptionDetailsViewModel> diaryDescriptions;
private DiaryDescriptionDetailsViewModel selectedDiaryDescription;
public DiaryDescriptionViewModel()
{
}
public DiaryDescriptionViewModel(Project project, Diary diary, UserViewModel user, List<SectionViewModel> sections)
: base(project, diary, user)
{
// Restore any previously saved descriptions.
var diaryRepository = new DiaryRepository();
List<DiaryDescription> descriptions = diaryRepository.GetDiaryDescriptionsByDiaryId(diary.DiaryId);
this.ProjectSections = sections;
// Reconstruct our descriptions
this.diaryDescriptions = new ObservableCollection<DiaryDescriptionDetailsViewModel>();
foreach (DiaryDescription description in descriptions)
{
SectionViewModel section = this.GetSectionContainingBidItemId(description.BidItemId);
BidItemViewModel bidItem = section.GetBidItem(description.BidItemId);
var details = new DiaryDescriptionDetailsViewModel(description, section, bidItem);
details.PropertyChanged += ChildViewModelPropertyChanged;
this.diaryDescriptions.Add(details);
}
this.diaryDescriptions.CollectionChanged += this.DiaryDescriptionsOnCollectionChanged;
this.IsDirty = false;
}
public ObservableCollection<DiaryDescriptionDetailsViewModel> DiaryDescriptions
{
get
{
return this.diaryDescriptions;
}
set
{
if (value != null)
{
this.diaryDescriptions.CollectionChanged -= this.DiaryDescriptionsOnCollectionChanged;
this.diaryDescriptions =
new ObservableCollection<DiaryDescriptionDetailsViewModel>(
value
.OrderBy(s => s.Section.Section)
.ThenBy(i => i.BidItem.BidItem.Number));
this.diaryDescriptions.CollectionChanged += this.DiaryDescriptionsOnCollectionChanged;
}
else
{
this.diaryDescriptions = new ObservableCollection<DiaryDescriptionDetailsViewModel>();
}
this.OnPropertyChanged();
}
}
public DiaryDescriptionDetailsViewModel SelectedDiaryDescription
{
get
{
return this.selectedDiaryDescription;
}
set
{
// Always unsubscribe from events before replacing the object. Otherwise we end up with a memory leak.
if (this.selectedDiaryDescription != null)
{
this.selectedDiaryDescription.PropertyChanged -= this.ChildViewModelPropertyChanged;
}
this.selectedDiaryDescription = value;
if (value != null)
{
// If the description contains a biditem DiaryId, then we go fetch the section and biditem
// associated with the diary description.
if (value.BidItemId > 0)
{
this.selectedDiaryDescription.Section = this.GetSectionContainingBidItemId(value.BidItemId);
this.selectedDiaryDescription.BidItem = this.selectedDiaryDescription.Section.GetBidItem(value.BidItemId);
}
// Subscribe to property changed events so we can set ourself to dirty.
this.selectedDiaryDescription.PropertyChanged += this.ChildViewModelPropertyChanged;
this.selectedDiaryDescription.IsDirty = false;
}
this.OnPropertyChanged();
this.IsDirty = false;
}
}
DiaryDescriptionDetailViewModel
子视图模型。
public class DiaryDescriptionDetailsViewModel : BaseChangeNotify
{
private readonly DiaryDescription diaryDescription;
private SectionViewModel section;
private BidItemViewModel bidItem;
public DiaryDescriptionDetailsViewModel(DiaryDescription description, SectionViewModel section = null, BidItemViewModel bidItem = null)
{
this.diaryDescription = description;
if (description.BidItemId > 0)
{
this.section = section;
this.bidItem = bidItem;
}
this.IsDirty = false;
}
public DiaryDescription Description
{
get
{
return this.diaryDescription;
}
}
public int BidItemId
{
get
{
return this.diaryDescription.BidItemId;
}
}
public BidItemViewModel BidItem
{
get
{
return this.bidItem;
}
set
{
this.bidItem = value;
this.diaryDescription.BidItemId = value.BidItem.BidItemId;
this.OnPropertyChanged();
}
}
public SectionViewModel Section
{
get
{
return this.section;
}
set
{
this.section = value;
this.OnPropertyChanged();
}
}
}
DiaryPayItemEditorViewModel
最后,视图模型没有将其集合呈现给视图。
public class DiaryPayItemEditorViewModel : BaseDiaryViewModel, IDataErrorInfo
{
private ObservableCollection<DiaryPayItemDetailViewModel> diaryPayItemDetails;
private DiaryPayItemDetailViewModel selectedDiaryPayItemDetail;
private List<DiaryPayItem> allPayItemsForSelectedBidItem;
private decimal sumOfAllPayItemsForBidItem;
public DiaryPayItemEditorViewModel()
{
}
public DiaryPayItemEditorViewModel(Project project, Diary diary, UserViewModel user, List<SectionViewModel> sections)
: base(project, diary, user)
{
this.Initialize(project, sections);
this.IsDirty = false;
}
public ObservableCollection<DiaryPayItemDetailViewModel> DiaryPayItemDetails
{
get
{
return this.diaryPayItemDetails;
}
set
{
this.diaryPayItemDetails = value;
this.OnPropertyChanged();
}
}
public DiaryPayItemDetailViewModel SelectedDiaryPayItemDetail
{
get
{
return this.selectedDiaryPayItemDetail;
}
set
{
if (this.selectedDiaryPayItemDetail != null)
{
this.selectedDiaryPayItemDetail.PropertyChanged -= this.ChildViewModelPropertyChanged;
}
if (value != null)
{
value.PropertyChanged += this.ChildViewModelPropertyChanged;
}
this.selectedDiaryPayItemDetail = value;
this.OnPropertyChanged();
}
}
private void Initialize(Project project, List<SectionViewModel> sections)
{
var repository = new DiaryRepository();
var projectRepository = new ProjectRepository();
this.DiaryPayItemDetails = new ObservableCollection<DiaryPayItemDetailViewModel>();
this.ProjectSections = sections;
// Repository calls to the database.
List<DiaryPayItem> payItems = repository.GetDiaryPayItemsByDiaryId(this.Diary.DiaryId);
var sectionItems = projectRepository.GetSectionHierarchy(project.ProjectId);
// Temporary, needs to be refined.
foreach (var diaryPayItem in payItems)
{
var subItem = sectionItems.SubItems.FirstOrDefault(sub => sub.SubItemId == diaryPayItem.SubItemId);
var bidItems =
sectionItems.BidItems.Where(bid => bid.BidItemId == subItem.BidItemId)
.Select(
bid =>
new BidItemViewModel(project, bid,
sectionItems.SubItems.Where(sub => sub.BidItemId == bid.BidItemId)));
var section = new SectionViewModel(
project,
sectionItems.Sections.FirstOrDefault(s => bidItems.Any(bid => bid.BidItem.SectionId == s.SectionId)),
bidItems);
this.DiaryPayItemDetails.Add(
new DiaryPayItemDetailViewModel(
diaryPayItem,
section,
bidItems.FirstOrDefault(bid => bid.BidItem.BidItemId == subItem.BidItemId),
subItem));
}
}
DiaryPayItemDetailViewModel -麻烦的视图模型的子视图模型
public class DiaryPayItemDetailViewModel : BaseChangeNotify
{
private DiaryPayItem diaryPayItem;
private SectionViewModel selectedSection;
private BidItemViewModel selectedBidItem;
private SubItem selectedSubItem;
public DiaryPayItemDetailViewModel(
DiaryPayItem diaryPayItem,
SectionViewModel section,
BidItemViewModel bidItem,
SubItem subItem)
{
this.DiaryPayItem = diaryPayItem;
this.SelectedSection = section;
this.SelectedBidItem = bidItem;
this.SelectedSubItem = subItem;
}
public DiaryPayItem DiaryPayItem
{
get
{
return this.diaryPayItem;
}
set
{
this.diaryPayItem = value;
this.OnPropertyChanged();
}
}
public SectionViewModel SelectedSection
{
get
{
return this.selectedSection;
}
set
{
this.selectedSection = value;
this.OnPropertyChanged();
}
}
public BidItemViewModel SelectedBidItem
{
get
{
return this.selectedBidItem;
}
set
{
this.selectedBidItem = value;
this.OnPropertyChanged();
}
}
public SubItem SelectedSubItem
{
get
{
return this.selectedSubItem;
}
set
{
this.selectedSubItem = value;
this.DiaryPayItem.SubItemId = value.SubItemId;
this.OnPropertyChanged();
}
}
DiaryDescription选项卡项的XAML。
<ListView ItemsSource="{Binding Path=DiaryDescriptions}"
SelectedItem="{Binding Path=SelectedDiaryDescription}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Section.SectionName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
XAML for Diary Pay Items标签项。
<ListView Name="PayItemListView"
ItemsSource="{Binding Path=DiaryPayItemDetails}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=SelectedBidItem.BidItem.Description}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
BaseChangeNotify
最后,为了展示我的INotifyPropertyChanged
实现,我展示了我的基类。它将对事件处理程序的所有调用包装在一个Application.Current.Dispatcher.Invoke()
动作中。这强制所有事件处理程序调用在主线程上运行,所以我不必担心继承对象中的跨线程问题。
public class BaseChangeNotify : INotifyPropertyChanged
{
private bool isDirty;
private UserViewModel user;
public BaseChangeNotify()
{
}
public BaseChangeNotify(UserViewModel user)
{
this.user = user;
}
public event PropertyChangedEventHandler PropertyChanged;
public bool IsDirty
{
get
{
return this.isDirty;
}
set
{
this.isDirty = value;
this.OnPropertyChanged();
}
}
public UserViewModel User
{
get
{
return this.user;
}
}
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
// Perform the IsDirty check so we don't get stuck in a infinite loop.
if (propertyName != "IsDirty")
{
this.IsDirty = true; // Each time a property value is changed, we set the dirty bool.
}
if (this.PropertyChanged != null)
{
// Invoke the event handlers attached by other objects.
try
{
// When unit testing, this will always be null.
if (Application.Current != null)
{
Application.Current.Dispatcher.Invoke(() =>
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
}
else
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
catch (Exception)
{
throw;
}
}
}
如果有人能帮我解决这个问题,我将非常感激。这两天我尝试了各种各样的方法,但还是没有弄明白。奇怪的是,一个视图模型工作得很好,本质上执行相同的操作,而另一个却不行。
DiaryEditorViewModel
是DiaryEditorWindow的视图模型。DiaryPayItemEditorViewModel属于驻留在窗口中的用户控件。在窗口级别为TabItem在XAML中设置数据上下文解决了这个问题。在UserControl级别设置DataContext会导致视图模型不能正确绑定。
我还尝试在构造函数中设置数据上下文,但这有同样的问题。它永远不会绑定。通过在与麻烦的视图模型相关联的TabItem的XAML中设置数据上下文,问题就解决了。我不明白为什么这是个问题。由于视图模型完全实现了属性更改事件,因此我应该能够在任何时候设置数据上下文,并调整值而不会出现问题。
无论如何,我已经能够解决这个问题了。