You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MAUI页面ObservableCollection数据变更时未正确刷新问题求助

MAUI MVVM课表切换场地后旧数据残留问题

问题描述

我使用MAUI结合MVVM模式开发校园场地/教室课表展示应用,采用ObservableCollection存储每周各天的时段数据。首次选择场地时课表显示正常,但切换Picker中的场地后,新场地信息展示的同时仍保留旧数据,我已执行ObservableCollection的清空与重新添加操作。

ViewModel代码

namespace SchoolTime.Maui.ViewModels
{
    public partial class TimeTableViewModel : ObservableObject, INotifyPropertyChanged
    {
        private readonly IViewVenuesUseCase viewVenuesUseCase;
        private readonly IViewTimeTableUseCase viewTimeTableUseCase;
        private readonly ICalculateTimeTableUseCase calculateTimeTable;
        private readonly IViewTeacherUseCase viewTeacherUseCase;
        private readonly ISchoolTimeRepository schoolTimeRepository;

        public ObservableCollection<Venue> Venues { get; set; }
        public ObservableCollection<TimeSlot> TimeSlots { get; set; }
        public ObservableCollection<Period> MondayPeriods { get; }


        // Collection of enum names for the Picker's ItemsSource
        public List<string> RosterViews { get; } = Enum.GetNames(typeof(Enum_RosterViews)).ToList();

        // Property to bind to the Picker's SelectedItem
        [ObservableProperty]
        private string _selectedRosterView;


        // Property to bind to the Label
        [ObservableProperty]
        private string _selectedObjectDetail;


        // Property to bind to the Label
        [ObservableProperty]
        private string _periodCount;


        // Property to hold the selected item
        [ObservableProperty]
        private Venue _selectedVenue;


        private int displayItemCount = 0;



        public TimeTableViewModel(IViewVenuesUseCase viewVenuesUseCase, 
            IViewTimeTableUseCase viewTimeTableUseCase, ICalculateTimeTableUseCase calculateTimeTable,
            IViewTeacherUseCase viewTeacherUseCase, ISchoolTimeRepository schoolTimeRepository)
        {
            Venues = new ObservableCollection<Venue>();
            TimeSlots = new ObservableCollection<TimeSlot>();
            MondayPeriods = new ObservableCollection<Period>();

            this.viewVenuesUseCase = viewVenuesUseCase;
            this.viewTimeTableUseCase = viewTimeTableUseCase;
            this.calculateTimeTable = calculateTimeTable;
            this.viewTeacherUseCase = viewTeacherUseCase;
            this.schoolTimeRepository = schoolTimeRepository;

            LoadTimeSlotsAsync();
        }


        partial void OnSelectedRosterViewChanged(string value)
        {
            displayItemCount = 0;

            if (_selectedRosterView == "Venue")
            {
                LoadVenuesAsync();
                if (Venues != null && Venues.Count > 0)
                {
                    SelectedVenue = Venues[displayItemCount];
                    SelectedObjectDetail = SelectedVenue.Name;
                    GetRosterByVenue();
                }
            }
        }


        public async Task LoadVenuesAsync()
        {
            this.Venues.Clear();

            var venues = await this.viewVenuesUseCase.GetVenuesAsync();
            if (venues != null && venues.Count > 0)
            {
                foreach (var venue in venues)
                {
                    this.Venues.Add(venue);
                }
            }
        }


        [RelayCommand]
        public async Task GoToMainPage()
        {
            await Shell.Current.GoToAsync($"{nameof(SchoolTimePage)}");
        }


        [RelayCommand]
        public async Task PreviousItem()
        {
            displayItemCount--;

            if (SelectedRosterView == "Venue")
            {
                if (Venues != null && Venues.Count > 0)
                {
                    if (displayItemCount < 0)
                    {
                        displayItemCount = Venues.Count - 1;
                    }
                    SelectedVenue = Venues[displayItemCount];

                    if (SelectedVenue.Name == "Class 25")
                    {
                        int k = 0;
                    }
                    SelectedObjectDetail = SelectedVenue.Name;
                    await GetRosterByVenue();
                }
            }
        }


        [RelayCommand]
        public async Task NextItem()
        {
            displayItemCount++;

            if (SelectedRosterView == "Venue")
            {
                if (Venues != null && Venues.Count > 0)
                {
                    if (displayItemCount >= Venues.Count)
                    {
                        displayItemCount = 0;
                    }
                    SelectedVenue = Venues[displayItemCount];
                    SelectedObjectDetail = SelectedVenue.Name;
                    await GetRosterByVenue();
                }
            }
        }


        public async Task LoadTimeSlotsAsync()
        {
            this.TimeSlots.Clear();

            var slots = await this.viewTimeTableUseCase.GetTimeSlotsAsync();
            if (slots != null && slots.Count > 0)
            {
                foreach (var slot in slots)
                {
                    this.TimeSlots.Add(slot);
                }
            }
        }



        [RelayCommand]
        public async Task CalculateRosters()
        {
            bool result = await Application.Current.MainPage.DisplayAlert(
                "MESSAGE", "This will override existing rosters. Do you want to continue?", "OK", "Cancel");
            if (result)
            {
                await Application.Current.MainPage.DisplayAlert(
                "MESSAGE", "Calculating Rosters", "OK");
                await calculateTimeTable.CalculateRostersBySubject();
            }
        }


        [RelayCommand]
        public async Task GetRosterByVenue()
        {
            MondayPeriods.Clear();

            if (SelectedVenue != null)
            {
                List<Period> periods = await this.viewTimeTableUseCase.GetPeriodsByVenueAsync(_selectedVenue.Id);

                PeriodCount = "Periods: " + periods.Count;

                await OrganisePeriods(periods);
            }
            else
            {
                return;
            }
        }


        private async Task OrganisePeriods(List<Period> periods)
        {
            DayOfTheWeek day = null;
            SubjectClass subjectClass = null;
            Teacher teacher = null;
            Subject subject = null;

            List<Period> MondayRoster = new List<Period>();
            await PrepareRoster(MondayRoster);

            if (periods != null)
            {
                foreach (Period period in periods)
                {
                    day = await this.viewTimeTableUseCase.GetDayOfWeekByIdAsync(period.DayOfWeekId);
                    subjectClass = await this.schoolTimeRepository.GetSubjectClassByIdAsync(period.SubjectClassId);
                    teacher = await this.viewTeacherUseCase.GetTeacherByIdAsync(subjectClass.TeacherId);
                    subject = await this.viewTimeTableUseCase.GetSubjectByIdAsync(subjectClass.SubjectId);
                    period.Teacher = teacher;
                    period.Subject = subject;

                    // Get Index of TimeSlot for this period
                    // TODO Improve on this
                    int slotId = period.TimeSlotId; // For this the first Id of list is 1
                    int slotIndex = slotId - 1;

                    switch (day.Day)
                    {
                        case DayOfWeek.Monday:
                            MondayRoster[slotIndex] = period;
                            break;
                    }
                }
            }

            await CopyRoster(MondayPeriods, MondayRoster);
        }


        private async Task PrepareRoster(List<Period> dayPeriods)
        {
            dayPeriods.Clear();

            for (int i = 0; i < TimeSlots.Count; i++)
            {
                dayPeriods.Add(null);
            }
        }


        private async Task CopyRoster(ObservableCollection<Period> periods, List<Period> roster)
        {
            periods.Clear();

            foreach (var period in roster)
            {
                periods.Add(period);
            }
        }


    }
}

ContentPage代码

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:SchoolTime.Maui.ViewModels"
             xmlns:m="clr-namespace:SchoolTime.CoreBusiness;assembly=SchoolTime.CoreBusiness"
             xmlns:controls="clr-namespace:SchoolTime.Maui.Views.Controls"
             x:DataType="vm:TimeTableViewModel"
             x:Class="SchoolTime.Maui.Views.TimeTablePage"
             Title="Time Table Page">

    <Grid RowDefinitions="70, 50, *, 50">
        
        <HorizontalStackLayout Grid.Row="0" Spacing="20" Margin="20,0,20,10">

            <Picker
                ItemsSource="{Binding RosterViews}"
                SelectedItem="{Binding SelectedRosterView, Mode=TwoWay}"
                Title="View roster by"
                >
                
            </Picker>

            <Button
                Text="Previous"
                Command="{Binding PreviousItemCommand}">
            </Button>
            <Label
                Text="{Binding SelectedObjectDetail}"
                VerticalTextAlignment="Center">
            </Label>
            <Button
                Text="Next"
                Command="{Binding NextItemCommand}">
            </Button>
            <Label                
                Text="{Binding PeriodCount}"
                VerticalTextAlignment="Center">
            </Label>
        </HorizontalStackLayout>
        

        <Grid Grid.Row="2" ColumnDefinitions="0.5*, *, *, *, *, *" Margin="10,0,10,0">
            
            <CollectionView Grid.Column="1" ItemsSource="{Binding MondayPeriods}">
                <CollectionView.ItemsLayout>
                    <GridItemsLayout Orientation="Vertical"/>
                </CollectionView.ItemsLayout>
                <CollectionView.ItemTemplate>
                    <DataTemplate x:DataType="m:Period">

                        <Grid Padding="0,0,0,0" RowDefinitions="{Binding Periods.Count}">

                            <Border>
                                <controls:PeriodControl
                                BindingContext="{Binding .}">
                                </controls:PeriodControl>
                            </Border>
                        </Grid>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>

        </Grid>

        <Button
            Grid.Row="3"
            Grid.Column="0"
            Text="Home"
            Command="{Binding GoToMainPageCommand}"
        ></Button>
        <Button
            Grid.Row="3"
            Grid.Column="1"
            Text="Calculate Rosters"
            HorizontalOptions="End"
            Command="{Binding CalculateRostersCommand}"></Button>
    </Grid>

</ContentPage>

内容的提问来源于stack exchange,提问作者Sabre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 18:45:57