Data Bound ListView - DataTemplate Delete Command and Command parameter - c#

I am using Xamarin.Forms, and I'm looking for a way to have a databound ListView with custom cells, and in those custom cells, there is a Delete button that is bound to a Command in my ViewModel.
Here is my code...
<ListView ItemsSource="{Binding SelectedServiceTypes}" IsVisible="{Binding ShowSelectedServiceTypes}" HeightRequest="110" RowHeight="30">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal" HeightRequest="30">
<Label Text="{Binding ServiceCode}" HeightRequest="30" />
<Label Text="-" HeightRequest="30" />
<Label Text="{Binding ServiceDescription}" HeightRequest="30" />
<Button
Text="Delete"
HeightRequest="30"
HorizontalOptions="End"
VerticalOptions="Start"
Command="{Binding DeleteServiceTypeCommand}"
CommandParameter="{Binding ID}" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The ListView is bound to an ObservableCollection. I want the delete button to use a Command and pass a CommandParameter to my View Model.
private Command _deleteServiceTypeCommand;
public Command DeleteServiceTypeCommand
{
get
{
if (_deleteServiceTypeCommand == null)
_deleteServiceTypeCommand = new Command<string> ((serviceTypeID) => RemoveServiceType (serviceTypeID) );
return _deleteServiceTypeCommand;
}
}
I wasn't sure if this is possible in Xamarin.Forms. It's almost like the cell needs to have two BindingContext, one being the element in the collection(to get the ID value) and the other being the View Model(to have the Command work).
Any ideas?

So after more research, I was able to find the nuget package Xamarin.Forms.Behaviors which adds something similar to RelativeSource binding.
https://github.com/corradocavalli/Xamarin.Forms.Behaviors
It's implementation example can be found here.
http://codeworks.it/blog/?p=216

Related

Xamarin Forms Bind command outside ItemSource

I have a problem. I created this ListView:
<ListView ItemsSource="{Binding knownDeviceList}" SelectionMode="None" RowHeight="90" ItemTapped="device_Clicked">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Command="{Binding DeleteDevice}"
CommandParameter="{Binding Id}"
Text="Delete" IsDestructive="True" />
</ViewCell.ContextActions>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The ListView is bound to a List with objects in it, but I need to bind the command to an ICommand outside the List on root level of the ViewModel. How can I do that, because now the ICommand doesn't get triggered when I try to remove an item from the List!
Here is my Command in my ViewModel:
public ICommand DeleteDevice
{
get
{
return new Command<int>((x) => RemoveDevice_Handler(x));
}
}
What am I doing wrong?
Your MenuItem.BindingContext is scoped to the actual item in that cell, not the view model of the whole page (or ListView). You will either need to tell the binding that it needs to looks else where, like this:
<ListView x:Name="MyListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Command="{Binding Path=BindingContext.DeleteDevice, Source={x:Reference MyListView}}}"/>
</ViewCell.ContextActions>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Note that I removed the attributes that you have in there just to make clear which ones I added. You can keep them in, this is just for readability.
Or, you can use the new Relative Bindings. Then you would implement the command binding like this:
Command="{Binding Source={RelativeSource AncestorType={x:Type local:YourViewModelClass}}, Path=DeleteDevice}"
The Context that you are trying to bind with from the Command is not the Page's one but the ItemSource, This is why you can simply bind the Id to the CommandParameter.
To fix this, you need to target the page's BindingContext since your Command lives at the ViewModel root level. You can achieve it by adding a x:Name property to your ListView and target the right Context through it.
Here the fix:
<ListView x:Name="listView" ItemsSource="{Binding knownDeviceList}" SelectionMode="None" RowHeight="90" ItemTapped="device_Clicked">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Command = "{Binding BindingContext.DeleteDevice, Source={x:Reference listView}}"
CommandParameter="{Binding Id}"
Text="Delete" IsDestructive="True" />
</ViewCell.ContextActions>
</ViewCell>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
Hope it helps & happy coding!

Why is my Picker's Focused+Unfocused event firing when an Editor is tapped in the same ViewCell?

