MVVM - How ListView ItemClick Execute RelayCommand with Bindig - c#

I would like to add a command to my listview. When an item is clicked I would like to execute my relayCommand.
Now I have these solution, it works, but I think it is not so MVVM :)
<ListView ItemsSource="{Binding Taxons}" IsItemClickEnabled="True" ItemClick="ListViewBase_OnItemClick">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code Behind:
private void ListViewBase_OnItemClick(object sender, ItemClickEventArgs e)
{
viewModel.TreeItemSelected.Execute(((Taxon)e.ClickedItem).Name);
}
I would like to use this way without any code behind, but it is not possible as the VS tells me:
<ListView ItemsSource="{Binding Taxons}" IsItemClickEnabled="True" ItemClick="{Binding TreeItemSelected}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I use the following extra dll-s, but i dont want to install anything else if it possible.
GalaSoft.MvvmLight.Extras.Win8
GalaSoft.MvvmLight.Win8
Can you suggest a solution to me?

Here's a working example :
<ListBox x:Name="name_here"
ItemsSource="{Binding your_item_source}"
SelectedItem="{Binding your_selected_item, UpdateSourceTrigger=PropertyChanged}"
...
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<Command:EventToCommand Command="{Binding your_command_here}"
CommandParameter="{Binding ElementName=name_here, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
It make use of Blend's Interaction triggers (you don't have to install blend, the dll is enough if you don't have it).
also, if you're using the CommandParameter, bind the ElementName to the name of the control (in this example it's name_here )

For those who have the same problem, I would like to say that there is another way:
You can define a TapGestureRecognizer for the ListView items and bind it to a command in your ViewModel, as I'm showing here :
<StackLayout x:Name="StackLayout1" Orientation="Vertical" HorizontalOptions="StartAndExpand" VerticalOptions="FillAndExpand">
<StackLayout.BindingContext>
<viewModels:NotificationsViewModel />
</StackLayout.BindingContext>
<ListView ItemsSource="{Binding NotificationsList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical">
<StackLayout.GestureRecognizers> <!-- here you bind the TAP event to your ViewModel -->
<TapGestureRecognizer Command="{Binding Path=BindingContext.OnListItemTapped, Source={x:Reference StackLayout1}}"
CommandParameter="{Binding}"/>
</StackLayout.GestureRecognizers>
<RelativeLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Image x:Name="IsReadImage" StyleId="IsReadImage" WidthRequest="{StaticResource OptionImageSize}"
Source='{Binding IsRead, Converter={StaticResource BooleanToValueConverter}, ConverterParameter="Resources.emailOpenIcon,Resources.emailIcon"}'
RelativeLayout.YConstraint="{ConstraintExpression Type=Constant, Constant=0}"
RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height, Factor=0.8, Constant=0}"></Image>
<Label Text="{Binding Title}" x:Name="TitleLabel" HorizontalTextAlignment="Start" FontAttributes='{Binding IsRead, Converter={StaticResource BooleanToValueConverter}, ConverterParameter="Bold?!"}'
RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToView, ElementName=IsReadImage, Property=Width, Constant=5}"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView, ElementName=IsReadImage, Property=Y, Constant=3}" /></RelativeLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
And then you can define your command in the page's ViewModel
public ICommand OnListItemTapped
{
get
{
return new Command<Notification>(item =>
{
Debug.WriteLine(item.Title + " Cliked!");
});
}
}

You can accomplish this two ways.
One would be to bind the list views SelectedItem to a property on your viewmodel. (maybe call that property SelectedTaxon), then in the property setter you could take whatever action you wish on the viewmodel.
The other way, if you are really tied to executing a command on your viewmodel. You can use the mvvmlight EventToCommand behavior.
You can find info about that via Google or at this link is a description of its use. http://adammills.wordpress.com/2011/02/14/eventtocommand-action-mvvm-glue/
I believe that you already have that behavior as part of the mvvmlight extras dll you have.

Related

MAUI ListView gets app to crash/is buggy on Android when scrolling

