I am currently making a note-taking app. I am new so excuse my lack of knowledge.
I am using the default flyout menu template given with changes made to cater for my needs. I am using a SwipeView in my CollectionView so when you swipe on a 'note' the item will delete on execute.
I have the swipe working but I cannot get the item to delete once swiped.
This is my ItemsPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="xamarinMobileTest.Views.ItemsPage"
Title="{Binding Title}"
xmlns:local="clr-namespace:xamarinMobileTest.ViewModels"
xmlns:model="clr-namespace:xamarinMobileTest.Models"
x:Name="BrowseItemsPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add Note" Command="{Binding AddItemCommand}" />
</ContentPage.ToolbarItems>
<ContentPage.BindingContext>
<local:ItemsViewModel/>
</ContentPage.BindingContext>
<RefreshView x:DataType="local:ItemsViewModel" Command="{Binding LoadItemsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<CollectionView x:Name="ItemsListView"
ItemsSource="{Binding Items}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<SwipeView>
<SwipeView.RightItems>
<SwipeItems Mode="Execute">
<SwipeItem Text="Delete"
BackgroundColor="Red"
Command="{Binding DeleteItemCommand}"
CommandParameter="{Binding .}"/>
</SwipeItems>
</SwipeView.RightItems>
<StackLayout Padding="10" x:DataType="model:Item">
<Label Text="{Binding Text}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16"
FontAttributes="Bold"
TextColor="Black"/>
<Label Text="{Binding Description}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemDetailTextStyle}"
FontSize="13"
TextColor="Black"/>
<Label Text="{Binding DueDate}"
Style="{DynamicResource ListItemDetailTextStyle}"
FontSize="13"
TextColor="Black"/>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer
NumberOfTapsRequired="1"
Command="{Binding Source={RelativeSource AncestorType={x:Type local:ItemsViewModel}}, Path=ItemTapped}"
CommandParameter="{Binding .}">
</TapGestureRecognizer>
</StackLayout.GestureRecognizers>
</StackLayout>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</ContentPage>
ItemsViewModel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
using xamarinMobileTest.Models;
using xamarinMobileTest.Views;
namespace xamarinMobileTest.ViewModels
{
public class ItemsViewModel : BaseViewModel
{
private Item _selectedItem;
public ObservableCollection<Item> Items { get; }
public Command LoadItemsCommand { get; }
public Command AddItemCommand { get; }
public Command<Item> ItemTapped { get; }
public Command<Item> DeleteItemCommand { get; }
public ItemsViewModel()
{
Title = "Notes";
Items = new ObservableCollection<Item>();
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
ItemTapped = new Command<Item>(OnItemSelected);
AddItemCommand = new Command(OnAddItem);
DeleteItemCommand = new Command<Item>(OnDeleteItem);
}
async Task ExecuteLoadItemsCommand()
{
IsBusy = true;
try
{
Items.Clear();
var items = await DataStore.GetItemsAsync(true);
foreach (var item in items)
{
Items.Add(item);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
public void OnAppearing()
{
IsBusy = true;
SelectedItem = null;
}
public Item SelectedItem
{
get => _selectedItem;
set
{
SetProperty(ref _selectedItem, value);
OnItemSelected(value);
}
}
private async void OnAddItem(object obj)
{
await Shell.Current.GoToAsync(nameof(NewItemPage));
}
async void OnItemSelected(Item item)
{
if (item == null)
return;
// This will push the ItemDetailPage onto the navigation stack
await Shell.Current.GoToAsync($"{nameof(ItemDetailPage)}?{nameof(ItemDetailViewModel.ItemId)}={item.Id}");
}
private void OnDeleteItem(Item item)
{
Items.Remove(item);
}
}
}
*Other code needed can be found by opening up default flyout menu template
How do I delete an Item on a swipe in the observable collection so that when I delete the Item it automatically is seen that the item is deleted?
Items.Remove(item); does not seem to work (no error, just does not remove the item from the CollectionView) Why is this?
await DataStore.DeleteItemAsync(item); has the same issue of nothing happening when the item is swiped.
Any help is appreciated.
Change your parameter to this
CommandParameter="{Binding Source={RelativeSource Self}, Path=BindingContext}"
the code you have in OnDeleteItem is nonsensical. It should look something like this
private void OnDeleteItem(Item item)
{
Items.Remove(item);
}
Trying same thing with Tabbed page from default project.
Noticed that Command is not running at all not matter what I do. (using Debug)
Try putting delete button in ItemDetailPage.
In ItemDetailPageViewModel put this code.
public Command DeleteCommand { get; set; }
public ItemDetailViewModel()
{
DeleteCommand = new Command(OnDelete);
}
private async void OnDelete()
{
await DataStore.DeleteItemAsync(Id);
await Shell.Current.GoToAsync("..");
}
with ItemDetailPageViewModel you have the Id to delete
ItemDetailPage code here
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XamarinDeleteItem.ViewModels"
x:Class="XamarinDeleteItem.Views.ItemDetailPage"
Title="{Binding Title}">
<ContentPage.BindingContext>
<local:ItemDetailViewModel />
</ContentPage.BindingContext>
<StackLayout Spacing="20" Padding="15">
<Label Text="Text:" FontSize="Medium" />
<Label Text="{Binding Text}" FontSize="Small"/>
<Label Text="Description:" FontSize="Medium" />
<Label Text="{Binding Description}" FontSize="Small"/>
<Button Text="Delete" Command="{Binding DeleteCommand}" />
</StackLayout>
Is SwipeView even supported by Command?
Goto page 221 in this PDF
found this in Xamarin.Forms Documentation. download here
https://opdhsblobprod03.blob.core.windows.net/contents/332e36c8b2484d748610a3dd6b6e98d6/190868cccec033e90406d3cafdb40d3d?skoid=29100048-1fa1-4ada-b0e0-e2aa294fc66a&sktid=975f013f-7f24-47e8-a7d3-abc4752bf346&skt=2022-09-18T13%3A52%3A55Z&ske=2022-09-25T13%3A57%3A55Z&sks=b&skv=2020-10-02&sv=2020-08-04&se=2022-09-20T23%3A36%3A35Z&sr=b&sp=r&sig=0myHYJIb3aEzMUIuCtmXn4VN%2F0MC8gzG1k7%2BQCMZVno%3D
To allow ViewModels to be moreindependent of particular user interface objects but still allow methods to be
called within the ViewModel,a command interfaceexists.This command interface is supported by the following
elements in Xamarin.Forms:
Button
MenuItem
ToolbarItem
SearchBar
TextCell (and hencealso ImageCell )
ListView
TapGestureRecognizer
With theexception of the SearchBar and ListView element, theseelements definetwo properties:
Command of type System.Windows.Input.ICommand
CommandParameter of type Object
The SearchBar defines SearchCommand and SearchCommandParameter properties, whilethe ListView defines a
RefreshCommand property of type ICommand .
The ICommand interface defines two methods and oneevent:
void Execute(object arg)
bool CanExecute(object arg)
event EventHandler CanExecuteChange
Instead use CollectionView Selection with multiple.
use OnCollectionViewSelectionChanged event in codebehind to delete items
<CollectionView ItemsSource="{Binding Monkeys}"
SelectionMode="Multiple"
SelectionChanged="OnCollectionViewSelectionChanged">
...
</CollectionView>
CollectionView collectionView = new CollectionView
{
SelectionMode = SelectionMode.Multiple
};
collectionView.SetBinding(ItemsView.ItemsSourceProperty, "Monkeys");
collectionView.SelectionChanged +=
OnCollectionViewSelectionChanged;
void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var previous = e.PreviousSelection;
var current = e.CurrentSelection;
...
}
Related
When ever I look at my UI the elements function as expected but anything bound to my view model doesn't show up however the binds seem to be working.
This is my view XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Fate.Views.GlobalChatPage"
x:DataType="viewmodels:GlobalChatPageViewModel"
xmlns:viewmodels="clr-namespace:Fate.ViewModels"
xmlns:converters="clr-namespace:Fate.Converters"
BackgroundColor="#242022">
<ContentPage.BindingContext>
<viewmodels:GlobalChatPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<converters:InverseBoolConverter x:Key="InverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<StackLayout Orientation="Horizontal">
<Button x:Name="ConnectButton" Text="Connect" Command="{Binding ConnectCommand}" />
<Button x:Name="DisconnectButton" Text="Disconnect" Command="{Binding DisconnectCommand}" />
<Entry IsReadOnly="False" x:Name="namenameEntry" Text="{Binding Name}" Placeholder="Enter Namename" />
</StackLayout>
<ListView x:Name="MessagesListView" ItemsSource="{Binding Messages}" HasUnevenRows="true" SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0" IsVisible="{Binding IsOwnMessage, Converter={StaticResource InverseBool}}">
<Label Text="{Binding Name}" />
<Frame x:Name="MessageFrame" CornerRadius="10" Padding="10" BackgroundColor="LightGray" HeightRequest="{Binding Source={x:Reference MessageLabel}, Path=Height}" Margin="0,0,0,20">
<Label x:Name="MessageLabel" Text="{Binding Message}"/>
</Frame>
</StackLayout>
<StackLayout Grid.Column="1" IsVisible="{Binding IsOwnMessage}">
<Label Text="{Binding Name}" HorizontalOptions="FillAndExpand" />
<Frame x:Name="MessageFrame2" CornerRadius="10" Padding="10" BackgroundColor="LightGray" HeightRequest="{Binding Source={x:Reference MessageLabel}, Path=Height}" Margin="0,0,0,20" HorizontalOptions="FillAndExpand">
<Label x:Name="MessageLabel2" Text="{Binding Message}" HorizontalOptions="FillAndExpand" />
</Frame>
</StackLayout>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackLayout Orientation="Horizontal">
<Entry IsReadOnly="False" x:Name="MessageEntry" Text="{Binding Message}" HorizontalOptions="FillAndExpand" />
<Button x:Name="SendButton" Text="Send" Command="{Binding SendMessageCommand}" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
And this is my view model:
using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Fate.ViewModels
{
public class GlobalChatPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
private string _message;
private ObservableCollection<string> _messages;
private bool _isConnected;
public bool _IsOwnMessage;
public bool _IsSystemMessage;
public bool IsSystemMessage
{
get
{
return _IsSystemMessage;
}
set
{
_IsSystemMessage = value;
OnPropertyChanged();
}
}
public bool IsOwnMessage
{
get
{
return _IsOwnMessage;
}
set
{
_IsOwnMessage = value;
OnPropertyChanged();
}
}
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged();
}
}
public string Message
{
get
{
return _message;
}
set
{
_message = value;
OnPropertyChanged();
}
}
public ObservableCollection<string> Messages
{
get
{
return _messages;
}
set
{
_messages = value;
OnPropertyChanged();
}
}
public bool IsConnected
{
get
{
return _isConnected;
}
set
{
_isConnected = value;
OnPropertyChanged();
}
}
private HubConnection hubConnection;
public Command SendMessageCommand { get; }
public Command ConnectCommand { get; }
public Command DisconnectCommand { get; }
public GlobalChatPageViewModel()
{
Messages = new ObservableCollection<string>();
SendMessageCommand = new Command(async () => { await SendMessage(Name, Message); });
ConnectCommand = new Command(async () => await Connect());
DisconnectCommand = new Command(async () => await Disconnect());
IsConnected = false;
hubConnection = new HubConnectionBuilder()
.WithUrl($"https://placeholderlink/chatHub")
.Build();
hubConnection.On<string>("JoinChat", (user) =>
{
Messages.Add($"{user} has joined the chat");
IsSystemMessage = true;
IsOwnMessage = false;
});
hubConnection.On<string>("LeaveChat", (user) =>
{
Messages.Add($"{user} has left the chat");
IsSystemMessage = true;
IsOwnMessage = false;
});
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
Messages.Add($"{user}: {message}");
IsSystemMessage = false;
if (Name == user)
{
IsOwnMessage = true;
}
});
}
public async Task Connect()
{
if (IsConnected == false)
{
await hubConnection.StartAsync();
await hubConnection.InvokeAsync("JoinChat", Name);
IsConnected = true;
}
}
public async Task SendMessage(string user, string message)
{
await hubConnection.InvokeAsync("SendMessage", user, message);
}
public async Task Disconnect()
{
if (IsConnected == true)
{
await hubConnection.InvokeAsync("LeaveChat", Name);
await hubConnection.StopAsync();
IsConnected = false;
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And this is my converter:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Xamarin.Forms;
namespace Fate.Converters
{
public class InverseBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool)value;
}
}
}
I was expecting for the text from each message to display on the page, on the left if its from someone else and on the right if its from the user but the text doesn't display at all. I've checked my binding context and I get intellisense and no build errors but it still doesn't display.
Problem 1
You're using compiled bindings by applying the x:DataType property. Therefore, you need to set the correct x:DataType also for any DataTemplate further down in the View hierarchy, because the binding context changes.
<?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="Fate.Views.GlobalChatPage"
x:DataType="viewmodels:GlobalChatPageViewModel"
xmlns:viewmodels="clr-namespace:Fate.ViewModels"
xmlns:converters="clr-namespace:Fate.Converters"
BackgroundColor="#242022">
<ContentPage.BindingContext>
<viewmodels:GlobalChatPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<converters:InverseBoolConverter x:Key="InverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<StackLayout Orientation="Horizontal">
<Button x:Name="ConnectButton" Text="Connect" Command="{Binding ConnectCommand}" />
<Button x:Name="DisconnectButton" Text="Disconnect" Command="{Binding DisconnectCommand}" />
<Entry IsReadOnly="False" x:Name="namenameEntry" Text="{Binding Name}" Placeholder="Enter Namename" />
</StackLayout>
<ListView x:Name="MessagesListView" ItemsSource="{Binding Messages}" HasUnevenRows="true" SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate
x:DataType="YOUR_TYPE_HERE">
<ViewCell>
<!-- ... -->
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackLayout Orientation="Horizontal">
<Entry IsReadOnly="False" x:Name="MessageEntry" Text="{Binding Message}" HorizontalOptions="FillAndExpand" />
<Button x:Name="SendButton" Text="Send" Command="{Binding SendMessageCommand}" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Where it says YOUR_TYPE_HERE, you need to specify the same type as the collection you're passing to the ItemsSource, in your case string (or the XAML equivalent of it, which would be {x:Type x:String}). You can also reset the binding to classic bindings by setting the type on the DataTemplate's x:DataType to {x:Null}.
Problem 2
In your DataTemplate you're binding to properties that don't exist in that context, because your ListView is populated with an ObservableCollection<string>.
If you want to bind to Name from your ViewModel, for example, you'll need to specify a relative binding:
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0">
<Label Text="{Binding Name, Source={RelativeSource AncestorType={x:Type viewmodels:GlobalChatPageViewModel}}" />
<!-- ... -->
</StackLayout>
</Grid>
</ViewCell>
Alternative
A quick fix might be to simply remove the x:DataType on the ContentPage.
Further observations
You're binding to Name multiple times. It's unclear how the two chat participants are managed and where each participant's name and the messages are coming from, especially since there is only a single Name property and a single Messages collection in your ViewModel.
I'm trying to add items to a listview from an Entry that is in a frame of its own though the listview won't update. The entry for reference, is added to the UI using the code behind. Here's the listview:
<ListView ItemsSource="{Binding TodoListItems}" x:Name="todoList" HeightRequest="400">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<FlexLayout JustifyContent="SpaceBetween" Padding="20,0">
<ContentView>
<FlexLayout AlignItems="Center" >
<input:CheckBox IsChecked="{Binding Complete}"
CheckChangedCommand="{Binding Path=BindingContext.CompleteTodoCommand, Source={x:Reference todoList}}"
CommandParameter="{Binding .}"
/>
<Label Text="{Binding TodoText}" Padding="10,0,0,0" FontSize="Large"/>
</FlexLayout>
</ContentView>
<ImageButton Source="trash_icon.png"
Command="{Binding Path=BindingContext.RemoveTodoCommand, Source={x:Reference todoList}}"
CommandParameter="{Binding .}"
Scale="1.2" BackgroundColor="White"
/>
</FlexLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The frame:
<Frame x:Name="bottomSheet" HasShadow="true" CornerRadius="8" Padding="1,4,1,0" BackgroundColor="DarkSlateGray"
RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,Property=Height,Factor=.85,Constant=0}"
RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToParent,Property=Width,Factor=1,Constant=0}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=1,Constant=0}"
>
<StackLayout x:Name="bottomSheetStackLayout">
<Button Text="Add new Todo" x:Name="addNewTodoButton" Clicked="AddNewTodoButton_Clicked"/>
</StackLayout>
</Frame>
The ViewModel:
public ObservableCollection<TodoItem> TodoListItems { get; set; }
public ObservableCollection<TodoItem> CompletedTodoItems { get; set; }
public TodoListViewModel()
{
TodoListItems = new ObservableCollection<TodoItem>();
TodoListItems.Add(new TodoItem("Walk the duggo", false));
TodoListItems.Add(new TodoItem("Do the washing", false));
TodoListItems.Add(new TodoItem("Brush off Cheeto dust", false));
CompletedTodoItems = new ObservableCollection<TodoItem>();
//CompletedTodoItems.Add(new TodoItem("Do the dishes",true));
}
public ICommand AddTodoCommand => new Command(AddTodoItem);
public string NewTodoInputValue { get; set; }
void AddTodoItem()
{
TodoListItems.Add(new TodoItem(NewTodoInputValue, false));
}
And the code behind:
private void AddNewTodoButton_Clicked(object sender, System.EventArgs e)
{
Entry addTodoEntryField = new Entry();
bottomSheetStackLayout.Children.Remove(addNewTodoButton);
bottomSheetStackLayout.Children.Add(addTodoEntryField);
addTodoEntryField.Completed += AddTodoEntryField_Completed;
}
private void AddTodoEntryField_Completed(object sender, EventArgs e)
{
Entry addTodoEntryField = (Entry)sender;
Console.WriteLine("The text in the entry field: "+addTodoEntryField.Text);
addTodoEntryField.BindingContext = ViewModel;
ShowTodoItemsText(ViewModel.TodoListItems);
addTodoEntryField.SetBinding(Entry.TextProperty, "NewTodoInputValue");
Console.WriteLine(value: ViewModel.NewTodoInputValue==null);
ViewModel.AddTodoCommand.Execute(null);
Console.WriteLine(ViewModel.TodoListItems.Count);
//Console.WriteLine(ViewModel.TodoListItems.Last().TodoText==null);
ViewModel.TodoListItems.Add(new TodoItem("Added manually", false));
}
Edit: To clarify, I have a frame with a button, which when clicked is replaced with an Entry, the completed text of which, I want to bind to the TodoListItems observable collection, which is displayed by the listView.
I've checked that there are objects being put into the observable collection. I'm not implementing INotifyPropertyChanged on the ViewModel yet though as the collection has that. (Also, the Entry at the top of the page does work fine.) Any help appreciated.
So I have a xamarin forms app that currently only implemented for android. I am attempting to implement a tap event. When tapped though this never hits the command in the ViewModel. I'm not sure if I have something the matter with my code or I am just implementing it wrong.
ViewModel Code:
private RelayCommand<object> _OnClickableLabel;
public RelayCommand<object> OnClickableLabel
{
get { return _OnClickableLabel ?? (_OnClickableLabel = new RelayCommand<object>((currentObject) => Test(currentObject))); }
}
private void Test(object currentObject)
{
Application.Current.MainPage.DisplayAlert("Alert", "were going down cap", "OK");
}
Page Xaml:
<ListView Grid.Row="1" ItemsSource="{Binding Notifications}" RowHeight="100">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" BackgroundColor="{Binding BackgroundColor}">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OnClickableLabel }" />
</StackLayout.GestureRecognizers>
<Label FontSize="Large" Text="{Binding Title}"></Label>
<Label FontSize="Small" Text="{Binding Text}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have tested it using a method in the page's cs code and that works fine but it has to be implemented in the ViewModel because it affects that data.
From your description, you want to add a tap gesture recognizer in ListView, and want to pass ListView current row data to TapGestureRecognizer event, am I right?
If yes, as Jason's opinion, you need to take a look Xamarin.Forms Relative Bindings firstly,name ListView as listview1, then take a look the following code:
<ListView
x:Name="listview1"
Grid.Row="1"
ItemsSource="{Binding Notifications}"
RowHeight="100">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="{Binding BackgroundColor}" Orientation="Vertical">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.OnClickableLabel, Source={x:Reference listview1}}" CommandParameter="{Binding .}" />
</StackLayout.GestureRecognizers>
<Label FontSize="Large" Text="{Binding Title}" />
<Label FontSize="Small" Text="{Binding Text}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public partial class Page16 : ContentPage
{
public ObservableCollection<Notclass> Notifications { get; set; }
public ICommand OnClickableLabel { get; set; }
public Page16()
{
InitializeComponent();
Notifications = new ObservableCollection<Notclass>()
{
new Notclass(){Title="title 1",Text="notification 1"},
new Notclass(){Title="title 2",Text="notification 2"},
new Notclass(){Title="title 3",Text="notification 3"},
new Notclass(){Title="title 4",Text="notification 4"},
new Notclass(){Title="title 5",Text="notification 5"}
};
OnClickableLabel = new Command(n=> {
var vm = (Notclass)n;
Application.Current.MainPage.DisplayAlert("Alert",vm.Title , "OK");
});
this.BindingContext = this;
}
}
public class Notclass
{
public string Title { get; set; }
public string Text { get; set; }
public Color BackgroundColor { get; set; } = Color.White;
}
I have an Expander in my View that every time I touch it it opens and displays data. Is it possible to open the Expander or close it from ViewModel? What I want to do is that the Expander can open or close it by pressing a Button.
MainPage.xaml:
<StackLayout>
<StackLayout>
<CollectionView>
<CollectionView.ItemTemplate>
<DataTemplate>
<ContentView>
<Frame>
<StackLayout>
<Expander x:Name="expander"></Expander>
</StackLayout>
</Frame>
</ContentView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
<StackLayout>
<ImageButton Source="img1" Command="{Binding xxxCommand}" CommandParameter="{Binding Source={x:Reference expander}, Path=.}"/>
</StackLayout>
</StackLayout>
ViewModel:
xxxCommand = new Command((sender)=> {
var exp = sender as Expander;
exp.IsExpanded = !exp.IsExpanded;
//...
});
When I open the app, I get this exception:
Xamarin.Forms.Xaml.XamlParseException: Can not find the object
referenced by expander.
You could pass the Expander as parameter to the Command of the button .
Set the name of Expander
<Expander x:Name="exp">
<Button Text="XXX" Command="{Binding xxxCommand}" CommandParameter="{Binding Source={x:Reference exp}, Path=.}" />
In ViewModel
xxxCommand = new Command((sender)=> {
var exp = sender as Expander;
exp.IsExpanded = !exp.IsExpanded;
//...
});
Update
In your case you could use Data binding
<StackLayout>
<Expander IsExpanded="{Binding IsExpanded}"></Expander>
</StackLayout>
<ImageButton Source="img1" Command="{Binding xxxCommand}"/>
Add a new property in your model
public class YourModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
public bool isExpanded;
public bool IsExpanded
{
get { return isExpanded; }
set
{
isExpanded= value;
OnPropertyChanged("IsExpanded");
}
}
//...other properties
}
In ViewModel
//list here is the ItemsSource of your listview
ObservableCollection<YourModel> list = new ObservableCollection<YourModel>();
//list.Add();
//list.Add();
//list.Add();
//list.Add();
Command xxxCommand = new Command(()=> {
//if you want to open the first Expander
list[0].IsExpanded = true;
});
My ListView does not load Data for clients as the app is Open but works fine when I swipe from top which call the RefreshCommand.
ClientViewModel.cs
public class ClientViewModel : BaseViewModel
{
private ObservableCollection<Client> _clients { get; set; }
private IClientManager ClientManager { get; }
public ClientViewModel(INavigationManager navigationManager, IClientManager clientManager)
:base(navigationManager)
{
Title = "Client";
Clients = new ObservableCollection<Client>();
ClientManager = clientManager;
}
public ObservableCollection<Client> Clients
{
get => _clients;
set
{
_clients = value;
OnPropertyChanged();
}
}
public Command LoadClientsCommand => new Command(async () => await ExecuteLoadClientsCommand());
public Command SelectedItemCommand => new Command<Client>(OnClientSelected);
//public Command AddClientCommand => new Command(() => OpenClientAddPage());
private void OnClientSelected(Client obj)
{
NavigationManager.NavigateToAsync<ClientDetailViewModel>(obj);
}
public override async Task InitializeAsync(object data)
{
await ExecuteLoadClientsCommand();
}
private async Task ExecuteLoadClientsCommand()
{
if (IsBusy)
return;
IsBusy = true;
try
{
Clients.Clear();
Clients = (await ClientManager.GetAllClient()).ToObservableCollection();
}
catch (System.Exception)
{
throw;
}
finally
{
IsBusy = false;
}
}
}
ClientPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Scheduler.Behaviours"
xmlns:localconverter="clr-namespace:Scheduler.Converters"
mc:Ignorable="d"
xmlns:utility="clr-namespace:Scheduler.Utility;assembly=Scheduler"
utility:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}"
x:Class="Scheduler.Views.ClientPage">
<ContentPage.Resources>
<ResourceDictionary>
<localconverter:SelectedItemConverter x:Key="SelectedClient" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add"/>
</ContentPage.ToolbarItems>
<StackLayout>
<ListView x:Name="ClientListView"
ItemsSource="{Binding Clients}"
VerticalOptions="FillAndExpand"
HasUnevenRows="true"
RefreshCommand="{Binding LoadClientsCommand}"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<!--<ViewCell.ContextActions>
<MenuItem Clicked="OnDelete_Clicked" Text="Delete" CommandParameter="{Binding .}"/>
</ViewCell.ContextActions>-->
<StackLayout Padding="10">
<Label Text="{Binding FullName}"
d:Text="{Binding .}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<local:EventToCommandBehaviour EventName="ItemSelected"
Command="{Binding SelectedItemCommand}"
Converter="{StaticResource SelectedClient}" />
</ListView.Behaviors>
</ListView>
</StackLayout>
</ContentPage>
First of all, you are calling the same API twice here
Clients.Clear();
await ClientManager.GetAllClient();
Clients = (await ClientManager.GetAllClient()).ToObservableCollection();
it can literally be like
Clients.Clear();
Clients = (await ClientManager.GetAllClient()).ToObservableCollection();
Secondly, I do not see you calling the same
API in the constructor in that case where would the data come from? I would suggest you make the above API call in the previous page and pass it on here!