Using Xamarin.Forms. I have a ListView containing multiple ViewCells and in each one is a Picker and an Editor. Tapping the Editor fires Editor_Focused, but for some reason also fires Picker_Focused and Picker_Unfocused. I use the Picker_Unfocused event handler to add some data to a SQLite database and then refresh the ListView, so I can't have it firing every time the Editor is tapped. Following along with breakpoints, my refresher method is called twice, because Picker_Unfocused is also called twice for unknown reaons. Also worth noting is that Editor_Completed won't fire no matter what I do.
Things I have tried:
Adding a TapGestureRecognizer to the Editor which would set a property, which would then stop Picker_Unfocused from continuing its execution, but the TapGestureRecognizer never fires when the Editor is tapped.
Assigning a value to a property when Editor_Focused is fired that would do the same as above. Editor_Focused does fire first, but then leaves that property in the "don't fire Picker_Unfocused" state even when the Picker actually needs to be used. Countering that by setting the property back to "do fire" state in Picker_Focused doesn't work, as the order goes Editor_Focused -> Picker_Focused -> Picker_Unfocused -> Refresh list method.
Update: Forgot to include any code.
ListView defined in XAML.
<ListView x:Name="ListView"
HasUnevenRows="True"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Margin="0,5,0,0">
<Label Text="{Binding Prop0}"
TextColor="DodgerBlue"
VerticalOptions="Center"/>
<Label Text="{Binding Prop1}"
TextColor="Black"
HorizontalOptions="StartAndExpand"
VerticalOptions="Center"/>
<Label Text="Task Time:" TextColor="Black" HorizontalOptions="End"
VerticalOptions="Center"/>
<Picker x:Name="Picker" WidthRequest="80"
Title="Enter time"
FontSize="15"
Focused="Picker_Focused"
Unfocused="Picker_Unfocused"
SelectedItem="{Binding Prop2}">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>0.25</x:String>
<x:String>0.50</x:String>
<x:String>0.75</x:String>
<x:String>1.00</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
</StackLayout>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
IsVisible="{Binding Prop3}">
<Label Text="Comments:" TextColor="Black" VerticalOptions="Center"/>
<Editor x:Name="Editor" Text="{Binding Prop4}" TextColor="Black"
VerticalOptions="Center" HorizontalOptions="StartAndExpand" Focused="Editor_Focused"/>
</StackLayout>
</StackLayout>
<ViewCell.ContextActions>
<MenuItem Text="Delete"
x:Name="DeleteButton"
Clicked="DeleteButton_Clicked"
IsDestructive="True"
CommandParameter="{Binding .}"/>
</ViewCell.ContextActions>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Xamarin Listview with grouping won't scroll

I'm working on my first Xamarin app. I want to make a listview with grouping and it succeeded. The only problem I have that it won't scroll. My other listview on another page scrolls without a problem, but my listview with grouping won't do that. I tried this both on an Android simulator as on my Android phone (I don't have a macbook or something to test on iOS), and it won't scroll on either of them. I looked it up but a lot of people put the listview in a scrollview, and I didn't do that.
This is my XAML code:
<StackLayout Margin="10" Orientation="Vertical">
<Label Text="{Binding Title}" FontAttributes="Bold"
FontSize="20" HorizontalOptions="CenterAndExpand" />
<Label Text="{Binding Subtitle}" FontAttributes="Italic"
FontSize="15" HorizontalOptions="CenterAndExpand" />
<ListView HasUnevenRows="True" SeparatorColor="Accent"
VerticalOptions="FillAndExpand" IsEnabled="False"
IsGroupingEnabled="True" GroupDisplayBinding="{Binding Key}"
ItemsSource="{Binding ScansGroup}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Spacing="4">
<StackLayout Orientation="Horizontal" Margin="10,7,10,1">
<Label Text="{Binding Location, StringFormat='{0}'}"
FontAttributes="Bold" FontSize="16" />
<Label Text="{Binding DateTime, StringFormat='{0:dd/MM/y HH:mm}'}"
HorizontalOptions="EndAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding ElapsedTimeOnLocation}"
HorizontalOptions="Start"
Margin="10,0,10,7" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
I put it in an MVVM structure, and the grouping I did with a MVVM helper that I got from here https://channel9.msdn.com/Shows/XamarinShow/The-Xamarin-Show-12-MVVM-Helpers .
My code behind that is meant for the grouping is the following:
public ObservableRangeCollection<Grouping<string, ScanViewModel>> ScansGroup { get; } =
new ObservableRangeCollection<Grouping<string, ScanViewModel>>();
void Group()
{
var grouped = from scan in Scans
group scan by scan.Day into scanGroup
select new Grouping<string, ScanViewModel>(scanGroup.Key, scanGroup);
ScansGroup.ReplaceRange(grouped);
}
The grouping shows perfectly and the list too. The only problem is that
I can't scroll. Can someone help me?
I've figured it out. There's nothing wrong with your grouping. In your code you set IsEnabled="False" to the ListView. It makes the listview's scroll ability to be handled just if you drag from an enabled item inside the ListView, and even thus, the scroll is like belittled and with a very bad user experience.
Just set your ListView like this:
<ListView HasUnevenRows="True"
SeparatorColor="Accent"
VerticalOptions="FillAndExpand"
IsEnabled="True"
IsGroupingEnabled="True"
GroupDisplayBinding="{Binding Key}"
ItemsSource="{Binding ScansGroup}">
...
</ListView>
I can't tell you if it was designed to be this way, or if it 's a bug, but it is the cause of your issue.
It's probable you have done this to handle some unwanted behavior. If so, let us know what is specifically your intend with this IsEnabled="False", then we'll be able to help you with this.
I hope it helps you.
You didn't add the template for the group. Add this in the <ListView>
<ListView.GroupHeaderTemplate >
<DataTemplate >
<ViewCell Height="28" >
<Label Text="{Binding GroupName}" VerticalTextAlignment="Center" HorizontalOptions="Start" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
Also, in the code bend or ViewModel, you need to bind to a group class of the items you want in the list such as:
public class GroupDetails : ObservableCollection<SomeItemsToShow>
{
public string GroupName { get; set; }
}
Check out this Example for more details:
https://xamarinhelp.com/xamarin-forms-listview-grouping/

