How to add data to CollectionView from BindingContext from another page - xamarin

I have this below click button on the MainPage, it works fine and I can see the data displayed if I pass(Bind) data to a Label in the second page(CourseDetailViewPage);
but now I want to display data in a collectionview located in the second page(CourseDetailPage).
//Click button in MainPage
public async void OnOkGetCourseButton(object sender, EventArgs e)
{
var inputtedCourseNumber = ctrlEntryTextBox.Text;
if(inputtedCourseNumber == string.Empty)
{
await DisplayAlert("Note", "\nPlease enter your Course number", "OK");
}
else
{
try
{
CourseService CourseService = new CourseService();
var data = await CourseService.getCourses();
var userCourse = data.Where(x => x.Id.ToString() == inputtedCourseNumber).FirstOrDefault();
UserDialogs.Instance.HideLoading();
if (userCourse == null)
{
await DisplayAlert("Note", "\nCourse number does not exist. Verify your Course number", "OK");
}
else
{
await DisplayAlert("Note", "\nOurahh - Course number found !!!", "OK");
await DisplayAlert("Note", "\n" + userCourse.Id + "\n" + userCourse.CourseDetail + "\n" + userCourse.IsCourseComplete + "\n" + userCourse.CourseDate, "OK");
await Navigation.PushAsync(new CourseDetailPage
{
BindingContext = userCourse as Course
});
}
}
catch (Exception)
{
await DisplayAlert("Error", "Error Network", "OK");
}
}
}
======
// CourseDetailPage (Second page)
<CollectionView x:Name="myCollection" ItemsSource="{Binding userCourse}"
HeightRequest="200">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid RowDefinitions="Auto,Auto,Auto">
<Label Grid.Row="0" Text="{Binding Id}"/>
<Label Grid.Row="0" Text="{Binding CourseDetail}"/>
<Label Grid.Row="0" Text="{Binding IsCourseComplete}"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

Change
await Navigation.PushAsync(new CourseDetailPage
{
BindingContext = userCourse as Course
});
To
ObservableCollection<Course> courses = new ObservableCollection<Course>();
courses.Add(userCourse);
await Navigation.PushAsync(new CourseDetailPage
{
BindingContext = courses
});
REASON: You need to pass a collection to ItemsSource. In this simple situation, it wouldn't have to be an "Observable"Collection, but in more dynamic uses, where code adds/removes elements while a list of items is displayed, that is likely to be what you'll want. So I used it here, as a good habit.
Change
<CollectionView x:Name="myCollection" ItemsSource="{Binding userCourse}"
HeightRequest="200">
To
<CollectionView x:Name="myCollection" ItemsSource="{Binding .}"
HeightRequest="200">
REASON: In the first code change, we set BindingContext of the new CourseDetailPage to the collection that we want to use as ItemsSource. "{Binding .}" is a way to refer to the BindingContext itself. In this case, the BindingContext is the list to use as ItemsSource.
NOTE: While the above should work, it is limiting. It doesn't offer any way to pass other values, to other parts of CourseDetailPage. If you find that you want to pass userCourse AND some other information, then it is slightly more involved.
Define a class that has a public property Courses, and pass an instance of that class as BindingContext. THEN ItemsSource will be {Binding Courses}:
public class Details
{
public property ObservableCollection<Course> Courses { get; set; }
public property int Prop2 { get; set; }
}
...
ObservableCollection<Course> courses = new ObservableCollection<Course>();
courses.Add(userCourse);
var details = new Details { Courses = courses, Prop2 = 123 };
await Navigation.PushAsync(new CourseDetailPage
{
BindingContext = details
});
...
<CollectionView x:Name="myCollection" ItemsSource="{Binding Courses}"
HeightRequest="200">
... some other view that uses `..="{Binding Prop2}"` ...
This is a "one-way" display of data. If user needs to interact with the data, then look into MVVM.

Related

Multi-select Listview

