Trying to set the initial value to a picker through the use of SelectedItem. I can do this without any issue if the picker isn't within a listview. However, once I try to accomplish this in a listview no dice.
I never can get the picker to display the initially downloaded value. If I use the same binding for an entry it displays the expected string.
Thoughts??
This can be reproduced in this simplistic standalone project. Please help. Thanks.
https://github.com/smarcus3/DebuggingProject
XAML
<ListView x:Name="listView" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" ItemsSource="{Binding downloadedRecipeIngredients}"> <!--SelectedItem="{Binding SelectedItem}"-->
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<!-- Element Label -->
<Entry VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" Text="{Binding IngredientName}"/>
<!--<Picker x:Name="pickerIngredient" HorizontalOptions = "StartAndExpand" ItemsSource="{Binding listIngredients}" BindingContext="{Binding Source={x:Reference Page}, Path=BindingContext}" SelectedItem="{Binding IngredientName}" WidthRequest="100"/>-->
<Picker x:Name="pickerIngredientancestor" HorizontalOptions = "StartAndExpand" WidthRequest="100" ItemsSource="{Binding listIngredients, Source={RelativeSource AncestorType={x:Type viewModel:testPageViewModel}}}" SelectedItem="{Binding IngredientName}"/>
<Entry Text="{Binding Quantity}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
<Entry Text="{Binding UnitName}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
<Entry Text="{Binding Comments}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
<!-- Assessment Menu Icon -->
<Label Text="Clickable Label" VerticalOptions="CenterAndExpand" HorizontalOptions="EndAndExpand">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.btnPress, Source={x:Reference Page}}" CommandParameter="{Binding .}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
VIEW MODEL
public class testPageViewModel : BaseViewModel
{
clRecipeIngredient[] _downloadedRecipeIngredients;
public clRecipeIngredient[] downloadedRecipeIngredients
{
get {
return _downloadedRecipeIngredients;
}
set
{
//if (downloadedRecipeIngredients != value)
//{
_downloadedRecipeIngredients = value;
OnPropertyChanged("downloadedRecipeIngredients");
//}
}
}
//Lists for Pickers
ObservableCollection<string> _listIngredients = new ObservableCollection<string>();
public ObservableCollection<string> listIngredients { get { return _listIngredients; } }
private clRecipeDataBase recipeDataBase;
public testPageViewModel()
{
recipeDataBase = new clRecipeDataBase();
btnPress = new Command<clRecipeIngredient>(madeIt);
getData();
}
async void getData()
{
//PICKER INGREDIENT DATA
clIngredient[] tmp = await recipeDataBase.getIngredientData();
for (int i = 0; i < tmp.Length; i++)
{
_listIngredients.Add(tmp[i].IngredientName);
}
_downloadedRecipeIngredients = await recipeDataBase.getRecipeIngredientsDataByRecipeID(310); //HARDCODED TO CRISPY PIZZA RECIPE
OnPropertyChanged("downloadedRecipeIngredients");
}
public ICommand btnPress { get; private set; }
void madeIt(clRecipeIngredient x)
{
Console.WriteLine(x.IngredientName + " -- " + x.Comments);
//_downloadedRecipeIngredients.Remove(x);
}
}
clRecipeIngredients
public class clRecipeIngredient
{
public int RecipeIngredientsID { get; set; }
public int RecipeIDLookedUP { get; set; }
public int IngredientIDLookedUp { get; set; }
public double Quantity { get; set; }
public int UnitIDLookedUp { get; set; }
public bool HiddenFlag { get; set; }
public string UnitName { get; set; }
public string IngredientName { get; set; }
public string Comments { get; set; }
I checked your sample and you could modify it like following .
in Xaml
<Picker HorizontalOptions = "StartAndExpand" WidthRequest="100" ItemsSource="{Binding Path=BindingContext.listIngredients, Source={x:Reference Page}}" SelectedItem="{Binding IngredientName, Mode=TwoWay}" />
in ViewModel
ObservableCollection had implemented the interface INotifyPropertyChanged in default . So you could simplify the code in your ViewModel .
Note : You could not set the value of SelectItem as a string directly even if they are equal . You need to set it like following
ing.IngredientName = listIngredients[0];
So the ViewModel could like
public class testPageViewModel : BaseViewModel
{
public ObservableCollection<clRecipeIngredient> downloadedRecipeIngredients
{
get;set;
}
public ObservableCollection<string> listIngredients { get; set; }
//private clRecipeDataBase recipeDataBase;
public testPageViewModel()
{
//recipeDataBase = new clRecipeDataBase();
btnPress = new Command<clRecipeIngredient>(madeIt);
downloadedRecipeIngredients = new ObservableCollection<clRecipeIngredient>();
listIngredients = new ObservableCollection<string>();
getData();
}
async void getData()
{
//PICKER INGREDIENT DATA
//clIngredient[] arrayIngredients = await recipeDataBase.getIngredientData();
//clIngredient[] arrayIngredients = new clIngredient[5];
//arrayIngredients[0].IngredientName = "Apple";
//arrayIngredients[1].IngredientName = "Salt";
//arrayIngredients[2].IngredientName = "Buuter";
//arrayIngredients[3].IngredientName = "Flour";
//arrayIngredients[4].IngredientName = "Egg";
listIngredients.Add("Apple");
listIngredients.Add("Salt");
listIngredients.Add("Butter");
listIngredients.Add("Flour");
listIngredients.Add("Egg");
//for (int i = 0; i < arrayIngredients.Length; i++)
//{
// _listIngredients.Add(arrayIngredients[i].IngredientName);
//}
//clRecipeIngredient[] arryRecipeIngredients = await recipeDataBase.getRecipeIngredientsDataByRecipeID(310); //HARDCODED TO CRISPY PIZZA RECIPE
clRecipeIngredient ing = new clRecipeIngredient();
ing.IngredientName = listIngredients[0];
ing.Quantity = 1;
ing.UnitName = "Cups";
ing.Comments = "Comments0";
clRecipeIngredient ing2 = new clRecipeIngredient();
ing2.IngredientName = listIngredients[1];
ing2.Quantity = 2;
ing2.UnitName = "Whole";
ing2.Comments = "Comments1";
downloadedRecipeIngredients.Add(ing);
downloadedRecipeIngredients.Add(ing2);
}
public ICommand btnPress { get; private set; }
void madeIt(clRecipeIngredient x)
{
Console.WriteLine(x.IngredientName + " -- " + x.Comments);
//_downloadedRecipeIngredients.Remove(x);
}
}
And don't forget to implement INotifyPropertyChanged in your model as the value of IngredientName will been changed .
public class clRecipeIngredient : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(
[CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this,new PropertyChangedEventArgs(propertyName));
}
//...
string ingredientName;
public string IngredientName
{
get
{
return ingredientName;
}
set
{
if (ingredientName != value)
{
ingredientName = value;
OnPropertyChanged("IngredientName");
}
}
}
//...
}
Its still unclear why you cannot directly set the selectedItem with a string as I can do in pickers not inside of a ListView.
However, setting the picker to use the SelectedIndex property works great. This is the KEY. For ListView's containing pickers I will not set their value based on INDEX instead of SelectedItem.
Final Code Snippets
XAML
<Picker HorizontalOptions = "StartAndExpand" WidthRequest="100" ItemsSource="{Binding Path=BindingContext.listIngredients, Source={x:Reference Page}}" SelectedIndex="{Binding IngredientIDLookedUp}" />
View Model
clRecipeIngredient[] arryRecipeIngredients = await recipeDataBase.getRecipeIngredientsDataByRecipeID(310); //HARDCODED TO CRISPY PIZZA RECIPE
clRecipeIngredient ing = new clRecipeIngredient();
_downloadedRecipeIngredients.Clear();
for (int i = 0;i < arryRecipeIngredients.Length;i++)
{
for (int j=0; j<_listIngredients.Count;j++)
{
//FIND THE SELECTED INDEX BASED ON PICKER’S LIST and STORE IN THE CUSTOM CLASS
if(arryRecipeIngredients[i].IngredientName == _listIngredients[j])
{
arryRecipeIngredients[i].IngredientIDLookedUp = j;
}
}
_downloadedRecipeIngredients.Add(arryRecipeIngredients[i]);
}
Related
I have a collection view with a checkbox. I want to count the amount of checkboxes which have been selected and show that value in a label (string Sel). I think I have mostly done it however the label doesn't update. I think this is due to not calling OnProperty changed in the correct place which would update the label. I'm still rapping my head round MVVM. Thanks
ModelView:
public class MeetAWalkerViewModel : INotifyPropertyChanged
{
public ObservableCollection<PetProfile> source;
public ObservableCollection<PetProfile> PetInfo { get; private set; }
public ObservableCollection<PetProfile> EmptyPetInfo
{
get => source;
private set
{
if (value != source)
{
source = value;
OnPropertyChanged(nameof(EmptyPetInfo));
}
}
}
public string Sel { get; private set; }
public MeetAWalkerViewModel()
{
var count = EmptyPetInfo.Count(t => t.Selected);
Sel = "Amount of selected pets" + Convert.ToString(count);
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Xaml:
<CollectionView x:Name="petCollectionView" ItemsSource="{Binding EmptyPetInfo}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10" RowDefinitions="80" ColumnDefinitions="120,60,60">
<Image Grid.Column="0"
Grid.Row="0"
x:Name="PetImage"
Source="{Binding imageUrl}"/>
<Label Grid.Column="1"
Grid.Row="0"
Text="{Binding PetName}"
FontAttributes="Bold"
x:Name="labelpetname" VerticalTextAlignment="Center" HorizontalTextAlignment="Center"/>
<CheckBox Grid.Row="0" Grid.Column="2" HorizontalOptions="End" IsChecked="{Binding Selected, Mode=TwoWay}" CheckedChanged="CheckBox_CheckedChanged" BindingContext="{Binding .}"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I made a sample based on your code, it works properly.
You can refer to the following code:
MeetAWalkerViewModel.cs
public class MeetAWalkerViewModel: INotifyPropertyChanged
{
public ObservableCollection<PetProfile> source;
//public ObservableCollection<PetProfile> PetInfo { get; private set; }
public ObservableCollection<PetProfile> EmptyPetInfo
{
get => source;
private set
{
if (value != source)
{
source = value;
OnPropertyChanged(nameof(EmptyPetInfo));
}
}
}
int _count;
public int Count
{
set
{
if (_count != value)
{
_count = value;
OnPropertyChanged(nameof(Count));
Sel = "Amount of selected pets is : " + Convert.ToString(_count);
}
}
get
{
return _count;
}
}
public void updateCount(int count) {
}
String sel;
public String Sel
{
set
{
if (sel != value)
{
sel = value;
OnPropertyChanged(nameof(Sel));
}
}
get
{
return sel;
}
}
public MeetAWalkerViewModel()
{
EmptyPetInfo = new ObservableCollection<PetProfile>();
EmptyPetInfo.Add(new PetProfile { PetName = "Pet1", IsSelected= false,ImageUrl= "cherry.png" });
EmptyPetInfo.Add(new PetProfile { PetName = "Pet2", IsSelected = false, ImageUrl = "watermelon.png" });
EmptyPetInfo.Add(new PetProfile { PetName = "Pet3", IsSelected = false, ImageUrl = "cherry.png" });
EmptyPetInfo.Add(new PetProfile { PetName = "Pet4", IsSelected = false, ImageUrl = "watermelon.png" });
EmptyPetInfo.Add(new PetProfile { PetName = "Pet5", IsSelected = false, ImageUrl = "cherry.png" });
EmptyPetInfo.Add(new PetProfile { PetName = "Pet6", IsSelected = false, ImageUrl = "watermelon.png" });
foreach (PetProfile petProfile in EmptyPetInfo) {
if (petProfile.IsSelected)
{
Count++;
}
}
Sel = "Amount of selected pets is : " + Convert.ToString(Count);
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
MainPage.xaml
<StackLayout HorizontalOptions="Center" Padding="10" >
<Label x:Name="countSelectedItemsLabel" Text="{Binding Sel}" FontSize="20" />
<CollectionView x:Name="petCollectionView" ItemsSource="{Binding EmptyPetInfo}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10" RowDefinitions="80" ColumnDefinitions="120,60,60">
<Image Grid.Column="0"
Grid.Row="0"
x:Name="PetImage"
Source="{Binding ImageUrl}"/>
<Label Grid.Column="1"
Grid.Row="0"
Text="{Binding PetName}"
FontAttributes="Bold"
x:Name="labelpetname" VerticalTextAlignment="Center" HorizontalTextAlignment="Center"/>
<CheckBox Grid.Row="0" Grid.Column="2" HorizontalOptions="End" IsChecked="{Binding IsSelected, Mode=TwoWay}" CheckedChanged="CheckBox_CheckedChanged" BindingContext="{Binding .}"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
MainPage.xaml.cs
public partial class MainPage : ContentPage
{
MeetAWalkerViewModel viewModel;
int selectedCount = 0;
public MainPage()
{
InitializeComponent();
viewModel = new MeetAWalkerViewModel();
BindingContext = viewModel;
}
private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
PetProfile model = (PetProfile)((CheckBox)sender).BindingContext;
if (model.IsSelected)
{
selectedCount++;
}
else
{
selectedCount--;
}
viewModel.Count = selectedCount;
}
}
PetProfile.cs
public class PetProfile
{
public string PetName { get; set; }
public string ImageUrl { get; set; }
public bool IsSelected { get; set; }
}
The result is:
You need to change the String like this
String sel ;
public String Sel
{
set
{
if (sel != value)
{
sel = value;
OnPropertyChanged(nameof(Sel ));
}
}
get
{
return sel ;
}
}
I'm unable to change an object based on Picker SelectedItem, but I can change a property of that object. What am I not seeing?
I have tried changing object that inherit ObservableCollections to have ObservableCollections as properties and inherit PropertyChanged instead, but property it's still not registering a change.
XAML Binding Failures is empty.
Page 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" xmlns:viewmodel="clr-namespace:ItemSourceBug.ViewModel"
x:Class="ItemSourceBug.Views.MainPage"
x:Name="MPage">
<ContentPage.BindingContext>
<viewmodel:MainViewModel/>
</ContentPage.BindingContext>
<StackLayout BindableLayout.ItemsSource="{Binding model.CurrentControlList}"
VerticalOptions="Center">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout BindableLayout.ItemsSource="{Binding .}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding typeAction}"/>
<StackLayout BindableLayout.ItemsSource="{Binding .}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding name}"/>
<Label Text="{Binding type}"/>
<Picker ItemsSource="{Binding Path=BindingContext.model.SelectFromList,
Source={x:Reference MPage}}"
ItemDisplayBinding="{Binding name}"
SelectedItem="{Binding }"> <!--This doesn't work-->
</Picker>
<Picker SelectedItem="{Binding type}"> <!--This works-->
<Picker.Items>
<x:String>3</x:String>
<x:String>4</x:String>
<x:String>5</x:String>
</Picker.Items>
</Picker>
<Label Text="{Binding Path=BindingContext.model.currentAction.name, Source={x:Reference MPage}}"/>
<Picker ItemsSource="{Binding Path=BindingContext.model.SelectFromList,
Source={x:Reference MPage}}"
ItemDisplayBinding="{Binding name}"
SelectedItem="{Binding Path=BindingContext.model.currentAction, Source={x:Reference MPage}}"> <!--This works-->
</Picker>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ContentPage>
Page ViewModel:
using ItemSourceBug.Model;
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace ItemSourceBug.ViewModel
{
public class MainViewModel : BaseViewModel
{
#region Properties
public MainModel model { get; set; } = new MainModel();
#endregion
#region Commands
#endregion
}
}
Page Model:
using ItemSourceBug.ViewModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
namespace ItemSourceBug.Model
{
public class MainModel: BaseViewModel
{
#region Properties
public ControlList CurrentControlList { get; set; } = new ControlList();
public Action currentAction { get; set; } = new Action { id = "6", name = "Leon" };
public Actions SelectFromList { get; set; } = new Actions();
#endregion
#region Constructor
public MainModel()
{
SelectFromList.Add(new Action { id = "1239", name="George" });
SelectFromList.Add(new Action { id = "1240", name = "Mike" });
}
#endregion
}
public class ControlList : ObservableCollection<TaskActions>
{
public string typeControl { get; set; }
public ControlList()
{
Add(new TaskActions { typeTask = "observ" });
Add(new TaskActions { typeTask = "discuss" });
}
}
public class TaskActions : ObservableCollection<Actions>
{
public string typeTask { get; set; }
public TaskActions()
{
Add(new Actions { typeAction = "sing" });
Add(new Actions { typeAction = "dance" });
}
}
public class Actions: ObservableCollection<Action>
{
public string typeAction { get; set; }
public Actions()
{
Add(new Action { id = "1231" , name = "John" });
Add(new Action { id = "1232", name = "Jane" });
Add(new Action { id = "1237", name = "Joseph" });
Add(new Action { id = "1238", name = "Jimbo" });
}
}
public class Action: BaseViewModel
{
public string id { get; set; }
public string name { get; set; }
public string type { get; set; } = "1";
/*
public override string ToString()
{
return name;
}
*/
}
}
You should keep the same binding path on Label.Text and Picker.SelectedItem + Picker.ItemDisplayBinding .
In your code
First one:
MPage.BindingContext.model.SelectFromList.name != model.CurrentControlList.x.x.name, they are different things , so it does not work .
Second one:
type = type , so it works .
Third one:
MPage.BindingContext.model.currentAction.name = MPage.BindingContext.model.currentAction.name , so it works.
The workaround I found was to change:
Action class to :
public class Action: INotifyPropertyChanged
{
public string id { get; set; }
public string name { get; set; }
public Action instance { get; set; }
public string type { get; set; } = "1";
/*
public override string ToString()
{
return name;
}
*/
//Reedited:
public Action()
{
instance = this;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
if (propertyName.Equals("instance"))
{
name = instance.name;
id = instance.id;
type = instance.type;
}
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And the Picker in my XAML file to:
<Picker ItemsSource="{Binding Path=BindingContext.model.SelectFromList,
Source={x:Reference MPage}}"
ItemDisplayBinding="{Binding name}"
Grid.Column="2"
SelectedItem="{Binding instance,Mode=TwoWay}"
>
</Picker>
I'd like to implement a simple picker XAML binded to 3 labels, where when I choose a value from the picker the labels will be populated automatically (the data comes from SQLite).
Here is what I have:
<Picker x:Name="ListJobs" Title="Select Job" ItemsSource="{Binding options}" ItemDisplayBinding="{Binding JobNo}" SelectedItem="{Binding SelectedJobs}"/>
<Label Text="{Binding JobsId}" IsVisible="True" x:Name="TxtId"/>
<Label Text="{Binding name}" IsVisible="True" x:Name="TxtName"/>
<Label Text="{Binding location}" IsVisible="True" x:Name="TxtLoc"/>
Model
public class Jobs
{
public string JobsId {get;set;}
public string name {get;set;}
public string location {get;set;}
public Jobs(){}
}
Code Behind:
protected override OnAppearing()
{
jobsInfo = (List<Jobs>) GetJob();
foreach (var item in jobsInfo)
{
Jobs options = new Jobs
{
JobsId = item.JobsId,
name = item.name,
location = item.location
};
BindingContext = options;
}
}
private IEnumerable<Jobs> GetJobsInfo()
{
var db = _connection.Table<Jobs>();
return db.ToList();
}
I would to select from picker (like dropdown) and populate the labels.
First, there are some mistakes in your code.
1.When you go through the loop (the data you gained from db), options is always updated with new data(so it generates using last object), and you set it to BindingContext. You should set a modelView here rather a model.
2.The itemSource of Picker must be a list, however you set a model here.
3.The viewmodel must implement INotifyPropertyChanged to notify the changes.
I think what you need understanding most is not this Picker , it is How to work with binding.
Bindable Properties
Data Binding Basics
From Data Bindings to MVVM
Okay, let us come back to this case. What you need is here
I simplified the demo and you can refer to it.
XAML
<Picker x:Name="picker"
Title="Select Job"
ItemsSource="{Binding JobList}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding SelectedJob}"/>
<Label Text="{Binding SelectedJob.JobsId}" IsVisible="True" x:Name="TxtId" Margin="0,100,0,0"/>
<Label Text="{Binding SelectedJob.Name}" IsVisible="True" x:Name="TxtName"/>
<Label Text="{Binding SelectedJob.Location}" IsVisible="True" x:Name="TxtLoc"/>
Model and ViewModel:
public class Jobs
{
public string JobsId { get; set; }
public string Name { get; set; }
public string Location { get; set; }
}
public class RootModel : INotifyPropertyChanged
{
List<Jobs> jobList;
public List<Jobs> JobList
{
get { return jobList; }
set
{
if (jobList != value)
{
jobList = value;
OnPropertyChanged();
}
}
}
Jobs selectedJob;
public Jobs SelectedJob
{
get { return selectedJob; }
set
{
if (selectedJob != value)
{
selectedJob = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Code behind:
public MainPage()
{
InitializeComponent();
this.BindingContext = new RootModel
{
JobList = GetJobsInfo()
};
}
private List<Jobs> GetJobsInfo()
{
var db = _connection.Table<Jobs>();
return db.ToList();
}
My Test:
XAML:
<Picker x:Name="ListJobs" Title="Select Job" ItemsSource="{Binding AllJobs}"
ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding SelectedJob}"/>
<Label Text="{Binding SelectedJob.JobId}" IsVisible="True" x:Name="TxtId"/>
<Label Text="{Binding SelectedJob.Name}" IsVisible="True" x:Name="TxtName"/>
<Label Text="{Binding SelectedJob.Location}" IsVisible="True" x:Name="TxtLoc"/>
Model:
public class Job
{
public string JobId { get; set; }
public string Name { get; set; }
public string Location {get; set; }
}
public class JobModel
{
public List<Job> AllJobs { get; set; }
public Job SelectedJob { get; set; }
}
Code behind:
protected override OnAppearing()
{
BindingContext = new JobsModel {
AllJobs = GetJobs()
};
}
private List<Jobs> GetJobs()
{
var db = _connection.Table<Jobs>();
return db.ToList();
}
//How to add picker or dropdownlist in Xamarin forms and bind text ?
//we will be using Picker Properties -> pickername.ItemsSource and pickername.SelectedItem
//In above line pickername is the x:Name given to picker
//page.xaml
<Frame CornerRadius="5" HeightRequest="50" Padding="0">
<StackLayout Orientation="Horizontal">
<controls:BorderlessPicker
x:Name="Pickdoctype"
ItemDisplayBinding="{Binding text}"
SelectedIndexChanged="Pickdoctype_SelectedIndexChanged"
HorizontalOptions="FillAndExpand"
Title="Enter Document Type"
FontSize="20"
TextColor="Gray">
</controls:BorderlessPicker>
<ImageButton BackgroundColor="Transparent" Padding="0">
<ImageButton.Source>
<FontImageSource Glyph="" Size="25"
FontFamily="{StaticResource FontAwesomeSolid}"
Color="Gray"></FontImageSource>
</ImageButton.Source>
</ImageButton>
</StackLayout>
</Frame>
//page.xaml.cs
//Picker Get API
var responseUserData = await httpService.Get("manageDocument/documentType/documentType/" + UserID);
if (responseUserData.IsSuccessStatusCode)
{
var responseUserDataString = await responseUserData.Content.ReadAsStringAsync();
var myDeserializedClass = JsonConvert.DeserializeObject<List<DocumentTypeModel>>(responseUserDataString);
Pickdoctype.ItemsSource = myDeserializedClass;
}
//Get Picker value functionality
private void Pickdoctype_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
DocumentTypeModel selectedItem = (DocumentTypeModel)Pickdoctype.SelectedItem;
string PickerValue = selectedItem.value;
string PickerText = selectedItem.text;
}
catch (Exception ex)
{
}
}
//Create a model class
public class DocumentTypeModel
{
public string masterName { get; set; }
public string text { get; set; }
public string value { get; set; }
}
Good morning to all, I've looked in several places I researched and did not found a solution that worked for my problems.
I'm new to C #, XAML and Xamarin, I am doing an application that creates lists with products on a screen, the screen with products is been populated via Json WebApi. Until there everthing looks fine, but since i tried to add 'ADD' and 'Share' functionalities it is loading the main page and crashes on navigating to listsPage.
I need to get a product from the product Page and add it to another view where my lists are. I created a ContextActions with 'Share' and 'AddToList' but i don't know how to get that product and 'Add' it to my lists. Same problem with 'Share' when i get the MenuItem and try to pass it to a Task in my ProductViewModel i get a NullReferenceException, but the object is not null.
I appreciate if someone could help me with this issues.
I know the post has got quite long but i wanted to give every possible info.
Here is my Lists Page:
<ListView x:Name="listaView" ItemSelected="listSelected" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Padding="20,0,20,0"
Orientation="Horizontal"
HorizontalOptions="FillAndExpand">
<Label Text="{Binding Name}"
VerticalTextAlignment="Center"
HorizontalOptions="StartAndExpand" />
<Image Source="check.png"
HorizontalOptions="Start"
IsVisible="{Binding Done}" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
and my listDetail:
<Label Text="Name" />
<Entry x:Name="nameEntry" Text="{Binding Name}" />
<Label Text="Description" />
<Entry x:Name="descriptionEntry" Text="{Binding Description}" />
<Label Text="Typ" />
<controls:BindablePicker x:TypeArguments="enums:Typ" SelectedItem="{Binding Typ}" />
<Label Text="Done" />
<Switch x:Name="doneEntry" IsToggled="{Binding Done}" />
<Label Text="Products:" />
<ListView ItemsSource="{Binding Products}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Image Aspect="AspectFit" HeightRequest="20" WidthRequest="20" Source="{Binding Image}" />
<Label Text="{Binding Name}" />
<Label Text="{Binding Price, StringFormat='R${0:C2}'}" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Text="Save" Clicked="salveClicked" />
<Button Text="Delete" Clicked="deleteClicked" />
<Button Text="Cancel" Clicked="cancelClicked" />
<Button Text="Speak" Clicked="speakClicked" />
</StackLayout>
</ContentPage>
ListDetails CODE BEHIND:
public ProductListDetailPage()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, true);
}
void saveClicked(object sender, EventArgs e)
{
var lista = (Lists)BindingContext;
App.Database.SaveList(lista);
this.Navigation.PopAsync();
}
void deleteClicked(object sender, EventArgs e)
{
var lista = (Lists)BindingContext;
App.Database.DeleteList(lista.ListaID);
this.Navigation.PopAsync();
}
void cancelClicked(object sender, EventArgs e)
{
var lista = (Lists)BindingContext;
this.Navigation.PopAsync();
}
void speakClicked(object sender, EventArgs e)
{
var lists = (Lists)BindingContext;
DependencyService.Get<ITextToSpeech>().Speak(lists.Name+ " " + lists.Descrip);
}
}
}
I believe the problem is in my model but have no idea what it is
Product Model:
public class Product : INotifyPropertyChanged
{
private int id;
public int ProductID
{
get { return id; }
set
{
id = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(id)));
}
}
private string name;
public string Name
{
get { return name; }
set
{
name= value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(name)));
}
}
private double price;
public double Price{
get { return price; }
set
{
price= value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(price)));
}
}
private string dtFab;
public string DtFab
{
get { return dtFab; }
set
{
dtFab= value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(dtFab)));
}
}
private string dtValid;
public string DtValid {
get { return dtValid; }
set
{
dtValid= value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(dtValid)));
}
}
private string amount;
public string Amount{
get { return quantidade; }
set
{
amount= value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(amount)));
}
}
private string descrip;
public string Descrip{
get { return descrip; }
set
{
descrip= value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(descrip)));
}
}
private string image;
public string Image
{
get { return image; }
set
{
image= value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(image)));
}
}
private ICollection<ListProduct> listProduct;
public ICollection<ListProduct> ListProduct{
get { return listProduct; }
set
{
listProduct= value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(listProduct)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
ListsModel:
public class Lists
{
public Lista()
{
}
[PrimaryKey, AutoIncrement]
public int ListID get; set; }
public string Name { get; set; }
public string Descrip { get; set; }
public bool Done { get; set; }
public Typ Typ { get; set; }
public virtual ICollection<ListsProduct> ListsProducts{ get; set; }
}
public class ListsProduct
{
public int ListsProductID{ get; set; }
public int ListID { get; set; }
public int ProductID { get; set; }
public virtual Lists Lists { get; set; }
public virtual Product Product { get; set; }
}
}
ListProductModel:
SearchProductPage:
<StackLayout Orientation="Vertical">
<SearchBar Text="{Binding SearchBarText}" />
<Button x:Name="btnPesquisar" Text="Search" Command="{Binding SearchCommand}" />
<ListView ItemsSource="{Binding Products}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Share" Clicked="ShareProduct" />
<MenuItem Text="Add To" Clicked="AddProduct" />
</ViewCell.ContextActions>
<ViewCell.View>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" >
<Image Aspect="AspectFit" HeightRequest="20" WidthRequest="20" Source="{Binding Image}" />
<Label Text="{Binding Name}" />
<Label Text="{Binding Price, StringFormat='R${0:C2}'}" HorizontalOptions="End" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
SearchPage CODE BEHIND:
public partial class SearchPage: ContentPage
{
ProductsViewModel viewModel;
public TelaPesquisaView()
{
InitializeComponent();
this.BindingContext = new ViewModels.ProductsViewModel();
}
public async void AddProduct(object sender, EventArgs e)
{
var al = ((MenuItem)sender);
await viewModel.AddToList(al.BindingContext as Product);
var produtoLista = new ListsPage();
await Navigation.PushAsync(produtoLista);
}
public async void ShareProduct(object sender, EventArgs e)
{
var al = ((MenuItem)sender);
if (al != null) {
await viewModel.Share(al.BindingContext as Produto);
}
}
}
}
And the ProductViewModel
public class ProductViewModel : INotifyPropertyChanged
{
private string searchBarText = string.Empty;
public string SearchBarText {
get { return searchBarText ; }
set
{
if (searchBarText != value)
{
searchBarText = value ?? string.Empty;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(searchBarText )));
if (SearchCommand.CanExecute(null))
{
SearchCommand.Execute(null);
}
}
}
}
// filtrar somente os 5 primeiros
#region Command SearchCommand
private Xamarin.Forms.Command searchCommand;
public ICommand SearchCommand{
get
{
searchCommand= searchCommand?? new Xamarin.Forms.Command(DoSearchCommand, ExecuteCommand);
return searchCommand;
}
set
{
searchCommand= (Xamarin.Forms.Command)value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(searchCommand)));
}
}
private void DoSearchCommand()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Products)));
}
private bool ExecuteCommand()
{
return true;
}
#endregion
private ObservableCollection<Models.Produto> products;
public ObservableCollection<Models.Produto> Products {
get
{
ObservableCollection<Models.Product> searchProducts = new ObservableCollection<Models.Product>();
if (products != null)
{
List<Models.Product> prod = (from p in products
where p.Name.ToLower().Contains(searchBarText.ToLower())select p).Take(3).ToList<Models.Product>();
if (prod != null && prod.Any())
{
searchedProducts = new ObservableCollection<Models.Product>(prod);
}
}
return searchedProducts ;
}
set
{
products = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Products)));
}
}
public ProductsViewModel()
{
SearchCommand = new Xamarin.Forms.Command(async () =>
{
var products = await ApiProducts.Api.GetAsync();
Products = new ObservableCollection<Models.Product>(products );
});
}
public async Task AddToList(ListsProduct prod)
{
Lists list = new Lists();
list.ListsProduct.Add(prod);
App.Database.SaveList(list);
}
public async Task Share(Models.Product prod)
{
var title = prod.NomeProduto;
var message = prod.ToString();
// Share message and an optional title.
await CrossShare.Current.Share(message, title );
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Here is the starting point for you. For the rest you can use the same pattern passing your new product in constructors or just implement setters in your view models.
Define binding to get a new product
<MenuItem Text="Adicionar à" Clicked="AdicionaProduto" CommandParameter="{Binding .}" />
Then pass it to your ListasView constructor as parameter
public async void AdicionaProduto(object sender, EventArgs e)
{
var al = ((MenuItem)sender);
var produtoLista = new ListasView(al.CommandParameter as Produto);
await Navigation.PushAsync(produtoLista);
}
To be able to do that you need to change a constructor
public ListasView(Produto newProduto = null)
{
InitializeComponent();
//this.BindingContext = new ViewModels.ListasViewModel();
if (newProduto != null)
{
//do something
int x = 0;
}
You can take that newProduto and store it in your DB or pass further to other models or views via constructor or some setters.
I have a PrimaryItems List & foreach PrimaryItem there is a SecondaryItems list.So i used a ListBox as ItempTemplate of another ListBox.
<ListBox ItemsSource="{Binding PrimaryItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ListBox ItemsSource="{Binding SecondaryItems}" SelectedItem="{Binding SelectedSecondaryItem}" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
My View Model Code
private List<PrimaryItem> _primaryItems;
public List<PrimaryItem> PrimaryItems
{
get { return _primaryItems; }
set { _primaryItems = value;RaisePropertyChanged(); }
}
//SecondaryItems list is inside in each PrimaryItem
//private List<SecondaryItem> _secondaryItems;
//public List<SecondaryItem> SecondaryItems
//{
// get { return _secondaryItems; }
// set { _secondaryItems = value; RaisePropertyChanged(); }
//}
private SecondaryItem _selectedSecondaryItem;
public SecondaryItem SelectedSecondaryItem
{
get { return _selectedSecondaryItem; }
set
{
_selectedSecondaryItem = value;
if (_selectedSecondaryItem != null)
{
//TO DO
}
}
}<br/>
This is the class structure
public class PrimaryItem
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondaryItem> SecondaryItems{ get; set; }
}
public class SecondaryItem
{
public int Id { get; set; }
public string Name { get; set; }
}
and I set SelectedItem Binding to the Second ListBox.
But am not getting the Selection Trigger on Second ListBox.
Can we use a ListBox inside another ListBox` Template ? If yes how do we overcome this problem?
First of all, use ObservableCollection instead of List since it implements INotifyPropertyChanged interface.
As far as I understand your requirements, PrimaryItem class should has a property SecondaryItems. So remove it from ViewModel and paste to PrimaryItem class (as well as SelectedSecondaryItem property):
private ObservableCollection<SecondaryItem> _secondaryItems;
public ObservableCollection<SecondaryItem> SecondaryItems
{
get { return _secondaryItems; }
set { _secondaryItems = value; RaisePropertyChanged(); }
}
EDIT:
I've fully reproduced your situation and get it working.
Classes:
public class PrimaryItem
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondaryItem> SecondaryItems { get; set; }
private SecondaryItem _selectedSecondaryItem;
public SecondaryItem SelectedSecondaryItem
{
get { return _selectedSecondaryItem; }
set
{
_selectedSecondaryItem = value;
if (_selectedSecondaryItem != null)
{ // My breakpoint here
//TO DO
}
}
}
}
public class SecondaryItem
{
public int Id { get; set; }
public string Name { get; set; }
}
ViewModel:
public class MyViewModel : ViewModelBase
{
private List<PrimaryItem> _primaryItems;
public List<PrimaryItem> PrimaryItems
{
get { return _primaryItems; }
set { _primaryItems = value; RaisePropertyChanged("PrimaryItems"); }
}
public ErrorMessageViewModel()
{
this.PrimaryItems = new List<PrimaryItem>
{
new PrimaryItem
{
SecondaryItems =
new List<SecondaryItem>
{
new SecondaryItem { Id = 1, Name = "First" },
new SecondaryItem { Id = 2, Name = "Second" },
new SecondaryItem { Id = 3, Name = "Third" }
},
Name = "FirstPrimary",
Id = 1
}
};
}
}
View:
<Window x:Class="TestApp.Views.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TestApp.ViewModels;assembly=TestApp.ViewModels"
xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="Title" Height="240" Width="270" ResizeMode="NoResize"
WindowStartupLocation="CenterOwner" WindowStyle="ToolWindow">
<Window.DataContext>
<vm:MyViewModel/>
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding PrimaryItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ListBox ItemsSource="{Binding SecondaryItems}" SelectedItem="{Binding SelectedSecondaryItem}" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
You can try to use LinqToVisualTree, it can get alomost all Controls in your app, you just have to select what you want to find(in your case ListBoxItem), and then cast it to your model. I used it, when I needed to get text from TextBox which was in ListboxItem. But it also fits to your task.