Reputation: 127
For a bit of context, I am trying to link my View to my ViewModel. I have a JSON array of Objects, and another of strings in my model. I want to display these objects in my view, however I am having trouble doing so.
My Model:
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();
}
}
My View Model:
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;
}
}
}
}
My model deserialises the JSON into C# objects and at the moment my ViewModel breaks them up into arrays.
I want to display these objects in a View. The view is a Syncfusion schedule. But at this point, I would just be happy to be able to display these objects in a list view. I also cannot seem to work that out. I have tried:
My View
<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>
As you can see I am trying to Bind to the Courses property that is in my ViewModel. Am I doing something wrong? I added the ContentPage. BindingContext as the correct ViewModel. How am I able to access individual values from within that?
Upvotes: 1
Views: 176
Reputation: 34003
From what I can see the binding is OK, but you're using a custom object. You can't expect the SfScheduler
to know which fields the data need to come from.
As documented here, you will need to tell it which fields to look for in the object to determine properties like the start and end date and time. I.e.
<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>
Looking at your TimetableElement
object you will need to add some more properties in there because I am pretty sure that StartTimeMapping
and EndTimeMapping
expect a DateTime
. But I might be wrong there.
Hope this sets you on the right path.
Upvotes: 1