Xamarin add new line to JSON - c#

wondering if anybody can help with a solution I am looking for. In my Xamarin project, I have a 'add another line' button which adds another stack layout underneath it with the same controls as the first one. I want to save the new added one to JSON but not sure where to start, has anybody done this before?
I have added the first stack to json but i need it to reckonise the new ones on the button press. (sorry if it does not make sense)
Page.xaml Code:
<Grid Margin="0, 30, 0, 30">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label x:Name="NameLabel" Text="Name" TextColor="Black" FontSize="20" Grid.Column="0" Grid.Row="0" />
<Label x:Name="ArrivalLabel" Text="Arrival" TextColor="Black" FontSize="20" Grid.Column="1" Grid.Row="0" />
<Label x:Name="DescriptionLabel" Text="Description" TextColor="Black" FontSize="20" Grid.Column="2" Grid.Row="0" />
<Label x:Name="DepartLabel" Text="Depart" TextColor="Black" FontSize="20" Grid.Column="3" Grid.Row="0" />
<Label x:Name="SignLabel" Text="Sign" TextColor="Black" FontSize="20" Grid.Column="4" Grid.Row="0" />
</Grid>
<StackLayout x:Name="AddMoreNotes">
<StackLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Entry x:Name="Name" FontSize="20" Grid.Column="0" Grid.Row="0" />
<TimePicker x:Name="Arrival" FontSize="20" Grid.Column="1" Grid.Row="0" />
<Entry x:Name="Description" FontSize="20" Grid.Column="2" Grid.Row="0" />
<TimePicker x:Name="Depart" FontSize="20" Grid.Column="3" Grid.Row="0" />
<Entry x:Name="Sign" FontSize="20" Grid.Column="4" Grid.Row="0"/>
</Grid>
</StackLayout>
<!--Add More View Spawns Here Do Not Change-->
</StackLayout>
<StackLayout Orientation="Horizontal" HorizontalOptions="End">
<Button x:Name="AddMoreButton" Text="Add More" FontSize="16" BorderRadius="6" Clicked="AddMoreButton_Clicked" />
</StackLayout>
Page.xaml.cs Code:
private void AddMoreButton_Clicked(object sender, EventArgs e)
{
AddMoreNotes.Children.Add(new AttendanceAddMoreView()
{
});
}
private void SubmitButton_Clicked(object sender, EventArgs e)
{
var attendanceChecklist = new AttendanceChecklist();
var AttentionOf1 = new List<AttentionOf>();
var AttendanceSection1 = new List<AttendanceSection>();
attendanceChecklist.TheDate = TheDate.Date;
attendanceChecklist.AttendanceNumber = AttendanceNumber.Text;
attendanceChecklist.EmployeeName = EmployeeName.Text;
AttentionOf1.Add(new AttentionOf()
{
FirstName = FirstName.Text,
LastName = LastName.Text,
AddressLine1 = AddressLine1.Text,
AddressLine2 = AddressLine2.Text,
CityOrTown = CityOrTown.Text,
County = County.Text,
Postcode = Postcode.Text,
ContractTitleOrRef = ContractTitleOrRef.Text,
CustomerName = CustomerName.Text,
SiteContact = SiteContact.Text,
});
AttendanceSection1.Add(new AttendanceSection()
{
Name = Name.Text,
Arrival = Arrival.Time,
Description = Description.Text,
Depart = Depart.Time,
Sign = Sign.Text
});
attendanceChecklist.attentionOf = AttentionOf1;
attendanceChecklist.attendanceSection = AttendanceSection1;
var json = JsonConvert.SerializeObject(attendanceChecklist, Newtonsoft.Json.Formatting.Indented);
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine(documents, "AttendanceNote.json");
File.WriteAllText(filename, json);
}
Edit:
added new code
public string LargerJsonString;
private void AddMoreButton_Clicked(object sender, EventArgs e)
{
AddMoreNotes.Children.Add(new AttendanceAddMoreView()
{
});
StringContent content = new StringContent(JsonConvert.SerializeObject(AddMoreNotes), Encoding.UTF8, "application/json");
LargerJsonString += content;
}

