Binding MenuItem in ListView xamarin - c#

i have 2 questions
How can i bind a command from MenuItem using Mvvm?
<StackLayout>
<Label Text="{Binding TestText}"/>
<SearchBar TextColor="Black" SearchCommand="{Binding SearchBar}" Text="{Binding TextChanging}"/>
<ListView ItemsSource="{Binding ListOfItems, Mode=TwoWay}" RefreshCommand="{Binding RefreshData}" IsPullToRefreshEnabled="True" IsRefreshing="{Binding IsRefreshing}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextCell TextColor="Black" Text="{Binding MainText}" Detail="{Binding SecondText}" >
<TextCell.ContextActions>
<MenuItem Command="{Binding Path=BindingContext.DeleteCommand}" CommandParameter="{Binding .}" Text="Delete"/>
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
public Command DeleteCommand(object sender, EventArgs e)
{
return new Command(() =>
{
});
}
I can't really binding this DeleteCommand using just MVVM.
Question2:
In runtime, my MenuItem look like this:
enter image description here
and i want to look like this:enter image description here
How can i do that?

When you use DeleteCommand to delete a selected item, you need get it and then update the itemsource for the listview.
Here's the code snippet for your reference:
Mode:
public class MyModel
{
public string MainText { get; set; }
}
ViewModel:
public class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<MyModel> ListOfItems { get; set; }
public ICommand DeleteCommand => new Command(Delete);
public MainPageViewModel()
{
ListOfItems = new ObservableCollection<MyModel>()
{
new MyModel(){ MainText = "test1"},
new MyModel(){ MainText = "test2"},
new MyModel(){ MainText = "test3"},
new MyModel(){ MainText = "test4"},
};
}
private void Delete(object obj)
{
//get the item and cast it to Model
var content = obj as MyModel;
ListOfItems.Remove(content);
}
}
Xaml Code:
<StackLayout>
<ListView x:Name="items" ItemsSource="{Binding ListOfItems, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell TextColor="Black" Text="{Binding MainText}" >
<TextCell.ContextActions>
<MenuItem Command="{Binding Path=BindingContext.DeleteCommand , Source={x:Reference Name=items }}" CommandParameter="{Binding .}" Text="Delete"/>
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Tips: Don't forget to add Source={x:Reference Name=items } for the menu item.
Last but not least, bind the View and Model in Code behind or in Xaml:
BindingContext = new MainPageViewModel();

Related

How to handle when Items Control element has changed? WPF, C#

Below I have a ListView with cities, when city is checked then the second list (with persons) must displays persons only from this city.
<ListView ItemsSource="{Binding Cities}">
<ListView.ItemTemplate >
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
<CheckBox IsChecked="{Bidning IsChecked}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And the second list view with persons:
<ListView ItemsSource="{Binding Persons}">
<ListView.ItemTemplate >
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
<Label Content="{Bidning Surname}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Below you can see view model with the lists which are binded with ListViews.
public ListCollectionView Cities
{
get { return _cities; }
set
{
_cities = value;
RaisePropertyChanged();
}
}
public ListCollectionView Persons
{
get { return _persons; }
set
{
_persons= value;
RaisePropertyChanged();
}
}
The question is how to handle when checkbox which is inside Cities ListView is checked by the user, so I can refresh the Persons list and return Persons only from checked City? I do not know what should I write in View Model, so I do not know how to handle when ListView children property has changed changed.
you have to add public City SelectedCity property to the view model and bind the selected city value in the view
<ListView ItemsSource="{Binding Cities}" SelectedItem="{Binding SelectedCity">
in the SelectedCity setter you can update the Persons collection like
class PersonsByCitiesViewModel
{
public City SelectedCity
{
get{}
set
{
Persons = LoadPersons(value);
}
}
public ListCollectionView LoadPersons(City selected)
{
var persons = LoadAll();
if (selected != null)
persons = persons.Where(_ => _.CityId = selected.CityId);
return new ListCollectionView(persons);
}
}
Handling SelectedItem is good but only if I want to select just one City from the list.
What If I want to select more than one?
I have tried to handle property IsChecked inside Cities class, but in Cities class I do not have an access to other elements which are in View Model where Cities List and Person list are.
I recommend handling the Checked and Unchecked event of the CheckBox elements.
View:
<ListView ItemsSource="{Binding Cities}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding CityName}" />
<CheckBox IsChecked="{Binding IsChecked}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding DataContext.SelectedCitiesChangedCommand, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding DataContext.SelectedCitiesChangedCommand, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView ItemsSource="{Binding ShownPeople}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel:
public List<City> Cities { get; } = new List<City>();
public List<Person> Persons { get; } = new List<Person>();
public ObservableCollection<Person> ShownPeople { get; } = new ObservableCollection<Person>();
public ICommand SelectedCitiesChangedCommand { get; }
public ViewModel()
{
SelectedCitiesChangedCommand = new DelegateCommand(SelectedCitiesChanged);
}
private void SelectedCitiesChanged()
{
var checkedCities = Cities.Where(city => city.IsChecked);
ShownPeople.Clear();
ShownPeople.AddRange(Persons.Where(person => checkedCities.Any(city => city.CityName == person.City)));
}

