Xamarin TapGestureRecognizer tap event fired on background taps - xamarin

I was faced with some weird behavior of TapGestureRecognizer. I have some simple Xamarin page like below
<StackLayout>
<Path
ClassId="BottomCone"
Fill="{AppThemeBinding Dark=#333333, Light=#444444}"
Data="{x:Static local:MainPage.BottomConeGeometry}"
BackgroundColor="Red"
>
<Path.GestureRecognizers>
<TapGestureRecognizer Tapped="BottomConeTapped" />
</Path.GestureRecognizers>
</Path>
</StackLayout>
Though it's actually just some simple closed shape drown with Path element on which I have some handler of tap event. And the problem is that event is firing every time when I'm clicking on Path or its background. Is it possible to not fire the tap event when the tap happened in the background of the path? And fire the event just in case the tap event happened inside a Path element.

So the shapes have overlapping "bounds" (the rectangle surrounding each shape).
One technique is to have a set of rectangles that approximates each of the shapes. The concept is to make the shapes themselves InputTransparent, and have an overlapping set of invisible boxes that capture touch.
OverlappingShapeButtonsPage.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="XFSOAnswers.OverlappingShapeButtonsPage">
<ContentPage.Resources>
<Style TargetType="BoxView">
<!-- Uncomment, to see where OnTapped1 boxes are. -->
<!--<Setter Property="BackgroundColor" Value="Red"/>-->
</Style>
</ContentPage.Resources>
<ContentPage.Content>
<Grid>
<AbsoluteLayout InputTransparent="True">
<Polygon Points="0,0 80,0 0,60" Fill="Green" />
<Polygon Points="80,0 80,60 0,60" Fill="Pink" />
</AbsoluteLayout>
<Grid RowDefinitions="20,20,20" ColumnDefinitions="20,20,20,20"
RowSpacing="0" ColumnSpacing="0">
<Grid.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapped2" />
</Grid.GestureRecognizers>
<BoxView Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
<BoxView.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapped1" />
</BoxView.GestureRecognizers>
</BoxView>
<BoxView Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<BoxView.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapped1" />
</BoxView.GestureRecognizers>
</BoxView>
<BoxView Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="1">
<BoxView.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapped1" />
</BoxView.GestureRecognizers>
</BoxView>
</Grid>
</Grid>
</ContentPage.Content>
</ContentPage>
OverlappingShapeButtonsPage.xaml.cs:
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace XFSOAnswers
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class OverlappingShapeButtonsPage : ContentPage
{
public OverlappingShapeButtonsPage()
{
InitializeComponent();
}
/// <summary>
/// This is called when any of the BoxViews are touched.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void OnTapped1(object sender, EventArgs e)
{
}
/// <summary>
/// This is called when the grid is touched anywhere that is not inside one of the BoxViews.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void OnTapped2(object sender, EventArgs e)
{
}
}
}
What user sees:
Red showing boxes for OnTapped1, approximating Green triangle:
This works reasonably well, because user will tend to touch near the center of the shape they are interested in.
You could refine this by doing simple (x,y) math calculations that approximate the edge between two shapes.
In General, Make sure that you aren't expecting unrealistic precision from user's touch. Primarily, that means that the touch areas should not be too small (more than one in a small area).
Consider these (or similar) guidelines: Optimal Size and Spacing for Mobile Touch.

Related

How to create a table with vertically sticky header and horizontally sticky first column using Xamarin Forms?

