I'm developing/learning XamarinForms. I'm using Web Api to get values and use them to populate a ListView. Now I want to get current values on ItemSelected and store them for later use, but I don't seem to work it out. I'm using the MVVM.
This is my ListView
<ListView x:Name="ProfileDetails" ItemsSource="{Binding Profiles}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="20,0,0,0" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label Text="{Binding ProfileType}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfileChamber}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfileWidhtMM}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfilePrice}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is the ViewModel
APIServices _apiServices = new APIServices();
private List<Profile> _profiles;
public List<Profile> Profiles
{
get { return _profiles; }
set
{
_profiles = value;
OnPropertyChanged();
}
}
public ICommand GetProfilesCommand
{
get
{
return new Command(async () =>
{
Profiles = await _apiServices.GetProfilesAsync(_accessToken);
});
}
}
And this is my API request
public async Task<List<Profile>> GetProfilesAsync(string accessToken)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var json = await client.GetStringAsync(Constants.GetProfilesUrl);
var profiles = JsonConvert.DeserializeObject<List<Profile>>(json);
return profiles;
}
And this is the Model
public long Id { get; set; }
public string ProfileType { get; set; }
public int ProfileChamber { get; set; }
public int ProfileWidhtMM { get; set; }
public double ProfilePrice { get; set; }
Answer from Oluwasayo is correct but I see that you are using MVVM already so using this solution might be better for you, just in case if you want to keep your code-behind class clean and leave any mixing code-behind and ViewModel code for one functionality.
I don't know if you are using some MVVM framework or not, but there is a very nice and helpful behavior called EventToCommandBehavior you can use it to "translate" any event and bind it to a command in your ViewModel.
You can find implementation of it here on GitHub, you can easly include it in your project. There is a lot of other implementations from MVVM frameworks.
Steps to make this in the way I advice you are:
Include that class in your project and you can use it in xaml pages.
Edit VM code and add some aditional lines of code.
ViewModel code:
// Property for binding SelectedItem from ListView.
private Profile _selectedProfile;
public Profile SelectedProfile
{
get { return _profiles; }
set
{
_selectedProfile= value;
OnPropertyChanged();
}
}
// Command which we will use for Event to Command binding.
public DelegateCommand ItemTappedCommand{ get; private set; }
// ...
// Code inside of the ctor in VM:
ItemTappedCommand = new Command(ItemTapped);
// ...
// Method which will be executed using our command
void ItemTapped()
{
// Here you can do whatever you want, this will be executed when
// user clicks on item in ListView, you will have a value of tapped
// item in SlectedProfile property
}
Edit your XAML page code and add few code lines.
XAML Page:
<!-- In your page xaml header add xaml namespace to EventToCommandBehaviour class-->
xmlns:b="clr-namespace:Namespace.ToYour.EventToCommandBehaviourClass"
<ListView x:Name="ProfileDetails" ItemsSource="{Binding Profiles}"
SelectedItem={Binding SelectedProfile}>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="20,0,0,0" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label Text="{Binding ProfileType}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfileChamber}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfileWidhtMM}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfilePrice}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<b:EventToCommandBehavior EventName="ItemTapped"
Command="{Binding ItemTappedCommand}" />
</ListView.Behaviors>
</ListView>
Hope that this makes sense to you, using this approach your code-behind class will stay clean without mixing the code between vm and code-behind, just my tip for you.
Wishing you lots of luck with coding!
In your xaml add the ItemTapped property. Can be:
ItemTapped="ProfileDetails_Selected"
Then you should have the method handling the event in your code behind as:
private void ProfileDetails_Selected(object sender, ItemTappedEventArgs e)
{
var myItem = e.Item ...; //from here you can get your item and store for later use
}
Related
my problem is when I send an object of Order to my function, it comes Null without updating my values, how can I edit them?
I try to update my Object. I expecting get in my function Object of Order with the value and not null
here is my function that needs to get the Object.
[RelayCommand]
async Task Continue(Order order)
{
//Order order = new Order()
//{
// DateStart = dateStart,
// DateEnd = dateEnd,
// DogName = dogName
//};
await Shell.Current.GoToAsync(nameof(ShowOrderPage), true, new Dictionary<string, object>
{
{"Order", order}
});
}
using Pension.ViewsModels;
namespace Pension.View;
public partial class MainPage : ContentPage
{
public MainPage(MainViewModel vm)
{
InitializeComponent();
BindingContext = vm;//here the error
}
}
my XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:model="clr-namespace:Pension.Models"
xmlns:viewmodel="clr-namespace:Pension.ViewsModels"
x:DataType="viewmodel:MainViewModel"
x:Class="Pension.Views.MainPage"
BackgroundColor="#F2F2F2">
<VerticalStackLayout Margin="0,20,0,0" Spacing="10" FlowDirection="RightToLeft">
<Label Text="הזמנת פנסיון לכלב" FontSize="40" HorizontalOptions="Center" Margin="5"/>
<Label Text="לפני שנתחיל נשמח לדעת מה התאריך בו תרצו להתארח אצלנו" HorizontalOptions="Center" FontSize="20" FontFamily="op-light"/>
<Grid Padding="20" Background="#FFFFFF" Margin="54">
<Grid.Shadow>
<Shadow Brush="#000"
Offset="5,0"
Opacity="0.26"/>
</Grid.Shadow>
<VerticalStackLayout Spacing="10" x:DataType="model:Order">
<Label Text="בחירת תאריך" FontSize="30" HorizontalOptions="Center" Margin="0,0,0,15"/>
<Frame x:Name="DateStart" CornerRadius="0" Padding="10,0">
<DatePicker MinimumDate="01/01/2022"
MaximumDate="12/31/2025"
Date="{Binding DateStart}"/>
</Frame>
<Frame CornerRadius="0" Padding="10,0">
<DatePicker x:Name="DateEnd" MinimumDate="01/01/2022"
MaximumDate="12/31/2025"
Date="{Binding DateEnd}" />
</Frame>
<Frame CornerRadius="0" Padding="10,0">
<Entry x:Name="dogName" Placeholder="שם הכלב/ה" Text="{Binding DogName}"/>
</Frame>
<Button Text="המשך" BackgroundColor="#EEBF3E"
TextColor="Black" CornerRadius="0"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MainViewModel}},Path=ContinueCommand}"
CommandParameter="{Binding Source={RelativeSource AncestorType={x:Type model:Order}}}">
<Button.Shadow>
<Shadow Brush="#ccccd0"
Offset="3,6"
Opacity="1"/>
</Button.Shadow>
</Button>
</VerticalStackLayout>
</Grid>
</VerticalStackLayout>
</ContentPage>
into the Entry, I try to insert my values and it's not working
why I need him in VM? I have a Model of Order, and I want to send the
object to function after I updated the values of this order in the
XMAL code.
You should not fully understand the meaning of BindingContext and MVVM.
From document Data Binding Basics,we know that:
Data bindings connect properties of two objects, called the source and
the target. In code, two steps are required: The BindingContext
property of the target object must be set to the source object, and
the SetBinding method (often used in conjunction with the Binding
class) must be called on the target object to bind a property of that
object to a property of the source object.
The target property must be a bindable property, which means that the
target object must derive from BindableObject. The online
Xamarin.Forms documentation indicates which properties are bindable
properties. A property of Label such as Text is associated with the
bindable property TextProperty
Simply put, when you set the BindingContext = vm; for your page(MainPage), you usually need to bind the properties(including simple types(e.g.string, int) or Object) and command from the vm(Yours is MainViewModel ) instead of binding from other classes.
Based on your code, your MainViewModel should code as follows:
MainViewModel.cs
public class MainViewModel
{
public Order order { get; set; }
public MainViewModel()
{
order = new Order { DateStart = "2022-11-16", DateEnd = "2022-11-17", DogName = "puppy" };
}
public ICommand ContinueCommand => new Command<Order>(continueItem);
private void continueItem(Order obj)
{
System.Diagnostics.Debug.WriteLine(" the passted Order's name is: " + obj.DogName);
}
}
Order.cs
public class Order: INotifyPropertyChanged
{
public string DateStart { get; set; }
public string DateEnd { get; set; }
//public string DogName { get; set; }
string _dogName;
public string DogName
{
get => _dogName;
set => SetProperty(ref _dogName, value);
}
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;
}
TestPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiCollectionApp.TestPage"
xmlns:model="clr-namespace:MauiCollectionApp.model"
Title="TestPage">
<VerticalStackLayout Margin="0,20,0,0" Spacing="10" FlowDirection="RightToLeft">
<Label Text="הזמנת פנסיון לכלב" FontSize="40" HorizontalOptions="Center" Margin="5"/>
<Label Text="לפני שנתחיל נשמח לדעת מה התאריך בו תרצו להתארח אצלנו" HorizontalOptions="Center" FontSize="20" FontFamily="op-light"/>
<Grid Padding="20" Background="#FFFFFF" Margin="54">
<Grid.Shadow>
<Shadow Brush="#000"
Offset="5,0"
Opacity="0.26"/>
</Grid.Shadow>
<VerticalStackLayout Spacing="10" >
<Label Text="בחירת תאריך" FontSize="30" HorizontalOptions="Center" Margin="0,0,0,15"/>
<Frame x:Name="DateStart" CornerRadius="0" Padding="10,0">
<DatePicker MinimumDate="01/01/2022"
MaximumDate="12/31/2025"
Date="{Binding order.DateStart}"/>
</Frame>
<Frame CornerRadius="0" Padding="10,0">
<DatePicker x:Name="DateEnd" MinimumDate="01/01/2022"
MaximumDate="12/31/2025"
Date="{Binding order.DateEnd}" />
</Frame>
<Frame CornerRadius="0" Padding="10,0">
<Entry x:Name="dogName" Placeholder="שם הכלב/ה" Text="{Binding order.DogName}"/>
</Frame>
<Button Text="המשך" BackgroundColor="#EEBF3E"
TextColor="Black" CornerRadius="0"
Command="{Binding ContinueCommand}"
CommandParameter="{Binding order}">
<Button.Shadow>
<Shadow Brush="#ccccd0"
Offset="3,6"
Opacity="1"/>
</Button.Shadow>
</Button>
</VerticalStackLayout>
</Grid>
</VerticalStackLayout>
</ContentPage>
TestPage.xaml.cs
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
this.BindingContext = new MainViewModel();
}
}
Note:
1.I defined a variable order and added ICommand ContinueCommand in ViewModel MainViewModel.
then, we can bind as follows:
<DatePicker MinimumDate="01/01/2022"
MaximumDate="12/31/2025"
Date="{Binding order.DateStart}"/>
And add command for button like this:
<Button Text="המשך" BackgroundColor="#EEBF3E"
TextColor="Black" CornerRadius="0"
Command="{Binding ContinueCommand}"
CommandParameter="{Binding order}">
<Button.Shadow>
<Shadow Brush="#ccccd0"
Offset="3,6"
Opacity="1"/>
</Button.Shadow>
</Button>
2.I defined class Order and implemented interface INotifyPropertyChanged for it. Then if we change the value of DogName, the value of variable order in MainViewModel will be updated.
public class Order: INotifyPropertyChanged
{
public string DateStart { get; set; }
public string DateEnd { get; set; }
//public string DogName { get; set; }
string _dogName;
public string DogName
{
get => _dogName;
set => SetProperty(ref _dogName, value);
}
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;
}
Update
I achieved this function with nuget CommunityToolkit.MVVM, and it works on my side.
You can refer to the following code:
MyMainViewModel.cs
public partial class MyMainViewModel
{
public MyOrder order { get; set; }
public MyMainViewModel()
{
order = new MyOrder { DateStart = "2022-11-16", DateEnd = "2022-11-17", DogName = "puppy" };
}
[RelayCommand]
async Task Continue(MyOrder order)
{
System.Diagnostics.Debug.WriteLine(" the passted Order's name is: " + order.DogName);
}
}
MyOrder.cs
public partial class MyOrder: ObservableObject
{
[ObservableProperty]
private string? dateStart;
[ObservableProperty]
private string? dateEnd;
[ObservableProperty]
private string? dogName;
[RelayCommand]
async Task Continue(Order order)
{
System.Diagnostics.Debug.WriteLine(" the passted Order's name is: " + order.DogName);
}
}
TestPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiCollectionApp.TestPage"
xmlns:model="clr-namespace:MauiCollectionApp.model"
Title="TestPage">
<VerticalStackLayout Margin="0,20,0,0" Spacing="10" FlowDirection="RightToLeft">
<Label Text="הזמנת פנסיון לכלב" FontSize="40" HorizontalOptions="Center" Margin="5"/>
<Label Text="לפני שנתחיל נשמח לדעת מה התאריך בו תרצו להתארח אצלנו" HorizontalOptions="Center" FontSize="20" FontFamily="op-light"/>
<Grid Padding="20" Background="#FFFFFF" Margin="54">
<Grid.Shadow>
<Shadow Brush="#000"
Offset="5,0"
Opacity="0.26"/>
</Grid.Shadow>
<VerticalStackLayout Spacing="10" >
<Label Text="בחירת תאריך" FontSize="30" HorizontalOptions="Center" Margin="0,0,0,15"/>
<Frame x:Name="DateStart" CornerRadius="0" Padding="10,0">
<DatePicker MinimumDate="01/01/2022"
MaximumDate="12/31/2025"
Date="{Binding order.DateStart}"/>
</Frame>
<Frame CornerRadius="0" Padding="10,0">
<DatePicker x:Name="DateEnd" MinimumDate="01/01/2022"
MaximumDate="12/31/2025"
Date="{Binding order.DateEnd}" />
</Frame>
<Frame CornerRadius="0" Padding="10,0">
<Entry x:Name="dogName" Placeholder="שם הכלב/ה" Text="{Binding order.DogName}"/>
</Frame>
<Button Text="המשך" BackgroundColor="#EEBF3E"
TextColor="Black" CornerRadius="0"
Command="{Binding ContinueCommand}"
CommandParameter="{Binding order}">
<Button.Shadow>
<Shadow Brush="#ccccd0"
Offset="3,6"
Opacity="1"/>
</Button.Shadow>
</Button>
</VerticalStackLayout>
</Grid>
</VerticalStackLayout>
</ContentPage>
TestPage.xaml.cs
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
//this.BindingContext = new MainViewModel();
this.BindingContext = new MyMainViewModel();
}
}
Do not think of binding, as creating and passing object to your command.
You ask why you need it in your VM?
The VM is you BindingContext for the View.
The memory of this VM is used to store the objects, that you will be binding to your View.
Those objects are not just instances of some Model. They are Observable. There is a code behind your setters and getters that handles notifications and update between your Interface and the Memory that stores those objects behind it.
So, to make the logic-chain now: You need to have object, with specific code in your view model, that ViewModel to be set as BindingContext of your View, and to link this observable property in your XAML of your Page, to make all this magic to work.
What the people in the comments section suggested to you, is to make such object in your ViewModel and use it for binding. When the command is triggered, you do not pass it as parameter to the command, because it is stored already in the memory, and simply use it there. (Pass it in as query parameter).
To do this, I recommend: CommunityToolkit.MVVM.
It will save you a lot of work.
(And the need to acquire detailed knowledge on binding)
I have a simple view model where one property contains a model, and another property contains a list of models.
I am able to bind the "Test" model's properties without an issue but I'm not able to get the XAML to recognize that "ListModel" contains a list with its own properties. I have looked at several examples for how to set up the view model and initialize the list correctly before binding it to the view, and while the XAML understands that "ListModel" is a property, I can't get it to recognize that it's a list, and thus it will not compile so that I can at least see if it isn't the intellisense that could be failing for whatever reason.
This is the view model in question with the list named "ListModel"
public class TestViewModel
{
public TestModel Test { get; } = new TestModel();
public List<TestListModel> ListModel { get; set; }
public TestViewModel()
{
Initialize();
}
public void Initialize()
{
ListModel = new List<TestListModel>();
ListModel.Add(new TestListModel
{
ListProp1 = "First",
ListProp2 = "Second",
ListProp3 = "Third"
});
}
}
This is the Model that is being put into a list. It seems like the view isn't seeing these properties.
public class TestListModel
{
public string ListProp1 { get; set; }
public string ListProp2 { get; set; }
public string ListProp3 { get; set; }
}
This is my XAML Currently.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp1.MainPage"
xmlns:local="clr-namespace:ViewModels"
x:DataType="local:TestViewModel"
>
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<!--This works-->
<Entry Text="{Binding Test.Property1}"/>
<Entry Text="{Binding Test.Property2}"/>
<Entry Text="{Binding Test.Property3}"/>
<!--This does not work-->
<ListView
ItemsSource="{Binding ListModel}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding ListProp1}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</VerticalStackLayout>
</ScrollView>
</ContentPage>
For anyone stumbling in on this: Jason in the comments has answered the question. The fix was simply to remove x:DataType from the top of the XAML, though I did not remove the "xmlns:local" from it.
What I had was a View Model that had more than just one model in it, which seemed to upset the intellisense when removing x:DataType. Removing it originally prevented the application from compiling because it couldn't find the properties I had in the XAML. Once I cleaned and rebuilt the solution it compiled and worked without a hitch.
The view and the viewmodel are often connected through data bindings defined in XAML. The BindingContext for the view is usually an instance of the viewmodel.So I think you forget to connect these two elements with BindingContext.
Code behind View:
public partial class MainPage : ContentPage
{
TestViewModel tv = new TestViewModel();
public MainPage()
{
InitializeComponent();
BindingContext = tv;
}
}
Code in Xaml:
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<!--This works-->
<Entry Text="{Binding Test.Property1}"/>
<Entry Text="{Binding Test.Property2}"/>
<Entry Text="{Binding Test.Property3}"/>
<!--This works too-->
<ListView
HasUnevenRows="True"
ItemsSource="{Binding ListModel}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<VerticalStackLayout>
<Label Text="{Binding ListProp1}"/>
<Label Text="{Binding ListProp2}"/>
<Label Text="{Binding ListProp3}"/>
</VerticalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</VerticalStackLayout>
</ScrollView>
Outcome:
Reference link.
I was able to fix the error while also keeping the compiled bindings in place by making the ItemSource bind to a List with a private backing field. It seems like the compiler is unable to resolve the list correctly without the private property being there. After i added listModel it compiled. So the issue seems to be that the setter is missing.
private List<TestListModel> listModel;
public List<TestListModel> ListModel { get => listModel; set => listModel = value; }
i´ve been able to generate and show a SQList database and now i wanna filter it with a Search Bar, i´ve been reading many tutorials, but i´m not able to make it work...
I have an SQLite Model with this code:
using SQLite;
namespace AppListo
{
public class Employee
{
[PrimaryKey, AutoIncrement]
public long EmpId
{ get; set; }
[NotNull]
public string EmpName
{ get; set; }
}
}
...and i retrieve a datalist from the database with:
public List<Employee> GetAllEmployees()
{
return dbConn.Query<Employee>("Select * From [Employee]");
}
...and i show the list in a ContentPage with this xaml code:
<ListView x:Name="lstData" HasUnevenRows="false" Header="Header Value" Footer="Footer" ItemSelected="OnSelection" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Padding="5,5,5,5">
<Label Text="{Binding EmpName}" FontSize="Medium" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
...and this code behind:
var vList = App.DAUtil.GetAllEmployees();
lstData.ItemsSource = vList;
...but i don´t know how to filter this list with a searchbar.
Could you help me to achieve it?
Thanks in advance
use LINQ
var vList = App.DAUtil.GetAllEmployees();
lstData.ItemsSource = vList.Where(e => e.Name.Contains(mySearchString));
Create a property on your View Model like
public ObservableCollection<Employee> Employees { get; set; }
<ListView x:Name="lstData" HasUnevenRows="false" ItemsSource="{Binding Employees}" Header="Header Value" Footer="Footer" ItemSelected="OnSelection" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Padding="5,5,5,5">
<Label Text="{Binding EmpName}" FontSize="Medium" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Then create a method to bind your dataset values to your list view
private async Task BindEmployees()
{
List<Employee> employees = await GetAllEmployees();
Employees = new ObservableCollection<Employee>(
vList.Where(e => e.Name.Contains(mySearchString)).
OrderBy(x => x.EmpName).ToList());
await RaisePropertyChanged("Employees");
}
Call this method on page load or inside your View Model's constructor
Task.Run(async() => {
await BindEmployees();
});
Also, create a Handler method which will be called every time your enter any thing on search bar
and again call BindEmployees method inside that handler function
FilterEmployee_Handler(TextChangedEventArgs args) {
await BindEmployees(),
}
I'm trying to handle something similar (from UI perspective), to:
in order to invoke two different business logics for:
tapping at ViewCell element itself (inside ListView) - in example navigate to different page
tapping at Label element (Clickable Label), which is inside given ViewCell element - in example delete given object or smth else
I would like to have whole "tapping" logic inside page ViewModel.
Based on Xamarin forum proposes, I'm able to invoke some logic of "tapping" my delete action from cell, however directly inside my data model - which in my PoV is not good solution, as I would like to manipulate my List collection (so the most preferable way, would be to have this logic at page ViewModel).
What I have right now:
My page View XAML code looks like:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="App.Views.TestView">
<ContentPage.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<ListView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" ItemsSource="{Binding MyItemsCollection}" SelectedItem="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<!-- Name Label -->
<Label Text="{Binding Name}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
<!-- Delete "Icon" -->
<Label Text="Clickable Label" VerticalOptions="CenterAndExpand" HorizontalOptions="EndAndExpand">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OnClickableLabel}" CommandParameter="{Binding .}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
My page View C# code looks like (not specific code there, except binding **BindingContext* to page ViewModel):
public partial class TestView : ContentPage
{
public TestView()
{
InitializeComponent();
BindingContext = ServiceLocator.Current.GetInstance<TestViewModel>();
}
}
My page ViewModel C# code looks like:
public class TestViewModel : ViewModelBase
{
public TestViewModel()
{
MyItemsCollection = GetMyItemsCollection();
}
private List<MyItem> GetMyItemsCollection()
{
return new List<MyItem>
{
new MyItem
{
ID = 1L,
Name = "Item 1 Name"
},
new MyItem
{
ID = 2L,
Name = "Item 2 Name"
},
new MyItem
{
ID = 3L,
Name = "Item 3 Name"
}
};
}
private List<MyItem> _myItemsCollection { get; set; }
public List<MyItem> MyItemsCollection
{
get
{
return _myItemsCollection;
}
set
{
_myItemsCollection = value;
RaisePropertyChanged();
}
}
private MyItem _SelectedItem { get; set; }
public MyItem SelectedItem
{
get
{
return _SelectedItem;
}
set
{
if (_SelectedItem != value)
{
_SelectedItem = value;
RaisePropertyChanged();
Debug.WriteLine("SelectedItem: " + _SelectedItem.Name);
}
}
}
private RelayCommand<object> _OnClickableLabel;
public RelayCommand<object> OnClickableLabel
{
get { return _OnClickableLabel ?? (_OnClickableLabel = new RelayCommand<object>((currentObject) => Test(currentObject))); }
}
private void Test(object currentObject)
{
Debug.WriteLine("This should work... but it's not working :(");
}
}
My data model code looks like:
public class MyItem
{
public long ID { get; set; }
public string Name { get; set; }
private RelayCommand<object> _OnClickableLabel;
public RelayCommand<object> OnClickableLabel
{
get { return _OnClickableLabel ?? (_OnClickableLabel = new RelayCommand<object>((currentObject) => Test(currentObject))); }
}
private void Test(object currentObject)
{
Debug.WriteLine("This works... but it's not good idea, to have it here...");
}
}
Any idea what needs to be changed, in order to invoke OnClickableLabel directly inside my page ViewModel ?
I know, that it's something wrong at:
<TapGestureRecognizer Command="{Binding OnClickableLabel}" CommandParameter="{Binding .}" />
but don't know what :/.
Help! Thanks a lot.
Ok, I found solution, by extending XAML code:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="App.Views.TestView" x:Name="Page">
<ContentPage.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<ListView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" ItemsSource="{Binding MyItemsCollection}" SelectedItem="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<!-- Name Label -->
<Label Text="{Binding Name}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
<!-- Delete "Icon" -->
<Label Text="Clickable Label" VerticalOptions="CenterAndExpand" HorizontalOptions="EndAndExpand">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.OnClickableLabel, Source={x:Reference Page}}" CommandParameter="{Binding .}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
After that, I got OnClickableLabel command invoked inside my page ViewModel, as expected :).
If someone know "better" solution (better from XAML code point of view), I would like to see it ;).
Thanks a lot everyone!
continuing with what #Namek said i would suggest to get the object of list view item first and then call the command or viewmodel method.
for more you can refer my blog post about interactive Listview at https://adityadeshpandeadi.wordpress.com/2018/07/15/the-more-interactive-listview/
feel free to drop by. :)
We have a custom ListView which has an ItemClickCommand property. We are binding this in a XAML file using the local keyword. It's working fine for Xamarin.Forms Android, but its not clickable in Xamarin.forms iOS.
Please suggest how to proceed or what we are doing wrong in code.
Code is written below:
XAML file:
<StackLayout x:Class="XYZ"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XYZ.UI;assembly=XYZ">
<local:ListView ItemClickCommand="{Binding OnDataSelectionCommand, Mode=TwoWay}" ItemsSource="{Binding DataList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label FontSize="17"
HorizontalOptions="Start"
Text="{Binding }"
TextColor="Black"
VerticalOptions="Center" />
<Label FontSize="17"
HorizontalOptions="Start"
Text=" , "
TextColor="Black"
VerticalOptions="Center" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</local:ListView>
View Model :
private Command _onDataSelectionCommand;
public Command OnDataSelectionCommand
{
get
{
return _onDataSelectionCommand ?? (_onDataSelectionCommand = new Command(async (s) =>
{
// TODO: get selected item from list !!
}));
}
}
Custom List Created :
using System.Windows.Input;
using Xamarin.Forms;
namespace XYZ.UI
{
public class ListView : Xamarin.Forms.ListView
{
public static BindableProperty ItemClickCommandProperty = BindableProperty.Create<ListView, ICommand>(x => x.ItemClickCommand, null);
public ListView()
{
this.ItemTapped += this.OnItemTapped;
}
public ICommand ItemClickCommand
{
get { return (ICommand)this.GetValue(ItemClickCommandProperty); }
set { this.SetValue(ItemClickCommandProperty, value); }
}
private void OnItemTapped(object sender, ItemTappedEventArgs e)
{
if (e.Item != null && this.ItemClickCommand != null && this.ItemClickCommand.CanExecute(e))
{
this.ItemClickCommand.Execute(e.Item);
}
}
}
You have to use the SelectedItem property of ListView to Bind. This will give you the object (item) of the collection you bound to the list.
Sample project can be found here.
Also please note that ItemTapped is not same as ItemSelected.
If you are interested in Binding a Command to ListView Item Tapped then please have a look at this forum post about the same - How to bind a Command to ListView.ItemTapped.