Xamarin Forms ListView does not update correctly

I'm working with Xamarin Forms and MVVMLight.
So I'm displaing a List of "Patients" with a ListView. Each Element of the ListView has a Delete-Button. When clicked the Patient is deleted from the List and the View is notified with RaisePropertyChanged().
The Problem now is that when I add multiple Patients and then want to delete one. Always the last one is removed in the View. I debugged the App and saw that the right one is delted from my List. But when the View updates its data, always the last Entry is removed.
<ListView ItemsSource="{Binding Patients}" HasUnevenRows="true" x:Name="Patientlist">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal" VerticalOptions="FillAndExpand">
<StackLayout HorizontalOptions="FillAndExpand">
<Label Text="Patient" Style="{DynamicResource TitleStyle}"/>
<Label Text="Benötigte Proben"/>
<Label Text="Untersuchungen"/>
<Button Text="Bearbeiten" />
</StackLayout>
<StackLayout HorizontalOptions="FillAndExpand">
<Label Text="{Binding PatientID}"/>
<Label Text="Test"/>
<Label Text="Test"/>
<Button Text="Löschen" Command="{Binding DelCMD}"/>
</StackLayout>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
So this is my ListView. The Button "Löschen" ist the delete-Button. It executes the following Command:
private RelayCommand delCMD;
public RelayCommand DelCMD
{
get
{
return delCMD ?? (delCMD = new RelayCommand (()=>Messenger.Default.Send<PatientM> (this, "delPat")));
}
}
This code is in my PatientModel-Class. And sends a Message to the VM where the Element is removed.
public void delPatient(PatientM Patient)
{
Patients.Remove (Patient);
RaisePropertyChanged ("Patientlist");
}
This code is then executed in the VM. An well like I said the right Patient is removed from the List (Patients.Remove). But after RaisePropertyChanged is called the wrong is Element is delted in the View. Always the last one even tough I deleted another on in the List.

How do I bind a observable list of name|values in Forms?

I am trying to bind a observable list containing a simple object with name and value to display in list or stack layout inside a Xamarin.Forms application.
The listView for whatever reason did not allow me to edit the content.
<ListView x:Name="lstEstablishmentType" Grid.Row="0" ItemsSource="{Binding States}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Name}"/>
<Entry Text="{Binding Value}" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I am not sure what I am doing wrong, things are displayed but I can't change value.
I have figured it out...
The version of Xamarin.Forms that comes with the project by default has a bug decribed in the link below
https://bugzilla.xamarin.com/show_bug.cgi?id=25948#c5
By updating the package to v1.4 the problem went away.

Categories

Resources