How to bind CommandParameter to viewCell Label in ListView (Xamarin Forms)

I'm trying to make grouped ListView for meals and foods with Add Food button for each meal. But when Add Food command is executed I don't know to which meal should I add the food. So I thought it would be good to pass the meal name as a parameter but I don't know how.
This is the ViewModel
public class ViewModel : BaseViewModel
{
public FoodViewModel()
{
this.AddFood = new Command<string>(this.OnAddFood);
this.MealGroups = new ObservableCollection<MealGroup>();
}
public ObservableCollection<MealGroup> MealGroups { get; set; }
public ICommand AddFood { get; private set; }
private void OnAddFood(string param)
}
This is the MealGroup
public class MealGroup : ObservableCollection<Food>
{
public string MealName { get; set; }
public ObservableCollection<Food> Foods => this;
}
This is the ListView
<StackLayout x:Name="MealLayout" HorizontalOptions="FillAndExpand">
<Button x:Name="AddMealButton" Text="Add Meal" Command="{Binding AddMeal}"/>
<ListView x:Name="MealGroupsListView" ItemsSource="{Binding MealGroups}" IsGroupingEnabled="true">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<StackLayout x:Name="MealInfoLayout" Orientation="Horizontal">
<Label x:Name="MealNameLbl" Text="{Binding MealName}" />
<StackLayout>
<StackLayout.BindingContext>
<local:FoodViewModel/>
</StackLayout.BindingContext>
<Button x:Name="AddFoodCommand"
Text="Add Food"
Command="{Binding AddFood}"
CommandParameter="{Binding }"/>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label x:Name="FoodNameLbl" Text="{Binding Name}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Simply pass the name of the property as the Path of your Binding:
<Button x:Name="AddFoodCommand"
Text="Add Food"
Command="{Binding AddFood}"
CommandParameter="{Binding MealName}"/>
Your AddFood command will be called with the MealName property as parameter.
There's one more better way to access whole object(not only name)
<Button x:Name="AddFoodCommand"
Text="Add Food"
Command="{Binding AddFood}"
CommandParameter="{Binding .}"
/>
Through this,CommandParameter will return whole item
Firstly, I should add a foodId on the class. It's more efficient to find the item back with such an instruction :
Mg = MealGroups.First(o => o.Id == SearchedMealGroupId);
In such case, i personnaly use the ItemTapped functionnality of the listview without button (or maybe just an image for UI Experience).
<ListView x:Name="listElement" .... ItemTapped="listElement_ItemTapped" ..../>
and in the .xmal.cs :
private void listElement_ItemTapped(object sender, ItemTappedEventArgs e)
{
int SelectedMealGroupId = ((MealGroup)e.Item).Id; // You are sure of the type of e.Item !
....
// You can call methods of the VM with :
((ViewModel)BindingContext).OnAddFood(SelectedMealGroupId); // Int parameter, not string
}

How to Bind a command of child class object declared in Main context ViewMOdel xamarin forms

