我在 Xamarin.forms 中遇到数据绑定问题

Posted

技术标签:

【中文标题】我在 Xamarin.forms 中遇到数据绑定问题【英文标题】:I'm having trouble with Data Binding in Xamarin.forms 【发布时间】:2020-11-09 07:24:46 【问题描述】:

对于一些上下文,我正在尝试将我的视图链接到我的视图模型。我的模型中有一个 JSON 对象数组和另一个字符串。我想在我的视图中显示这些对象,但是我无法这样做。

我的模特:

namespace Timetabler.Models

public partial class Timetable

    [JsonProperty("id")]
    public long Id  get; set; 

    [JsonProperty("nid")]
    public long Nid  get; set; 

    [JsonProperty("iid")]
    public long Iid  get; set; 

    [JsonProperty("lid")]
    public long Lid  get; set; 

    [JsonProperty("start")]
    public double Start  get; set; 

    [JsonProperty("dur")]
    public double Dur  get; set; 

    [JsonProperty("weeks")]
    public string Weeks  get; set; 

    [JsonProperty("day")]
    public long Day  get; set; 

    [JsonProperty("note", NullValueHandling = NullValueHandling.Ignore)]
    public string Note  get; set; 


public partial struct TimetableElement

    public long? Integer;
    public string String;
    public Timetable TimetableClass;

    public static implicit operator TimetableElement(long Integer) => new TimetableElement  Integer = Integer ;
    public static implicit operator TimetableElement(string String) => new TimetableElement  String = String ;
    public static implicit operator TimetableElement(Timetable TimetableClass) => new TimetableElement  TimetableClass = TimetableClass ;


public partial class Timetable

    public static TimetableElement[][] FromJson(string json) => JsonConvert.DeserializeObject<TimetableElement[][]>(json, Converter.Settings);


public static class Serialize

    public static string ToJson(this TimetableElement[][] self) => JsonConvert.SerializeObject(self, Converter.Settings);


internal static class Converter

    public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
    
        MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
        DateParseHandling = DateParseHandling.None,
        Converters =
        
            TimetableElementConverter.Singleton,
            new IsoDateTimeConverter  DateTimeStyles = DateTimeStyles.AssumeUniversal 
        ,
    ;


internal class TimetableElementConverter : JsonConverter

    public override bool CanConvert(Type t) => t == typeof(TimetableElement) || t == typeof(TimetableElement?);

    public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
    
        switch (reader.TokenType)
        
            case JsonToken.Integer:
                var integerValue = serializer.Deserialize<long>(reader);
                return new TimetableElement  Integer = integerValue ;
            case JsonToken.String:
            case JsonToken.Date:
                var stringValue = serializer.Deserialize<string>(reader);
                return new TimetableElement  String = stringValue ;
            case JsonToken.StartObject:
                var objectValue = serializer.Deserialize<Timetable>(reader);
                return new TimetableElement  TimetableClass = objectValue ;
        
        throw new Exception("Cannot unmarshal type TimetableElement");
    

    public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
    
        var value = (TimetableElement)untypedValue;
        if (value.Integer != null)
        
            serializer.Serialize(writer, value.Integer.Value);
            return;
        
        if (value.String != null)
        
            serializer.Serialize(writer, value.String);
            return;
        
        if (value.TimetableClass != null)
        
            serializer.Serialize(writer, value.TimetableClass);
            return;
        
        throw new Exception("Cannot marshal type TimetableElement");
    

    public static readonly TimetableElementConverter Singleton = new TimetableElementConverter();


我的视图模型:

namespace Timetabler.ViewModels

class TimetableViewModel : BaseViewModel 

    // First list in JSON data
    public ObservableRangeCollection<TimetableElement> Names get;

    // Second list in JSON data
    public ObservableRangeCollection<TimetableElement> TypeOfClass get;

    // Third list in JSON data
    public ObservableRangeCollection<TimetableElement> Location get;

    // Fourth list in JSON data
    public ObservableRangeCollection<TimetableElement> Courses get;
    public Command GetCoursesCommand  get; 

    public Command GetNamesCommand  get; 

    public TimetableViewModel()
    
        Title = "Timetable";

        // First list in JSON data
        Names = new ObservableRangeCollection<TimetableElement>();

        // Second list in JSON data
        TypeOfClass = new ObservableRangeCollection<TimetableElement>();

        // Third list in JSON data
        Location = new ObservableRangeCollection<TimetableElement>();

        // Fourth list in JSON data
        Courses = new ObservableRangeCollection<TimetableElement>();


        GetCoursesCommand = new Command(async () => await GetCoursesAsync());
        GetNamesCommand = new Command(async () => await GetNamesAsync());
    
    async Task GetNamesAsync()
    
        if (IsBusy)
        
            return;
        
        try
        
            IsBusy = true;
            var timetableElements = await DataService.GetTimetablesAsync();
            var names = timetableElements[0];
            Names.ReplaceRange(names);

            Title = $"Courses available(Names.Count";

        
        catch (Exception ex)
        
            Debug.WriteLine($"Unable to get Names: ex.Message");
            await Application.Current.MainPage.DisplayAlert("Error!", ex.Message, "OK");

        
        finally
        
            IsBusy = false;
        
    

    async Task GetTypeOfClassAsync()
    
        if (IsBusy)
        
            return;
        
        try
        
            IsBusy = true;
            var timetableElements = await DataService.GetTimetablesAsync();
            var typeOfClass = timetableElements[1];
            TypeOfClass.ReplaceRange(typeOfClass);

            Title = $"Courses available(TypeOfClass.Count";

        
        catch (Exception ex)
        
            Debug.WriteLine($"Unable to get TypeOfClass: ex.Message");
            await Application.Current.MainPage.DisplayAlert("Error!", ex.Message, "OK");

        
        finally
        
            IsBusy = false;
        
    

    async Task GetLocationsAsync()
    
        if (IsBusy)
        
            return;
        
        try
        
            IsBusy = true;
            var timetableElements = await DataService.GetTimetablesAsync();
            var location = timetableElements[2];
            Location.ReplaceRange(location);

            Title = $"Courses available(Courses.Count";

        
        catch (Exception ex)
        
            Debug.WriteLine($"Unable to get Location: ex.Message");
            await Application.Current.MainPage.DisplayAlert("Error!", ex.Message, "OK");

        
        finally
        
            IsBusy = false;
        
    


    async Task GetCoursesAsync()
    
        if (IsBusy)
        
            return;
        
        try
        
            IsBusy = true;
            var timetableElements = await DataService.GetTimetablesAsync();
            var courses = timetableElements[3];
            Courses.ReplaceRange(courses);

            Title = $"Courses available(Courses.Count";
                
        
        catch (Exception ex)
        
            Debug.WriteLine($"Unable to get courses: ex.Message");
            await Application.Current.MainPage.DisplayAlert("Error!", ex.Message, "OK");

        
        finally
        
            IsBusy = false;
        
    

    


