Xamarin.Android Tap Gesture and Long Press Gesture not working together - c#

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>

Related

In my Touchscreen laptop , My Custom Slider is not working in UWP application but slider is working fine?

using System;
using System.Collections.Generic;
using System.Text;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
namespace APO.UIElements
{
class CustomAutomationPeerSlider : Slider
{
public string GEQBarHeaderName
{ get; set; }
protected override AutomationPeer OnCreateAutomationPeer()
{
this.GotFocus += OnGEQBarFocus;
this.LostFocus += OnGEQBarFocus;
return new SliderPeer(this);
}
private void OnGEQBarFocus(object sender,
Windows.UI.Xaml.RoutedEventArgs e)
{
this.GEQBarHeaderName = "";
}
private class SliderPeer : RangeBaseAutomationPeer
{
public SliderPeer(RangeBase owner)
: base(owner)
{
}
#region AutomationPeer Overrides
protected override string GetClassNameCore()
{
return "Slider";
}
protected override string GetLocalizedControlTypeCore()
{
return db;
}
protected override string GetNameCore()
{
//My logic on what it should call
return "value of slider in number not in percentage";
}
protected override bool IsControlElementCore()
{
return true;
}
protected override AutomationControlType
GetAutomationControlTypeCore()
{
return AutomationControlType.Custom;
}
protected override IList<AutomationPeer> GetChildrenCore()
{
return null;
}
#endregion
}
}
}
}
**This above code is like I have CustomAutomationPeerSlider slider which is extending the slider **
**Now as all other function I have not added here the main issue is the when I start the Narrator of the Window and try to slider/touch the slider it didn't response but the normal slider is working fine .
Why?? **
{
<localUIElement:CustomAutomationPeerSlider x:Name="Band_2"
Minimum="-10"
Maximum="10"
Margin="10,0"
utilities:StringResourceLoader.SliderHeaderResourceKey="xper_geq_band_03"
Orientation="Vertical"
Grid.Row="1"
Grid.Column="4"
ValueChanged="Band_ValueChanged"
Value="{x:Bind viewModel.GEQBands[2].Value, Mode=OneWay, Converter={StaticResource GEQBandValueConverter}}"
IsEnabled="{Binding IsOn, ElementName=GEQToggle}"
Style="{StaticResource SliderStyle}" Tag="{x:Bind viewModel.GEQBands[2].Value, Mode=OneWay, Converter={StaticResource GEQBandValueConverter}}"
AutomationProperties.Name="{x:Bind Converter={StaticResource AutomationPropertyNameConverter}, ConverterParameter='xper_geq_band_03'}"/>
}
For more clarification:
{
protected override AutomationPeer OnCreateAutomationPeer()
{
return new SliderPeer(this);
}
}

When other controls is placed inside datagrid column it is not binding