This is my list view it should contain the list of activities came from my SQLite database:
<ListView SeparatorVisibility="None" x:Name="lstActivity" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame StyleClass="lstframe" CornerRadius="0" BorderColor="Transparent" HasShadow="False">
<StackLayout StyleClass="lstContainer" VerticalOptions="CenterAndExpand">
<Grid>
<Label StyleClass="lstActivityName" Grid.Row="0" Grid.Column="0" Text="{Binding ActivityDescription}">
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String">
<On Platform="Android" Value="Poppins-Regular.otf#Poppins-Regular"/>
</OnPlatform>
</Label.FontFamily>
</Label>
<Switch Grid.Row="0" Grid.Column="1" IsToggled="{Binding Selected}" />
</Grid>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here is how I populate the list view this will return atleast five(5) activities:
public void Get_Activities()
{
try
{
var db = DependencyService.Get<ISQLiteDB>();
var conn = db.GetConnection();
var getActivity = conn.QueryAsync<ActivityTable>("SELECT * FROM tblActivity WHERE Deleted != '1' ORDER BY ActivityDescription");
var resultCount = getActivity.Result.Count;
if (resultCount > 0)
{
var result = getActivity.Result;
lstActivity.ItemsSource = result;
lstActivity.IsVisible = true;
}
else
{
lstActivity.IsVisible = false;
}
}
catch (Exception ex)
{
//Crashes.TrackError(ex);
}
}
Selected item binding:
public class SelectData
{
public bool Selected { get; set; }
}
Get selected Items on click:
private void BtnClose_Clicked(object sender, EventArgs e)
{
foreach (var x in result)
{
if (x.Selected)
{
// do something with the selected items
}
}
}
I posted another question regarding on multi-select list view my problem is I don't know how to proceed when I use the answers given to me. How can I get the the selected values because I will save the selected values to my database?
your Switch is bound to the Selected property of your model. Just iterate (or use LINQ) to get the items that are selected.
// you need to maintain a reference to result
foreach(var x in result)
{
if (x.Selected)
{
// do something with the selected items
}
}
LINQ
var selected = result.Where(x => x.Selected).ToList();
you will also need to have a class level reference to result
// you will need to change this to reflect the actual type of result
List<MyClass> result;
public void Get_Activities()
{
...
result = getActivity.Result;
...
For multi-select Listview, I have wrote a working example in my blog. Hope this helps : https://androidwithashray.blogspot.com/2018/03/multiselect-list-view-using-xamarin.html?view=flipcard

Passing data from list to new view xamarin

Ive created a picker with data inside, when selected the data is added to a list. I want to the values that are added to the list to follow to the next page when calculate button is pressed.. tried a few different approaches but i cant get it right..
.cs
// add from picker to listview function
ObservableCollection<LayersClass> listProducts = new ObservableCollection<LayersClass>();
private void MainPicker_SelectedIndexChanged(object sender, EventArgs e)
{
// feedback popup box
var product = MainPicker.Items[MainPicker.SelectedIndex];
DisplayAlert(product, "Layer added to calculation list", "OK");
// if selected add to list
if (null != product)
{
LayersClass layer = new LayersClass();
layer.Product = product;
listProducts.Add(layer);
}
}
//calculate button
private async void Button_Clicked(object sender, EventArgs e)
{
// send selected values to CalculationPage ??
await Navigation.PushAsync(new CalculationPage());
}
xaml:
<ListView
x:Name="productsListView"
HasUnevenRows="False"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
BackgroundColor="White">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Clicked="MenuItem_Clicked" Text="Delete" IsDestructive="true" CommandParameter="{Binding .}" />
</ViewCell.ContextActions>
<StackLayout>
<Label Text="{Binding Product}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Margin="0,0,0,90" Text="Calculate" Clicked="Button_Clicked" FontSize="Medium" TextColor="#00AB8E" HorizontalOptions="Center" BackgroundColor="Transparent"/>
You should use parameter to pass data to the another page.
private async void Button_Clicked(object sender, EventArgs e)
{
// send selected values to CalculationPage ??
//Also you should write there an event.
var item = sender as Button;
var selectedItem = item as LayersClass;
await Navigation.PushAsync(new CalculationPage(item));
}
public CalculationPage(string item){
//when you type here dot after typing item like this -> item. you can see the layerclass items.
}

Xamarin Forms ListView text not displayed

I'm new to Xamarin Forms, I'm following the official tutorial for learning Xamarin forms. While learning about navigation using Phoneword project of the following link
https://developer.xamarin.com/guides/xamarin-forms/getting-started/hello-xamarin-forms-multiscreen/quickstart/
The listview text is not appearing. Please help me!
CallHistoryPage.xaml: Here the listview is there.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App1;assembly=App1"
x:Class="App1.CallHistoryPage"
Title="Call History">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="20, 40, 20, 20"/>
<On Platform="Android" Value="20"/>
</OnPlatform>
</ContentPage.Padding>
<StackLayout>
<ListView ItemsSource="{x:Static local:App.PhoneNumbers}" />
</StackLayout>
</ContentPage>
MainPage.xaml.cs: SourceItem values are updated in this class.
namespace App1
{
public partial class MainPage : ContentPage
{
string translatedNumber;
public MainPage()
{
InitializeComponent();
}
void OnTranslate(object sender, EventArgs e)
{
translatedNumber = PhonewordTranslator.ToNumber(phoneNumberText.Text);
if (!string.IsNullOrWhiteSpace(translatedNumber))
{
callButton.IsEnabled = true;
callButton.Text = "Call " + translatedNumber;
}
else
{
callButton.IsEnabled = false;
callButton.Text = "Call";
}
}
async void OnCall(object sender, EventArgs e)
{
if (await this.DisplayAlert(
"Dial a Number",
"Would you like to call " + translatedNumber + "?",
"Yes",
"No"))
{
var dialer = DependencyService.Get<IDialer>();
if (dialer != null)
{
App.PhoneNumbers.Add(translatedNumber);
callHistoryButton.IsEnabled = true;
dialer.Dial(translatedNumber);
}
}
}
async void OnCallHistory(object sender, EventArgs e)
{
await Navigation.PushAsync(new CallHistoryPage());
}
}
}
App.xaml.cs: Sourceitem for listview is in this class
namespace App1
{
public partial class App : Application
{
public static IList<string> PhoneNumbers { get; set; }
public App()
{
InitializeComponent();
PhoneNumbers = new List<string>();
MainPage = new NavigationPage(new MainPage());
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
For more details please follow the link added above. Same tutorial is followed.
You forgot to 'tell' ListView what to display.
<ListView ItemsSource="{x:Static local:App.PhoneNumbers}" />
creates a ListView with empty cells, hence they are not displaying anything. You'll have to set the ListView.ItemTemplate in order to display anything
<ListView ItemsSource="{x:Static local:App.PhoneNumbers}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding .}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The BindingContext within the DataTemplate will be the respective item from App.PhoneNumbers. Since the items are bare strings we bind to ., which refers to the bound element itself.
See here for ListViews in Xamarin.Forms.
You have not added any numbers in PhoneNumbers list. Add number first in PhoneNumbers list and then check.
public App()
{
InitializeComponent();
PhoneNumbers = new List<string>();
PhoneNumbers.Add("123456789");
PhoneNumbers.Add("178967897");
PhoneNumbers.Add("178945678");
MainPage = new NavigationPage(new MainPage());
}
I think you have forget to take input from user.So add this line in OnCall method
translatedNumber = PhonewordTranslator.ToNumber(phoneNumberText.Text);
Try this,
async void OnCall(object sender, EventArgs e)
{
translatedNumber = PhonewordTranslator.ToNumber(phoneNumberText.Text);
if (await this.DisplayAlert(
"Dial a Number",
"Would you like to call " + translatedNumber + "?",
"Yes",
"No"))
{
var dialer = DependencyService.Get<IDialer>();
if (dialer != null)
{
App.PhoneNumbers.Add(translatedNumber);
callHistoryButton.IsEnabled = true;
dialer.Dial(translatedNumber);
}
}
}

How to change Label Text color of masterPageitem after selection

I'm new to Xamarin framework and want to create an app using Master-Detail Page
I did simple Master-Detail Navigation page demo from xamarin websit
master-detail-page xamarin webise
only difference is I used ViewCell inside DataTemplate.In ViewCell I have Label
instead of Image.
after clicking on MasterPageItems navigation is working fine but now I want to change the label Text color also .
<ListView x:Name="listView" VerticalOptions="FillAndExpand" SeparatorVisibility="None" RowHeight="50" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Title}" TextColor="#1ca7ec" FontSize="18"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as MasterPageItem;
if (item != null)
{
Detail = new NavigationPage((Page)Activator.CreateInstance(typeof(ContactsPage)));
masterPage.ListView.SelectedItem = null;
IsPresented = false;
}
}
I think you can do in this way:
1- in your model you should have a "TextColor" property and a "Selected" property
public bool Selected { get; set; }
// I think you should not return "Color" type (for strong MVVM) but, for example, a value that you can convert in XAML with a IValueConverter...
public Color TextColor
{
get
{
if (Selected)
return Color.Black;
else
return Color.Green;
}
}
2- In your XAML you should have something like
<ListView SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding List}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}" TextColor="{Binding TextColor}" FontSize="18"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
3- and in your ViewModel something like
MyModel _selectedItem { get; set; }
public ObservableCollection<MyModel> List { get; set; } = new ObservableCollection<MyModel>();
public MyModel SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem != null)
_selectedItem.Selected = false;
_selectedItem = value;
if (_selectedItem != null)
_selectedItem.Selected = true;
}
}
When your item in the list is selected , SelectedItem property change and Selected property in your model became True or False, changing the TextColor property (I use PropertyChanged.Fody for INPC).
Hope this help
You can find the repo on GitHub
Instead of use a TextColor Property in your Model, I think you can also use only Selected property and an IValueConverter that convert Selected property to a color

