当使用Parallel.Invoke设置时,数据绑定列表视图不刷新

本文关键字:列表 数据绑定 视图 刷新 Parallel Invoke 设置 | 更新日期: 2023-09-27 18:06:19

我有一个窗口与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;
            }
        }
    }

如果有人能帮我解决这个问题,我将非常感激。这两天我尝试了各种各样的方法,但还是没有弄明白。奇怪的是,一个视图模型工作得很好,本质上执行相同的操作,而另一个却不行。

当使用Parallel.Invoke设置时,数据绑定列表视图不刷新

DiaryEditorViewModel是DiaryEditorWindow的视图模型。DiaryPayItemEditorViewModel属于驻留在窗口中的用户控件。在窗口级别为TabItem在XAML中设置数据上下文解决了这个问题。在UserControl级别设置DataContext会导致视图模型不能正确绑定。

我还尝试在构造函数中设置数据上下文,但这有同样的问题。它永远不会绑定。通过在与麻烦的视图模型相关联的TabItem的XAML中设置数据上下文,问题就解决了。我不明白为什么这是个问题。由于视图模型完全实现了属性更改事件,因此我应该能够在任何时候设置数据上下文,并调整值而不会出现问题。

无论如何,我已经能够解决这个问题了。