Could you please let me know how can I recognize long press gesture in Xamarin Forms application?
This is my xaml page which i created referring to this thread: Xamarin.forms.DataGrid
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataGridSample_01"
xmlns:dg="clr-namespace:Xamarin.Forms.DataGrid;assembly=Xamarin.Forms.DataGrid"
x:Class="DataGridSample_01.MainPage">
<ContentView BackgroundColor="White" Padding="20" >
<dg:DataGrid ItemsSource="{Binding Data}" SelectionEnabled="True" RowHeight="70" HeaderHeight="50" BorderColor="#CCCCCC" HeaderBackground="#E0E6F8" ActiveRowColor="#8899AA">
<dg:DataGrid.Columns>
<dg:DataGridColumn Title="Logo" PropertyName="Logo" Width="50" SortingEnabled="False">
<dg:DataGridColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding}" HorizontalOptions="Center" VerticalOptions="Center" Aspect="AspectFit" HeightRequest="60" />
</DataTemplate>
</dg:DataGridColumn.CellTemplate>
</dg:DataGridColumn>
<dg:DataGridColumn Title="Team" PropertyName="Name" Width="2*" >
</dg:DataGridColumn>
<dg:DataGridColumn Title="Win" PropertyName="Win" Width="2*">
<dg:DataGridColumn.CellTemplate>
<DataTemplate>
<Picker x:Name="Fruits" Title="Fruits" HorizontalOptions="FillAndExpand">
<Picker.Items>
<x:String>Apple</x:String>
<x:String>Mango</x:String>
<x:String>PineApple</x:String>
<x:String>Orange</x:String>
</Picker.Items>
</Picker>
</DataTemplate>
</dg:DataGridColumn.CellTemplate>
</dg:DataGridColumn>
<dg:DataGridColumn Title="Loose" PropertyName="Loose" Width="1*">
</dg:DataGridColumn>
<dg:DataGridColumn PropertyName="Home">
<dg:DataGridColumn.FormattedTitle>
<FormattedString>
<Span Text="Home" ForegroundColor="Black" FontSize="13" FontAttributes="Bold"/>
<Span Text=" (win-loose)" ForegroundColor="#333333" FontSize="11" />
</FormattedString>
</dg:DataGridColumn.FormattedTitle>
</dg:DataGridColumn>
<dg:DataGrid.RowsBackgroundColorPalette>
<dg:PaletteCollection>
<Color>#FFFFFF</Color>
</dg:PaletteCollection>
</dg:DataGrid.RowsBackgroundColorPalette>
</dg:DataGrid>
</ContentView>
</ContentPage>
I want to add a Long Press Gesture recognizer on Image Control.I tried to do it referring to this StackOverflow Thread.But it doesn't seem to work.
I am very new to this.
Any help is very much appreciated.
It is no longer required to define your own effect, since TouchEffect already exists in Xamarin Community Toolkit package (which is a package that gathers a lot of cool reusable/common controls, effects, behaviors, converters...).
xaml namespace:
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
Sample of usage
You can set a duration of the long press required to trigger the command, or leave it by default = 500ms. In this example LongPressCommand will be fired/triggered after 2 seconds.
<Image Source="{Binding}" HorizontalOptions="Center" VerticalOptions="Center"
Aspect="AspectFit" HeightRequest="60"
xct:TouchEffect.LongPressCommand="{Binding LongPressCommand}"
xct:TouchEffect.LongPressDuration="2000"/>
ICommand LongPressCommand = new Command(() =>
{
LongPressCount++;
OnPropertyChanged(nameof(LongPressCount));
});
Also, the same as CommandParameter you have an optional LongPressCommandParameter, where you can bind a custom parameter to your command.
Resource
Documentation (under work) https://learn.microsoft.com/en-us/xamarin/community-toolkit/
Repo https://github.com/xamarin/XamarinCommunityToolkit/
https://www.youtube.com/watch?v=BcFlZMhPmVk
You can use Effect to add LongPressGestureRecognizer to any control.
in Forms ,creat a new shared Effect.
using System;
using System.Windows.Input;
using Xamarin.Forms;
namespace App15
{
public class LongPressedEffect : RoutingEffect
{
public LongPressedEffect() : base("MyApp.LongPressedEffect")
{
}
public static readonly BindableProperty CommandProperty = BindableProperty.CreateAttached("Command", typeof(ICommand), typeof(LongPressedEffect), (object)null);
public static ICommand GetCommand(BindableObject view)
{
//do something you want
Console.WriteLine("long press Gesture recognizer has been striked");
return (ICommand)view.GetValue(CommandProperty);
}
public static void SetCommand(BindableObject view, ICommand value)
{
view.SetValue(CommandProperty, value);
}
public static readonly BindableProperty CommandParameterProperty = BindableProperty.CreateAttached("CommandParameter", typeof(object), typeof(LongPressedEffect), (object)null);
public static object GetCommandParameter(BindableObject view)
{
return view.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(BindableObject view, object value)
{
view.SetValue(CommandParameterProperty, value);
}
}
}
in Android Project
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using App15;
using App15.Droid;
[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(AndroidLongPressedEffect), "LongPressedEffect")]
namespace AndroidAppNamespace.Effects
{
public class AndroidLongPressedEffect : PlatformEffect
{
private bool _attached;
public static void Initialize() { }
public AndroidLongPressedEffect()
{
}
protected override void OnAttached()
{
//because an effect can be detached immediately after attached (happens in listview), only attach the handler one time.
if (!_attached)
{
if (Control != null)
{
Control.LongClickable = true;
Control.LongClick += Control_LongClick;
}
else
{
Container.LongClickable = true;
Container.LongClick += Control_LongClick;
}
_attached = true;
}
}
// Invoke the command if there is one
private void Control_LongClick(object sender, Android.Views.View.LongClickEventArgs e)
{
Console.WriteLine("Invoking long click command");
var command = LongPressedEffect.GetCommand(Element);
command?.Execute(LongPressedEffect.GetCommandParameter(Element));
}
protected override void OnDetached()
{
if (_attached)
{
if (Control != null)
{
Control.LongClickable = true;
Control.LongClick -= Control_LongClick;
}
else
{
Container.LongClickable = true;
Container.LongClick -= Control_LongClick;
}
_attached = false;
}
}
}
in iOS Project
using Foundation;
using UIKit;
using App15;
using App15.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(iOSLongPressedEffect), "LongPressedEffect")]
namespace App15.iOS
{
public class iOSLongPressedEffect : PlatformEffect
{
private bool _attached;
private readonly UILongPressGestureRecognizer _longPressRecognizer;
public iOSLongPressedEffect()
{
_longPressRecognizer = new UILongPressGestureRecognizer(HandleLongClick);
}
protected override void OnAttached()
{
//because an effect can be detached immediately after attached (happens in listview), only attach the handler one time
if (!_attached)
{
Container.AddGestureRecognizer(_longPressRecognizer);
_attached = true;
}
}
// Invoke the command if there is one
private void HandleLongClick(UILongPressGestureRecognizer sender)
{
if(sender.State==UIGestureRecognizerState.Began)
{
var command = LongPressedEffect.GetCommand(Element);
command?.Execute(LongPressedEffect.GetCommandParameter(Element));
}
}
protected override void OnDetached()
{
if (_attached)
{
Container.RemoveGestureRecognizer(_longPressRecognizer);
_attached = false;
}
}
}
}
Now,you can add LongPressGestureRecognizer to controls.Such as Label or Image.
<Label Text="Long Press Me!" local:LongPressedEffect.Command="{Binding ShowAlertCommand}" local:LongPressedEffect.CommandParameter="{Binding .}">
<Label.Effects>
<local:LongPressedEffect />
</Label.Effects>
</Label>
<Image Source="{Binding}" local:LongPressedEffect.Command="{Binding ShowAlertCommand}" local:LongPressedEffect.CommandParameter="{Binding .}">
<Image.Effects>
<local:LongPressedEffect />
</Image.Effects>
</Image>
For more detail about Effect you can refer here
Related
I am running through a Pluralsight Xamarin.Forms tutorial where in the video, tapping the StackLayout in iOS opens a detail page for the specific item by firing an ItemTapped command.
For me, the ItemTapped command is not firing for iOS.
Any suggestions on what I am missing ?
Below is the ItemsPage.xaml, ItemsPage.xaml.cs, and ItemsViewModel.cs.
Please let me know if more information is needed, or any feedback :)
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="App1.Views.ItemsPage"
Title="{Binding Title}"
xmlns:local="clr-namespace:App1.ViewModels"
xmlns:model="clr-namespace:App1.Models"
x:Name="BrowseItemsPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddItemCommand}" />
</ContentPage.ToolbarItems>
<!--
x:DataType enables compiled bindings for better performance and compile time validation of binding expressions.
https://learn.microsoft.com/xamarin/xamarin-forms/app-fundamentals/data-binding/compiled-bindings
-->
<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>
<StackLayout Padding="10" x:DataType="model:Item">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer
NumberOfTapsRequired="1"
Command="{Binding Source={RelativeSource AncestorType={x:Type local:ItemsViewModel}}, Path=ItemTapped}"
CommandParameter="{Binding .}">
</TapGestureRecognizer>
</StackLayout.GestureRecognizers>
<Label Text="{Binding Text}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16"
InputTransparent="True"/>
<Label Text="{Binding Description}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemDetailTextStyle}"
FontSize="13"
InputTransparent="True"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
</ContentPage>
ItemsPage.xaml.cs
using App1.Models;
using App1.ViewModels;
using App1.Views;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace App1.Views
{
public partial class ItemsPage : ContentPage
{
ItemsViewModel _viewModel;
public ItemsPage()
{
InitializeComponent();
BindingContext = _viewModel = new ItemsViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
}
}
}
ItemsViewModel.cs
using App1.Models;
using App1.Views;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace App1.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 ItemsViewModel()
{
Title = "Browse";
Items = new ObservableCollection<Item>();
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
ItemTapped = new Command<Item>(OnItemSelected);
AddItemCommand = new Command(OnAddItem);
}
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}");
}
}
}
UPDATE - Issue #1 is Solved, Issue#2 is still unsolved
You can view a very crude demonstration video of my issue at https://www.youtube.com/watch?v=5_6KJ0QJouM
I am building have a Xamarin.Forms app with an SQLite database using the MVVM design pattern and C#
When try to Save a record to the database from a View the update/save does not appear to be saving to the SQLite database or reflect in other Views.
I know the database Save method does work as I have created some dummy data when the application first loads (in App.xaml.cs) using the DeveloperData.cs file.
I have two issues.
(SOLVED) Issue 1 - Data not Saving to Database
when I call the Save command from the MerchandiserEditPage.xaml, which uses the MerchandiserEditPageViewModel.cs ViewModel, the record does not appear to save.
Issue 2 - Changes Reflecting in other Views
Once the updated data is saved to the database, how can I reflect that change in other views? After I Save a record from the MerchandiserEditPage that View is "Popped" off the stack and the user is returned to the MerchandiserProfileView. I want the updated data to be reflected in all other views on the stack. But this doesn't appear to be happening? (I tested this using hardcoded data and the same issue occurred, so problem is not directly related to issue 1)
There are many files in my project, that can be viewed/downloaded from my GitHub repository but I will concentrate on the following in this question.
MerchandiserEditPage.xaml (View)
MerchandiserProfilePage.xaml (View)
MerchandiserDatabase.cs (Database Functions)x
MerchandiserEditPageViewModel.cs x
View my GitHub repository for the full project.
MerchandiserDatabase.cs (Database Functions)
using SQLite;
namespace MobileApp.Database
{
public class MerchandiserDatabase
{
private static SQLiteConnection database = DependencyService.Get<IDatabaseConnection>().DbConnection();
private readonly static object collisionLock = new object();
public MerchandiserDatabase()
{
database.CreateTable<Models.Merchandiser>();
}
public static void SaveMerchandiser(Models.Merchandiser merchandiser)
{
lock (collisionLock)
{
if (merchandiser.Id != 0)
{
database.Update(merchandiser);
}
else
{
database.Insert(merchandiser);
}
}
}
}
}
MerchandiserEditPageViewModel.cs (ViewModel) UPDATED
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace MobileApp.ViewModels
{
public class MerchandiserEditPageViewModel : BaseViewModel
{
public string PageTitle { get; } = "Edit Merchandiser Profile";
public Command SaveCommand { get; set; }
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
private string phone;
public string Phone
{
get { return phone; }
set
{
phone = value;
OnPropertyChanged();
}
}
private string email;
public string Email
{
get { return email; }
set
{
email = value;
OnPropertyChanged();
}
}
public MerchandiserEditPageViewModel(Models.Merchandiser selectedMerchandiser)
{
Name = selectedMerchandiser.Name;
Phone = selectedMerchandiser.Phone;
Email = selectedMerchandiser.Email;
SaveCommand = new Command( async ()=> {
selectedMerchandiser.Name = this.Name;
selectedMerchandiser.Phone = this.Phone;
selectedMerchandiser.Email = this.Email;
Database.MerchandiserDatabase.SaveMerchandiser(selectedMerchandiser);
await Application.Current.MainPage.Navigation.PopModalAsync();
});
}
}
}
MerchandiserEditPage.xaml (View)
<?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="MobileApp.Views.MerchandiserEditPage">
<ContentPage.Content>
<StackLayout>
<!--Page Heading-->
<StackLayout Spacing="0">
<Label Text="{Binding PageTitle}"
Style="{StaticResource PageTitle}"/>
<BoxView HeightRequest="1" Color="LightGray" />
</StackLayout>
<!-- Merchandiser Profile -->
<StackLayout Margin="10">
<Label Text="Name"/>
<Entry Text="{Binding Name}"/>
<Label Text="Phone"/>
<Entry Text="{Binding Phone}"/>
<Label Text="Email"/>
<Entry Text="{Binding Email}"/>
<StackLayout Orientation="Horizontal"
HorizontalOptions="Center">
<Button Text="Cancel"
Clicked="CancelButton_Clicked"/>
<Button Text="Save"
Command="{Binding SaveCommand}"/>
</StackLayout>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
MerchandiserEditPage.xaml.cs (View - Code Behind)
public partial class MerchandiserEditPage : ContentPage
{
Models.Merchandiser SelectedMerchandiser { get; set; }
public MerchandiserEditPage (Models.Merchandiser selectedMerchandiser)
{
InitializeComponent ();
SelectedMerchandiser = selectedMerchandiser;
this.BindingContext = new ViewModels.MerchandiserEditPageViewModel(selectedMerchandiser);
}
async private void CancelButton_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
}
MerchandiserProfilePage.xaml (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="MobileApp.Views.MerchandiserProfilePage"
NavigationPage.HasNavigationBar="False">
<ContentPage.Content>
<StackLayout>
<!--Page Heading-->
<StackLayout Spacing="0">
<Label Text="{Binding PageTitle}"
Style="{StaticResource PageTitle}"/>
<BoxView HeightRequest="1" Color="LightGray" />
</StackLayout>
<!-- Merchandiser Profile -->
<StackLayout Margin="10">
<Label Text="Name"/>
<Entry Text="{Binding Name}"
IsEnabled="False"/>
<Label Text="Phone"/>
<Entry Text="{Binding Phone}"
IsEnabled="False"/>
<Label Text="Email"/>
<Entry Text="{Binding Email}"
IsEnabled="False"/>
<StackLayout Orientation="Horizontal"
HorizontalOptions="Center">
<Button Text="Back"
Clicked="BackButton_Clicked"/>
<Button Text="Edit"
Clicked="EditButton_Clicked"/>
</StackLayout>
<Button Text="Delete"
Command="{Binding DeleteCommand}"/>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
MerchandiserProfilePage.xaml.cs - (View - Code Behind)
public partial class MerchandiserProfilePage : ContentPage
{
private Models.Merchandiser SelectedMerchandister { get; set; }
public MerchandiserProfilePage (Models.Merchandiser selectedMerchandiser)
{
InitializeComponent ();
SelectedMerchandister = selectedMerchandiser;
this.BindingContext = new ViewModels.MerchandiserProfilePageViewModel(selectedMerchandiser);
}
async private void BackButton_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
async private void EditButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushModalAsync(new Views.MerchandiserEditPage(SelectedMerchandister));
}
}
MerchandiserProfilePageViewModel.cs (ViewModel)
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace MobileApp.ViewModels
{
public class MerchandiserProfilePageViewModel : BaseViewModel
{
public string PageTitle { get; } = "Merchandiser Profile";
public Command DeleteCommand { get; }
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
private string phone;
public string Phone
{
get { return phone; }
set
{
phone = value;
OnPropertyChanged();
}
}
private string email;
public string Email
{
get { return email; }
set
{
email = value;
OnPropertyChanged();
}
}
public MerchandiserProfilePageViewModel(Models.Merchandiser selectedMerchandiser)
{
Name = selectedMerchandiser.Name;
Phone = selectedMerchandiser.Phone;
Email = selectedMerchandiser.Email;
DeleteCommand = new Command( async()=> {
bool deleteConfirmed = await Application.Current.MainPage.DisplayAlert("Confirm Delete",$"Are you sure you want to delete {selectedMerchandiser.Name} as a Merchandiser?","Yes","No");
if (deleteConfirmed)
{
// TODO: Delete Merchandiser
await Application.Current.MainPage.Navigation.PopModalAsync();
}
});
}
}
}
you have a hardcoded set of data in your VM instead of loading it from the db
public MerchandisersPageViewModel()
{
//Merchandisers = new ObservableCollection<Models.Merchandiser>(Database.MerchandiserDatabase.GetMerchandisers());
Merchandisers = new ObservableCollection<Models.Merchandiser>()
{
new Models.Merchandiser { Id=1, Name="Barney Rubble", Phone="021 321 654", Email="barney#rubble.com"},
new Models.Merchandiser { Id=2, Name="Frank Grimes", Phone="022 456 789", Email="grimey#homersfriend.com"},
new Models.Merchandiser { Id=3, Name="Perry Platypus", Phone="023 789 456", Email="perry#agentp.com"},
};
}
Update:
in MerchandiserProfilePageViewModel, get rid of the properties for Name, Phone and EMail
then in MerchandiserProfilePage.xaml change the bindings
<Entry Text="{Binding SelectedMerchandiser.Name}" IsEnabled="False"/>
I have created a custom effect by subclassing RoutingEffect in order to allow LongPressGesture for both iOS and Android in my Xamarin project.
I am using this effect on an Image in in the XAML of my shared project, and this same Image is also using a TapGesture, see code below:
<Image x:Name="TapRight" Grid.Row="4" Grid.Column="2" Source="right64"
VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
IsEnabled="{Binding RightEnabled}"
Opacity="{Binding RightEnabled, Converter={StaticResource OpacityConverter}}"
effects:LongPressEffect.Command="{Binding LongPressGestureCommand}"
effects:LongPressEffect.CommandParameter="{x:Static common:NavType.Right}">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapGestureNavCommand}"
NumberOfTapsRequired="1"
CommandParameter="{x:Static common:NavType.Right}"/>
</Image.GestureRecognizers>
<Image.Effects>
<effects:LongPressEffect></effects:LongPressEffect>
</Image.Effects>
</Image>
This works fine for iOS (I get separate functionality when I tap vs when I long press the image), however for Android, it only allows me to do Long Press, and does not execute the command for the TapGesture, any ideas on how to fix this?
NOTE: If I use a Button instead of an Image it works fine. However, I would really like to use an Image.
I have added more code below for reference:
Code for the effect in shared project:
using System.Windows.Input;
using Xamarin.Forms;
namespace MyApp.Effects
{
public class LongPressEffect : RoutingEffect
{
public LongPressEffect() : base("Xamarin.LongPressEffect")
{
}
public static readonly BindableProperty CommandProperty =
BindableProperty.CreateAttached("Command",
typeof(ICommand),
typeof(LongPressEffect),
(object)null,
propertyChanged: OnCommandChanged);
public static ICommand GetCommand(BindableObject view)
{
return (ICommand)view.GetValue(CommandProperty);
}
public static void SetCommand(BindableObject view, ICommand value)
{
view.SetValue(CommandProperty, value);
}
static void OnCommandChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as View;
if (view == null)
{
return;
}
ICommand command = (ICommand)newValue;
if (command != null)
{
view.SetValue(CommandProperty, command);
}
}
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.CreateAttached("CommandParameter",
typeof(object),
typeof(LongPressEffect),
(object)null,
propertyChanged: OnCommandParameterChanged);
public static object GetCommandParameter(BindableObject view)
{
return (object)view.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(BindableObject view, object value)
{
view.SetValue(CommandParameterProperty, value);
}
static void OnCommandParameterChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as View;
if (view == null)
{
return;
}
object commandParameter = (object)newValue;
if (commandParameter != null)
{
view.SetValue(CommandParameterProperty, commandParameter);
}
}
}
}
Code for effect in iOS:
using System;
using System.ComponentModel;
using MyApp.Effects;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ResolutionGroupName("Xamarin")]
[assembly:ExportEffect (typeof(MyApp.iOS.Effects.LongPressEffect), "LongPressEffect")]
namespace MyApp.iOS.Effects
{
public class LongPressEffect : PlatformEffect
{
private readonly UILongPressGestureRecognizer _longPressGestureRecognizer;
private bool _attached;
public LongPressEffect()
{
_longPressGestureRecognizer = new UILongPressGestureRecognizer(HandleLongClick);
_attached = false;
}
protected override void OnAttached()
{
if (!_attached)
{
Container.AddGestureRecognizer(_longPressGestureRecognizer);
_attached = true;
}
}
private void HandleLongClick()
{
if (_longPressGestureRecognizer.State == UIGestureRecognizerState.Ended)
// Only execute when the press is ended.
{
var command = MyApp.Effects.LongPressEffect.GetCommand(Element);
command?.Execute(MyApp.Effects.LongPressEffect.GetCommandParameter(Element));
}
}
protected override void OnDetached()
{
if (_attached)
{
Container.RemoveGestureRecognizer(_longPressGestureRecognizer);
_attached = false;
}
}
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(args);
}
}
}
Code for effect in Android:
using System;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ResolutionGroupName("Xamarin")]
[assembly: ExportEffect(typeof(MyApp.Droid.Effects.LongPressEffect), "LongPressEffect")]
namespace MyApp.Droid.Effects
{
public class LongPressEffect: PlatformEffect
{
private bool _attached;
public static void Initialize() { }
public LongPressEffect()
{
_attached = false;
}
protected override void OnAttached()
{
Console.WriteLine("Invoking long click command...");
//throw new NotImplementedException();
if (!_attached) {
if (Control != null)
{
Control.LongClickable = true;
Control.LongClick += HandleLongClick;
}
_attached = true;
}
}
private void HandleLongClick(object sender, Android.Views.View.LongClickEventArgs e) {
Console.WriteLine("Invoking long click command...");
var command = MyApp.Effects.LongPressEffect.GetCommand(Element);
command?.Execute(MyApp.Effects.LongPressEffect.GetCommandParameter(Element));
}
protected override void OnDetached()
{
//throw new NotImplementedException();
if (_attached) {
if (Control != null) {
Control.LongClickable = true;
Control.LongClick -= HandleLongClick;
}
_attached = false;
}
}
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(args);
}
}
}
This is a bug in Xamarin, more details can be found here
As a workaround I have used an ImageButton or Android and Image for IOS and made the visibility platform dependent. my XAML now looks like this:
<ImageButton x:Name="Tap" Grid.Row="4" Grid.Column="2" Source="right64"
IsEnabled="{Binding RightEnabled}"
Opacity="{Binding RightEnabled, Converter={StaticResource OpacityConverter}}"
effects:LongPressEffect.Command="{Binding LongPressGestureCommand}"
effects:LongPressEffect.CommandParameter="{x:Static common:NavType.Right}"
Command="{Binding TapGestureNavCommand}"
CommandParameter="{x:Static common:NavType.Right}">
<ImageButton.IsVisible>
<OnPlatform x:TypeArguments="x:Boolean"
iOS="False"
Android="True"/>
</ImageButton.IsVisible>
<ImageButton.Effects>
<effects:LongPressEffect></effects:LongPressEffect>
</ImageButton.Effects>
</ImageButton>
<!--Due to different behaviour on platform(s) different views were needed for different platforms.-->
<Image x:Name="TapIOS" Grid.Row="4" Grid.Column="2" Source="right64"
IsEnabled="{Binding RightEnabled}"
Opacity="{Binding RightEnabled, Converter={StaticResource OpacityConverter}}"
effects:LongPressEffect.Command="{Binding LongPressGestureCommand}"
effects:LongPressEffect.CommandParameter="{x:Static common:NavType.Right}">
<Image.IsVisible>
<OnPlatform x:TypeArguments="x:Boolean"
iOS="True"
Android="False"/>
</Image.IsVisible>
<Image.GestureRecognizers>
<TapGestureRecognizer
NumberOfTapsRequired="1"
Command="{Binding TapGestureNavCommand}"
CommandParameter="{x:Static common:NavType.Right}"/>
</Image.GestureRecognizers>
<Image.Effects>
<effects:LongPressEffect></effects:LongPressEffect>
</Image.Effects>
</Image>
I dont know how is it called.
I need to create something that works this way:
Do button, when you click button under button you have list and you can choos one option. List should be button's width.
You can find it in aplication to choose for example language of country.
Do Xamarin built-in something to create this? Or can someone show me how implement this?
Or you could roll your own in Forms, something like:
ImagePickerDropDown.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ImagePickerDropdownSample.ImagePickerDropdown"
x:Name="imagePickerDropDown" >
<ContentView.Content>
<StackLayout>
<ImageButton x:Name="mainButton"
Source="{Binding Source={x:Reference imagePickerDropDown}, Path=SelectedImage}"
Clicked="ImageClicked" />
<StackLayout x:Name="stackView"
BindableLayout.ItemsSource="{Binding Source={x:Reference imagePickerDropDown}, Path=Images}"
IsVisible="False">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<ImageButton Source="{Binding .}" Clicked="ImageSelected"/>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</ContentView.Content>
</ContentView>
ImagePickerDropDown.xaml.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Xamarin.Forms;
namespace ImagePickerDropdownSample
{
public partial class ImagePickerDropdown : ContentView
{
public ImagePickerDropdown()
{
InitializeComponent();
}
private void ImageSelected(object sender, EventArgs e)
{
var imageSource = (sender as ImageButton).Source;
SelectedImage = imageSource;
mainButton.IsEnabled = true;
stackView.IsVisible = false;
}
private void ImageClicked(object sender, EventArgs e)
{
mainButton.IsEnabled = false;
stackView.IsVisible = true;
}
public static readonly BindableProperty SelectedImageProperty =
BindableProperty.Create(nameof(SelectedImage), typeof(ImageSource), typeof(ImagePickerDropdown), null);
public ImageSource SelectedImage
{
get
{
return (ImageSource)GetValue(SelectedImageProperty);
}
set
{
SetValue(SelectedImageProperty, value);
}
}
public static readonly BindableProperty ImagesProperty =
BindableProperty.Create(nameof(Images), typeof(ObservableCollection<ImageSource>), typeof(ImagePickerDropdown), null);
public ObservableCollection<ImageSource> Images
{
get
{
return (ObservableCollection<ImageSource>)GetValue(ImagesProperty);
}
set
{
SetValue(ImagesProperty, value);
}
}
}
}
Using it 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"
mc:Ignorable="d"
x:Class="ImagePickerDropdownSample.MainPage"
xmlns:local="clr-namespace:ImagePickerDropdownSample"
Padding="0,50,0,0"
BackgroundColor="Black">
<StackLayout
x:Name="mainLayout">
<Label Text="Welcome to Xamarin.Forms!"
HorizontalOptions="Center"
VerticalOptions="Start"
TextColor="White"/>
<local:ImagePickerDropdown SelectedImage="{Binding SelectedImage}"
Images="{Binding Images}"
WidthRequest="50"
HorizontalOptions="Center"
BackgroundColor="Black"/>
</StackLayout>
</ContentPage>
Using it code behind:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace ImagePickerDropdownSample
{
// Learn more about making custom code visible in the Xamarin.Forms previewer
// by visiting https://aka.ms/xamarinforms-previewer
[DesignTimeVisible(false)]
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
Images = new ObservableCollection<ImageSource>();
Images.Add(new FileImageSource() { File = "image1.png" });
Images.Add(new FileImageSource() { File = "image2.png" });
Images.Add(new FileImageSource() { File = "image3.png" });
SelectedImage = Images[0];
BindingContext = this;
}
ImageSource _selectedImage;
public ImageSource SelectedImage
{
get
{
return _selectedImage;
}
set
{
if (_selectedImage != value)
{
_selectedImage = value;
OnPropertyChanged(nameof(SelectedImage));
}
}
}
ObservableCollection<ImageSource> _images;
public ObservableCollection<ImageSource> Images
{
get
{
return _images;
}
set
{
if (_images != value)
{
_images = value;
OnPropertyChanged(nameof(Images));
}
}
}
}
}
Use a Spinner .. basically you need to first create an ArrayAdapter then attach the ArrayAdapter to a Spinner :
//we need a List of some type because the ArrayAdapter takes one as param
var items = new List<string>() {"one", "two", "three"};
//instantiate the ArrayAdapter with context, your Resource is a layout, items is the List
var adapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleSpinnerItem, items);
//then instantiate your spinner
var spinner = FindViewById<Spinner>(Resource.Id.spinner);
//and attach the adapter to the spinner like this
spinner.Adapter = adapter;
from #Aaron He
Create android spinner dynamically in Xamarin
I try to rewrite my UWP C# app for Windows10 to Xamarin app using XAML. But Binding (for example here in ListView ItemSource=...) is not working for me and I don´t know why.
Visual Studio tells me, Cannot Resolve Symbol Recording due to unknown Data Context.
Here is my XAML (MainPage.xaml) for testing purpose:
<?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:XamarinTest;assembly=XamarinTest"
x:Class="XamarinTest.MainPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recording}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Image Source="Accept" WidthRequest="40" HeightRequest="40" />
<StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand">
<Label Text="TEST" HorizontalOptions="FillAndExpand" />
<Label Text="TEST" />
</StackLayout>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage>
Here is C# (MainPage.xaml.cs):
namespace XamarinTest
{
public partial class MainPage : ContentPage
{
public MainPage()
{
this.InitializeComponent();
this.AllTestViewModel = new RecordingViewModel();
this.BindingContext = AllTestViewModel;
}
public RecordingViewModel AllTestViewModel { get; set; }
}
}
And finally ViewModel (RecordingViewModel.cs):
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using XamarinTest.Model;
namespace XamarinTest.ViewModel
{
public class RecordingViewModel : INotifyPropertyChanged
{
public ObservableCollection<Recording> Recordings { get; } = new TrulyObservableCollection<Recording>();
public RecordingViewModel()
{
Recordings.Add(new RecordingTest2()
{
TestName = "Test 1",
TestNote = "Vytvoreni DB",
TestTime = new TimeSpan(0, 0, 0)
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
}
Everything (models and viewmodels) are working in native UWP Windows 10 app. Only the Binding and making same view is problem in Xamarin. Could someone please help with binding?
Thx.
EDIT
Recording.cs is here:
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace XamarinTest.Model
{
public abstract class Recording : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string TestName { get; set; }
private TimeSpan _testTime;
private string _testNote;
private string _actualIco = "Play";
private bool _isActive = false;
private bool _enabled = true;
public double IcoOpacity { get; private set; } = 1.0;
public string ActualIco
{
get => _actualIco;
set
{
if (_actualIco == null) _actualIco = "Admin";
_actualIco = value;
NotifyPropertyChanged("ActualIco");
}
}
public bool IsActive
{
get => _isActive;
set
{
if (_isActive == value) return;
_isActive = value;
IcoOpacity = !value ? 1.0 : 0.3;
NotifyPropertyChanged("IsActive");
NotifyPropertyChanged("IcoOpacity");
}
}
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
NotifyPropertyChanged("Enabled");
}
}
public string TestNote
{
get => _testNote;
set
{
if (_testNote == value) return;
_testNote = value;
NotifyPropertyChanged("TestNote");
}
}
public TimeSpan TestTime
{
get => _testTime;
set
{
if (_testTime == value) return;
_testTime = value;
NotifyPropertyChanged("TestTime");
}
}
protected Recording()
{
TestName = "Unkonwn";
TestNote = "";
_testTime = new TimeSpan(0, 0, 0);
}
protected Recording(string testName, string testNote, TimeSpan testTime)
{
TestName = testName;
TestNote = testNote;
_testTime = testTime;
}
public string OneLineSummary => $"{TestName}, finished: "
+ TestTime;
private void NotifyPropertyChanged(string propertyName = "")
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public abstract bool playTest();
}
}
I tried add DataContext in XAML (postet in origin question), because of intellisence like this:
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:dvm="clr-namespace:XamarinTest.ViewModel"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
d:DataContext="{system:Type dvm:RecordingViewModel}"
and this to Grid:
<Label Text="{Binding Recordings[0].TestName}" Grid.Row="0" Grid.Column="2" />
IntelliSence is OK, but text doesn´t show in app.
Finally is working!
XAML should looks like code below.
Imporant is xmls:viewModel="..." and <ContentPage.BindingContext>...</>.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModel="clr-namespace:XamarinTest.ViewModel;assembly=XamarinTest"
x:Class="XamarinTest.MainPage"
>
<ContentPage.BindingContext>
<viewModel:RecordingViewModel/>
</ContentPage.BindingContext>
<ListView x:Name="listView" ItemsSource="{Binding Recordings}" Grid.Row="1" Grid.Column="1">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Image Source="Accept" WidthRequest="40" HeightRequest="40" />
<StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand">
<Label Text="{Binding TestName}" HorizontalOptions="FillAndExpand" />
<Label Text="{Binding TestNote}" />
</StackLayout>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage>
and MainPage.xaml.cs is okey
namespace XamarinTest
{
public partial class MainPage : ContentPage
{
public MainPage()
{
this.InitializeComponent();
this.AllTestViewModel = new RecordingViewModel();
this.BindingContext = AllTestViewModel;
}
public RecordingViewModel AllTestViewModel { get; set; }
}
}
looking at your ViewModel, it looks like there is no Recording member, but you do have a Recordings member.
EDIT
So you are adding your DataContext in the code behind so ignore the Xaml part.
Your View (MainPage.xaml) has a ViewModel(RecordingViewModel.cs). The ViewModel has a member called Recordings (a collection of type Recording). But in your Xaml, you are try to bind to Recording.
Change:
<ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recording}">
to:
<ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recordings}">
2nd EDIT
The only Labels in your example is the one inside of the ListView yes?
If so, you can access the Recordings children like TestNote by: