Xamarin Forms Make detail view editable - c#

My first try with Xamarin, and stuck on the basic.
Build Master-View, all working fine, can add new items.
However when click on existing item it is read only.
I have implemented edit button, but do not know how to make item editable in the detail view.
My ItemDetailPage.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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="DataCollector.Views.ItemDetailPage"
Title="{Binding Title}">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Delete" Clicked="ToolbarItem_Clicked" />
<ToolbarItem Text="Edit" Clicked="Edit_Clicked" />
</ContentPage.ToolbarItems>
<StackLayout Spacing="20" Padding="15">
<Label Text="WTN Number:" FontSize="Medium" />
<Label Text="{Binding Item.WTNNumber}" d:Text="Item name" FontSize="Small"/>
<Label Text="Description:" FontSize="Medium" />
<Label Text="{Binding Item.Description}" d:Text="Item description" FontSize="Small"/>
</StackLayout>
</ContentPage>
and ItemDetailPage.xaml.cs
using System;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using DataCollector.Models;
using DataCollector.ViewModels;
namespace DataCollector.Views
{
// Learn more about making custom code visible in the Xamarin.Forms previewer
// by visiting https://aka.ms/xamarinforms-previewer
[DesignTimeVisible(false)]
public partial class ItemDetailPage : ContentPage
{
ItemDetailViewModel viewModel;
public ItemDetailPage(ItemDetailViewModel viewModel)
{
InitializeComponent();
BindingContext = this.viewModel = viewModel;
}
public ItemDetailPage()
{
InitializeComponent();
var item = new Item
{
WTNNumber = "Item 1",
Description = "This is an item description."
};
viewModel = new ItemDetailViewModel(item);
BindingContext = viewModel;
}
private void Delete_Clicked(object sender, EventArgs e)
{
}
private void Edit_Clicked(object sender, EventArgs e)
{
}
}
}
and ItemDetailViewModel.cs
using System;
using DataCollector.Models;
namespace DataCollector.ViewModels
{
public class ItemDetailViewModel : BaseViewModel
{
public Item Item { get; set; }
public ItemDetailViewModel(Item item = null)
{
Title = item?.WTNNumber;
Item = item;
}
}
}
How to do it through ItemDetailViewModel changes?

Since you had used MVVM .It would be better to handle the logic in ViewModel .
So you could improve it like following .
in xaml
<ContentPage.ToolbarItems>
<ToolbarItem Text="Delete" Command="{Binding DeleteCommand}"/>
<ToolbarItem Text="Edit" Command="{Binding EditCommand}" />
</ContentPage.ToolbarItems>
in View Model
public class ItemDetailViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Item item;
public Item Item {
get {
return item;
}
set
{
if(item!=value)
{
item = value;
NotifyPropertyChanged("Item");
}
}
}
public ICommand DeleteCommand { get; set; }
public ICommand EditCommand { get; set; }
public ItemDetailViewModel (Item item )
{
var Title = item?.WTNNumber;
Item = item;
DeleteCommand = new Command(()=> {//...
});
EditCommand = new Command(()=> {
Item.WTNNumber = "new item";
Item.Description = "new Description";
// do something when click the edit button
});
}
}
in model
public class Item : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
string wTNNumber;
public string WTNNumber
{
get
{
return wTNNumber;
}
set
{
if (wTNNumber != value)
{
wTNNumber = value;
NotifyPropertyChanged("WTNNumber");
}
}
}
string description;
public string Description
{
get
{
return description;
}
set
{
if (description != value)
{
description = value;
NotifyPropertyChanged("Description");
}
}
}
}

Related

xamarin initializecompontent() nullReferenceException

I'm pretty new at xamarin and I'm trying to read a value out of an editor/entry using MVVM but every time I launch the code and I open the page my initializer gives me a nullreferencexception. From what I think I know I should just bind the editor text to a value in my viewmodel and reference it in my xaml file (xmlns), even looking at the documentation and seeing other people doing it in example code and videos I don't seem to be able to fix this issue.
If anyone knows if I missed a step or don't understand something, it would be a massive help if you could inform me of this.
Thank you for your time and help.
this is what my xaml looks like:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:student_app.ViewModels"
x:Class="student_app.Views.AddCategoryPage"
xmlns:model="clr-namespace:student_app.Models" >
<ContentPage.BindingContext>
<local:AddCategoryViewModel/>
</ContentPage.BindingContext>
<RefreshView x:DataType="local:AddCategoryViewModel" Command="{Binding LoadCategoriesCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<StackLayout>
<Label Text="Add Category:"/>
<Editor Grid.Row="1" Grid.Column="0" Width="300" Text="{Binding CategoryNameEditor}" Placeholder="enter category name"/>
<Button x:Name="saveButton" Text="Save" Command="{Binding SaveCommand}"/>
</StackLayout>
</RefreshView>
</ContentPage>
this is what my code behind looks like:
namespace student_app.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class AddCategoryPage : ContentPage
{
AddCategoryViewModel _viewModel;
public AddCategoryPage()
{
InitializeComponent();
this.BindingContext = _viewModel = AppContainer.Resolve<AddCategoryViewModel>();
}
protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
}
}
}
viewmodel code:
namespace student_app.ViewModels
{
public class AddCategoryViewModel : BaseViewModel
{
private readonly IToastService _toastService;
private readonly INavigationService _navigationService;
private readonly ICategoryService _categoryService;
public string categoryName;
public Command SaveCommand { get; }
public Command LoadCategoriesCommand { get; }
public event PropertyChangingEventHandler PropertyChanged;
public string CategoryNameEditor
{
get => categoryName;
set
{
categoryName = value;
var args = new PropertyChangingEventArgs(nameof(CategoryNameEditor));
PropertyChanged?.Invoke(this, args);
}
}
public AddCategoryViewModel(IToastService toastService, INavigationService navigationService, ICategoryService categoryService)
{
_toastService = toastService;
_navigationService = navigationService;
_categoryService = categoryService;
SaveCommand = new Command(async () => await ExecuteSaveCommandCommand());
LoadCategoriesCommand = new Command(async () => await ExecuteLoadCategoriesCommand());
}
public async Task ExecuteLoadCategoriesCommand()
{
IsBusy = true;
try
{
categoryName = string.Empty;
}
catch (Exception ex)
{
await _toastService.DisplayToastAsync(ex.Message);
}
finally
{
IsBusy = false;
}
}
public async Task ExecuteSaveCommandCommand()
{
IsBusy = true;
try
{
_categoryService.addCategory(categoryName);
categoryName = string.Empty;
await _navigationService.NavigateAsync("CalanderPage");
}
catch (Exception ex)
{
await _toastService.DisplayToastAsync(ex.Message);
}
finally
{
IsBusy = false;
}
}
public void OnAppearing()
{
IsBusy = true;
}
}
}

Progress Bar Not Updating Xamarin Forms MVVM

