Xamarin Forms ActivityIndicator UWP Always Running - xamarin

I have a problem with the ActivityIndicator in a Xamarin UWP project. The indicator is always running. I have to set the property IsVisible to hide the indicator. I want to do a platform specific condition on ActivityIndicator and to set the property IsVisible only when platform is Windows.
This is what I tried:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.LoginPage">
<StackLayout Padding="10" Spacing="10">
<Label Text="User" />
<Entry Text="{Binding Email}" Placeholder="User" />
<Label Text="Pass" />
<Entry Text="{Binding Password}" Placeholder="Pass" />
<Button Text="Autentificare" />
<ActivityIndicator IsRunning="{Binding IsBusy}">
<OnPlatform x:TypeArguments="x:Boolean">
<On Platform="Windows" Value="IsVisible">{Binding IsBusy}</On>
</OnPlatform>
</ActivityIndicator>
</StackLayout>
</ContentPage>
I tried to use the OnPlatform property, but I don't know how to do it correctly. Any idea?

I have tested your code and reproduce your issue. You could find the cause from source code.
void UpdateIsRunning()
{
Control.ElementOpacity = Element.IsRunning ? Element.Opacity : 0;
}
The IsRunning property is only a condition for setting the transparency of the
native Control rather than changing Active property for native control . But it does not work as expected. I will report this issue to related team. Currently there is a workaround. You could bind IsBusy to IsVisible and IsRunning just like the following.
<ActivityIndicator IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}"/>
UPDATE
You could create CustomActivityIndicator class that inherit ActivityIndicator. And then implement the custom renderer for it within native client project. For more please refer to the following code.
CustomActivityIndicator.cs
public class CustomActivityIndicator : ActivityIndicator
{
public CustomActivityIndicator()
{
}
}
CustomActivityIndicatorRenderer.cs
[assembly: ExportRenderer(typeof(CustomActivityIndicator), typeof(CustomActivityIndicatorRenderer))]
namespace XamarinActivatorTest.UWP
{
public class CustomActivityIndicatorRenderer : ActivityIndicatorRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ActivityIndicator> e)
{
base.OnElementChanged(e);
if (Control != null)
{
UpdateStatus();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(Element.IsRunning))
{
UpdateStatus();
}
}
private void UpdateStatus()
{
Control.ShowPaused = !Element.IsRunning;
Control.Opacity = Element.IsRunning ? 1 : 0;
}
}
}
You could bind IsRunning property directly. Because the function of IsRunningproperty was changed in your custom renderer.
<StackLayout Padding="10" Spacing="10">
<Button Text="Autentificare" Clicked="Button_Clicked"/>
<local:CustomActivityIndicator IsRunning="{Binding IsBusy}" >
</local:CustomActivityIndicator>
</StackLayout>
I have uploaded the code sample to git hub.

Related

Custom Button renderer and unexpected properties change

