使用Parallel.Invoke设置时,数据绑定Listview不会刷新

时间:2014-06-02 15:14:05

标签: c# wpf data-binding task-parallel-library inotifypropertychanged

我有一个带有TabControl的窗口。 TabControl包含5个不同的TabItem。每个TabItem都有自己的ViewModel作为其DataContext关联,而Window有一个DataContext,它将所有5个TabItem的视图模型作为属性。我遇到的问题是设置。当我启动Window(来自我的MainWindow)时有一个明显的滞后,我花了很多时间重构我的代码并通过并行运行来加快速度,减少对数据库的调用并在半昂贵的情况下运行Tasks操作。一切都很好,除了一个TabItem及其视图模型。出于某种原因,视图无法正确刷新。

例如,我有一个名为DiaryDescriptionViewModel的视图模型,它接收List<SectionViewModel>并对其进行处理,视图绑定到结果集合。它工作正常。我麻烦的视图模型被称为DiaryPayItemEditorViewModel,它也需要一个List<SectionViewModel>并对其进行处理,视图绑定到结果集合。两个视图模型都不会在工作线程或任何事情上对List<SectionViewModel>执行工作。但是,两个视图模型都是并行设置和设置的,我认为这不是问题的根源。

在我的DiaryPayItemEditorViewModel中,我有一个ObservableCollection<DiaryPayItemDetailViewModel>,ListView是绑定到的数据。 ListView从不显示数据,即使它存在。如果我从Parallel.Invoke调用中获取所有视图模型初始化代码,则它会绑定并显示数据。

我的假设是在this.InitializeComponents完全设置之前初始化视图(DiaryPayItemEditorViewModel),这应该没问题。由于我的视图模型都实现了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;
            }
        }
    }

如果有人能帮我解决这个问题,我会非常感激。过去两天我一直在尝试各种各样的事情,但无法弄明白。奇怪的是,一个视图模型如何工作正常,基本上执行相同类型的操作,而另一个则没有。

提前致谢。

1 个答案:

答案 0 :(得分:1)

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

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

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