I am developing the prototype for a mobile application in MAUI, just for Android right now. The ListView is not working as expected for me though. Everything is fine when the number of items fit the screen, but the moment there is scrolling involved, the app crashes. The underlying Android error seems to be:
java.lang.IllegalStateException: The specified child already has a parent.
If I scroll very slowly, the issue does not seem to occur. Here's the ListView:
<ListView ItemsSource="{Binding Devices}"
Style="{StaticResource ListViewSimpleStyle}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:DataType="local:DeviceEntryModel">
<RadioButton IsChecked="{Binding IsChecked, Mode=TwoWay}"
GroupName="Devices">
<RadioButton.Content>
<Label BindingContext="{Binding Source={RelativeSource AncestorType={x:Type RadioButton}}, Path=BindingContext}"
Text="{Binding Name}"
IsEnabled="{Binding Source={RelativeSource AncestorType={x:Type RadioButton}}, Path=IsEnabled}" />
</RadioButton.Content>
</RadioButton>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The item source is an ObservableCollection<DeviceEntry>:
public partial class DeviceEntryModel : ObservableObject
{
public DeviceEntry DeviceEntry { get; init; }
public string Name { get => DeviceEntry.Name; }
public string Address { get => DeviceEntry.Address; }
[ObservableProperty]
bool isChecked;
}
What am I doing wrong?
One thing I have tried that does fix the crashing is to set the ListView CachingStrategy to "RecycleElementAndDataTemplate". The problem with that is, if a radio button is checked and goes out of view while scrolling, it loses its state and becomes unchecked. There are also various other bugs, like the list item text disappearing.
I fixed all the issues, wrap the whole ListView XAML in a ScrollView.
<ScrollView>
<ListView ItemsSource="{Binding Devices}"
Style="{StaticResource ListViewSimpleStyle}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:DataType="local:DeviceEntryModel">
<RadioButton IsChecked="{Binding IsChecked, Mode=TwoWay}"
GroupName="Devices">
<RadioButton.Content>
<Label BindingContext="{Binding Source={RelativeSource AncestorType={x:Type RadioButton}}, Path=BindingContext}"
Text="{Binding Name}"
IsEnabled="{Binding Source={RelativeSource AncestorType={x:Type RadioButton}}, Path=IsEnabled}" />
</RadioButton.Content>
</RadioButton>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollView>
#something5143
I don't known the solution but on the .NET MAUI documentation
Warning
ScrollView objects should not be nested. In addition, ScrollView
objects should not be nested with other controls that provide
scrolling, such as CollectionView, ListView, and WebView.

Change a button's image on tap of other button inside a list view, view cell

I have a listview. This list view has 5 rows with two buttons namely A & B. When I tap on button A on a row, I want to change the image on A as well as B on the same row and vice-versa. I am able to individually tap and change the image on the same button but don't know how to change the image on the other button. Here is my listview:
<ListView x:Name="GroupedView" SeparatorColor="Transparent" GroupDisplayBinding="{Binding Title}" IsGroupingEnabled="true" HasUnevenRows="true" >
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Padding="5" BackgroundColor="#E2F5F9">
<Label Text="{Binding Title}" TextColor="{StaticResource NavyBlue}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Spacing="2" Padding="5">
<StackLayout VerticalOptions="FillAndExpand">
<Label Text="{Binding QuestionName}" />
</StackLayout>
<StackLayout IsVisible="{Binding ShowYesNo}" Spacing="15" Orientation="Horizontal" HorizontalOptions="End">
<Button ClassId="Yes" Clicked="ChoiceSelected" CommandParameter="{Binding question_id}" Image="{Binding YesChoiceImg}" />
<Button ClassId="No" Clicked="ChoiceSelected" CommandParameter="{Binding question_id}" Image="{Binding NoChoiceImg}" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I am then using the sender to identify the class ID and change the image of the button.
Should I be using a command? Should I be doing something else? Please help. Thanks
If you want to use the CommandParameter you should be using a Command. At the moment you're mixing 2 different ways to handle the click.
One thing to note though is that if you use a command you usually want to bind it to a Command that is defined on your ViewModel but since inside the DataTemplate your BindingContext is the ListView item instead of the ViewModel so you have to reference around that. Something like this should work:
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Spacing="2" Padding="5">
<StackLayout VerticalOptions="FillAndExpand">
<Label Text="{Binding QuestionName}" />
</StackLayout>
<StackLayout IsVisible="{Binding ShowYesNo}" Spacing="15" Orientation="Horizontal" HorizontalOptions="End">
<Button ClassId="Yes" Command="{Binding Path=BindingContext.ButtonCommand, Source={x:Reference MyQuestionPage}}" CommandParameter="{Binding .} Image="{Binding YesChoiceImg}" />
<Button ClassId="No" Clicked="ChoiceSelected" CommandParameter="{Binding question_id}" Image="{Binding NoChoiceImg}" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
Note here that you have to give your ContentPage an x:Name (so you can reference it and call the BindingContext on it). And the "{Binding .}" binds to the current list item. So no need to search for it on id you can just plug it into the command directly!