I'm currently trying to create a Button with custom color text when disabled. For this point i successfully did the job with a custom renderer.
But an unexpected side effect as came. Some button style are disapplying when passing on this specific state.
As you can see on the image above the button icon position is different and the cornerRadius property isn't applied anymore.
In the enable state with the custom button renderer i recover the cornerRadius property (actually i can resolve this problem setting each time the radius in the renderer, so isn't really a problem). The icon remain at the top and doesn't respect the ContentLayout margin property. The renderer i expect is the left button who is did with normal button component.
Here is the declaration of my control (i put the left button too to demonstrate the settings are the same:
<StackLayout Orientation="Horizontal" Margin="0,50,0,0">
<Button Text="Disponibilités de l'élève" BackgroundColor="{StaticResource Primary}" BorderColor="LightGray" BorderWidth="1" TextColor="White" FontAttributes="Bold" CornerRadius="10" ContentLayout="Top,25" WidthRequest="{Binding ButtonWidth}" HorizontalOptions="CenterAndExpand" Command="{Binding OpenMapCommand}">
<Button.ImageSource>
<FontImageSource FontFamily="FASolid" Color="White" Glyph="{x:Static fa:FontAwesomeIcons.CalendarDays}"/>
</Button.ImageSource>
</Button>
<!--<Button Text="Poser une option" BackgroundColor="{StaticResource Primary}" TextColor="White" BorderColor="LightGray" BorderWidth="1" FontAttributes="Bold" CornerRadius="10" ContentLayout="Top,25" WidthRequest="{Binding ButtonWidth}" HorizontalOptions="CenterAndExpand" Command="{Binding SubmitOptionCommand}">
<Button.ImageSource>
<FontImageSource FontFamily="FARegular" Color="White" Glyph="{x:Static fa:FontAwesomeIcons.CalendarPlus}"/>
</Button.ImageSource>
</Button>-->
<controls:DisableExtentButton Text="Poser une option" BackgroundColor="{StaticResource Primary}" DisabledBackgroundColor="LightGreen" TextColor="White" DisabledTextColor="Green" BorderColor="LightGray" BorderWidth="1" FontAttributes="Bold" CornerRadius="10" ContentLayout="Top,25" WidthRequest="{Binding ButtonWidth}" HorizontalOptions="CenterAndExpand" Command="{Binding SubmitOptionCommand}">
<Button.Triggers>
<DataTrigger TargetType="Button" Binding="{Binding OptionTaken}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="ImageSource">
<Setter.Value>
<FontImageSource FontFamily="FARegular" Color="Green" Glyph="{x:Static fa:FontAwesomeIcons.CalendarCheck}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Button.Triggers>
<Button.ImageSource>
<FontImageSource FontFamily="FARegular" Color="White" Glyph="{x:Static fa:FontAwesomeIcons.CalendarPlus}"/>
</Button.ImageSource>
</controls:DisableExtentButton>
</StackLayout>
Here is the declaration of my custom Control:
public class DisableExtentButton : Button
{
public Color DisabledTextColor
{
get { return (Color)GetValue(DisabledTextColorProperty); }
set { SetValue(DisabledTextColorProperty, value); }
}
public Color DisabledBackgroundColor
{
get { return (Color)GetValue(DisabledBackgroundColorProperty); }
set { SetValue(DisabledBackgroundColorProperty, value); }
}
public static readonly BindableProperty DisabledTextColorProperty = BindableProperty.Create(nameof(DisabledTextColor), typeof(Color), typeof(DisableExtentButton), Color.Gray);
public static readonly BindableProperty DisabledBackgroundColorProperty = BindableProperty.Create(nameof(DisabledBackgroundColor), typeof(Color), typeof(DisableExtentButton), Color.DarkGray);
}
The android renderer:
public class DisableExtentButtonRenderer : ButtonRenderer
{
public DisableExtentButtonRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
if (Control != null)
SetColors();
base.OnElementChanged(e);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Button.IsEnabled))
SetColors();
base.OnElementPropertyChanged(sender, e);
}
private void SetColors()
{
var element = (DisableExtentButton)this.Element;
Control.SetTextColor(Element.IsEnabled ? Element.TextColor.ToAndroid() : element.DisabledTextColor.ToAndroid());
Control.SetBackgroundColor(Element.IsEnabled ? Element.BackgroundColor.ToAndroid() : element.DisabledBackgroundColor.ToAndroid());
}
}
I didn't successfully found why the ContentLayout property isn't applied correctly anymore and more for informational purpose why the corner radius is reseted when passing into OnElementPropertyChanged. If i redo the button without custom renderer it work as expected like the left button.

Passing an object to a Behaviour in XAML is not working