我的模型将 JSON 反序列化为 C# 对象,此时我的 ViewModel 将它们分解为数组。

我想在视图中显示这些对象。该视图是一个 Syncfusion 时间表。但在这一点上,我很高兴能够在列表视图中显示这些对象。我似乎也无法解决这个问题。我试过了:

我的观点

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:button="clr-namespace:Syncfusion.XForms.Buttons;assembly=Syncfusion.Buttons.XForms"
         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:schedule="clr-namespace:Syncfusion.SfSchedule.XForms;assembly=Syncfusion.SfSchedule.XForms"
         xmlns:viewmodel="clr-namespace:Timetabler.ViewModels"
         mc:Ignorable="d"
         Title="Binding Title"
         x:Class="Timetabler.Views.TimetablePage">

<ContentPage.BindingContext>
    <viewmodel:TimetableViewModel/>
</ContentPage.BindingContext>

<ContentPage.ToolbarItems>
    <ToolbarItem Text="Add Course" Clicked="AddItem_Clicked" />
</ContentPage.ToolbarItems>
<schedule:SfSchedule x:Name="schedule" 
    ScheduleView ="WorkWeekView"
    TimeIntervalHeight="130" 
    ShowCurrentTimeIndicator="True"
    DataSource="Binding Courses">

    <schedule:SfSchedule.ViewHeaderStyle>
        <schedule:ViewHeaderStyle
        BackgroundColor="#FFFFFF" 
        CurrentDayTextColor="#d1d119"
        CurrentDateTextColor="#d1d119"
        DayTextColor="#44453e" 
        DateTextColor="#44453e" 
        DayFontFamily="Arial" 
        DateFontFamily="Arial">
        </schedule:ViewHeaderStyle>
    </schedule:SfSchedule.ViewHeaderStyle>
</schedule:SfSchedule>

如您所见,我正在尝试绑定到我的 ViewModel 中的 Courses 属性。难道我做错了什么?我添加了 ContentPage。 BindingContext 作为正确的 ViewModel。我如何能够从其中访问单个值?

【问题讨论】:

【参考方案1】:

据我所知,绑定没问题,但您使用的是自定义对象。你不能指望SfScheduler 知道数据需要来自哪些字段。

正如here 所记录的那样,您需要告诉它要在对象中查找哪些字段以确定开始和结束日期和时间等属性。即

<syncfusion:SfSchedule x:Name="schedule">
    <syncfusion:SfSchedule.AppointmentMapping>
        <syncfusion:ScheduleAppointmentMapping
            ColorMapping="color"
            EndTimeMapping="To"
            StartTimeMapping="From"
            SubjectMapping="EventName" 
            IsAllDayMapping="AllDay"/>
    </syncfusion:SfSchedule.AppointmentMapping>
</syncfusion:SfSchedule>

查看您的TimetableElement 对象,您需要在其中添加更多属性,因为我很确定StartTimeMappingEndTimeMapping 期望DateTime。但我可能错了。

希望这能让你走上正确的道路。

【讨论】:

我尝试在我的 xaml 中实现一个条目,用户可以在自定义会议中键入该条目。但即使这样也不行??如何将输入到条目中的文本与 ViewModel 的逻辑绑定? Entry 是另一回事。如果你想在你的支持属性中捕获它的值,你需要将BindingMode=TwoWay 设置为它

以上是关于我在 Xamarin.forms 中遇到数据绑定问题的主要内容,如果未能解决你的问题,请参考以下文章

在 Xamarin Forms 中绑定之前修改数据

将方法从后面的代码转换为 ICommand 数据绑定 Xamarin.Forms

从绑定 ListView Xamarin Forms 中检索 firebase 数据

Xamarin Forms 中数据模板视图单元格内的绑定上下文

ReactiveUI 中的 Xamarin.Forms 控件是不是需要自定义绑定?

Xamarin.Forms Android 绑定库错误