I know this has been asked before but I've spent ages and nothing has helped.
I'm trying to update a progress bar from a ViewModel however it will not update.
Recipe.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:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="FitnessScript.Views.Recipes">
<ContentPage.Content>
<StackLayout>
<Label Text="Please Enter Ingredients and Requirements!"
HorizontalOptions="Center"
VerticalOptions="Start" HorizontalTextAlignment="Center" TextType="Text"
Margin="0,20,0,0"
FontSize="25"/>
<Label Text="Enter Ingredients" Margin="5"/>
<Entry x:Name="Ingredients"
Text="{Binding Ingredients}"
Placeholder="Ingredients"
PlaceholderColor="LightGray" />
<Label Text="Enter Calories" Margin="5"/>
<Entry x:Name="Calories"
Text="{Binding Calories}"
Placeholder="Calories"
PlaceholderColor="LightGray" />
<Button x:Name="RecipeSearchBtn"
Text="Find Recipes"
Command="{Binding RequestRecipeCommand}" />
<ProgressBar x:Name="ProgressB"
Progress="{Binding ProgressValue}"
ProgressColor="Purple"
IsVisible="True"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Recipes.xmal.cs
namespace FitnessScript.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Recipes : ContentPage
{
RecipeSearchViewModel recipeSearchViewModel;
public Recipes()
{
recipeSearchViewModel = new RecipeSearchViewModel();
InitializeComponent();
BindingContext = recipeSearchViewModel;
}
}
}
RecipeSearchViewModel
namespace FitnessScript.ViewModels
{
public class RecipeSearchViewModel : BaseViewModel
{
private static readonly IRecipeService _recipeService = new RecipeService();
private readonly BackgroundWorker worker;
#region Getters/Setters
string _ingredients;
public string Ingredients
{
get { return _ingredients; }
set
{
_ingredients = value;
OnPropertyChanged("Ingredients");
}
}
int _calories;
public int Calories
{
get { return _calories; }
set
{
_calories = value;
OnPropertyChanged("Calories");
}
}
float _progressValue;
public float ProgressValue
{
get { return _progressValue; }
set
{
_progressValue = value;
OnPropertyChanged("ProgressValue");
}
}
#endregion
public RecipeSearchViewModel()
{
this.worker = new BackgroundWorker();
}
public Command RequestRecipeCommand
{
get
{
return new Command(async () => await RequestRecipe());
}
}
private async Task RequestRecipe()
{
await Task.Run(() =>
{
Device.BeginInvokeOnMainThread(() =>
{ ProgressValue = 1; }
);
});
List<string> ingredientsList = await _recipeService.GetRecipe(Ingredients, Calories);
App.Current.MainPage.DisplayAlert("Success", $"{Ingredients}, {Calories}", "Close");
}
}
}
I Have tired many different alternatives, such as setting ProgressValue to Double and Decimal, forcing the UI thread, with and without adding a parameter to OnPropertyChange(). I've attempted background works too, just nothing sadly.
I'm debugging using a S10+ via USB as I prefer it to emulation.
The overall aim is to press the RecipeSearchBtn, do the logic, and update the progress bar along with it, however for debugging purposes I just want to change the progress to 100% when the button command executes
Any help would be appreaciated, thanks
Also I have tried the Activity Indicator however similar issues, it never showed while debugging though my phone when setting the visibility ect to true through binding IsBool
About binding ActivityIndicator isvisible, I do one sample that you can take a look:
Please take a look the following code, ActivityIndicator display firstly, clicking button to load data, setting ActivityIndicator isVisible and IsRunning as false.
<StackLayout>
<Button
x:Name="btn1"
Command="{Binding command1}"
Text="load data" />
<ActivityIndicator
HeightRequest="50"
IsRunning="{Binding isvisible}"
IsVisible="{Binding isvisible}"
WidthRequest="50"
Color="Red" />
<ListView ItemsSource="{Binding students}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding name}" />
<Label Text="{Binding age}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
public partial class Page2 : ContentPage
{
public Page2()
{
InitializeComponent();
this.BindingContext = new studentviewmodel();
}
}
public class studentviewmodel:ViewModelBase
{
public ObservableCollection<studentmodel> students { get; set; }
public Command command1 { get; set; }
private bool _isvisible;
public bool isvisible
{
get { return _isvisible; }
set
{
_isvisible = value;
RaisePropertyChanged("isvisible");
}
}
public studentviewmodel()
{
command1 = new Command(loaddata);
isvisible = true;
students = new ObservableCollection<studentmodel>();
}
private async void loaddata()
{
//call service to do other something.
await Task.Delay(5000);
students.Add(new studentmodel() { name = "cherry", age = 29 });
students.Add(new studentmodel() { name = "barry", age = 30 });
students.Add(new studentmodel() { name = "annine", age = 15 });
isvisible = false;
}
}
public class studentmodel
{
public string name { get; set; }
public int age { get; set; }
}
The ViewModelBase is the class that implementing INotifyPropertyChanged, to notify data changed.
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 screenshot:

Changing ViewModel Property, in another class Doesn't Update UI