When displaying tabular data, I think that in some cases having an always visible header row and an always visible first column can really improve the readability and the overall usability of a table, especially if there is a lot of data in the table. The problem occurs when the table has to support both horizontal and vertical scrolling. A good example of such a table can be found from the NBA application when viewing box score of a past game. Here's an example image from the NBA Android application:
Example table from NBA mobile application
As you can clearly see from the image the header row is horizontally aligned with the actual table data and the first column is vertically aligned with the table data. I don't know whether or not it's an involuntary or a voluntary decision to prevent scrolling both horizontally and vertically with the same touch motion but that's a minor detail I don't care about.
I don't know how to implement this using Xamarin Forms. I am not interested in a closed source / paid solution since I would like to actually learn how to accomplish this by myself. I do realize that I most likely have to use custom renderers for both Android and IOS. My current idea is that I have an absolute layout where I have the following elements:
The first cell (it's stationary and the only stationary thing)
Rest of the header row inside a horizontal scrollview
First column inside a listview/stacklayout + vertical scrollview
The actual table data inside a listview + horizontal scrollview / stacklayout + horizontal and vertical scrollview
With this setup I would capture the touch event and send it to the other listviews/scrollviews, thus synchronizing the scrolling. In fact I can easily achieve the synchronized scrolling with the first column and the actual table data by setting the table data inside the same vertical scrollview as the first column. But I don't know how to synchronize the horizontal scrolling to the header row and I do believe that this can't be accomplished by clever component structure. I have tested only on Android so far that I can capture the touch event in a scrollview custom renderer's OnTouchEvent -method but I don't know how I could send this to the header row scrollview from the custom renderer.
Here is a draft XAML illustrating my approach.
<AbsoluteLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
HorizontalOptions="FillAndExpand">
<ScrollView
Orientation="Horizontal"
x:Name="HeaderScrollView"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<!-- Skip first column, leave it empty for stationary cell -->
<Label Text="Column 1" Grid.Row="0" Grid.Column="1" />
<Label Text="Column 2" Grid.Row="0" Grid.Column="2" />
<Label Text="Column 3" Grid.Row="0" Grid.Column="3" />
<Label Text="Column 4" Grid.Row="0" Grid.Column="4" />
</Grid>
</ScrollView>
<ScrollView
x:Name="FirstColumnScrollView"
Orientation="Vertical"
AbsoluteLayout.LayoutBounds="0,50,1,1"
AbsoluteLayout.LayoutFlags="SizeProportional"
BackgroundColor="Aqua">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackLayout
Grid.Column="0"
Grid.Row="0"
BindableLayout.ItemsSource="{Binding DataSource}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Column1}" Grid.Row="0" Grid.Column="0" />
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
<ScrollView
x:Name="TableDataScrollView"
Grid.Column="1"
Grid.Row="0"
Orientation="Horizontal">
<StackLayout
BindableLayout.ItemsSource="{Binding DataSource}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Label Text="{Binding Column2}" Grid.Row="0" Grid.Column="0" />
<Label Text="{Binding Column3}" Grid.Row="0" Grid.Column="1" />
<Label Text="{Binding Column4}" Grid.Row="0" Grid.Column="2" />
<Label Text="{Binding Column5}" Grid.Row="0" Grid.Column="3" />
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
</Grid>
</ScrollView>
<Label Text="First Column" BackgroundColor="White" AbsoluteLayout.LayoutBounds="0,0,200,50" />
</AbsoluteLayout>
As you can see the problem is that horizontal scrolling events between HeaderScrollView and TableDataScrollView are not shared and I don't know how to accomplish this in the best way possible or at all.
I do appreciate all the help and feedback with this!
What you are looking for is a DataGrid component with Frozen row and Frozen column feature. There are some third party components that would meet your requirements.
Syncfusion, Telerik and Infragistics DataGrids have the features you are looking for. Refer below links.
https://www.syncfusion.com/xamarin-ui-controls/xamarin-datagrid
https://www.telerik.com/xamarin-ui/datagrid
https://www.infragistics.com/products/xamarin/grids-and-lists/data-grid
There are few open-source DataGrid available as well. But not sure whether they have the row and column pinning features. Check the below links.
https://github.com/akgulebubekir/Xamarin.Forms.DataGrid
https://www.nuget.org/packages/Xamarin.Forms.DataGrid/
For open source, you could use Zumero DataGrid for Xamarin.Forms. It supports scrolling, both horizontal and vertical, optional top frozen header row, optional left frozen column and so on. You could download the sample code form the link below.
Zumero DataGrid for Xamarin.Forms: https://github.com/zumero/DataGrid/tree/8caf4895e2cc4362da3dbdd4735b5c6eb1d2dec4
For the sample code, if you get the error below, run as admin would be okay.
Build action 'EmbeddedResource' is not supported by one or more of the project's targets
Thanks for the help with this #Harikrishnan and #Wendy Zang - MSFT ! The Zumero DataGrid inspired me to do the motion event handling differently from the usual motion event handling flow. I basically created the following custom renderer for the AbsoluteLayout
using Android.Content;
using Android.Views;
using Test.Droid;
using Test.Views;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using View = Android.Views.View;
[assembly: ExportRenderer(typeof(StatisticsTable), typeof(StatisticsTableRenderer))]
namespace Test.Droid
{
public class StatisticsTableRenderer : ViewRenderer
{
private View _headerScrollView;
private View _tableScrollView;
private float _startX;
public StatisticsTableRenderer(Context context) : base(context)
{
}
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
if (_headerScrollView == null || _tableScrollView == null)
{
// Completely dependant on the structure of XAML
_headerScrollView = GetChildAt(0);
_tableScrollView = GetChildAt(1);
}
return true;
}
public override bool OnTouchEvent(MotionEvent ev)
{
if (ev.Action == MotionEventActions.Down)
{
_startX = ev.GetX();
}
var headerScroll = false;
if (_startX > _headerScrollView.GetX())
{
headerScroll = _headerScrollView.DispatchTouchEvent(ev);
}
var tableScroll = _tableScrollView.DispatchTouchEvent(ev);
return headerScroll || tableScroll;
}
}
}
As you can see I always intercept the motion event and then manually dispatch it to the children. However that was not enough. I had to prevent HeaderScrollView from scrolling when the motion event didn't start inside of it because the TableDataScrollView wouldn't scroll if the motion event wasn't started inside of it. I also had to create custom renderers for all scrollviews in this table. TableDataScrollView and HeaderScrollView were using the same custom renderer. The only thing that custom renderer implemented was OnInterceptTouchEvent like this:
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
return false;
}
I am not quite sure why this is necessary but it seems to have done the trick for me. I suppose that sometimes the HeaderScrollView would intercept the motion event and this caused the header to scroll without scrolling of the table data.
The vertical scrollview aka FirstColumnScrollView in the question's XAML had to implement motion event handling differently because it is the parent of the TableDataScrollView and we are now handling motion events in a top-to-bottom manner instead of the default Android way of bottom-to-top. This caused issues where FirstColumnScrollView would simply handle the motion event and not pass it to TableDataScrollView which would then lead to the header and actual table data to be out of sync with each other. This is why I added the following custom renderer for it
using Android.Content;
using Android.Views;
using Test.Droid;
using Test.Views;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using View = Android.Views.View;
[assembly: ExportRenderer(typeof(ChildFirstScrollView), typeof(ChildFirstScrollViewRenderer))]
namespace Test.Droid
{
public class ChildFirstScrollViewRenderer : ScrollViewRenderer
{
private View _childView;
public ChildFirstScrollViewRenderer(Context context) : base(context)
{
}
public override bool DispatchTouchEvent(MotionEvent e)
{
if (_childView == null)
{
_childView = GetChildAt(0);
}
_childView.DispatchTouchEvent(e);
return base.DispatchTouchEvent(e);
}
public override bool OnInterceptTouchEvent(MotionEvent ev)
{
return true;
}
}
}
In this ScrollView we always intercept/handle the motion event and we always send it to the child ScrollView first before handling the motion event.
I also had to do some minor adjustments to the XAML shown in the question. I set the starting X of HeaderScrollView to the width of the first column so it doesn't actually go under the static header of the first column. However this caused issues because I was unable to use width of the AbsoluteLayout (Why is it so hard in XAML?) to calculate the correct width for the HeaderScrollView. Now the width was set in a way that a part of the HeaderScrollView will always be outside of the viewport causing the last header to be never shown. So I added a "PaddingColumn" to the header grid with a width equal to the first column. I also had to add a "PaddingRow" to the FirstColumnScrollView grid for the same reason.
One other thing I had to do was to set the spacing of the grid inside FirstColumnScrollView to 0. Without that, there was this small gap from where you could start motion events that would only scroll the header and not the table data.
This is only the Android solution at the moment but I'll come back with the iOS one if I can accomplish it.