How set the item in ContextActions Xamarin MVVM

How set the item in ContextActions. In my example I want to set default car.
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Ustaw domyslny" Command="{Binding DefultCarCommand}" CommandParameter="{Binding .}"/>
</ViewCell.ContextActions>
<StackLayout>
<Label Text="{Binding RegistrationNumber}" />
<Label Text="{Binding Brand}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
In ViewModel I use DelegateCommand
public DelegateCommand DefultCarCommand { get; private set; }
Now it does not work and I do not know why
Please note BindingContext of MenuItem is the Single Model that you bind in ItemSource of ListView not the whole ViewModel. You need to use reference binding to bind to ViewModel properties like this:
<ListView x:Name="MyListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Ustaw domyslny" Command="{Binding Path=BindingContext.DefultCarCommand, Source={x:Reference Name=MyListView}}" CommandParameter="{Binding .}"/>
</ViewCell.ContextActions>
<StackLayout>
<Label Text="{Binding RegistrationNumber}" />
<Label Text="{Binding Brand}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

How can i bind a button in listviewitem to a Command in ViewModel in Winrt

I have a NavigateToAccountsCommand RelayCommand property in the ViewModel. When I bind the same to a button on the page anywhere outside the ListView the command binding is working. However as soon as I move this to a ListView's DataTemplate its not working.
I have tried changing the binding from NavigateToAccountsCommand to DataContext.NavigateToAccountsCommand still not working.
Appreciate your help...
<Page
x:Class="FinancePRO.App.Views.AccountsView"
DataContext="{Binding AccountsViewModel, Source={StaticResource MainViewModelLocator}}"
mc:Ignorable="d">
<Grid>
<!--**This one is working**-->
<Button Command="{Binding NavigateToAccountsCommand}" >
<!--**This one is not working**-->
<ListView ItemsSource="{Binding AllAccounts}" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Stretch">
<TextBlock Text="{Binding AccountName}"/>
<Button Command="{Binding NavigateToAccountsCommand}">
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When you are inside the DataTemplate of the ListView, your data context is the current item of the ListView's ItemsSource. As there is nothing called "NavigateToAccountsCommand" property within your AllAcounts' each individual element, binding isn't working.
To fix that, you will need to reference something from the outside of DataTemplate; following should work. It changes the binding to reference the root Grid's DataContext which should have the property NavigateToAccountsCommand accessible. To reference the grid, you have to add Name attribute, and then use ElementName binding.
<Grid Name="Root">
<!--**This one is working**-->
<Button Command="{Binding NavigateToAccountsCommand}" >
<!--**This one is not working**-->
<ListView ItemsSource="{Binding AllAccounts}" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Stretch">
<TextBlock Text="{Binding AccountName}"/>
<Button Command"{Binding ElementName=Root, Path=DataContext.NavigateToAccountsCommand}">
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
You can use
<Button x:Name="cmdTabItemCloseButton"
Style="{StaticResource TabItemCloseButtonStyle}"
Grid.Column="1" Margin="15,0,0,0"
Command="{Binding RelativeSource=
{RelativeSource FindAncestor,
AncestorType={x:Type ListView}},
Path=DataContext.NavigateToAccountsCommand}"
CommandParameter="{Binding}"/>
I had a similar problem (Win RT) which I solved by just using:
<GridView
x:Name="itemGridView"
ItemClick="ItemView_ItemClick"
IsItemClickEnabled="True"/>
and then in the Page class:
private void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
//e is the object being clicked
}

Is it possible to access a property from the control's view model from a listview item template?

I am creating a feature where as you navigate through menu items you are given breadcrumbs to go back. In order to create this in my Windows 8 app, I am generating a collection of items and adding on to that collection as I navigate through the menu.
The xaml code for displaying the breadcrumbs is:
<ListView VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="120,60,0,0"
ItemsSource="{Binding Parents}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text=">>" />
<Button Content="{Binding Name}" Command="{Binding OpenCommand}" CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
While this works, I am not 100% happy with the bindings of the button. The problem is I am invoking functionality that is already up and running on my overall control's view model, and this seems to require me to have an OpenCommand property on my inner item command.
Is it possible to bind my button's Command= attribute to a command on the control's overall view model, instead of the list item itself?
It should be possible by something like this:
<ListView x:Name="listView" ...>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text=">>" />
<Button Content="{Binding Name}"
Command="{Binding DataContext.OpenCommand, ElementName=listView}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
...
</ListView>

Categories