I'm having this issuse in a more complex App so I built a simple App to test it out on. but first the code, which is an odd layout for a simple app but bare in mind that this mimics my other app.
Note, Baseviewmodel inherits from basemodel.
MainPage
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="TestINotify.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TestINotify">
<ContentPage.BindingContext>
<local:MainPageViewModel />
</ContentPage.BindingContext>
<StackLayout>
<!-- Place new controls here -->
<Label HorizontalOptions="Center"
Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand" />
<Label HorizontalOptions="Center"
Text="{Binding LabelTextProperty}"
VerticalOptions="CenterAndExpand" />
<Button Command="{Binding ChangeTextCommand}" Text="Change Label Text" />
</StackLayout>
</ContentPage>
MainPageViewModel
public class MainPageViewModel : BaseViewModel
{
public ICommand ChangeTextCommand { private set; get; }
private string _labelText = "Default text" ;
public string LabelTextProperty
{
get
{
return _labelText;
}
set
{
_labelText = value;
OnPropertyChanged();
}
}
public MainPageViewModel()
{
ChangeTextCommand = new Command(execute: () =>
{
var handler = new ChangeTextClass();
handler.ChangeTexts();
});
}
}
ChangeTextClass
public class ChangeTextClass
{
public MainPageViewModel mpvm = new MainPageViewModel();
public void ChangeTexts()
{
mpvm.LabelTextProperty = "The Text Was Changed ?";
}
}
BaseModel
public abstract class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
When i assign the label's text value in the viewmodel by
ChangeTextCommand = new Command(execute: () =>
{
LabelTextProperty = "Local works";
});
It works fine but, so maybe it's be something to do with me creating a new instances of the viewmodel in the class ?

Why is binding not updating picker selected item