Xamarin Forms ProgressBar Height is not controllable

We have upgraded from Xamarin Forms 2.5 to 3.4.
The ProgressBar on Android becomes thicker and not controllable by XAML's HeightRequest, while it was before.
It is within a grid layout.
Any ideas how to control the height?
You can modify the height of the progressbar by using Render in xamarin.
CustomProgressBarRenderer.cs
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(App69.CustomProgressBar), typeof(App69.Droid.CustomProgressBarRenderer))]
namespace App69.Droid
{
public class CustomProgressBarRenderer : ProgressBarRenderer
{
public CustomProgressBarRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ProgressBar> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.ScaleY = 10; //Changes the height
}
}
}
}
MainPage.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:App69"
x:Class="App69.MainPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="Top Left" Grid.Row="0" Grid.Column="0" />
<Label Text="Top Right" Grid.Row="0" Grid.Column="1" />
<Label Text="Bottom Left" Grid.Row="0" Grid.Column="1" />
<local:CustomProgressBar Progress="0.5" Grid.Row="1" Grid.Column="1" />
</Grid>
</ContentPage>
class CustomProgressBarRenderer : ProgressBarRenderer
{
/// <summary>
/// Raises the <see cref="E:ElementChanged" /> event.
/// </summary>
/// <param name="e">The <see cref="ElementChangedEventArgs{Xamarin.Forms.ProgressBar}"/> instance containing the event data.</param>
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ProgressBar> e)
{
base.OnElementChanged(e);
var element = Element as CustomProgressBar;
Control.IndeterminateDrawable.SetColorFilter(element.BarColor.ToAndroid(), PorterDuff.Mode.SrcIn);
Control.ProgressDrawable.SetColorFilter(element.BarColor.ToAndroid(), PorterDuff.Mode.SrcIn);
Control.ScaleY = element.BarHeight;
}
/// <summary>
/// Called when [element property changed].
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var element = Element as CustomProgressBar;
Control.IndeterminateDrawable.SetColorFilter(element.BarColor.ToAndroid(), PorterDuff.Mode.SrcIn);
Control.ProgressDrawable.SetColorFilter(element.BarColor.ToAndroid(), PorterDuff.Mode.SrcIn);
Control.ScaleY = element.BarHeight;
}
}