To Start this I have done this in MVVM and hence you might have to use that if you are not already using it.
Now I made a dummy layout and a dummy model which can be seen below:
<?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="XamarinCertUnderstanding.Views.MainPage">
<StackLayout Margin="0, 15, 0, 0">
<Button VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" BorderColor="Black" BackgroundColor="Blue" Command="{Binding AddButton}" TextColor="Green" Text="Add Button"/>
<ListView x:Name="MainList" ItemsSource="{Binding DefaultModels}" BackgroundColor="Transparent" HasUnevenRows="True"
SeparatorVisibility="Default" Margin="0, 15, 0, 0">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout BackgroundColor="White" Spacing="10" >
<Entry Text="{Binding Name,Mode=TwoWay}" Placeholder="Name" FontSize="20" BackgroundColor="White"/>
<TimePicker Time="{Binding Arrival,Mode=TwoWay}" FontSize="20" BackgroundColor="White"/>
<Entry Text="{Binding Description,Mode=TwoWay}" Placeholder="Description" FontSize="20" BackgroundColor="White"/>
<TimePicker Time="{Binding Depart,Mode=TwoWay}" FontSize="20" BackgroundColor="White"/>
<Entry FontSize="20" Text="{Binding Sign,Mode=TwoWay}" Placeholder="Sign" BackgroundColor="White"/>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
I am using BindingMode as TwoWay so that any change on the View is reflected to the ViewModel Collection and Vice versa
The DummyModel that I use is as shown below:
public class DefaultModel : INotifyPropertyChanged
{
private string userName = string.Empty;
private string description = string.Empty;
private string sign = string.Empty;
private TimeSpan arrival = TimeSpan.Zero;
private TimeSpan depart = TimeSpan.Zero;
public string Name
{
get
{
return userName;
}
set
{
userName = value;
RaisePropertyChanged(nameof(Name));
}
}
public TimeSpan Arrival
{
get
{
return arrival;
}
set
{
arrival = value;
RaisePropertyChanged(nameof(Arrival));
}
}
public string Description
{
get
{
return description;
}
set
{
description = value;
RaisePropertyChanged(nameof(Description));
}
}
public TimeSpan Depart
{
get
{
return depart;
}
set
{
depart = value;
RaisePropertyChanged(nameof(Depart));
}
}
public string Sign
{
get
{
return sign;
}
set
{
sign = value;
RaisePropertyChanged(nameof(Sign));
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I am using the INotifyPropertyChanged interface to notify any changes in the property. For better understanding check this:https://learn.microsoft.com/en-us/dotnet/framework/winforms/how-to-implement-the-inotifypropertychanged-interface
In your MainPage.Xaml.cs set the BindingContext to MainViewModel in the constructor.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainViewModel();
}
}
Now in your ViewModel do the following:
public class MainViewModel : BindableObject
{
public Command AddButton { get; set; }
private ObservableCollection<DefaultModel> defaultModels;
public ObservableCollection<DefaultModel> DefaultModels
{
get { return defaultModels; }
set
{
defaultModels = value;
OnPropertyChanged(nameof(DefaultModels));
}
}
public MainViewModel()
{
DefaultModels = new ObservableCollection<DefaultModel>();
DefaultModels.Add(new DefaultModel());
AddButton = new Command(AddButtonCommand);
}
private void AddButtonCommand(object obj)
{
DefaultModels.Add(new DefaultModel());
}
}
Now how this works is quite simple on AddButton Click I call the AddButtonCommand and I initialize a blank object and add it to the ObservableCollection which then updates my View and adds another set of controls as far as get this data goes you can get all the data from your DefaultModels collection.
Goodluck
Revert in case of queries

Sorry I'm not able to comment so I'll leave my thoughts here: first off I'd use a ListView as it allows you to easily bind items to your ListView. A little example beneath
<ListView x:Name="MainList" ItemsSource="{Binding Messages}" BackgroundColor="Transparent" HasUnevenRows="True" SeparatorVisibility="Default" Margin="0, 15, 0, 0" IsPullToRefreshEnabled="True" RefreshCommand="{Binding RefreshingMessages}" IsRefreshing="{Binding IsRefreshing}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
Put your code in here
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This should work if you're doing this in the MVVM, Model View to View Model, structure which makes life a lot easier. However I don't believe this is a necessity. In the case of you wanting to add another line to your JSON, I see you're already using JsonConvert, so can't you deserialize the the JSON to a list of the object and loop through the list and output a number of items on your screen?
string JsonContent = await WhateverYourJsonIs.Content.ReadAsStringAsync().ConfigureAwait(false);
var something = JsonConvert.DeserializeObject<List<WhateverYourObjectIsCalled>>(JsonContent)
I'm not exactly sure if that's what you're trying to achieve, if not, let me know and I'll try to help! In this case, WhateverYourJsonIs would most likely need to be a string of Json, you might not need that line, in my case of returning information from a server I do need to read it as a string however you may not need to as it may already be a string, that would be a little testing on your part as I haven't done it this way so I've grabbed snips from my code. Hope this helps!
Edit:
If you serialize your object and have a list of string using this method
StringContent content = new StringContent(JsonConvert.SerializeObject(NewItem), Encoding.UTF8, "application/json");
That'll convert it into Json for you, you can then append it on the end of a larger Json string for example:
LargerJsonString += content
And then add it to your File. Alternatively you could use something such as SQLite and store it in a local Database Table? This is the method I use
Edit 2
So I did a little digging on the Docs.Microsoft to try and find what you're using for "File.WriteAllText" and I found this method which appends extra text onto the end of the file
string appendText = "This is extra text" + Environment.NewLine;
File.AppendAllText(path, appendText);
Try using this rather than WriteAll

Related

Xamarin Forms - Translating drawer menu items at runtime

I'm following the exelent tutorial (Link) on Multilingual in Xamarin.Forms
Everything is working well but I have one issue.
In my application I'm using Navigation drawer by Syncfusion as I'm generating the menu Items in a ListView as seen below.
DrawerPage.xaml
<ListView x:Name="listView"
SelectionMode="Single"
RowHeight="70"
ItemSelected="listView_ItemSelected"
SeparatorColor="Transparent">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="32,10,0,0"
VerticalOptions="Center" >
<Grid RowDefinitions="1*, Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0"
Grid.Column="0"
HorizontalTextAlignment="Start"
HorizontalOptions="Start"
FontSize="25"
FontFamily="Material-Outlined"
Style="{StaticResource IconLabelStyle}"
Text="{Binding Icon}" />
<Label Grid.Row="0"
Grid.Column="1"
HorizontalTextAlignment="Start"
HorizontalOptions="Start"
Margin="10,0,0,0"
Text="{Binding Name}"
FontSize="16"/>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
DrawerPage.cs
private static string MAP = Lang.ResourceManager.GetString("Map");
private static string MAPICON = IconFont.ResourceManager.GetString("LocationOn");
private static string SETTINGS = Lang.ResourceManager.GetString("Settings");
private static string SETTINGSICON = IconFont.ResourceManager.GetString("Settings");
public partial class DrawerPage : ContentPage {
drawerNavItems();
}
private void drawerNavItems()
{
List<MenuItem> itemList = new List<MenuItem>();
itemList.Add(new MenuItem { Icon = MAPICON, Name = MAP });
itemList.Add(new MenuItem { Icon = SETTINGSICON, Name = SETTINGS });
listView.ItemsSource = itemList;
}
The issue I'm having is that I don't understand how I'm supposed to use the helper class in the tutorial link above to translate the drawer menu items.
In Xaml we can just translate the strings like this
Text="{helpers:Translate Support}"
But how do I do the same in the code Behind ?

data from firebase to listView in xamarin forms

I have this Task which bring data from firebase :
public async Task<List<Player>> GetPlayersData()
{
var playersData = (await client.Child("Players")
.OnceAsync<Player>())
.Select(f => new Player
{
PlayerName = f.Object.PlayerName,
PlayerTime = f.Object.PlayerTime,
PlayerRank = f.Object.PlayerRank
}).ToList();
return playersData;
}
it worked perfectly , but i want to get the (playersData) to put it as "ItemSource" in "Listview" in XAML, so i call the task in the page constructor :
public RankTable()
{
InitializeComponent();
BindingContext = this;
client = new FirebaseClient("https://numbergame-*****firebaseio.com/");
GetPlayersData();
}
Now it will run the task and brings the players data but I don't know how to control it and put it as ItemSource in the Following XAML code :
<StackLayout>
<CollectionView Grid.ColumnSpan="2" Grid.RowSpan="2" SelectionMode="None" ItemsSource="{Binding ??}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame HasShadow="True" BackgroundColor="Beige" CornerRadius="15"
IsClippedToBounds="True" Visual="Material" InputTransparent="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Text="{Binding PlayerRank}" FontSize="15" Grid.Column="1"/>
<Label Text="{Binding PlayerName}" FontSize="15" Grid.Column="2"/>
<Label Text="{Binding PlayerTime}" FontSize="15" Grid.Column="3"/>
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
any help please and sorry for my bad english..
<CollectionView x:Name="Players" ... /
// GetPlayersData is async so you can't call it from the constructor
protected override void OnAppearing()
{
Players.ItemsSource = await GetPlayersData();
}

Xamarin Forms Switch Nightmare

I can't believe I have spent a day trying to get this to work! I began developing in Xamarin.Forms last month and for the most part things have been going pretty smooth. My background is ASP.Net MVC and Windows Forms so the MVVM concept is new to me.
The issue that I am having is very frustrating and seems silly really. I imagine it could be my implementation possibly because I am not using MVVM but am performing all binding in the code behind? I have a page that has a search box and a ListView controll. Each of the items in the ListView is fairly simple... it only consists of a few labels and a switch. The ListView is bound every time someone performs a search. The problem that I am having is that every time someone performs a search it refreshes the Model everyting is bound to... this in turn triggers the Switches Toggle Event. Within the Toggle Event I have code that would update the database when the value is changed so I do not want this fired off every time the model changes. It is very frustrating as there does not appear to be a Click Event. See Xaml and the Method I am using below:
public async void SearchItems()
{
PageLoaded = false;
if(string.IsNullOrEmpty(this.txtSearch.Text)) { return; }
this.LoadSearch.IsRunning = true;
// Populate Locations For Item
string ItemType = HttpUtility.UrlEncode(this.SelectItemType.SelectedItem.ToString());
string Search = HttpUtility.UrlEncode(this.txtSearch.Text.ToLower());
string url = string.Format("https://test.com/api/LocationsSearch?itemType={0}&search={1}", ItemType, Search);
LocationsList = new List<Locations>();
var json = await func.GetIBApi(url);
LocationsList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Locations>>(json);
this.ItemsList.ItemsSource = LocationsList;
this.LoadSearch.IsRunning = false;
PageLoaded = true;
}
Toggle Event:
private async void Switch_Toggled(object sender, ToggledEventArgs e)
{
try
{
if (PageLoaded)
{
bool isPrimary = e.Value;
ViewCell cell = (sender as Switch).Parent.Parent.Parent as ViewCell;
var location = cell.BindingContext as Locations;
string display = "";
if (isPrimary)
{
display = "Are you sure you want to make this the 'Primary Location'?";
}
else
{
display = "Are you sure you want to unset this as the 'Primary Location'? Note: Make sure you set another location as the 'Primary' on this PartNumber!";
}
var result = await App.Current.MainPage.DisplayAlert("Primary Change Confirmation", display, "Yes", "No");
if (result)
{
string url = string.Format("https://test.com/api/LocationsSearch?partNumber={0}&location={1}&isPrimary={2}", location.ID, location.Location, isPrimary);
var json = await func.PostIBApi(url);
await Navigation.PopModalAsync();
}
}
}
catch (Exception e2)
{
}
}
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="IBMobile.Views.LocationsPage"
xmlns:local2="clr-namespace:FontAwesome"
Visual="Material">
<ContentPage.IconImageSource>
<FontImageSource FontFamily="{StaticResource FontAwesomeSolid}" Glyph="{x:Static local2:IconFont.MapMarkerAlt}" />
</ContentPage.IconImageSource>
<ContentPage.Content>
<StackLayout Padding="25">
<Picker x:Name="SelectItemType" SelectedIndexChanged="SelectItemType_SelectedIndexChanged" />
<SearchBar x:Name="txtSearch" SearchButtonPressed="TxtSearch_SearchButtonPressed" Placeholder="Search..." FontSize="Title"></SearchBar>
<ActivityIndicator Color="#007D5D" IsRunning="false" x:Name="LoadSearch" HorizontalOptions="Center" />
<ListView x:Name="ItemsList" ItemTapped="ItemsList_ItemTapped" HasUnevenRows="True" BackgroundColor="White">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout VerticalOptions="FillAndExpand">
<Grid VerticalOptions="FillAndExpand" Padding="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"></ColumnDefinition>
<ColumnDefinition Width="3*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label TextColor="#007D5D" Text="{Binding ViewID}" FontSize="Title" VerticalTextAlignment="Center" Grid.Column="0" Grid.Row="0"></Label>
<Label TextColor="#007D5D" Text="{Binding Location}" FontSize="Title" VerticalTextAlignment="Center" Grid.Column="1" Grid.Row="0"></Label>
<Label Text="Primary" Grid.Column="2" Grid.Row="0"></Label>
<Switch IsToggled="{Binding IsPrimary}" x:Name="Toggle1" ThumbColor="#007D5D" Grid.Column="2" Grid.Row="1" Toggled="Switch_Toggled"/>
<Label Text="{Binding Description}" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2"></Label>
<Label Text="{Binding Message}" Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="3" FontSize="Small" TextColor="Red"></Label>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
if (PageLoaded)
{
//entered handler
bool isPrimary = e.Value; //we are invoking the handler again from inside itsself
// all the mess comes from this
Use another lock:
if (PageLoaded && !_internal)
{
//entered handler
_internal=true;
bool isPrimary = e.Value;
_internal=false;

Xamarin Forms - ListviewSelectedItem MVVM

Im unable to set the selectedItem in a listview control. Ive set the binding context and can debug the code without issue. Onload of the form I can see the get and set method being called but when I select an item in the listview the set is not called. I cannot understand why
private NavigationItems _SelectedNavigationItem;
public NavigationItems SelectedNavigationItem
{
get { return _SelectedNavigationItem; }
set
{
if (value != null)
{
_SelectedNavigationItem = value;
NavigateToPage(value);
}
}
}
public async void NavigateToPage(NavigationItems selectedItem )
{
switch (selectedItem.Id)
{
case "Appointments":
await Navigation.PushAsync(new MyAppointments());
break;
case "Companies":
await Navigation.PushAsync(new Companies());
break;
case "Messages":
await Navigation.PushAsync(new Messages());
break;
default:
break;
}
}
View
<!-- Navigation -->
<ListView Grid.ColumnSpan="2" Margin="20,30,20,10" Grid.Row="1"
ItemsSource="{Binding NavigationCollection}"
SelectedItem="{Binding SelectedNavigationItem}"
HasUnevenRows="false"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<Grid Padding="5,5,5,5" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<!--Icon-->
<Image Aspect="AspectFit" Grid.Column="0" HeightRequest="18" WidthRequest="18" Source="{Binding Icon}"/>
<!--Navigation Name-->
<Label Margin="10,0,30,0" Grid.Column="1" Grid.ColumnSpan="2" Style="{StaticResource SecondaryLabelStyle}" Text="{Binding Name}" FontSize="Medium"/>
<!--Navigation Totals-->
<Frame VerticalOptions="Center" Grid.Column="3" Style="{StaticResource CircleFrameStyle}">
<Label TextColor="#d2d2d2" HorizontalTextAlignment="Center" VerticalOptions="Center" Text="{Binding Total}" FontSize="Small">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ForgotPassword}" />
</Label.GestureRecognizers>
</Label>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Everything appears to be in order but something strange is happening. Ive also tried setting mode=TwoWay explicitly in the xaml but I know listview mode is twoway by default.
Kevin
I think doing your setter code this way will solve your problems
private NavigationItems _SelectedNavigationItem;
public NavigationItems SelectedNavigationItem
{
get { return _SelectedNavigationItem; }
set
{
if (_SelectedNavigationItem != value)
{
_SelectedNavigationItem = value;
NavigateToPage(value);
}
}
}

ObservableCollection<> and ListView binding issue

I'm coming today because I'm not sure to understand something about the Binding in C#. I'm currently making a stupid app to help myself with my classes and their locations. Some others information also comes in the app.
So I have this design, which is a simple ListView of my 4 classes.
MainPage.xaml :
<ContentPage.Content>
<AbsoluteLayout>
<Label
AbsoluteLayout.LayoutBounds="0.5, 0, 1, 0.1"
AbsoluteLayout.LayoutFlags="All"
Text="My CSULB Schedule" />
<ListView x:Name="CoursesListView" ItemsSource="{Binding Courses}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="6*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<!-- TOP -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
HorizontalTextAlignment="Center"
Text="{Binding DaysToString}"
VerticalTextAlignment="Center" />
<Label
Grid.Column="1"
HorizontalTextAlignment="Center"
Text="{Binding Title}"
VerticalTextAlignment="Center" />
<Label
Grid.Column="2"
HorizontalTextAlignment="Center"
Text="{Binding TypeAndNumber}"
VerticalTextAlignment="Center" />
</Grid>
<!-- Top -->
<!-- Center -->
<ListView Grid.Row="1" ItemsSource="{Binding Sections}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<ColumnDefinition Width="25*" />
<ColumnDefinition Width="5*" />
<ColumnDefinition Width="25*" />
</Grid.RowDefinitions>
</Grid>
<Label
Grid.Column="0"
HorizontalTextAlignment="Center"
Text="{Binding Location}"
VerticalTextAlignment="Center" />
<Label
Grid.Column="1"
HorizontalTextAlignment="Center"
Text="{Binding TeacherName}"
VerticalTextAlignment="Center" />
<Label
Grid.Column="2"
HorizontalTextAlignment="Center"
Text="{Binding Time}"
VerticalTextAlignment="Center" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- Center -->
<!-- Bottom -->
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="8*" />
<ColumnDefinition Width="1*" />
</Grid.RowDefinitions>
<!-- Useless stuff at the moment -->
</Grid>
<!-- Bottom -->
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</AbsoluteLayout>
</ContentPage.Content>
In the CS part, I have this:
MainPage.xaml.cs :
public partial class MainPage : ContentPage
{
public ObservableCollection<Course> Courses { get; set; }
public MainPage()
{
base.BindingContext = this;
Debug.WriteLine("1.");
InitCoursesList();
Debug.WriteLine("2.");
InitializeComponent();
Debug.WriteLine("3.");
//CoursesListView.ItemsSource = Courses;
}
private void InitCoursesList()
{
Debug.WriteLine("1.1");
this.Courses = new ObservableCollection<Course>()
{
new Course()
{
Days = new List<Course.Day>() { Course.Day.M, Course.Day.W },
Title = "Object Oriented Programming",
Type = "CECS",
Number = "274",
Sections = new List<Section>()
{
new Section()
{
Location = "VEC-419",
TeacherName = "Foss S",
Time = "1-1:50pm"
},
new Section()
{
Location = "ECS-414",
TeacherName = "Foss S",
Time = "2-3:15pm"
}
}
},
new Course()
{
Days = new List<Course.Day>() { Course.Day.M, Course.Day.W },
Title = "Adv Artificial Intelligence",
Type = "CECS",
Number = "551",
Sections = new List<Section>()
{
new Section()
{
Location = "VEC-331",
TeacherName = "Todd Ebert",
Time = "3:30-4:45pm"
}
}
},
new Course()
{
Days = new List<Course.Day>() { Course.Day.Tu, Course.Day.Th },
Title = "Operating Syetems",
Type = "CECS",
Number = "326",
Sections = new List<Section>()
{
new Section()
{
Location = "VEC-330",
TeacherName = "Lam S",
Time = "9:30-10:20am"
},
new Section()
{
Location = "ECS-416",
TeacherName = "Lam S",
Time = "10:30-11:45am"
}
}
},
new Course()
{
Days = new List<Course.Day>() { Course.Day.Tu, Course.Day.Th },
Title = "C++ for Java Developers",
Type = "CECS",
Number = "282",
Sections = new List<Section>()
{
new Section()
{
Location = "VEC-518",
TeacherName = "Nachawati S",
Time = "11-11:50am"
},
new Section()
{
Location = "ECS-403",
TeacherName = "Nachawati S",
Time = "12-1:15pm"
}
}
},
};
Debug.WriteLine("1.2");
}
}
Everything is coming in the good way, I mean the display of the Debug.WriteLine();. The thing I don't understand now it's why my listview is empty? For me, it doesn't make sense but everyone knows that, often, the problem comes between the computer and the chair so.. Where am I wrong?
I bind my main ListView which is <ListView x:Name="CoursesListView" ...> with my public ObservableCollection<Course> Courses { get; set; }. I fill it, initalize everything, I set the binding context but at the end.. The page is empty, why?
Also I was asking to myself another question. If I bind a collection to a list, can I bind another list inside of every element of this list view? If you are confuse, take a look at the List<Section> Sections in every Course object. In the xaml part, I have a list in the center part of each element.
I hope my question is clear, maybe it's a stupid question, maybe this question is interesting, I don't know but for sure, I didn't and I still don't understand something I think.
Thank for your help.
PS: Tell me if you want any edit.
You're creating the ObservableCollection in your InitCoursesList function. The binding, which will have already bound to Courses with a value of null, won't update when you create the ObservableCollection because you're not raising the PropertyChanged event (see INotifyPropertyChanged) so any additions to the collection won't register. The solution is to create the collection when the containing class is created:
public ObservableCollection<Course> Courses { get; set; } = new ObservableCollection<Course>();
And then just populate it in the initialise function:
private void InitCoursesList()
{
Courses.Add(new Course { etc });
}
And of course, if you wanted to bind a second list (ObservableCollection) you can do that. In your Course class you'd simply have an ObservableCollection and bind that to whatever control you want to use to view that data.
Your code will work if you change the Courses property to a dependency property.
public ObservableCollection<Course> Courses
{
get { return (ObservableCollection<Course>)GetValue(CoursesProperty); }
set { SetValue(CoursesProperty, value); }
}
public static readonly DependencyProperty CoursesProperty =
DependencyProperty.Register(nameof(Courses), typeof(ObservableCollection<Course>), typeof(MainPage));
The reason is, as stated in an answer already, is that you're re-creating the ObservableCollection and breaking the binding to the old one because there is no PropertyChanged event fired when you re-assigned it. Since this is a UIElement it is best to just make this a DependencyProperty rather than implementing INotifyPropertyChanged, which you also have the choice of doing. Then you would need to write out a full Courses property call PropertyChanged in the setter of the Courses property.
Furthermore you need to update your binding statement as well... It needs to look at the MainPage rather than the DataContext.
ItemsSource="{Binding Courses, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainPage}}}
Or name your MainPage x:Name="mainPage" and use this binding...
ItemsSource="{Binding Courses, ElementName=mainPage}"

Categories