make Xamarin DisplayActionSheet select, display value in Label

I have wired up my ContentPage to an instance of a class (g), and this case works fine:
open the page
enter values in the Entry boxes
make a selection from a DisplayActionSheet
click Save
OnSave all the values from the UI are in g, but the value from the DisplayActionSheet is not in the UI where I expect it.
After the DisplayActionSheet thing runs, I want a value for AisleDepthText to display in the UI.
Here is the class that I instantiate into a variable, g
public class GroceryItemForSaving
{
public GroceryItemForSaving() { }
public event PropertyChangedEventHandler PropertyChanged;
private string _AisleDepth;
public string AisleDepth
{
get
{
return _AisleDepth;
}
set
{
_AisleDepth = value;
OnPropertyChanged();
}
}
private string _AisleDepthText;
public string AisleDepthText
{
get
{
return _AisleDepthText;
}
set
{
_AisleDepthText = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I make g the BindingContext like this:
public NewGrocery()
{
InitializeComponent();
BindingContext = g;
}
Here is the relevant XAML.
<Label Text="GroceryName" Grid.Row="0" Grid.Column="1" ></Label>
<Entry Text="{Binding GroceryName}" Grid.Row="0" Grid.Column="2" ></Entry>
<Label Text="Aisle" Grid.Row="1" Grid.Column="1" ></Label>
<Entry Text="{Binding Aisle}" Grid.Row="1" Grid.Column="2"></Entry>
<Label Text="Aisle Depth" Grid.Row="2" Grid.Column="1" ></Label>
<Label Text="{Binding AisleDepthText, Mode=OneWay}" Grid.Row="2" Grid.Column="2" ></Label>
<Button Clicked="ShowAisleDepthChoices" Grid.Row="2" Grid.Column="3" Text="Aisle Depth" ></Button>
The button click handler ShowAisleDepthChoices, makes the ActionSheet display. In the code for that I set the values for AisleDepth and AisleDepthText like this:
public async void ShowAisleDepthChoices(object sender, EventArgs e)
{
var AisleDepth = 0;
var SelectedAisleDepth = await DisplayActionSheet("Aisle Depth", "Cancel", null, "Front", "Middle", "Back", "Back Wall");
switch (SelectedAisleDepth)
{
case "Front":
AisleDepth = 1;
break;
case "Middle":
AisleDepth = 2;
break;
case "Back":
AisleDepth = 3;
break;
case "Back Wall":
AisleDepth = 4;
break;
}
g.AisleDepthText = SelectedAisleDepth;
g.AisleDepth = AisleDepth.ToString();
}
Then after that no value appears in AisleDepthText Label, but when I click Save, the values are in g.AsileDepthText and g.AisleDept exactly where I expect them. NOTE: I can enter a GroceryName directly in the UI and it ends up in g.GroceryName on save.
What do I need to do to make the value for g.AisleDepthText appear in the UI after the DisplayActionSheet does its thing?
GroceryItemForSaving needs to implement INotifyPropertyChanged. You have to code to satisfy the implementation, but you're not declaring that the class uses the interface, so your binding isn't updating like it should.
public class GroceryItemForSaving : INotifyPropertyChanged

Resources