Xamarin Forms Switch Toggled event doesn't bind with viewmodel

I have a Forms XAML Page and in there I have a listview, and each element has a Switch (xamarin default). I can bind the data from the items to the listview, but I cannot subscrive the Switch event "Toggled", as it causes the item not to show. I also tried with ICommand and Command, as it is instructed to do with buttons, but the result is the same, nothing shown. How can I handle the switch toggle from the my viewmodel?
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="TouristicWallet.Views.WalletManagementPage"
xmlns:vm="clr-namespace:TouristicWallet.ViewModels"
xmlns:converters="clr-namespace:TouristicWallet.Converters"
>
<ContentPage.BindingContext>
<vm:WalletManagementViewModel x:Name="ViewModel"/>
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<converters:CurrencyIdToCodeConverter x:Key="idToCodeConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<ListView x:Name="MyCurrencies" ItemsSource="{Binding Currencies, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Currency.Initials, Mode=OneWay}" />
<Switch IsToggled="{Binding IsOwned, Mode=TwoWay}"
Toggled="{Binding Toggled}"
/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
ViewModel
public class WalletManagementViewModel : ViewModelBase
{
private readonly List<OwnedCurrencyWrapper> _currencies = new List<OwnedCurrencyWrapper>();
public List<OwnedCurrencyWrapper> Currencies { get { return _currencies; } }
public WalletManagementViewModel()
{
CurrencyDataAccess cda = new CurrencyDataAccess();
foreach (var item in cda.GetCurrencies())
{
Currencies.Add(new OwnedCurrencyWrapper(item));
}
OnPropertyChanged(nameof(Currencies));
}
public class OwnedCurrencyWrapper
{
public Currency Currency { get; private set; }
public Boolean IsOwned { get; set; }
public ICommand Toggled { get; set; }
public OwnedCurrencyWrapper(Currency currency)
{
Currency = currency;
WalletDataAccess wda = WalletDataAccess.Instance;
IsOwned = wda.IsOwned(Currency.Id);
Toggled = new Command(() => Update());
}
public void Update()
{
WalletDataAccess wda = WalletDataAccess.Instance;
if (IsOwned) wda.RemoveOwnedCurrency(Currency.Id);
else wda.OwnCurrency(Currency.Id);
}
public void Toggled_handler(object sender, ToggledEventArgs e)
{
Update();
}
}
}
I am not using any mvvm framework
First off a Switch can not bind to a Command. See:
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/#Commanding_with_ViewModels
From the above, the Forms controls that can bind to an ICommand are:
Button
MenuItem
ToolbarItem
SearchBar
TextCell (and hence also
ImageCell )
ListView
TapGestureRecognizer
you can just do the following to run code in the View's code behind file, do this in the XAML:
<Switch IsToggled="{Binding IsOwned, Mode=TwoWay}"
Toggled="Handle_Toggled" />
And then in the Code behind file:
void Handle_Toggled(object sender, Xamarin.Forms.ToggledEventArgs e)
{
// Do stuff
}
Alternately, since you are binding, you could run code in the actual OwnedCurrencyWrapper class (which is what you seem to want) just by adding code to the setter for IsOwned. IN this case, don't assign anything to the Toggled property of your switch::
<Switch IsToggled="{Binding IsOwned, Mode=TwoWay}" />
And then in your OwnedCurrencyWrapper class:
bool _isOwned;
public bool IsOwned {
get
{
return _isOwned;
}
set
{
_isOwned = value;
// Do any other stuff you want here
}
}
That said, your binding is not complete since your view model is not implementing INotifyPropertyChanged so changes made directly to the view model will not be reflected in the UI. For more info on binding with Forms MVVM, see:
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/
UPDATE: I was not aware of Behaviors in Xamarin Forms. See:
https://github.com/xamarin/xamarin-forms-samples/tree/master/Behaviors/EventToCommandBehavior
In the context of commanding, behaviors are a useful approach for connecting a control to a command. In addition, they can also be used to associate commands with controls that were not designed to interact with commands. This sample demonstrates using a behavior to invoke a command when an event fires.
So this should allow you to bind the Toggled event to a Command.
If you adhere to Prism framework you may easily wire an event to a command. Your xaml will look like in the following example.
<?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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:b="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
x:Class="TouristicWallet.Views.WalletManagementPage">
<ContentPage.Content>
<StackLayout VerticalOptions="CenterAndExpand" Padding="20">
<Switch IsToggled="{Binding IsOwned}" x:Name="IsOwnedSwitch">
<Switch.Behaviors>
<b:EventToCommandBehavior EventName="Toggled" Command="{Binding ToggleIsOwnedCommand}"/>
</Switch.Behaviors>
</Switch>
</StackLayout>
</ContentPage.Content>
</ContentPage>
As others have mentioned, you should bind the Toggled event to an eventHandler behavior which will forward a command. The code below can be used.
<Switch IsToggled="{Binding SwitchEnabled}" x:Name="MySwitch">
<Switch.Behaviors>
<!-- behaviors namespace comes from "Xamarin.Forms Behaviors" nuget -->
<behaviors:EventHandlerBehavior EventName="Toggled">
<behaviors:InvokeCommandAction Command="{Binding ToggleSwitchCommand}" />
</behaviors:EventHandlerBehavior>
</Switch.Behaviors>
</Switch>
Solution : After doing some R&D i found the root cause of this issue,
Error Code in very first post:
<Switch IsToggled="{Binding IsOwned, Mode=TwoWay}"
Toggled="{Binding Toggled}"
/>
Just do Two steps.
Declare event listener function OnToggled in ContentPage class and not into your ViewModel class that you need to bind
In your ContentPage class
void OnToggled(object sender, ToggledEventArgs e){
}
change Toggled="{Binding Toggled}" == to ==> Toggled="OnToggled"
it will fix the issue, Don't know why it don't work for event listener function declared in ViweModel class.
--I hope it will work.
I had the same issue and solved it in a very easy way.
=> Goal: Get items with a switch control in a listview to respond to a command.
<?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="TouristicWallet.Views.WalletManagementPage"
xmlns:vm="clr-namespace:TouristicWallet.ViewModels"
x:Name="pageName"
xmlns:converters="clr-namespace:TouristicWallet.Converters"
>
<ContentPage.BindingContext>
<vm:WalletManagementViewModel x:Name="ViewModel"/>
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<converters:CurrencyIdToCodeConverter x:Key="idToCodeConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<ListView x:Name="MyCurrencies" ItemsSource="{Binding Currencies, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Currency.Initials, Mode=OneWay}" />
<Switch IsToggled="{Binding Selected}" HorizontalOptions="Start">
<Switch.Behaviors>
<b:EventToCommandBehavior
EventName="Toggled" Command="
{Binding
Path=BindingContext.SendCommand,
Source={x:Reference
Name=pageName}}" />
</Switch.Behaviors>
</Switch>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
In ViewModel
Define your Command /ICommand
public ICommand SendCommand { get; set; }
SendCommand = new Command(() => //do something.....);
Please Take special note of the areas in bold.

Xamarin Forms TableView selected item handler?

I need to trigger a command when user clicks on the table cell.
I looked at documentation on tablecell, it doesn't talk about any click event handlers.
This is how my XAML looks -
<ContentPage Title="Default">
<TableView>
<TableView.Root>
<TableSection>
<ViewCell>
<StackLayout Padding="10,0,0,0" Orientation="Horizontal">
<Image Source="..." />
<Button Text="Home" Command="{Binding NavigateCommand}" CommandParameter="Home" />
</StackLayout>
</ViewCell>
</TableSection>
</TableView.Root>
</TableView>
</ContentPage>
Right now, command is triggered only when clicked on button, but I want to trigger command when the cell is clicked.
I am new to xamarin, is custom viewcell rendering the only way to achieve this?
If so, any pointers there?
Thanks!
You can use the Tapped gesture recognizer within a ViewCell.
Xaml:
<TableView>
<TableView.Root>
<TableSection>
<ViewCell Tapped="OnViewCellTapped">
~~~~~
</ViewCell>
</TableSection>
</TableView.Root>
</TableView>
Code behind:
protected void OnViewCellTapped(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Tapped");
}

Scrollviewer with TextBoxes inside Pivot behavior make TextBoxes disappear under keybord

Does any body know a nice way to manage the behavior of the scrollviewer inside a PivotItem. While the Scrollviewer contains a Stackpannel with 6 TextBoxes and 6 TextBlockes. (See my code ferder below...)
The way I want it to work: No matter what Textbox I select... It should stay visible and all other textboxes should be reachable while the keybord is shown...
It is no problem if the header of the pivot disappears... But it would be nice if it stay on screan too...
I've tried with margins, rectangles with dynamic hights, resizing of the RootFrame, resizing the Pivot, resizing the ViewScroller height... to make the it fit and work... I come close with the resizing actions. But the focus of the TextBox is sometimes behind the keybord.
How to manage to scroll/move the selected TextBox to the top of my screen in this situation...
I hope one of you can help me out...
Here my code of my XAML:
<Grid x:Name="LayoutRoot"
Background="Transparent">
<!--Pivot Control-->
<phone:Pivot Title="MY APPLICATION"
x:Name="PivotRoot">
<phone:PivotItem Header="first"
x:Name="PivotFirst">
<ScrollViewer x:Name="Scroller">
<StackPanel Background="Orange">
<TextBlock Text="hoi" />
<TextBox GotFocus="TextBlock_GotFocus_1"
LostFocus="TextBlock_LostFocus_1"></TextBox>
<TextBlock Text="hoi" />
<TextBox GotFocus="TextBlock_GotFocus_1"
LostFocus="TextBlock_LostFocus_1"></TextBox>
<TextBlock Text="hoi" />
<TextBox GotFocus="TextBlock_GotFocus_1"
LostFocus="TextBlock_LostFocus_1"></TextBox>
<TextBlock Text="hoi" />
<TextBox GotFocus="TextBlock_GotFocus_1"
LostFocus="TextBlock_LostFocus_1"></TextBox>
<TextBlock Text="hoi" />
<TextBox GotFocus="TextBlock_GotFocus_1"
LostFocus="TextBlock_LostFocus_1"></TextBox>
</StackPanel>
</ScrollViewer>
</phone:PivotItem>
</phone:Pivot>
</Grid>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar x:Name="xxxx"
IsVisible="True"
IsMenuEnabled="True">
<shell:ApplicationBarIconButton x:Name="appBarRegisterButton"
IconUri="/Images/next.png"
Text="Login"
Click="appBarRegisterButton_Click_1"
IsEnabled="True" />
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
Here my codebehind:
private void appBarRegisterButton_Click_1(object sender, EventArgs e)
{ }
private void TextBlock_GotFocus_1(object sender, RoutedEventArgs e)
{ }
private void TextBlock_LostFocus_1(object sender, RoutedEventArgs e)
{ }
Cheers,
K
The closest solution I was able to get is the following. The use of this solution managed in my flow of the application. It is not perfict yet. I'll say 95%... The biggest problem now is to manage the "dynamically appear and disappear" of the "Rectangle 'Keybordfiller'" I've used... I thought it was easy... unfortunately I didn't manage to make it happen... Feel free to use this and improve to make it happen... This will help a lot of people with this hard problem...
The MainPage.Xaml:
<phone:PhoneApplicationPage x:Class="PivotApp1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait"
Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<Grid x:Name="LayoutRoot"
Background="Transparent">
<!--Pivot Control-->
<phone:Pivot Title="MY APPLICATION"
x:Name="PivotRoot">
<phone:PivotItem Header="first"
x:Name="PivotFirst">
<ScrollViewer x:Name="Scroller">
<StackPanel Background="Orange">
<TextBlock Text="hey" />
<TextBox GotFocus="TextBlock_GotFocus" />
<TextBlock Text="how" />
<TextBox GotFocus="TextBlock_GotFocus" />
<TextBlock Text="are" />
<TextBox GotFocus="TextBlock_GotFocus" />
<TextBlock Text="you" />
<TextBox GotFocus="TextBlock_GotFocus" />
<TextBlock Text="doing" />
<TextBox GotFocus="TextBlock_GotFocus" />
<Rectangle x:Name="Keybordfiller"
Height="350" />
</StackPanel>
</ScrollViewer>
</phone:PivotItem>
</phone:Pivot>
</Grid>
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar x:Name="Xxxx"
IsVisible="True"
IsMenuEnabled="True">
<shell:ApplicationBarIconButton x:Name="AppBarRegisterButton"
IconUri="/Assets/Tiles/IconicTileSmall.png"
Text="Login"
Click="appBarRegisterButton_Click"
IsEnabled="True" />
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
The Code behind:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Navigation;
namespace PivotApp1
{
public partial class MainPage
{
public MainPage()
{
InitializeComponent();
DataContext = App.ViewModel;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
private void appBarRegisterButton_Click(object sender, EventArgs e)
{
//TODO: do some action with the button click
}
private void TextBlock_GotFocus(object sender, RoutedEventArgs e)
{
ScrollToControl((FrameworkElement)sender);
}
/// The issue to make it 100% fine For some reason it is not possible to make the Rectangle "Keybordfiller" Dynamically appear and disappear
/// to have this issue solved...
///
/// When you use this code and manage to make it work 100%...
/// Please post your code/solution at: http://social.msdn.microsoft.com/Forums/en-US/wpdevelop/thread/b6fb4623-2fd3-459e-8c80-6ac2a77ee849/#a1f7d0fc-289c-40c9-8716-06e90c9dacd1
///
#region 90% fix of 'ScrollViewer with stackpannel full of Textboxes' handling in Pivot
//TODO: Manage to make the Rectangle "Keybordfiller" appear and disappear Dynamically on the stackpannel
private void ScrollToControl(FrameworkElement ui)
{
var currentoffset = Scroller.VerticalOffset;
var y = GetVerticalOffset(ui, Scroller);
Scroller.ScrollToVerticalOffset(y + currentoffset - 30);
Scroller.UpdateLayout();
}
private double GetVerticalOffset(FrameworkElement child, ScrollViewer scrollViewer)
{
GeneralTransform focusedVisualTransform = child.TransformToVisual(scrollViewer);
var topLeft = focusedVisualTransform.Transform(new Point(0, 0));
return topLeft.Y;
}
#endregion
}
}
I hope some one find the time to complete the functionality and make it Perfect ;-)
Cheers,
K

Resources