Xamarin 数据绑定 MVVM 未在 UI 上呈现文本内容

Xamarin databinding MVVM not rendering text content on UI

这是我在 Xamarin 中使用 MVVM 进行的第一次测试。我正在尝试显示由 Web API 提供的标记为 ExampleList 的对象列表(示例)。它接收正确的数据和对象结构,api 和应用程序模型几乎 1:1。

当页面加载时,它会显示所有 9 个正确的元素,但这些元素的文本不会显示。我知道此数据是正确的,因为当单击一个元素时,它会在显示警报中显示其对象的字段。

我确定问题出在标记上,如下所示,它声明为: object.property ,通常我会在没有对象的情况下将其声明为 属性 但是当执行此方法时,应用程序将无法编译并显示以下错误:

"Severity   Code    Description Project File    Line    Suppression State
Error   XFC0045 Binding: Property "ID" not found on "App2.ViewModels.ExampleListViewModel". App2    E:\App2\App2\App2\Views\ExampleListPage.xaml    22  " 

据我所知,假设和绑定属性对象的父级是来自视图模型的示例。

我也曾尝试在 ViewModel 中手动声明对象字段,这确实可以编译,但具有不显示文本的类似结果。

这个问题的奇怪之处在于,当应用程序是 运行 时,文本单元格 属性 声明可以从 Object.Property 更改为 属性 并将显示文本但重新编译时又会产生与上述相同的错误。

xaml的代码如下:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="App2.Views.ExampleListPage"
             xmlns:viewmodels="clr-namespace:App2.ViewModels"
             x:DataType="viewmodels:ExampleListViewModel" 
             Title="Example List">


    <ContentPage.BindingContext>
        <viewmodels:ExampleListViewModel/>
    </ContentPage.BindingContext>

    <ListView x:Name="listView" Margin="20" SelectedItem="{Binding SelectedElement, Mode=TwoWay}" ItemsSource="{Binding ExampleList}"
              RefreshCommand="{Binding RefreshCommand}" IsRefreshing="{Binding IsBusy, Mode=OneWay}" IsPullToRefreshEnabled="True">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextCell Text="{Binding ID}" Detail="{Binding Example.Desc}"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

页面后台代码如下:

using App2.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace App2.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class ExampleListPage : ContentPage
    {
        public ExampleListPage()
        {
            InitializeComponent();
            BindingContext = new ExampleListViewModel();
        }
    }
}

该页面的视图模型如下:

using App2.Models;
using System;
using System.Collections.Generic;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;

namespace App2.ViewModels
{
    public class ExampleListViewModel : BindableObject // < this is needed for mvvm
    {
        public ExampleListViewModel()
        {
            //buttons delcared here also need to be declared below as icommands
            ListElementSelect = new Command(ElementSelect);
            RefreshCommand = new Command(OnRefresh);
            AddElement = new Command(OnAddElement);

            OnRefresh(); // unsure if ok to call here
        }
        /// <summary>
        /// private elements are here
        /// </summary>
        private example _example;
        private List<example> _exampleList = new List<example>();
        private bool _isBusy = false;
        private example _selectedElement;

        /// <summary>
        /// public elements are here
        /// </summary>
        /// 


        // this does fix compile error but doesn't display text still
        //public int ID {
        //    get => this.Example.ID;
        //}
        //private string _desc;
        //public string Desc {
        //    get => this.Example.Desc;
        //}

        public example SelectedElement
        {
            get => _selectedElement;
            set
            {
                if (value != null)
                {
                    Application.Current.MainPage.DisplayAlert("Selected", value.ID + value.Content, "x");
                    value = null;
                }
                _selectedElement = value;
                OnPropertyChanged();
            }
        }
        public example Example
        {
            get => _example;
            set
            {
                _example = value;
                OnPropertyChanged(nameof(Example));
            }
        }
        public List<example> ExampleList
        {
            get => _exampleList;
            set
            {
                _exampleList = value;
                OnPropertyChanged(nameof(ExampleList));
            }
        }
        public bool IsBusy
        {
            get => _isBusy;
            set
            {
                if (value == _isBusy) return;
                _isBusy = value;
                OnPropertyChanged(nameof(IsBusy));
            }
        }
        /// <summary>
        /// button commands are here
        /// </summary>
        public ICommand ListElementSelect { get; }
        public ICommand RefreshCommand { get; }
        public ICommand AddElement { get; }

        /// <summary>
        /// functions here
        /// </summary>
        /// 
        async void ElementSelect()
        {

        }

        async void OnAddElement()
        {

        }

        async void OnRefresh()
        {
            OnBusy();
            await Load(); // request list from API service
            await Task.Delay(2000); //artifical package delay
            OnBusy();
        }

        void OnBusy() // flip instance from true or false - for displaying loading symbol while performing requests
        {
            switch (IsBusy)
            {
                case true:
                    IsBusy = false;
                    break;
                case false:
                    IsBusy = true;
                    break;
            }
        }

        async Task Load()
        {
            string searchTerm = "";
            Expression<Func<example, bool>> searchLambda = x => x.Content.Contains("SearchTerm"); // instanciate searchLambda
            string stringLambda = searchLambda.ToString().Replace("SearchTerm", $"{searchTerm}"); //crashes here
            searchLambda = DynamicExpressionParser.ParseLambda<example, bool>(new ParsingConfig(), true, stringLambda);
            ExampleList = await App.DataService.GetAllAsync<example>();

            //listView.ItemsSource = ExampleList;

        }
    }
}

这就是例子 class:

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using App2.ViewModels;
using Newtonsoft.Json;

namespace App2.Models
{
    public class example
    {
        public event PropertyChangedEventHandler PropertyChanged; //this does event things
        public int ID { get; set; }
        public string Content { get; set; }
        public bool Status { get; set; }

        public string WebLink { get; set; }
        [JsonIgnore] // need newtonsoft
        public string Desc
        { // for list display, not transfered to backend
            get
            {
                return ID.ToString() + " - " + Content.ToString() + " - " + Status.ToString();
            }
        }

        public example(int id, string content, bool status, string webLink)
        { this.ID = id; this.Content = content; this.Status = status; this.WebLink = webLink; }

        public example() { }

        public string toString()
        {
            return ID.ToString() + " - " + Content.ToString() + " - " + Status.ToString() + " - " + WebLink.ToString();
        }
    }

}

您不需要删除页面级 x:DataType 属性。您可以通过在 DataTemplate 本身上指定 x:DataType 属性来通知编译器 DataTemplate 的数据类型(请注意,您需要添加一个 xmlns 引用才能工作,类似于您的 xmlns:viewmodels参考):

<DataTemplate x:DataType="models:example">
     <TextCell Text="{Binding ID}" Detail="{Binding Example.Desc}"/>
</DataTemplate>

至于列表没有显示任何项目,我会仔细检查这一行:

ExampleList = await App.DataService.GetAllAsync<example>();

正在返回一个非空的项目列表。由于您没有在问题中包含该方法,因此我无法对它的作用做出任何假设。

最后一点:您不一定需要在 ViewModel 中继承 BindableObject MVVM 才能工作; INotifyPropertyChanged 就足够了。