I have a list of Settings that are a mix of types (int and string, url, ip address etc.) and the parameters that allow these to be validated are stored alongside the actual value in an object. This object is selected from a list of settings and passed to a detail page where the value can be modified.
On the detail page, I have an Entry control that contains the value that requires validation. The Entry takes it's inital value from the ViewModel property SettingItem. This works fine.
I have a Behaviour that i'm going to use to validate the user input to the value on-the-fly however, when the HandleTextChanged method is fired, ItemToValidate is null. I can't work out why.
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"
x:Class="DwyApp.Views.ItemDetailPage"
xmlns:local="clr-namespace:DwyApp.Behaviours"
Title="{Binding Title}">
<StackLayout Spacing="20" Padding="15">
<Label Text="{Binding SettingItem.ParameterName}" FontSize="Medium" FontAttributes="Bold"/>
<Entry Text="{Binding SettingItem.Value}" FontSize="Medium">
<Entry.Behaviors>
<local:ValidateBehaviour ItemToValidate="{Binding SettingItem}" />
</Entry.Behaviors>
</Entry>
<Label x:Name="Value_Error" Text="{Binding SettingItem.ValidationMessage}" FontSize="Medium" IsVisible="{Binding SettingItem.ValidateValue}" TextColor="Red" />
<Button Text="Save"
VerticalOptions="Center"
HorizontalOptions="Center"/>
<Label Text="{Binding SettingItem.Description}" FontSize="Medium"/>
</StackLayout>
Behaviour:
using DwyApp.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Xamarin.Forms;
namespace DwyApp.Behaviours
{
public class ValidateBehaviour:Behavior<Entry>
{
public static readonly BindableProperty ItemToValidateProperty = BindableProperty.Create(nameof(ItemToValidate),
typeof(SettingItem),
typeof(ValidateBehaviour),
defaultValue: null);
public SettingItem ItemToValidate
{
get {
return (SettingItem)GetValue(ItemToValidateProperty);
}
set {
SetValue(ItemToValidateProperty, value);
}
}
protected override void OnAttachedTo(Entry bindable)
{
bindable.TextChanged += HandleTextChanged;
base.OnAttachedTo(bindable);
}
void HandleTextChanged(object sender, TextChangedEventArgs e)
{
bool IsValid = false;
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.TextChanged -= HandleTextChanged;
base.OnDetachingFrom(bindable);
}
}
}
A breakpoint at HandleTextChanged is always hit but the value of ItemToValidate is always null. Any ideas?
Change your binding to something like this:
<Entry x:Name="EntryValue" Text="{Binding SettingItem.Value}" FontSize="Medium">
<Entry.Behaviors>
<local:ValidateBehaviour
ItemToValidate="{Binding BindingContext.SettingItem, Source={x:Reference EntryValue}}" />
</Entry.Behaviors>
</Entry>
As you can see I added a x:Name on the Entry and used that value to tell the Binding on the Behaviour what will its Source.
Hope this helps.-

How can I stop the clicking on a ViewCell from changing the background color for a brief time?