I am working on datagrid sample based on this one.
<dg:DataGrid x:Name="datagrid" ItemsSource="{Binding Teams}" SelectionEnabled="True" SelectedItem="{Binding SelectedTeam}" ActiveRowColor="Red"
RowHeight="70" HeaderHeight="50" BorderColor="#CCCCCC" HeaderBackground="#E0E6F8" Focused="Datagrid_Focused"
PullToRefreshCommand="{Binding RefreshCommand}" IsRefreshing="{Binding IsRefreshing}"
>
<x:Arguments>
<ListViewCachingStrategy>RetainElement</ListViewCachingStrategy>
</x:Arguments>
<dg:DataGrid.HeaderFontSize>
<OnIdiom x:TypeArguments="x:Double">
<OnIdiom.Tablet>15</OnIdiom.Tablet>
<OnIdiom.Phone>12</OnIdiom.Phone>
</OnIdiom>
</dg:DataGrid.HeaderFontSize>
<dg:DataGrid.Columns>
<dg:DataGridColumn Title="Logo" PropertyName="Logo" Width="100" 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.CellTemplate>
<DataTemplate>
<Grid x:Name="gridtest" BindingContext="{Binding .}">
<Grid.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</Grid.GestureRecognizers>
<Label x:Name="label1" Text="{Binding Name,Mode=TwoWay}" />
<Editor x:Name="editorTest" Completed="Editor_Completed" BindingContext="{Binding .}" Focused="Editor_Focused">
</Editor>
</Grid>
</DataTemplate>
</dg:DataGridColumn.CellTemplate>
</dg:DataGridColumn>
<dg:DataGridColumn Title="Win" PropertyName="Win" Width="0.95*"/>
<dg:DataGridColumn Title="Loose" PropertyName="Loose" Width="1*"/>
<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:DataGridColumn Title="Percentage" PropertyName="Percentage" StringFormat="{}{0:0.00}" />
</dg:DataGrid.Columns>
</dg:DataGrid>
I have a Team model, the name what i entered in editor should be bind in that model property. In static cases it is working. How to acheive in dynamic case like,
I have add button . In add button only i am adding item to the collection Teams,
private void AddButton_Clicked(object sender, EventArgs e)
{
viewModel.Teams.Add(new Models.Team()
{
Win = 73,
Name = "",
Loose = 9,
Percentage = 0.89,
Conf = "46-6",
Div = "15-1",
Home = "39-2",
Road = "34-7",
Last10 = "8-2",
Streak = "W 4",
Logo = "gsw.png"
});
}
When editor is opened, datagrid selectedItem event is not working.and the text is not binding to the model property Name.
On clicking the Save Button, I need updated collection.
How to acheive this scenario?
Since you had used MVVM , you should handle all the logic in ViewModel. You could bind the Text of Editor in model.
<Editor Text="{Binding editorText,Mode=TwoWay}"... />
in model
define a new property
public class Team : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
string editorText;
public string EditorText
{
get
{
return editorText;
}
set
{
if(value!=null)
{
editorText = value;
OnPropertyChanged("EditorText");
}
}
}
//...
}
When editor is opened, datagrid selectedItem event is not working.
This maybe a issue of the plugin . You could set the SelectItem when the editor is focused .
Add the following class in your project
using System;
using Xamarin.Forms;
namespace xxx
{
public class BehaviorBase<T> : Behavior<T> where T : BindableObject
{
public T AssociatedObject { get; private set; }
protected override void OnAttachedTo (T bindable)
{
base.OnAttachedTo (bindable);
AssociatedObject = bindable;
if (bindable.BindingContext != null) {
BindingContext = bindable.BindingContext;
}
bindable.BindingContextChanged += OnBindingContextChanged;
}
protected override void OnDetachingFrom (T bindable)
{
base.OnDetachingFrom (bindable);
bindable.BindingContextChanged -= OnBindingContextChanged;
AssociatedObject = null;
}
void OnBindingContextChanged (object sender, EventArgs e)
{
OnBindingContextChanged ();
}
protected override void OnBindingContextChanged ()
{
base.OnBindingContextChanged ();
BindingContext = AssociatedObject.BindingContext;
}
}
}
using System;
using System.Reflection;
using System.Windows.Input;
using Xamarin.Forms;
namespace xxx
{
public class EventToCommandBehavior : BehaviorBase<View>
{
Delegate eventHandler;
public static readonly BindableProperty EventNameProperty = BindableProperty.Create ("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
public static readonly BindableProperty CommandProperty = BindableProperty.Create ("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create ("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty InputConverterProperty = BindableProperty.Create ("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);
public string EventName {
get { return (string)GetValue (EventNameProperty); }
set { SetValue (EventNameProperty, value); }
}
public ICommand Command {
get { return (ICommand)GetValue (CommandProperty); }
set { SetValue (CommandProperty, value); }
}
public object CommandParameter {
get { return GetValue (CommandParameterProperty); }
set { SetValue (CommandParameterProperty, value); }
}
public IValueConverter Converter {
get { return (IValueConverter)GetValue (InputConverterProperty); }
set { SetValue (InputConverterProperty, value); }
}
protected override void OnAttachedTo (View bindable)
{
base.OnAttachedTo (bindable);
RegisterEvent (EventName);
}
protected override void OnDetachingFrom (View bindable)
{
DeregisterEvent (EventName);
base.OnDetachingFrom (bindable);
}
void RegisterEvent (string name)
{
if (string.IsNullOrWhiteSpace (name)) {
return;
}
EventInfo eventInfo = AssociatedObject.GetType ().GetRuntimeEvent (name);
if (eventInfo == null) {
throw new ArgumentException (string.Format ("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
}
MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo ().GetDeclaredMethod ("OnEvent");
eventHandler = methodInfo.CreateDelegate (eventInfo.EventHandlerType, this);
eventInfo.AddEventHandler (AssociatedObject, eventHandler);
}
void DeregisterEvent (string name)
{
if (string.IsNullOrWhiteSpace (name)) {
return;
}
if (eventHandler == null) {
return;
}
EventInfo eventInfo = AssociatedObject.GetType ().GetRuntimeEvent (name);
if (eventInfo == null) {
throw new ArgumentException (string.Format ("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
}
eventInfo.RemoveEventHandler (AssociatedObject, eventHandler);
eventHandler = null;
}
void OnEvent (object sender, object eventArgs)
{
if (Command == null) {
return;
}
object resolvedParameter;
if (CommandParameter != null) {
resolvedParameter = CommandParameter;
} else if (Converter != null) {
resolvedParameter = Converter.Convert (eventArgs, typeof(object), null, null);
} else {
resolvedParameter = eventArgs;
}
if (Command.CanExecute (resolvedParameter)) {
Command.Execute (resolvedParameter);
}
}
static void OnEventNameChanged (BindableObject bindable, object oldValue, object newValue)
{
var behavior = (EventToCommandBehavior)bindable;
if (behavior.AssociatedObject == null) {
return;
}
string oldEventName = (string)oldValue;
string newEventName = (string)newValue;
behavior.DeregisterEvent (oldEventName);
behavior.RegisterEvent (newEventName);
}
}
}
in xaml
<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:xxx"
mc:Ignorable="d"
x:Name="page" // set name here
x:Class="xxx.MainPage">
<Editor.Behaviors>
<local:EventToCommandBehavior EventName="Focused" Command="{Binding Source={x:Reference page},Path=BindingContext.xxxCommand}" CommandParameter="{Binding }" />
</Editor.Behaviors>
in ViewModel
xxxCommand = new Command((model)=>{
var item = model as Team;
SelectedTeam = item;
// ...
});

How to create a drag and drop interface for audio files

I'm trying to create an interface that allows the user to drag in an mp3 or mp4 file and get the file path.
I created a rectangle to represent the drop area, but I'm struggling with the code for the View Model
<Rectangle x:Name="MyRectangle"
Width="200"
Height="200"
Fill="Gray"
Drop="MyRectangle_Drop"
AllowDrop="True"/>
If you are using MVVM structure with Dependency Injection, create a public class. Here is an example of what I did.
using System.Windows;
using System.Windows.Input;
namespace Test.Common
{
public class Behaviors
{
public static readonly DependencyProperty DropFileCommandProperty =
DependencyProperty.RegisterAttached("DropFileCommand", typeof(ICommand),
typeof(Behaviors), new FrameworkPropertyMetadata(
new PropertyChangedCallback(DropFileCommandChanged)));
private static void DropFileCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
element.Drop += Element_DropFile;
}
private static void Element_DropFile(object sender, DragEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
ICommand command = GeDropFileCommand(element);
command.Execute(e);
}
public static void SetDropFileCommand(UIElement element, ICommand value)
{
element.SetValue(DropFileCommandProperty, value);
}
public static ICommand GeDropFileCommand(UIElement element)
{
return (ICommand)element.GetValue(DropFileCommandProperty);
}
}
}
you can now in your view reference your class like this.
<Window x:Class="Test.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:common="clr-namespace:Test.Common"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
AllowDrop="True"
common:Behaviors.DropFileCommand="{Binding DropFile}"
Title="{Binding Title}">
<Grid>
</Grid>
</Window>
Now on your ViewModel you can do the following.
using Prism.Commands;
using Prism.Mvvm;
using System.Windows;
namespace Test.Views
{
public class MainWindowViewModel : BindableBase
{
private string _title = "TestDrop";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public MainWindowViewModel()
{
DropFile = new DelegateCommand<DragEventArgs>(dropFile);
}
public DelegateCommand<DragEventArgs> DropFile { get; }
private void dropFile(DragEventArgs obj)
{
var files = obj.Data.GetData(DataFormats.FileDrop, true) as string[];
//implement rest of code here
}
}
}
In your MyRectangle_Drop EventHandler, try this statement to get the directories of the dropped files.
var directories = (string[])e.Data.GetData(DataFormats.FileDrop);

Adding long press gesture recognizer in Xamarin forms

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

Xamarin Forms ListView Command to VM with EventToCommandBehavior

So i wanted to use commanding inside my ViewModel so i can interact with my listview. I looked at the forms example of EventToCommandBehavior. I tried to replicate the code inside my project but for some reason i can't get it to work.
What i got is this:
BehaviorBase.cs inside Sogeti.Core.Behaviors folder
using System;
using Xamarin.Forms;
namespace Sogeti.Core
{
public class BehaviorBase<T> : Behavior<T> where T : BindableObject
{
public T AssociatedObject { get; private set; }
protected override void OnAttachedTo(T bindable)
{
base.OnAttachedTo(bindable);
AssociatedObject = bindable;
if (bindable.BindingContext != null)
{
BindingContext = bindable.BindingContext;
}
bindable.BindingContextChanged += OnBindingContextChanged;
}
protected override void OnDetachingFrom(T bindable)
{
base.OnDetachingFrom(bindable);
bindable.BindingContextChanged -= OnBindingContextChanged;
AssociatedObject = null;
}
void OnBindingContextChanged(object sender, EventArgs e)
{
OnBindingContextChanged();
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
BindingContext = AssociatedObject.BindingContext;
}
}
}
EventToCommandBehavior.cs inside Sogeti.Core.Behaviors folder
using System;
using System.Reflection;
using System.Windows.Input;
using Xamarin.Forms;
namespace Sogeti.Core
{
public class EventToCommandBehavior : BehaviorBase<View>
{
Delegate eventHandler;
public static readonly BindableProperty EventNameProperty = BindableProperty.Create("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
public static readonly BindableProperty CommandProperty = BindableProperty.Create("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
public static readonly BindableProperty InputConverterProperty = BindableProperty.Create("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);
public string EventName
{
get { return (string)GetValue(EventNameProperty); }
set { SetValue(EventNameProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public IValueConverter Converter
{
get { return (IValueConverter)GetValue(InputConverterProperty); }
set { SetValue(InputConverterProperty, value); }
}
protected override void OnAttachedTo(View bindable)
{
base.OnAttachedTo(bindable);
RegisterEvent(EventName);
}
protected override void OnDetachingFrom(View bindable)
{
DeregisterEvent(EventName);
base.OnDetachingFrom(bindable);
}
void RegisterEvent(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return;
}
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
if (eventInfo == null)
{
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
}
MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo().GetDeclaredMethod("OnEvent");
eventHandler = methodInfo.CreateDelegate(eventInfo.EventHandlerType, this);
eventInfo.AddEventHandler(AssociatedObject, eventHandler);
}
void DeregisterEvent(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return;
}
if (eventHandler == null)
{
return;
}
EventInfo eventInfo = AssociatedObject.GetType().GetRuntimeEvent(name);
if (eventInfo == null)
{
throw new ArgumentException(string.Format("EventToCommandBehavior: Can't de-register the '{0}' event.", EventName));
}
eventInfo.RemoveEventHandler(AssociatedObject, eventHandler);
eventHandler = null;
}
void OnEvent(object sender, object eventArgs)
{
if (Command == null)
{
return;
}
object resolvedParameter;
if (CommandParameter != null)
{
resolvedParameter = CommandParameter;
}
else if (Converter != null)
{
resolvedParameter = Converter.Convert(eventArgs, typeof(object), null, null);
}
else
{
resolvedParameter = eventArgs;
}
if (Command.CanExecute(resolvedParameter))
{
Command.Execute(resolvedParameter);
}
}
static void OnEventNameChanged(BindableObject bindable, object oldValue, object newValue)
{
var behavior = (EventToCommandBehavior)bindable;
if (behavior.AssociatedObject == null)
{
return;
}
string oldEventName = (string)oldValue;
string newEventName = (string)newValue;
behavior.DeregisterEvent(oldEventName);
behavior.RegisterEvent(newEventName);
}
}
}
SelectedItemEventArgsToSelectedItemConverter.cs inside Sogeti.Core.Converters folder
using System;
using System.Globalization;
using Xamarin.Forms;
namespace Sogeti.Core
{
public class SelectedItemEventArgsToSelectedItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var eventArgs = value as SelectedItemChangedEventArgs;
return eventArgs.SelectedItem;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
MyViewModel inside Sogeti.Core.ViewModel folder
using System;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Input;
using Xamarin.Forms;
using System.Diagnostics;
using System.Threading.Tasks;
namespace Sogeti.Core
{
public class SogetistDetailsViewModel : SimpleViewModel
{
private Sogetist sogetist;
public ICommand ViewSelectedCommand { get; private set; }
public string FullName
{
get
{
return sogetist.Name + " " + sogetist.LastName;
}
}
public string Introduction
{
get
{
return sogetist.Introduction;
}
set
{
if (sogetist.Introduction != value)
{
sogetist.Introduction = value;
RaisePropertyChanged(() => Introduction);
}
}
}
public string Function
{
get
{
return sogetist.Function.Name;
}
set
{
if (value != sogetist.Function.Name)
{
sogetist.Function.Name = value;
RaisePropertyChanged(() => Function);
}
}
}
public string Skills
{
get
{
List<string> skills = sogetist.Skill.Select(x => x.Name).ToList();
return string.Join(", ", skills);
}
}
public string Image
{
get
{
return sogetist.Image;
}
set
{
if (value != sogetist.Image)
{
sogetist.Image = value;
RaisePropertyChanged(() => Image);
}
}
}
public SogetistDetailsViewModel() : this(new Sogetist())
{
}
public SogetistDetailsViewModel(Sogetist sogetist)
{
this.sogetist = sogetist;
Image = this.sogetist.Image;
ViewSelectedCommand = new Command<Sogetist>(OnViewSelected);
}
void OnViewSelected(Sogetist obj)
{
String a = obj.Name;
}
}
}
MainPage.Xaml inside Sogeti namespace
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="Sogeti.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:core="clr-namespace:Sogeti.Core;assembly=Sogeti.Core"
Title="Sogetist list">
<ContentPage.Resources>
<ResourceDictionary>
<core:SelectedItemEventArgsToSelectedItemConverter x:Key="SelectedItemConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ListView x:Name="listView"
CachingStrategy="RecycleElement"
Footer="{Binding Count}"
IsPullToRefreshEnabled="True"
ItemsSource="{Binding .}">
<ListView.Behaviors>
<core:EventToCommandBehavior EventName="ItemSelected" Command="{Binding ViewSelectedCommand}" Converter="{StaticResource SelectedItemConverter}" />
</ListView.Behaviors>
<ListView.FooterTemplate>
<DataTemplate>
<ContentView BackgroundColor="#FF4411" Padding="0,5">
<Label FontSize="Micro"
HorizontalTextAlignment="Center"
Text="{Binding .,
StringFormat='{0} Sogetists'}"
TextColor="White"
VerticalTextAlignment="Center">
<Label.Triggers>
<DataTrigger Binding="{Binding .}"
TargetType="Label"
Value="1">
<Setter Property="Text" Value="{Binding ., StringFormat='{0} Sogetist'}" />
</DataTrigger>
</Label.Triggers>
</Label>
</ContentView>
</DataTemplate>
</ListView.FooterTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell Detail="{Binding Function}"
ImageSource="{Binding Image}"
Text="{Binding FullName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
MainPage.Xaml.cs
using Xamarin.Forms;
using Sogeti.Core;
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using System.Collections.ObjectModel;
using System.Linq;
namespace Sogeti
{
public partial class MainPage : ContentPage
{
private readonly BackendlessHandler backendless = new BackendlessHandler();
public ObservableCollection<SogetistDetailsViewModel> Sogetists { get; private set; }
public MainPage()
{
InitializeComponent();
}
protected override async void OnAppearing()
{
base.OnAppearing();
if (Sogetists == null)
{
await LoadSogetistsAsync();
BindingContext = Sogetists;
}
}
private async Task LoadSogetistsAsync()
{
IsBusy = true;
try
{
var sogetistDetailsViewModelList = (await backendless.GetAllSogetistsAsync()).OrderBy(x => x.Name).Select(x => new SogetistDetailsViewModel(x));
Sogetists = new ObservableCollection<SogetistDetailsViewModel>(sogetistDetailsViewModelList);
}
catch (Exception ex)
{
await this.DisplayAlert("Error", "Failed to download sogetists: " + ex.Message, "OK");
}
finally
{
IsBusy = false;
}
}
}
}
Your problem is the binding to command you defined.
There is no ViewModel behind your MainPage, instead, currently you just write the logic in the View itself. I would suggest to use pure MVVM approach in order to simplify your solution and for this you need to:
Create a MainViewModel
Move all the logic from MainPage in to MainViewModel
Set the BindingContext of the MainPage to MainViewModel
So your MainViewModel will contain a list of ObservalbeCollection< SogetistDetailsViewModel> & your command with SogetistDetailsViewModel parameter defined only once.
I suggest to use this plugin. (here the source)
You can find a sample here, in my GitHub repository.
This is the 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:local="clr-namespace:TestListViewMultiSelectItems"
xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors"
x:Class="TestListViewMultiSelectItems.TestListViewMultiSelectItemsPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:SelectedItemEventArgsToSelectedItemConverter x:Key="SelectedItemConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Padding="20,20,20,20">
<Label Text = "{Binding SelectedItemsCounter, StringFormat='SelectedItems\' Counter {0}'}" HorizontalTextAlignment = "Center"/>
<ListView ItemsSource="{Binding Items}">
<ListView.Behaviors>
<behaviors:EventHandlerBehavior EventName="ItemTapped">
<behaviors:InvokeCommandAction Command="{Binding ItemTappedCommand}"/>
</behaviors:EventHandlerBehavior>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding DisplayName}" TextColor = "Fuchsia" HorizontalOptions = "StartAndExpand"/>
<BoxView Color="Fuchsia" IsVisible="{Binding Selected}" HorizontalOptions = "End"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
In this sample I use an ItemTappedCommand.
In my ViewModel
ItemTappedCommand = new Command((object model) => {
if (model != null && model is ItemTappedEventArgs) {
if (!((Model)((ItemTappedEventArgs)model).Item).Selected)
SelectedItemsCounter++;
else
SelectedItemsCounter--;
((Model)((ItemTappedEventArgs)model).Item).Selected = !((Model)((ItemTappedEventArgs)model).Item).Selected;
}
});

Categories

Resources