首页 > 解决方案 > 尽管 PropertyChanged 事件成功并正确抛出,绑定仍未更新

问题描述

我有一个名为SubjectsPage的页面,其绑定上下文设置为SubjectsPageViewModel。在该SubjectsPage中有一个SfListView控件 (Syncfusion),其 ItemsSource 已设置为Subjects ObservableCollection 属性,该属性使用 INotifyPropertyChanged 模式,如下所示:

        private ObservableCollection<Subject> _subjects;
        public ObservableCollection<Subject> Subjects
        {
            get => _subjects;
            set => SetProperty(ref _subjects, value, Subjects);
        }

基础视图模型:

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public void SetProperty<T>(ref T property, T value, T publicProperty)
        {
            property = value;
            OnPropertyChanged(nameof(publicProperty));
        }

同样的模式在从SubjectsPage上的一个主题导航到的页面中工作得非常好。我已经监视了Subject模型的属性通知,它们肯定被抛出了。

我创建了一个绑定到SubjectsPageViewModel中的TestInt属性的测试标签,它还使用了上面的属性通知模式。在 OnAppearing 中,我增加了这个标签的值以查看它是否在视觉上更新——它没有。调试输出每次都会写入值,因此我可以看到它应该在哪里,但这根本没有在视觉上反映出来。

尽管显示的初始值是正确的,但没有任何绑定在SubjectsPage上的任何内容部分进行更新。

为 SubjectsPage 设置 BindingContext

        protected async override void OnAppearing()
        {
            base.OnAppearing();

            if (Initialised is false)
                await SetContext();

            IncrementTest();
        }

        async Task SetContext()
        {
            BindingContext = await SubjectsPageViewModel.InitialiseAsync();
            Initialised = true;
        }

如上所述,我的 BindingContext 是在 OnAppearing 中设置的,因为我需要使用异步方法来初始化视图模型属性设置为的一些支持属性。在这里设置它会造成某种断开连接吗?

SubjectsPage继承自BasePage,后者继承自 ContentPage,因为我的所有页面都需要一个附加属性。这不应该影响任何事情,因为导航到的下一页也继承自这个BasePage

主题页面

<views:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:xforms="clr-namespace:Syncfusion.ListView.XForms;assembly=Syncfusion.SfListView.XForms"
             xmlns:viewmodels="clr-namespace:TraceIt.ViewModels"
             xmlns:models="clr-namespace:TraceIt.Models"
             xmlns:views="clr-namespace:TraceIt.Views"
             mc:Ignorable="d"
             x:Class="TraceIt.Views.SubjectsPage"
             Title="Subjects"
             BackgroundColor="Black"
             x:Name="Page">

    //Styling and ToolbarItems

    <!--<views:BasePage.BindingContext>
        <viewmodels:SubjectsPageViewModel/>
    </views:BasePage.BindingContext>-->

    <views:BasePage.Content>
        <AbsoluteLayout Style="{DynamicResource gradientAbsoluteLayout}">

            
            <StackLayout Padding="0" AbsoluteLayout.LayoutFlags="All"
                         AbsoluteLayout.LayoutBounds="0, 0, 1, 1">

                <Label Text="{Binding TestInt}" TextColor="White"/> // Test label (doesn't work)

                <xforms:SfListView x:Name="collectionViewSubjects" AutoFitMode="Height" 
                                   ItemSpacing="{OnIdiom Tablet=20, Phone=10}"
                                   SelectionMode="None" ItemsSource="{Binding Subjects}">

                    <xforms:SfListView.LayoutManager>
                        <xforms:GridLayout SpanCount="2"/>
                    </xforms:SfListView.LayoutManager>

                    <xforms:SfListView.ItemTemplate>
                        <DataTemplate> // Not single binding here updates after property changes
                            
                                <Frame Style="{DynamicResource listItemFrame}" Padding="0">
                                    <StackLayout Padding="10, 13, 10, 20">

                                        <Label Text="{Binding Name}" FontAttributes="Bold" 
                                           Margin="0, 0, 0, 12"/>

                                        <Label Text="{Binding Credits, StringFormat='0 / {0} credits'}"/>

                                        <Label Text="{Binding StandardsCount, StringFormat='{0} standards'}" Margin="0, 0, 0, 12"/>

                                        <Button Text="View Info" CornerRadius="20"
                                                     TextColor="Black" FontAttributes="Bold" 
                                                     FontSize="17" Style="{DynamicResource subjectButtonGradient}"
                                                     Clicked="buttonViewInfo_Clicked" HorizontalOptions="Center"
                                                     WidthRequest="200" HeightRequest="40"
                                                     VerticalOptions="End"/>

                                    </StackLayout>
                                </Frame>
                            </SwipeView>
                            
                        </DataTemplate>
                    </xforms:SfListView.ItemTemplate>
                    
                    

                </xforms:SfListView>
            </StackLayout>
        </AbsoluteLayout>
    </views:BasePage.Content>
</views:BasePage>

修改属性的示例方法:

        private void SetCredits()
        {
            Credits = 0;

            foreach (var standard in Standards)
                AddStandardCredits(standard);
        }

        void AddStandardCredits(Standard standard)
        {
            if (standard.FinalGrade is Standard.Grade.Excellence)
                ExcellenceCredits += standard.Credits;
            else if (standard.FinalGrade is Standard.Grade.Merit)
                MeritCredits += standard.Credits;

            Credits += standard.Credits;
        }

Subject 模型的片段,在 SubjectsPage 中没有视觉更新的属性之一:

public class Subject : BaseModel, ISubjectItem
        private int _credits;
        [NotNull]
        public int Credits
        {
            get => _credits;
            set => SetProperty(ref _credits, value, Credits);
        }

随时询问可能有助于您回答的任何其他信息。

标签: c#xamlxamarin.formsxamarin.androidxamarin.ios

解决方案


所以,我发现问题不是源于 ViewModel,也不是源于 Model,而是源于 BaseViewModel(和 BaseModel)。为了让自己更轻松,我尝试使用以下语法:

public void SetProperty<T>(ref T property, T value, T publicProperty)
        {
            property = value;
            OnPropertyChanged(nameof(publicProperty));
        }

这样做的问题是,nameof(publicProperty它没有给出参数中传递的属性的名称,而只是给出了局部变量名称“publicProperty”。因此,尽管PropertyChanged引发了事件,但每次它都以错误的名称抛出,因此永远无法到达其各自属性的事件订阅者。

我已经修改了对 SetProperty 的使用,它现在需要一个字符串作为第三个参数传递,我nameof()每次都必须使用它。


推荐阅读