I have this XAML code:
<TableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
<TableSection>
<TableSection.Title>
Card Selection
</TableSection.Title>
<ViewCell Height="50">
<Grid>
<Grid x:Name="deselectGridLink" VerticalOptions="CenterAndExpand" Padding="20, 0">
<Label TextColor="Blue" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLink" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
</Grid>
<Grid x:Name="deselectGridLabel" VerticalOptions="CenterAndExpand" Padding="20, 0">
<Label TextColor="Silver" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLabel" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
</Grid>
</Grid>
</ViewCell>
<ViewCell Height="50">
<Grid x:Name="selectGridLink" VerticalOptions="CenterAndExpand" Padding="20, 0">
<Label TextColor="Blue" Style="{DynamicResource ListItemTextStyle}" x:Name="selectLink" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Select All" />
</Grid>
</ViewCell>
</TableSection>
</TableView>
When other parts of my code call: SetPageDetails() then the label in the grid is changed to a link or the link is changed to a label. So for this when it is a label I would like to have no background flash event and no action called.
I attach a tap gesture recognizer like this. Note it's all on one line but covers two lines so it's more visible here in the SO question:
deselectGridLink.GestureRecognizers
.Add(NewTapGestureForUpdateCategories(false));
private TapGestureRecognizer NewTapGestureForUpdateCategories(bool val)
{
return new TapGestureRecognizer()
{
Command = new Command(() =>
{
App.DB.UpdateAllCategoryGroups(val);
App.DB.UpdateAllCategories(val);
GetPageData();
RemoveTableViewClickSection();
tableView.Root.Add(CreateTableSection());
})
};
}
When the user clicks the row when deselectGridLink grid is visible then:
The deselectGridLink visibility is set to false
The deselectGridLabel visibility is set to true
private void SetPageDetails()
{
Title = App.cardCountForSelectedCategories + (App.cardCountForSelectedCategories == 1 ? " Card Selected" : " Cards Selected");
if (App.cardCountForSelectedCategories == 0)
{
deselectGridLink.IsVisible = false;
deselectGridLabel.IsVisible = true;
}
else
{
deselectGridLink.IsVisible = true;
deselectGridLabel.IsVisible = false;
}
}
The effect of this is that the grid link text will change to silver and the link becomes a label.
However even though it's a gray color label when the label is clicked there is still a brief background row color change from white to a dark color when the label is clicked. I assume it's just the way a view cell works.
Is there a way to suppress this from happening?
EDIT 1 - Updated answer as per updates to question. i.e. add support for switching between highlight enabled/disabled mode.
EDIT 2 - Restructure answer and add more details.
Option-1: Enable/disable view-cell through IsEnabled
The simplest option would be to use the IsEnabled property, which in turn enables/disables the background flash behavior. The only downside to this approach is that it will also disable the taps on child controls, i.e. tap events/gesture recognizer(s) will not be triggered if parent view-cell's IsEnabled is false.
For example:
XAML
<!-- Add name attribute to view-cell -->
<ViewCell x:Name="deselectCell" ..>
<Grid>
<Grid x:Name="deselectGridLink" ..
....
</ViewCell>
Code-behind
private void SetPageDetails()
{
if (App.cardCountForSelectedCategories == 0)
{
deselectCell.IsEnabled = false; //disable background flash
...
}
else
{
deselectCell.IsEnabled = true;
...
}
}
Recommendation 1 - Use data-binding and triggers
Instead of controlling visibility for each label in code-behind, you can use triggers and data-binding as follows (view-model will have a IsDeselectEnabled property):
<ViewCell IsEnabled="{Binding IsDeselectEnabled}" Height="50">
<Label Margin="20,0,20,0" Style="{DynamicResource ListItemTextStyle}" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All">
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding IsDeselectEnabled}" Value="true">
<Setter Property="TextColor" Value="Blue" />
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding IsDeselectEnabled}" Value="false">
<Setter Property="TextColor" Value="Silver" />
</DataTrigger>
</Label.Triggers>
</Label>
</ViewCell>
Recommendation 2 - Use triggers with view as source
<ViewCell x:Name="deselectCell" Height="50">
<Label Margin="20,0,20,0" Style="{DynamicResource ListItemTextStyle}" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All">
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding IsEnabled, Source={x:Reference deselectCell}}" Value="true">
<Setter Property="TextColor" Value="Blue" />
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding IsEnabled, Source={x:Reference deselectCell}}" Value="false">
<Setter Property="TextColor" Value="Silver" />
</DataTrigger>
</Label.Triggers>
</Label>
</ViewCell>
Option-2: Enable/disable highlight, but allow taps
To allow taps while toggling ViewCell's background-highlight behavior, we will need to implement platform-renderer(s).
In case of iOS, we can use SelectionStyle to toggle this behavior, while in case of android, we can use Clickable property.
Shared control:
public class CustomViewCell : ViewCell
{
public static readonly BindableProperty AllowHighlightProperty =
BindableProperty.Create(
"AllowHighlight", typeof(bool), typeof(CustomViewCell),
defaultValue: true);
public bool AllowHighlight
{
get { return (bool)GetValue(AllowHighlightProperty); }
set { SetValue(AllowHighlightProperty, value); }
}
}
iOS renderer:
[assembly: ExportRenderer(typeof(CustomViewCell), typeof(CustomViewCellRenderer))]
namespace SampleApp.iOS
{
public class CustomViewCellRenderer : ViewCellRenderer
{
UITableViewCell _nativeCell;
//get access to the associated forms-element and subscribe to property-changed
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
_nativeCell = base.GetCell(item, reusableCell, tv);
var formsCell = item as CustomViewCell;
if (formsCell != null)
{
formsCell.PropertyChanged -= OnPropertyChanged;
formsCell.PropertyChanged += OnPropertyChanged;
}
//and, update the style
SetStyle(formsCell);
return _nativeCell;
}
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var formsCell = sender as CustomViewCell;
if (formsCell == null)
return;
//TODO: Trying to find a nicer and more robust way to dispose and unsubscribe :(
if (_nativeCell == null)
formsCell.PropertyChanged -= OnPropertyChanged;
if (e.PropertyName == CustomViewCell.AllowHighlightProperty.PropertyName)
{
SetStyle(formsCell);
}
}
private void SetStyle(CustomViewCell formsCell)
{
//added this code as sometimes on tap, the separator disappears, if style is updated before tap animation finishes
//https://stackoverflow.com/questions/25613117/how-do-you-prevent-uitableviewcellselectionstylenone-from-removing-cell-separato
Device.StartTimer(TimeSpan.FromMilliseconds(50), () => {
Device.BeginInvokeOnMainThread(() =>
{
if (formsCell.AllowHighlight)
_nativeCell.SelectionStyle = UITableViewCellSelectionStyle.Default;
else
_nativeCell.SelectionStyle = UITableViewCellSelectionStyle.None;
});
return false;
});
}
}
}
Android renderer:
[assembly: ExportRenderer(typeof(CustomViewCell), typeof(CustomViewCellRenderer))]
namespace SampleApp.Droid
{
public class CustomViewCellRenderer : ViewCellRenderer
{
Android.Views.View _nativeCell;
protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, Android.Views.ViewGroup parent, Android.Content.Context context)
{
_nativeCell = base.GetCellCore(item, convertView, parent, context);
SetStyle();
return _nativeCell;
}
// this one is simpler as the base class has a nice override-able method for our purpose - so we don't need to subscribe
protected override void OnCellPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnCellPropertyChanged(sender, e);
if(e.PropertyName == CustomViewCell.AllowHighlightProperty.PropertyName)
{
SetStyle();
}
}
private void SetStyle()
{
var formsCell = Cell as CustomViewCell;
if (formsCell == null)
return;
_nativeCell.Clickable = !formsCell.AllowHighlight;
}
}
}
Sample usage 1 - Through data-binding
<local:CustomViewCell AllowHighlight="{Binding IsHighlightEnabled}" ..>
<Grid>
<Grid x:Name="deselectGridLink" ..
...
</local:CustomViewCell>
Sample usage 2 - Through code-behind
XAML
<!-- Add name attribute to view-cell -->
<local:CustomViewCell x:Name="deselectCell" ..>
<Grid>
<Grid x:Name="deselectGridLink" ..
...
</local:CustomViewCell>
Code-behind
private void SetPageDetails()
{
if (App.cardCountForSelectedCategories == 0)
{
deselectCell.AllowHighlight= false; //disable background flash
...
}
else
{
deselectCell.AllowHighlight= true;
...
}
}
Option-3: Disable highlight, selection for all items
This particularly applies to ListView. The updated question now specifies that the cells are part of TableView, so this option is no longer valid in current question context.
You will need to implement platform renderers to disable highlight colors, and add ItemTapped handler to ListView to disable selection by setting SelectedItem as null always. References used:
Disable highlight item
Disable selection
Code
To get started, create a custom view-cell:
public class NoSelectViewCell : ViewCell { }
Implement iOS renderer as:
[assembly: ExportRenderer(typeof(NoSelectViewCell), typeof(NoSelectViewCellRenderer))]
namespace SampleApp.iOS
{
public class NoSelectViewCellRenderer : ViewCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var nativeCell = base.GetCell(item, reusableCell, tv);
nativeCell.SelectionStyle = UITableViewCellSelectionStyle.None;
return nativeCell;
}
}
}
Implement android renderer as:
[assembly: ExportRenderer(typeof(NoSelectViewCell), typeof(NoSelectViewCellRenderer))]
namespace SampleApp.Droid
{
public class NoSelectViewCellRenderer : ViewCellRenderer
{
protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, Android.Views.ViewGroup parent, Android.Content.Context context)
{
var cell = base.GetCellCore(item, convertView, parent, context);
cell.Focusable = false;
cell.FocusableInTouchMode = false;
var listView = parent as Android.Widget.ListView;
if (listView != null)
{
listView.SetSelector(Android.Resource.Color.Transparent);
listView.CacheColorHint = Xamarin.Forms.Color.Transparent.ToAndroid();
}
return cell;
}
}
}
Sample Usage:
XAML
<ListView ItemTapped="Handle_ItemTapped">
<ListView.ItemTemplate>
<DataTemplate>
<local:NoSelectViewCell Height="50">
<Grid>
<Grid x:Name="deselectGridLink" VerticalOptions="CenterAndExpand" Padding="20, 0">
<Label TextColor="Blue" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLink" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
</Grid>
<Grid x:Name="deselectGridLabel" VerticalOptions="CenterAndExpand" Padding="20, 0">
<Label TextColor="Silver" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLabel" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
</Grid>
</Grid>
</local:NoSelectViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code-behind
void Handle_ItemTapped(object sender, Xamarin.Forms.ItemTappedEventArgs e)
{
// don't do anything if we just de-selected the row
if (e.Item == null) return;
// do something with e.SelectedItem
((ListView)sender).SelectedItem = null; // de-select the row
}
What G.Sharada proposes is very nicely working for iOS, but on Android I still had blinks on click.
Adding this line to the styles file solved the problem.
<item name="android:colorActivatedHighlight">#android:color/transparent</item>

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.

