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
就足够了。
这是我在 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
就足够了。