I am having a page(MyPage) and Corresponding view Model(MypageViewModel). I have another view model called(listItemLogicVM) and its declared as observable collection in MypageViewModel.
MyPage XAML
<ListView x:Name="lstvwItemSelected" ItemsSource="{Binding
LstlistItemLogicVM}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout>
<Entry Text="{Binding RequestingQty,Mode=TwoWay}"/>
<Image Source="ArrowUp.png"><Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding QtyDecrementCommand}"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
MypageViewModel
public class MypageViewModel
{
ObservableCollection<listItemLogicVM> lstvwItemSelected = new ObservableCollection<listItemLogicVM>()
.
-- OtherLogics
.
}
listItemLogicVM
public class listItemLogicVM
{
public Command QtyDecrementCommand { get; set; }
public int RequestingQty
{
get { return _requestingQty; }
set { _requestingQty = value; OnPropertyChanged();}
}
}
Above Binding throws error. Can anyone help me to bind the command and property of listItemLogicVM to the list view in MyPage XAML
It looks to me like your listview XAML is incorrect. Try this (note the only changes are to the x:Name and ItemsSource attribute of your ListView):
<ListView x:Name="myUniqueListName" ItemsSource="{Binding
lstvwItemSelected}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout>
<Entry Text="{Binding RequestingQty,Mode=TwoWay}"/>
<Image Source="ArrowUp.png"><Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding QtyDecrementCommand}"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
That should call the QtyDecrementCommand on the item that is tapped in the list. This assumes that you've correctly assigned some code to that command for each item in the list.
You need to add command in your MypageViewModel and your xaml code look like :
<TapGestureRecognizer Command="{Binding Source={x:Reference lstvwItemSelected}, Path=BindingContext.QtyDecrementCommand}"/>
</Image.GestureRecognizers>

Xamarin.Forms ListView Deletion of Items holds old values