How to disable highlight on ListView in Xamarin.Forms

I'm working with Xamarin.Forms and XAML, and I'm trying to create an application that stores a list of products. I put my list of products in a ListView. This works fine. Here is my XAML:
<ListView x:Name="listSushi"
ItemsSource="{x:Static local:myListSushi.All}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
RowHeight="{StaticResource rowHeight}"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Padding="5, 5, 0, 5"
Orientation="Horizontal"
Spacing="15">
<StackLayout>
<Image Source="{Binding ImageSource}" />
</StackLayout>
<StackLayout Padding="0, 0, 0, 0"
VerticalOptions="Center"
HorizontalOptions="FillAndExpand">
<Label Text="{Binding Name}"
Font="Bold, Medium" />
<Label Text="{Binding Description}"
Font="Small"/>
</StackLayout>
<StackLayout Orientation="Horizontal"
Padding="0, 0, 10, 0">
<Button Text=" - "
HorizontalOptions="EndAndExpand"
VerticalOptions="FillAndExpand"
Command="{Binding DeleteSushiCommand}"
CommandParameter="{Binding Name}"
/>
<Label VerticalOptions="Center"
Text="{Binding Number,StringFormat='{0}'}"
TextColor="Black"/>
<Button Text=" + "
HorizontalOptions="EndAndExpand"
VerticalOptions="FillAndExpand"
Command="{Binding AddSushiCommand}"
CommandParameter="{Binding Name}"
/>
</StackLayout>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
I've just the problem that if I click on a cell of my listView, the cell is highlight, and stay highlight. I've try to disable it with this code in the xaml.cs
listSushi.ItemSelected+= (object sender, SelectedItemChangedEventArgs e) => {
// don't do anything if we just de-selected the row
if (e.SelectedItem == null) return;
// do something with e.SelectedItem
((ListView)sender).SelectedItem = null; // de-select the row
};
But when I touch a cell, now my list is scrolling automatically. It's very strange.
Does anyone know if this is a bug, or know a fix, like if there is a property where I can disable the highlight?
You might try using the ItemTapped event instead, i.e.
listSushi.ItemTapped += (object sender, ItemTappedEventArgs e) => {
// don't do anything if we just de-selected the row.
if (e.Item == null) return;
// Optionally pause a bit to allow the preselect hint.
Task.Delay(500);
// Deselect the item.
if (sender is ListView lv) lv.SelectedItem = null;
// Do something with the selection.
...
};
I have tested this on a ListView (on an Android device) that has enough items to bring scrolling into the mix. I see no auto-scroll behavior, and your idea to set SelectedItem null to defeat the highlight works great.
Current we can set ListView.SelectionMode to None to do this.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/interactivity
I just found another method to disable the highlight effect. And i would like to share this with other users.
You can do it directly at xaml. But this method not only disable the highlight effect, it will also disable the click event.
You can set the IsEnabled attribute of ViewCell to false.
<ViewCell IsEnabled="false">
//Your Item Layout Coding
</ViewCell>
In addition, you can also disable/enable each item highlight effect by binding:
<ViewCell IsEnabled="{Binding IsHighlightEnabled}">
//Your Item Layout Coding
</ViewCell>
Hope that helps, thanks.
YourList.ItemSelected+=DeselectItem;
public void DeselectItem(object sender, EventArgs e)
{
((ListView)sender).SelectedItem = null;
}
This should be helpful for your scenario. #dpfauwadel
I am assuming you are using MVVM. In these cases I assign a null to the property after using it. In your viewmodel you seem to have a SelectedItem property since you are binding it to the SelectedItem property of the ListView. So I would do something like this:
private Product _selectedItem;
public Product SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
//USE THE VALUE
_selectedItem = null;
NotifyPropertyChanged("SelectedItem");
}
}
On iOS mark's solution didn't solve it for me, I had to create a CustomRenderer for the list view and use that instead.
NonSelectableListView.cs (In your Forms Project)
using System;
using Xamarin.Forms;
namespace YourProject
{
public class NonSelectableListView : ListView
{
public NonSelectableListView()
{
}
}
}
NonSelectableListViewRenderer.cs (CustomRenderer in your iOS project)
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using YourProject.iOS;
using YourProject;
[assembly: ExportRenderer(typeof(NonSelectableListView), typeof(NonSelectableListViewRenderer))]
namespace YourProject.iOS
{
public class NonSelectableListViewRenderer : ListViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe from event handlers and cleanup any resources
}
if (e.NewElement != null)
{
// Configure the native control and subscribe to event handlers
Control.AllowsSelection = false;
}
}
}
}

Resources