I have a Picker in my Xamarin form, bound to a model (code below).
The Load method sets SelectedVehicle but the picker does not show the selected vehicle. When the page is first loaded the picker shows the correct item in the list. But on a page reload after App.VehicleId has been changed, the picker shows blank.
Even if I explicitly set SelectedIndex on the picker during OnAppearing, the picker shows blank, and the SelectedIndex has been set back to -1 when I look later.
How do I correctly update the picker selection when the page is reloaded?
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:local="clr-namespace:DefectReport"
x:Class="DefectReport.MainPage">
<ContentPage.Content>
<ScrollView>
<StackLayout>
<Label x:Name="Message" TextColor="Red" />
<Label Text="Welcome to Vehicle Check" />
<Label Text="Choose vehicle" />
<Picker ItemsSource="{Binding Vehicles}" ItemDisplayBinding="{Binding RegistrationNumber}" SelectedItem="{Binding SelectedVehicle}" SelectedIndexChanged="Picker_SelectedIndexChanged" />
<Label Text="{Binding SelectedVehicle.Description}" />
<Button Text="Add vehicle not in list" Clicked="SelectVehicle_Clicked" />
<Button Text="Check vehicle" Clicked="CheckVehicle_Clicked" />
<Button Text="Log out" Clicked="LogOut_Clicked" />
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
The code behind is this:
public partial class MainPage : ContentPage {
public class VehicleInfo : Model {
public async void Load() {
Vehicles = new ObservableCollection<Vehicle>(await App.Database.ReadAsync<Vehicle>().ToListAsync());
SelectedVehicle = Vehicles.FirstOrDefault(v => v.DeviceRecordId == App.VehicleId) ?? Vehicles.FirstOrDefault();
System.Diagnostics.Debug.WriteLine("App.VehicleId = " + App.VehicleId);
System.Diagnostics.Debug.WriteLine("SelectedVehicle.Id = " + selectedVehicle.DeviceRecordId);
}
private Vehicle selectedVehicle;
public Vehicle SelectedVehicle {
get { return selectedVehicle; }
set {
if (selectedVehicle != value) {
selectedVehicle = value;
OnPropertyChanged("SelectedVehicle");
}
}
}
private ObservableCollection<Vehicle> vehicles;
public ObservableCollection<Vehicle> Vehicles {
get { return vehicles; }
set {
if (vehicles != value) {
vehicles = value;
OnPropertyChanged("Vehicles");
}
}
}
}
VehicleInfo data;
public MainPage() {
data = new VehicleInfo();
BindingContext = data;
InitializeComponent();
}
protected override void OnAppearing() {
base.OnAppearing();
data.Load();
}
Model is a trivial class implementing INotifyPropertyChanged:
public class Model : System.ComponentModel.INotifyPropertyChanged {
public void OnPropertyChanged(string name) {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
It turns out the problem was creating a new ObservableCollection. I changed the code
Vehicles = new ObservableCollection<Vehicle>(await App.Database.ReadAsync<Vehicle>().ToListAsync());
and instead cleared the existing collection, and added the new records to it.

Xamarin - Bind view model in xaml

I'm binding grouped listview to view model data source via xaml.
But when the app is launched the list is empty even when I populate it from code behind in ctor. When I declare the ListView x:Name="myList" and populate it from code behind it works, but it's not "binded" to the view model directly.
<?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:viewModels="clr-namespace:Atc.Obedy.ViewModels;assembly=Atc.Obedy"
x:Class="Atc.Obedy.MainPage" Title="Jídelníček">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="20, 40, 20, 20"
Android="20, 20, 20, 20"
WinPhone="20, 20, 20, 20" />
</ContentPage.Padding>
<ContentPage.BindingContext>
<viewModels:MainPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Orientation="Vertical"
Spacing="15">
<ListView BindingContext="{ Binding MealsGroups }" GroupDisplayBinding="{ Binding DisplayName }" IsGroupingEnabled="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding MealTitle}" />
<Button Image="icon.png" Command="{ Binding OrderCommand }" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Code behind:
public partial class MainPage : ContentPage
{
protected IMealsRepository MealsRepository { get; }
public MainPage()
{
MealsRepository = IoC.Container.Get<IMealsRepository>();
var mealGroups = new ObservableCollection<MealsGroup>();
foreach (var meals in MealsRepository.GetMeals(DateTime.MinValue, DateTime.MaxValue).Where(x => x.DayOfOrder != null).GroupBy(x=>x.DayOfOrder))
{
mealGroups.Add(ProvideMealsGroup(meals.Key.Value, meals));
}
InitializeComponent();
var viewModel = BindingContext as MainPageViewModel;
viewModel.MealsGroups = mealGroups;
}
This is View Model:
public class MainPageViewModel : INotifyPropertyChanged, IViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<MealsGroup> _mealGroups = null;
public ObservableCollection<MealsGroup> MealsGroups
{
get { return _mealGroups; }
set
{
_mealGroups = value;
OnPropertyChanged(nameof(MealsGroups));
}
}
public ICommand OrderCommand { get; set; }
public MainPageViewModel()
{
OrderCommand = new Command(() =>
{
Debug.WriteLine("MealsGroups");
});
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And meal group
public class MealsGroup : List<MealViewModel>
{
public string DisplayName { get; set; }
public string ShortName { get; set; }
public event MealGroupOrderSwitched MealGroupOrderSwitched;
public ICommand OrderCommand { get; set; }
public MealsGroup()
{
OrderCommand = new Command(() =>
{
Debug.WriteLine("MealGroup");
});
}
public void AddMeal(Meal meal)
{
var model = new MealViewModel
{
IsOrdered = meal.IsOrdered,
MealTitle = meal.MealTitle,
MealId = meal.MealId.Value,
DayOfOrder = meal.DayOfOrder.Value,
IsOrderable = meal.IsOrderable,
IsSoup = meal.IsSoup
};
model.MealOrdered += meaId =>
{
for (var i = 0; i < Count; i++)
{
var mealViewModel = this[i];
if (mealViewModel.MealId != meaId && mealViewModel.IsOrderable)
mealViewModel.IsOrdered = false;
}
MealGroupOrderSwitched?.Invoke(this);
};
Add(model);
}
}
but the android app when launched has empty list. Even when I added items in code behind in ctor.
Solved by changing in xaml ListView property BindingContext={binding MealsGroups} to ItemsSource={binding MealsGroups}

Categories

Resources