I have a ListView and populate it via DataBinding to a Lists Property in my ViewModel. Additionally, I have a menu for the ListView, with a Delete Command, also bound to my ViewModel.
My Problem is now, if I have the ListView initialized, I can delete the lists in it. If I add new lists, I can delete all lists. But then, if I add new items, I can't delete them, because the List I get from the DeleteCommand is the old, already deleted list.
So, after deleting lists, they seem to be somehow, somewhere still present and I can only delete new lists, if the total amount of current lists is higher, than any previous amount of deleted lists.
I hope this is a somehow understandable explanation of my problem.
The Binding is working and Lists Property in my ViewModel holds the correct values, but the "sender" ItemList in the DeleteListCommand is the old ItemList.
Here is my XAML for my ListView:
<ListView x:Name="listView" ItemsSource="{Binding Lists}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="viewCell">
<ViewCell.ContextActions>
<MenuItem Command="{Binding BindingContext.RenameListCommand, Source={x:Reference listView}}" CommandParameter="{Binding .}" Text="Rename" />
<MenuItem Command="{Binding BindingContext.DeleteListCommand, Source={x:Reference listView}}" CommandParameter="{Binding .}" IsDestructive="True" Text="Delete" />
</ViewCell.ContextActions>
<ContentView Margin="0,2,0,2"
HeightRequest="50"
BackgroundColor="{Binding Color}">
<ContentView.GestureRecognizers>
<TapGestureRecognizer BindingContext="{Binding Source={x:Reference listView}, Path=BindingContext}"
Command="{Binding ListTappedCommand}"
CommandParameter="{Binding Source={x:Reference viewCell}, Path=BindingContext}" />
</ContentView.GestureRecognizers>
<ContentView.Content>
<Label Text="{Binding Name}"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
TextColor="White"
IsEnabled="True"/>
</ContentView.Content>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And here is my ViewModel:
...
public ObservableCollection<ItemList> lists = new ObservableCollection<ItemList>();
public ObservableCollection<ItemList> Lists
{
get { return lists; }
set
{
lists = value;
OnPropertyChanged("Lists");
}
}
public event PropertyChangedEventHandler PropertyChanged;
...
this.DeleteListCommand = new Command<ItemList>((sender) =>
{
OnDeleteList(sender);
});
...
public ICommand DeleteListCommand { get; set; }
private void OnDeleteList(ItemList itemList)
{
Lists.Remove(itemList);
}
...
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Based on sample project here is what you need to do: Name you cell "viewCell" because you need to pass cell to your model to be able to reset ContextAction.
Change binding on menu item to pass the cell instead of ItemList. Then in model reset context action and get an item from binding context of the cell
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:TobyList_XamarinForms"
xmlns:localVM="clr-namespace:TobyList_XamarinForms.ViewModels"
Title="Toby"
x:Class="TobyList_XamarinForms.Views.MasterPage">
<StackLayout Padding="5" VerticalOptions="FillAndExpand" BackgroundColor="#F9F9F9">
<StackLayout.BindingContext>
<localVM:MasterPageViewModel />
</StackLayout.BindingContext>
<ListView x:Name="listView" ItemsSource="{Binding Lists}" CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="viewCell">
<ViewCell.ContextActions>
<MenuItem Command="{Binding Path=BindingContext.DeleteListCommand, Source={x:Reference Name=listView}}" CommandParameter="{Binding Source={x:Reference viewCell}}" Text="Delete" />
</ViewCell.ContextActions>
<ContentView Margin="0,2,0,2"
HeightRequest="50"
BackgroundColor="{Binding Color}">
<ContentView.GestureRecognizers>
<TapGestureRecognizer BindingContext="{Binding Source={x:Reference listView}, Path=BindingContext}"
Command="{Binding ListTappedCommand}"
CommandParameter="{Binding Source={x:Reference viewCell}, Path=BindingContext}" />
</ContentView.GestureRecognizers>
<ContentView.Content>
<Label Text="{Binding Name}"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
TextColor="White"/>
</ContentView.Content>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackLayout Orientation="Horizontal" HeightRequest="30" Margin="7">
<Label Text="Add">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding AddListCommand}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>
Model:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
using TobyList_XamarinForms.Models;
using Xamarin.Forms;
using System.Linq;
namespace TobyList_XamarinForms.ViewModels
{
public class MasterPageViewModel : INotifyPropertyChanged
{
public ObservableCollection<ItemList> lists = new ObservableCollection<ItemList>();
public ObservableCollection<ItemList> Lists
{
get { return lists; }
set
{
lists = value;
OnPropertyChanged("Lists");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MasterPageViewModel()
{
this.AddListCommand = new Command(() =>
{
OnAddList();
});
//this.DeleteListCommand = new Command<ItemList>((sender) =>
//{
// OnDeleteList(sender);
//});
this.DeleteListCommand = new Command<ViewCell>((sender) =>
{
OnDeleteList(sender);
});
}
public ICommand AddListCommand { get; protected set; }
private void OnAddList()
{
ItemList itemList = new ItemList() { Id = Guid.NewGuid().ToString().ToUpper(), Name = "Lorem Ipsum", Color = "#000000" };
Lists.Add(itemList);
}
public ICommand DeleteListCommand { get; set; }
//public void OnDeleteList(ItemList itemList)
// {
// Lists.Remove(itemList);
// }
public void OnDeleteList(ViewCell viewCell)
{
viewCell.ContextActions.Clear();
Lists.Remove((ItemList)viewCell.BindingContext);
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Using Tap Gestures on images within listview

I am using Prism in my Xamarin forms app, I have a ListView where each Listitem has 2 images, an edit image and delete image.
I have usedTapGesturesRecognizers on these 2 images, and binded _DelegateCommands for these TapGestureRecognizers. However these DelegateCommands do not get called.
Thanks in advance
This is my XAML code
<ListView x:Name="lstAddress" HasUnevenRows="True" SeparatorVisibility="None" ItemsSource="{Binding ListItems}" CachingStrategy="RecycleElement" >
<ListView.Behaviors>
<b:EventToCommandBehavior EventName="ItemTapped"
Command="{Binding ListItemSelectCommand}" EventArgsParameterPath="Item"/>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Description}" FontSize="15" TextColor="#959595">
</Label>
<Image Source="Edit.png" HeightRequest="50" WidthRequest="50" IsVisible="{Binding ShowEdit}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command ="{Binding EditAddressCommand}"></TapGestureRecognizer>
</Image.GestureRecognizers>
</Image>
<Image Source="Delete.png" ClassId="{Binding ListID}" HeightRequest="30" WidthRequest="30" IsVisible="{Binding ShowIcon}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command ="{Binding DeleteAddressCommand}"></TapGestureRecognizer>
</Image.GestureRecognizers>
</Image>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In my ViewModel:
public class AddressBookViewModel : BindableBase
{
INavigationService _navigationService;
public DelegateCommand EditAddressCommand { get; set; }
public DelegateCommand DeleteAddressCommand { get; set; }
public ObservableCollection<ListModel> ListItems {get;set;} = new ObservableCollection<ListModel>();
public DelegateCommand<ListModel> ListItemSelectCommand { get; set; }
public MyZypListsViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
EditAddressCommand = new DelegateCommand(EditAddress);
DeleteAddressCommand = new DelegateCommand(DeleteAddress);
ListItemSelectCommand = new DelegateCommand<ListModel>(ListItemSelected);
}
private void EditAddress()
{
//Edit listitem
}
private void DeleteAddress()
{
//Delete listitem
}
}
For each item on your list, your BindingContext is not the same as the ListView's BindingContext. Locally your BindingContext is the item itself, this is why you can obtain the 'Description' property, for example. Xamarin is searching for an ICommand called EditAddressCommand in your item, not in your view model.
You can make a Cross Binding reference if you want... Just replace your commands at item template to this way:
Command ="{Binding BindingContext.EditAddressCommand, Source={x:Reference lstAddress}"
It may work.

Categories

Resources