I have a xaml view with a CollectionView. The ItemSource is set to a list initiated in the xaml.cs class. The xaml view can not find the binding property "Id". If I remove id from the model I get the same error with "ListName".
Binding: Property "Id" not found on
"TestApp.Shared.Items.ViewModels.MainViewModel".
I have searched around and found this. But the fix does not solve my problem. Does anybody have an idea what the problem might be?
I am using:
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0"/>
The code:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestApp.Shared.Items.MainPage"
xmlns:viewmodel="clr-namespace:TestApp.Shared.Items.ViewModels"
x:DataType="viewmodel:MainViewModel">
<CollectionView
x:Name="CollectionOfListNames"
ItemsSource="{Binding ListNames}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid
x:DataType="viewmodel:MainViewModel"
Padding="0,5">
<Grid x:DataType="viewmodel:ListModel" ColumnDefinitions="2" Padding="0,5">
<Label Grid.Column="0" Text="{Binding Id}" />
<Label Grid.Column="1" Text="{Binding ListName}" />
</Grid>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
namespace TestApp.Shared.Items.ViewModels
{
public partial class MainViewModel : ObservableObject
{
[ObservableProperty]
ObservableCollection<ListModel> listNames;
public MainViewModel()
{
ListNames = new ObservableCollection<ListModel>()
{
new ListModel(){ Id = 0, ListName = "Matlista"},
new ListModel(){ Id = 1, ListName = "Räkningar"},
new ListModel(){ Id = 2, ListName = "Att köpa idag"}
};
}
}
}
namespace TestApp.Shared.Items.Models
{
public partial class ListModel : ObservableObject
{
private int id;
public int Id
{
get => id;
set => SetProperty(ref id, value);
}
private string listName;
public string ListName
{
get => listName;
set => SetProperty(ref listName, value);
}
}
}
You will have to set the x:DataType="viewmodel:MainViewModel" also on the Grid inside of the DataTemplate but now for the ListModel object.
So:
<DataTemplate>
<Grid x:DataType="model:ListModel" ColumnDefinitions="2" Padding="0,5">
<Label Grid.Column="0" Text="{Binding Id}" />
<Label Grid.Column="1" Text="{Binding ListName}" />
</Grid>
</DataTemplate>
Note that the ListModel is in a different namespace, so add the right xmlns accordingly.
Right now XAML Compilation will think that all the children are using the MainViewModel and thus it (rightfully) can't find those properties.
Inside of a DataTemplate the scope changes, in your case to ListModel, we need to let XAML compilation know.
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'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
}
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. :)
I'm having issues getting the data binding to work in xaml with ListView. The ListView displays all cells as expected but doesn't display the value for any of the bound variables.
I have my sample data constructed like this:
public class DataItem
{
public int Number { get; set; }
public string Message { get; set; }
public DataItem (int i)
{
this.Number = i + 1;
this.Message = string.Format ("Data Item Hello {0}", i + 1);
}
}
The ListView in xaml is defined this way:
<ListView x:Name="uiList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" BackgroundColor="Silver">
<Label TextColor="Green" Text="Pre Xamarin" />
<Label BackgroundColor="Aqua" TextColor="Red" Text="{Binding Path=Message}" />
<Label TextColor="Green" Text="Hello Xamarin" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have created 200 sample objects and assigned to uiList.ItemsSource. This results in the ListView showing 200 items as expected but it doesn't show the value of the Message property in the DataItem.
I've also tried using {Binding Message} (without Path=) but the result didn't change. I'm sure there is something obvious I'm missing but can't figure it out.
--- UPDATE ---
I have a reliable repro now. Create a new project and add the ListView and population as provided above.
Go to the solution properties for the iOS project, select the iOS Build page and change 'Link behavior' to 'Link All' and change the 'Supported architecture' to 'x86_64'. Now Clean, Build and Run and the binding will not work.
Hey I just tried your code and it worked for me out of the box. I just added my logic to populate a list of DataItem and set it as ItemsSource of the listview. Here I is my code and the result.
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="TestForms.TestListView" xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms">
<ContentPage.Content>
<ListView x:Name="uiList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" BackgroundColor="Silver">
<Label TextColor="Green" Text="Pre Xamarin" />
<Label BackgroundColor="Aqua" TextColor="Red" Text="{Binding Message}" />
<Label TextColor="Green" Text="Hello Xamarin" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
public partial class TestListView : ContentPage
{
public TestListView ()
{
InitializeComponent ();
ObservableCollection<DataItem> employeeList = new ObservableCollection<DataItem>();
for(int i=0;i<100;i++)
employeeList.Add(new DataItem(i));
uiList.ItemsSource = employeeList;
//Mr. Mono will be added to the ListView because it uses an ObservableCollection
}
}
public class DataItem
{
public int Number { get; set; }
public string Message { get; set; }
public DataItem (int i)
{
this.Number = i + 1;
this.Message = string.Format ("Data Item Hello {0}", i + 1);
}
}