Xamarin Forms Editing Item with SQL - c#

I have an application where items are added to a collection view and then you can tap on that item where a detail page comes up. With this detail page, you can edit the title and description given and then it should update in the collection view. However, when changes are made to the item, the item is not changed within the observable collection. How do I go about this?
Here is my xaml for the detail page:
<StackLayout Spacing="5" Padding="10" AnchorY="0.5">
<!--<Label Text="Title:" FontSize="Medium" FontAttributes="Bold" TextColor="White"/>-->
<Editor Text="{Binding Text}" FontSize="Title" TextColor="White"/>
<!--<Label Text="Note:" FontSize="Medium" FontAttributes="Bold" TextColor="White"/>-->
<Editor Text="{Binding Description}" FontSize="Small" TextColor="White"/>
<Grid ColumnDefinitions="*,*,*">
<Label Grid.Column="0" Grid.ColumnSpan="2" Text="Due Date:" FontSize="Medium" FontAttributes="Bold" TextColor="White" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand"/>
<Label Grid.Column="0" Grid.ColumnSpan="2" Padding="50,4,0,0" Text="{Binding DueDate}" FontSize="Small" TextColor="White" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand"/>
</Grid>
<Button Text="Save"
Command="{Binding SaveChangesCommand}"
VerticalOptions="EndAndExpand"
HorizontalOptions="EndAndExpand"
BackgroundColor="Black"
BorderWidth="1.5"
BorderColor="White"
CornerRadius="50">
</Button>
</StackLayout>
Here is my DataBaseService:
public async Task UpdateItem(Item item)
{
var databasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MyData3.db");
var db = new SQLiteAsyncConnection(databasePath);
await db.UpdateAsync(item);
OnUpdatedItem?.Invoke();
}
OnUpdateItem?.Invoked();
private void LocalDatabaseService_OnUpdatedItem()
{
_ = ExecuteLoadItemsCommand();
}
and here is the OnSaveChanges
private async void OnSaveChanges(Item item)
{
Text = Text;
Description = Description;
DueDate = DueDate;
await LocalDatabaseService.UpdateItem(item);
await Shell.Current.GoToAsync("..");
}
LoadItems:
async Task ExecuteLoadItemsCommand()
{
IsBusy = true;
try
{
Items.Clear();
var items = await LocalDatabaseService.GetAllItems();
foreach (var item in items)
{
Items.Add(item);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
Item Model:
public class Item : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Text
{
get { return Text; }
set
{
Text = value;
if (Text != value)
{
OnPropertyChanged("Text");
Text = value;
}
}
}
public string Description
{
get { return Description; }
set
{ Description = value;
if(Description != value)
{
OnPropertyChanged("Description");
Description = value;
}
}
}
public DateTime DueDate { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
any help is appreciated, Thanks.

From the code you posted, we found the parameter in method OnSaveChanges is useless since it is always null.
You can change your code like this in class ItemDetailViewModel:
public Command SaveChangesCommand { get; }
public ItemDetailViewModel()
{
SaveChangesCommand = new Command(OnSaveChanges);
}
And method OnSaveChanges:
private async void OnSaveChanges()
{
Item item = new Item();
item.Id = Int32.Parse(ItemId);
item.Text = Text;
item.Description = Description;
item.DueDate = DueDate;
await LocalDatabaseService.UpdateItem(item);
await Shell.Current.GoToAsync("..");
}

Related

Xamarin Forms Binding Base64 image to CarouselView Image Source

I have the following code in my XAML (Visual Studio 2019 Community Edition)
<CarouselView x:Name="TheCarousel" PeekAreaInsets="50" Loop="False" >
<CarouselView.EmptyView>
<Label Text="Search Results"/>
</CarouselView.EmptyView>
<CarouselView.ItemTemplate>
<DataTemplate>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="10"
HeightRequest="20"
VerticalOptions="Start" >
<StackLayout>
<Image x:Name="ProductImage" Source="{Binding Image}"/>
<Label x:Name="ProduceDescription" Text="{Binding ProductDescription}"/>
<Label x:Name="AmountInStock" Text="{Binding AmountInStock}"/>
<Label x:Name="ProductPrice" Text="{Binding productprice}"/>
</StackLayout>
</Frame>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
And the following code in my model
private string imageBase64;
public string productphoto1
{
get { return imageBase64; }
set
{
try
{
imageBase64 = value;
if (imageBase64 != null)
{
if (imageBase64.Trim() != "")
{
//imageBase64 = "data:image/png;base64," + imageBase64;
var byteArray = Convert.FromBase64String(imageBase64);
if (byteArray != null)
{
//Stream stream = new MemoryStream(byteArray);
//Image = ImageSource.FromStream(() => stream);
Image = Xamarin.Forms.ImageSource.FromStream(
() => new MemoryStream(Convert.FromBase64String(imageBase64)));
}
}
}
}
catch (Exception ex)
{
string strErrMessage = ex.Message + " " + ex.Source;
}
}
}
private Xamarin.Forms.ImageSource image;
public Xamarin.Forms.ImageSource Image
{
get { return image; }
set
{
image = value;
}
}
The code is returning values and returns the base64 image and seems to convert it properly. However, the images are not displaying in the CarouselView. Is there something that I am missing?
I have tried setting "Binding Image" to "Binding Path=Image", but the images are still not loading.
Thanks in advance.
//James
I couldn't see other codes of your app, but based on your code,I created a demo which works on my android emulator.
You can refer to the following code:
1.create an item and implement interface INotifyPropertyChanged. Then once changing the value of field ImageBase64, the UI will update automatically.
public class Item: INotifyPropertyChanged
{
private string imageBase64;
public string ImageBase64
{
get { return imageBase64; }
set
{
SetProperty(ref imageBase64, value);
try
{
// imageBase64 = value;
SetProperty(ref imageBase64, value);
if (imageBase64 != null)
{
if (imageBase64.Trim() != "")
{
//imageBase64 = "data:image/png;base64," + imageBase64;
var byteArray = Convert.FromBase64String(imageBase64);
if (byteArray != null)
{
Image = Xamarin.Forms.ImageSource.FromStream(
() => new MemoryStream(Convert.FromBase64String(imageBase64)));
}
}
}
}
catch (Exception ex)
{
string strErrMessage = ex.Message + " " + ex.Source;
}
}
}
private Xamarin.Forms.ImageSource image;
public Xamarin.Forms.ImageSource Image
{
get => image;
set => SetProperty(ref image, value);
}
public double productprice { get; set; }
public int AmountInStock { get; set; }
public string ProductDescription { get; set; }
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
2.create viewmodel MyViewModel
public class MyViewModel
{
string base64Image = "iVBORw0KGgoAAAANSUhEUgAAAHwAAAB8CAMAAACcwCSMAAAAZlBMVEUAAAD///+cnJz7+/vz8/MdHR329vbw8PBRUVHh4eEaGhqsrKzn5+dZWVnk5OQyMjLZ2dmOjo6ysrIoKCjR0dFiYmKjo6NKSkoNDQ3ExMQ4ODi5ubmHh4cWFhbLy8tBQUF6enpvb2/Qp8GOAAAFzUlEQVRogcWb56KyOhBFIyBFQUGkiQXe/yVvQPFQUvYEvN/+q7CYlMnMJGG7fyhm8pDrnZ71Mbd65cf6efLc/wPuhlXeRLf0wUZ6pLeoyauQ+gUkuFPd/ZRJlfr3yvkN3L20pRw8qGwvuP0g3K0aPXhQU4F8CB5aLxzd6WWFG8HDRNHPMqUJgNfCPRP0G++thNtHQ3SPP9pr4NfCHN2puBrDvWYdulOjansFvLqtZzN2qwzgtrUFupMl7XkZPGy3YjPWymadBB4TvYpar5gCrwEvTlFZ4/DjtuhORxT+A7aYLoD/hC2kL+E/YovoC/jlV2zGLjr483dsxp5qeLBiEdMrDVRwz/8lmzHfU8DvlDelLz/yX7SmusvhhIEe5XHYLRh2GOcRgX6UwWPUqT6s08SCUwK74zKWwFELmuUqFTYoPRLDwQX8JY4O6jNIt0Tw00P/IJcvWR53MThTHn9d9gdvoEezQAR+Nz1Ib5bwK/TgSxUPBmDQ9w1pB7gNffZDbndvANhzQ1A3wGvoMUuG/SiH3sKGuOYDd6FpVuiybxtLMiJ3AscMX6yJC4ELcj2BQ4b7+rKDg425aAzHhrquxzsl0Js+A/4Nb5AHynksIFKFefnmD25DD9y0+TbXCfSy9heODbcIqbQ44OpUf+EZ9P9GjX3LbjB4NsA97P8JAoeDIe8DBycnMtjh4d77jA4OZsPbWt6+4R44QLE+R/P6s9fD0Txh09HeZxAMXoq2neeM5T0cLoBs6OFY3+ls58AVkA19O9fL4fATnHP4+nb38FpOeuJwvKGA9Rxz1L3KisMJOVKhqaXudpQi1pHDKcU+nZ8hFQ4tDm8I/3/IMoa3CD3IOqfF4AytV6Eq4RMrhxGH057I5PSQZAafaxxOLIQUsrwhoNbm0x2z99RnJFkquWK6t+lwVjZL44OG+hZDODf+PsXHd4OXmMK59ZEV9Ju3rhfnkVmN2hjeKS2yNivMC3er4Gv1r+E7OPSYqTy//CiLipvp15+5kyHv2z3SqLWqZ8xHnG27Thhfq2PTFlhVYqSCw7F0ZdA+S56xIJK0w+ulIXpqDqfUW8/WVRXCnmqKJXcOR4NX7lrUO6O9wiNcuM45HIx8brN6q1R2lWHdX3P49QD88ZCA6E5ujVh/uHK4B5RRsqc2eps2vqW3iOcgbGdrY4CDcEdOrUA79CK7y1h0cX5GaPGRjhrjkz5d0qTnidHJI66ruj8vPTxQfmJuiOYKVOPuEPRwV/Uf8fYvKE8xnHz3XZmQd3qpT5CUUmTrXf7RweX1xxVt/rFdumxdP3BbtqpiVRilZFuUZ/sDl7V7azrOx6oUdvXwQPxx6m0FVGLLgi9cnDIZ+DWRhFXw1+4PLlpWXzR3LpfIieUjuCMYFqtm+ETLdk2dEVzQMe1mbEHE8JlGH/hiRpSKI01UufMVbtjEH7a25pFcgVT8UM3LPsMu+gAPHuLfN1E8bdfv1uB3O3PW6xu2+qLdv47zC3cmPvYBBKoENeN3n787ZH+7yJMxuVeXnaiajKi/OTzavB8vf4dNLZ9svIyODozg8XjMyY/vGSgYuZlxKW98ZmJcPyxbazMlY+8+Ll1PjqrQckYjZTsZPDTN1WGdQymcWDyla+a0Zwez4JTVTLOYcH4krfkle745tjiM98NzYf6ctYA7P6MvDx4sz0CiZ23I7GWxXHD68/QTui/IdUXnXsOVp9pFEm5RCE/8KlNHI/nC/ENy0Jp0HlEvSVwkgdv5hr6ulKUf0sP1z838/Fm6ASy/VqDK7CmK5HGw6kJFTq7lLvVQpfjKqySn1Qu8upKluURTr7rRcdPke7rrQ45lvImSWrpTZPqLU0b3prCbU8iVsZBufQrdGcMuy7kX0ryLwNt68DXB0ALH3g27KEeCc5cbW5FmJ2kfWTEh26BdDbXDOsluwlrt4ZYldUjLc+iXYm0vrvOk9c/7Q6/92W+TvI49eoJldCP38xUfmb/hPxbhSmN+ggQuAAAAAElFTkSuQmCC";
string base64Image2 = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=";
public ObservableCollection<Item> Items { get; set; }
public ICommand ResetImage => new Command(reSetImage);
private void reSetImage(object obj)
{
Items[1].ImageBase64 = base64Image2;
}
public MyViewModel() {
byte[] Base64Stream = Convert.FromBase64String(base64Image);
Items = new ObservableCollection<Item>();
Items.Add(new Item { AmountInStock = 900, productprice = 10.0,ProductDescription = " ProductDescription1" , ImageBase64 = base64Image });
Items.Add(new Item { AmountInStock = 600, productprice = 19.0, ProductDescription = " ProductDescription2", ImageBase64 = base64Image });
Items.Add(new Item { AmountInStock = 890, productprice = 68.0, ProductDescription = " ProductDescription3", ImageBase64 = base64Image });
}
}
3.TestPage.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:formapp0111="clr-namespace:FormApp0111"
x:Class="FormApp0111.TestPage1">
<ContentPage.BindingContext>
<formapp0111:MyViewModel></formapp0111:MyViewModel>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<Button Text="reset" Command="{Binding ResetImage}" />
<CarouselView x:Name="TheCarousel" PeekAreaInsets="50" Loop="False" ItemsSource="{Binding Items}" >
<CarouselView.EmptyView>
<Label Text="Search Results"/>
</CarouselView.EmptyView>
<CarouselView.ItemTemplate>
<DataTemplate>
<Frame HasShadow="True"
BorderColor="DarkGray"
CornerRadius="5"
Margin="10"
HeightRequest="20"
VerticalOptions="Start" >
<StackLayout>
<Image x:Name="ProductImage" Source="{Binding Image}"/>
<Label x:Name="ProduceDescription" Text="{Binding ProductDescription}"/>
<Label x:Name="AmountInStock" Text="{Binding AmountInStock}"/>
<Label x:Name="ProductPrice" Text="{Binding productprice}"/>
</StackLayout>
</Frame>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Note:
1.In class Item.cs, I replaced productphoto1 with ImageBase64.
private string imageBase64;
public string ImageBase64{// other code}
2.I also added a Button to reset the base64 image.
public ICommand ResetImage => new Command(reSetImage);

OnPropertyChanged not fire with label text

I am in a Xamarin app I which I have to make a to-do list. I am using listview. I have to set a label text dynamically every time I add a new to-do like so: Number of tasks 2/4 where 2 are the done tasks and 4 are the total ones. Everything goes right but I found some issues when I have to update the label text. I am using the MVVM pattern. In XAML I bind the text value to SetInfoDoneText. In MainPage, I have the bindigContext set to VM(TodoListViewModel). I use INotifyPropertyChanged with OnPropertyChanged. I made a method in which the value of setInfoDoneText is changed. The problem is that the set and get is called only once and when the setInfoDoneText is updated by the method OnPropertyChanged does not fire again. Here is the code.
THE PROBLEM IS WHEN I TRY TO UPDATE lblDoneInfo text (
)
class TodoListViewModel: INotifyPropertyChanged
{
public ObservableCollection<TodoItem> todoItems { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public String setInfoDoneText;
public String SetInfoDoneText
{
get => setInfoDoneText;
set
{
setInfoDoneText = value;
OnPropertyChanged("SetInfoDoneText");
}
}
protected void OnPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
int doneTask = 0;
int totalTask = 0;
public TodoListViewModel()
{
this.todoItems = new ObservableCollection<TodoItem>();
setInfoDoneText = "Number of tasks: " + doneTask + "/" + totalTask;
}
public ICommand AddTodoCommand => new Command(AddTodoItem);
public String newTodoInputValue { get; set; }
public String selectedItem { get; set; }
public bool highPriority { get; set; }
public bool midPriority { get; set; }
public bool lowPriority { get; set; }
Color newColor = new Color();
public void AddTodoItem()
{
if (highPriority)
{
newColor = Color.Red;
AddNewItem(newColor);
highPriority = false;
}
if (midPriority)
{
newColor = Color.Orange;
AddNewItem(newColor);
midPriority = false;
}
if (lowPriority)
{
newColor = Color.Yellow;
AddNewItem(newColor);
lowPriority = false;
}
}
public TodoItem AddNewItem(Color newColor)
{
TodoItem newItem = new TodoItem(newTodoInputValue,
false,
highPriority,
midPriority,
lowPriority,
newColor);
todoItems.Add(newItem);
UpdateDoneInfo();
return newItem;
}
public ICommand RemoveTodoCommand => new Command(RemoveTodoItem);
public void RemoveTodoItem(object o)
{
TodoItem todoItemBeingRemoved = o as TodoItem;
todoItems.Remove(todoItemBeingRemoved);
}
public ICommand EditTodoCommand => new Command(EditTodoItem);
public void EditTodoItem(object o)
{
TodoItem todoItemBeingEdited = o as TodoItem;
int newIndex = todoItems.IndexOf(todoItemBeingEdited);
todoItems.Remove(todoItemBeingEdited);
TodoItem updatedTodo = AddNewItem(newColor);
//todoItems.Add(updatedTodo);
int oldIndex = todoItems.IndexOf(updatedTodo);
todoItems.Move(oldIndex, newIndex);
}
public String UpdateDoneInfo()
{
totalTask = todoItems.Count;
foreach (TodoItem item in todoItems)
{
if (item.complete) doneTask++;
}
return setInfoDoneText = "Number of tasks: " + doneTask + "/" + totalTask;
}
}
<ContentPage.BindingContext>
<local:TodoListViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<Entry
x:Name="inputField"
Text="{Binding newTodoInputValue}"
Placeholder="Enter a todo..."
/>
<Label x:Name="lblDoneInfo" Text="{Binding SetInfoDoneText, Mode=TwoWay }">
</Label>
<FlexLayout AlignItems="Center" JustifyContent="SpaceBetween">
<input:CheckBox x:Name="highP"
IsChecked="{Binding highPriority}"
CheckChangedCommand="{Binding AddTodoCommand}"
Margin="0,0,20,0" />
<Label Text="High Priority" FontSize="Medium"/>
<input:CheckBox x:Name="midP"
IsChecked="{Binding midPriority}"
CheckChangedCommand="{Binding AddTodoCommand}"
Margin="0,0,20,0" />
<Label Text="Medium Priority" FontSize="Medium"/>
<input:CheckBox x:Name="lowP"
IsChecked="{Binding lowPriority}"
CheckChangedCommand="{Binding AddTodoCommand}"
Margin="0,0,20,0" />
<Label Text="Low Priority" FontSize="Medium"/>
</FlexLayout>
<ListView x:Name="todoList" ItemsSource="{Binding todoItems}" SelectedItem="{Binding selectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell Height="20">
<FlexLayout JustifyContent="SpaceBetween" AlignItems="Center" Padding="20,0">
<ContentView>
<FlexLayout AlignItems="Center">
<input:CheckBox IsChecked="{Binding complete}" Margin="5" />
<Label x:Name="todoText" TextColor="{Binding color}" Text="{Binding todoText}" FontSize="Large"/>
</FlexLayout>
</ContentView>
<ImageButton
Source="editar_24.png"
BackgroundColor="Transparent"
WidthRequest="100"
HeightRequest="100"
Margin="0,0,20,0"
Command="{Binding Path=BindingContext.EditTodoCommand,
Source={x:Reference todoList}}"
CommandParameter="{Binding .}"/>
<ImageButton
Source="basura_24.png"
BackgroundColor="Transparent"
WidthRequest="100"
HeightRequest="100"
Command="{Binding Path=BindingContext.RemoveTodoCommand,
Source={x:Reference todoList}}"
CommandParameter="{Binding .}"/>
</FlexLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
PropertyChanged only fires when you set the value of the public property SetInfoDoneText. Your code is setting the value of the field setInfoDoneText (lowercase).
It is generally best practice to make the field private to prevent this
private String setInfoDoneText;

CheckBox create a string from the values of the selected objects

I have a collection view with a checkbox.
It looks like the following:
[Image][PetName][Checkbox]
I want to create a string with all the names of the pets which have been selected the pass this value through a function.
I have tried the following code but I am getting object reference is null in selectedPets.Add(ob) I'm sure Im proably going the wrong way with this but I am new to coding.
public List<PetProfile> selectedPets;
private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var checkbox = sender as CheckBox;
var ob = checkbox.BindingContext as PetProfile;
if (ob != null)
{
selectedPets.Add(ob);
}
}
private string CreatePetName()
{
var stringBuilder = new StringBuilder();
var listlenght = selectedPets.Count;
foreach (var pet in selectedPets)
{
if (selectedPets.Count == 0)
{
stringBuilder.Append(pet.PetName);
}
else if (listlenght > 0 && pet == selectedPets[0] )
{
stringBuilder.Append(pet.PetName + " ");
}
else if (pet == selectedPets[listlenght])
{
stringBuilder.Append(" " + pet.PetName );
}
else
{
stringBuilder.Append(" " + pet.PetName + " ");
}
}
return stringBuilder.ToString();
}
private async void SubmitBtn_Clicked(object sender, EventArgs e)
{
var Petnames = CreatePetName();
}
XAML:
<CollectionView x:Name="petCollectionView" ItemsSource="{Binding EmptyPetInfo}" HeightRequest="200">
<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}" BindingContext="{Binding .}" CheckedChanged="CheckBox_CheckedChanged"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I want to create a string with all the names of the pets which have been selected the pass this value through a function.
I suggest you don't need to use CheckBox_CheckedChanged event to get selected PetName, you can create one class name Petclass, implementing INotifyPropertyChanged, to notify Selected property changed.
public class Petclass:ViewModelBase
{
public string imageUrl { get; set; }
public string PetName { get; set; }
private bool _Selected;
public bool Selected
{
get { return _Selected; }
set
{
_Selected = value;
RaisePropertyChanged("Selected");
}
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then loading some data to test in collectionview. foreach EmptyPetInfo Collectionview ItemsSource="{Binding EmptyPetInfo}" to check Selected property is true or false.
<CollectionView
x:Name="petCollectionView"
HeightRequest="200"
ItemsSource="{Binding EmptyPetInfo}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid
Padding="10"
ColumnDefinitions="120,60,60"
RowDefinitions="80">
<Image
x:Name="PetImage"
Grid.Row="0"
Grid.Column="0"
Source="{Binding imageUrl}" />
<Label
x:Name="labelpetname"
Grid.Row="0"
Grid.Column="1"
FontAttributes="Bold"
HorizontalTextAlignment="Center"
Text="{Binding PetName}"
VerticalTextAlignment="Center" />
<CheckBox
Grid.Row="0"
Grid.Column="2"
HorizontalOptions="End"
IsChecked="{Binding Selected, Mode=TwoWay}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
public partial class Page17 : ContentPage
{
public ObservableCollection<Petclass> EmptyPetInfo { get; set; }
public Page17()
{
InitializeComponent();
EmptyPetInfo = new ObservableCollection<Petclass>()
{
new Petclass(){imageUrl="check.png",PetName="pet 1"},
new Petclass(){imageUrl="delete.png",PetName="pet 2"},
new Petclass(){imageUrl="favorite.png",PetName="pet 3"},
new Petclass(){imageUrl="flag.png",PetName="pet 4"}
};
this.BindingContext = this;
}
private void btn1_Clicked(object sender, EventArgs e)
{
var stringBuilder = new StringBuilder();
foreach (Petclass pet in EmptyPetInfo)
{
if(pet.Selected)
{
stringBuilder.Append(pet.PetName + " ");
}
}
string str = stringBuilder.ToString();
}
}
Using ObservableCollection Class, represent a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
you need to initialize selectedPets before you use it
public List<PetProfile> selectedPets = new List<PetProfile>();

Can't update data bindings from object in Xamarin forms

Im trying to update my xamarin bindings from a object in my viewmodel. What am I doing wrong.
When i see look at the page and look at "User", i see my user object is there.
Page:
<StackLayout BindableLayout.ItemsSource="{Binding User}"
Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate x:DataType="model:User">
<StackLayout>
<Label Text="{Binding Name, Mode=OneWay}"/>
<Label Text="{Binding PhoneNumber, Mode=OneWay}"/>
<Label Text="{Binding Adress, Mode=OneWay}"/>
<Label Text="{Binding Email, Mode=OneWay}"/>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
What you need to know about the Viewmodel with mvvhelpers
public User User { get; set; }
public MyProfileViewModel()
{
Title = "MyProfile";
RefreshCommand = new AsyncCommand(Refresh);
AddCommand = new AsyncCommand(Add);
// RemoveCommand = new AsyncCommand<User>(Remove);
User = new User();
}
public async refrsh(){
User = await MyProfileService.GetMyProfile(2);
nameDisplay = User.Name;
}
string nameDisplay;
public string NameDisplay
{
get => nameDisplay;
set => SetProperty(ref nameDisplay, value);
}
According to Jason's opinion, you don't need to use BindableLayout and a DataTemplate if you only have a single User object, you can bind User Object to StackLayout BindingContext directly.
<StackLayout BindingContext="{Binding User}">
<Label Text="{Binding Name, Mode=OneWay}" />
<Label Text="{Binding PhoneNumber, Mode=OneWay}" />
<Label Text="{Binding Adress, Mode=OneWay}" />
<Label Text="{Binding Email, Mode=OneWay}" />
</StackLayout>
If you have collection of IEnumerable User, you can use BindableLayout.ItemsSource and a DataTemplate
You need to implement INotifyPropertyChanged for User properties, it will notify the data changes.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The complete code:
<StackLayout>
<StackLayout BindingContext="{Binding User}">
<Label Text="{Binding Name, Mode=OneWay}" />
<Label Text="{Binding PhoneNumber, Mode=OneWay}" />
<Label Text="{Binding Adress, Mode=OneWay}" />
<Label Text="{Binding Email, Mode=OneWay}" />
</StackLayout>
<Button
x:Name="btn1"
Command="{Binding changecommand}"
Text="change user data" />
</StackLayout>
public partial class Page27 : ContentPage
{
public Page27()
{
InitializeComponent();
this.BindingContext = new MyProfileViewModel();
}
}
public class MyProfileViewModel
{
public User User { get; set;}
public ICommand changecommand { get; }
public MyProfileViewModel()
{
User = new User();
User.Name = "cherry";
User.PhoneNumber = "123";
User.Adress = "location 1";
User.Email = "xxxxx.#outlook.com";
changecommand = new Command(() =>
{
User.Name = "barry";
});
}
}
public class User:ViewModelBase
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
RaisePropertyChanged("Name");
}
}
private string _PhoneNumber;
public string PhoneNumber
{
get { return _PhoneNumber; }
set
{
_PhoneNumber = value;
RaisePropertyChanged("PhoneNumber");
}
}
private string _Adress;
public string Adress
{
get { return _Adress; }
set
{
_Adress = value;
RaisePropertyChanged("Adress");
}
}
private string _Email;
public string Email
{
get { return _Email; }
set
{
_Email = value;
RaisePropertyChanged("Email");
}
}
}

Not Refreshing Spesific Label While Binding ObservableCollection Listview in Xamarin & MVVM

public class Zicker : INotifyPropertyChanged
{
public class MyClass
{
public string HeyName { get; set; }
public string HeySurname { get; set; }
public int HeyAge { get; set; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string name = null)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
}
}
private ObservableCollection<MyClass> _yourList = new ObservableCollection<MyClass>();
public ObservableCollection<MyClass> YourList
{
get
{
return _yourList;
}
set
{
_yourList = value;
RaisePropertyChanged("YourList");
RaisePropertyChanged("BindMeLabel");
}
}
public int BindMeLabel
{
get { return _yourList.Sum(a => a.HeyAge); }
}
public void WonCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("BindMeLabel");
}
public List<string> heresamplenames = new List<string> { "Mohamed", "Zaran", "Ivan" };
public List<string> heresamplesurnames = new List<string> { "Pakou", "Simmone", "Zagoev" };
public List<int> heresampleages = new List<int> { 17,33,50 };
public Zicker()
{
ObservableCollection<MyClass> vs = new ObservableCollection<MyClass>();
for (int i = 0; i < 3; i++)
{ vs.Add(new MyClass { HeyName = heresamplenames[i], HeySurname = heresamplesurnames[i], HeyAge = heresampleages[i] }); }
YourList = vs; YourList.CollectionChanged += WonCollectionChanged;
}
}
<ContentPage.Content>
<StackLayout Orientation="Vertical" HorizontalOptions="Center" VerticalOptions="Center">
<ContentView HorizontalOptions="Fill" VerticalOptions="Fill">
<ListView HorizontalOptions="Center" VerticalOptions="Center" HasUnevenRows="True" ItemsSource="{Binding YourList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label HorizontalTextAlignment="Center" VerticalTextAlignment="Center" Text="{Binding Path=HeyName}" Grid.Column="0" FontSize="12" TextColor="Black"></Label>
<Label HorizontalTextAlignment="Center" VerticalTextAlignment="Center" Text="{Binding Path=HeySurname}" FontSize="12" TextColor="Black" Grid.Column="1"/>
<Entry HorizontalTextAlignment="Center" VerticalOptions="Center" Text="{Binding Path=HeyAge}" FontSize="12" Keyboard="Numeric" TextColor="Black" Grid.Column="2"/>
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentView>
<Label Text="{Binding BindMeLabel}" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" FontSize="40" TextColor="Black"></Label>
</StackLayout>
</ContentPage.Content>
public MainPage()
{
InitializeComponent();
BindingContext = new Zicker();
}
My Problem: In this List, there are three names, surnames, and ages. At the bottom, there is also a label which should be shown as the sum of Ages collection.
When the UI is starting, Label is working well. But, if I try to change any Ages entries, there is a big problem with the binding label.
I want to use MVVM structure but due to this problem, label binding is working just start up.
If you are updating the HeyName property, binding is not updating because the class MyClass does not implement INotifyPropertyChanged.
Try to replace the MyClass class with this code:
public class MyClass : INotifyPropertyChanged
{
private string name;
private string surname;
private int age;
public string HeyName
{
get => name;
set
{
name = value;
RaisePropertyChanged("HeyName");
}
}
public string HeySurname
{
get => surname;
set
{
surname = value;
RaisePropertyChanged("HeySurname");
}
}
public int HeyAge
{
get => age;
set
{
age = value;
RaisePropertyChanged("HeyAge");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string name = null)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
EDIT:
Sorry, the CollectionChanged is not called when you update the HeyAge property, because it is called only if the collection is changed, but not when a property of an item in the collection changes.
Try to add the OnAgeChanged event into the class MyClass and call it when the HeyAge property change:
public class MyClass : INotifyPropertyChanged
{
public event EventHandler OnAgeChanged;
public int HeyAge
{
get => age;
set
{
age = value;
RaisePropertyChanged("HeyAge");
OnAgeChanged?.Invoke(this, EventArgs.Empty);
}
}
...
...
Then, when you add a new MyClass object into the collection, register the event in the ViewModel like this:
public Zicker()
{
ObservableCollection<MyClass> vs = new ObservableCollection<MyClass>();
for (int i = 0; i < 3; i++)
{
var test = new MyClass()
{
HeyName = heresamplenames[i],
HeySurname = heresamplesurnames[i],
HeyAge = heresampleages[i],
};
test.OnAgeChanged += Test_OnAgeChanged;
vs.Add(test);
}
YourList = vs;
YourList.CollectionChanged += WonCollectionChanged;
}
private void Test_OnAgeChanged(object sender, EventArgs e)
{
RaisePropertyChanged("BindMeLabel");
}
Note that the WonCollectionChanged it's not necessary any more.
Note also that the variable vs is not needed, you can work directly into the YourList object instead.

Categories