Xamarin Forms Switch using EventToCommandBehavior not firing

Gordon S 501 Reputation points
2021-03-23T17:50:29.453+00:00

I have a page with a "CollectionView", the DataTemplate has a "Switch" for which I have added a "behaviors:EventToCommandBehavior" (from Xamarin.CommunityToolkit).

The project compiles and runs without error, but the command assigned to the Switch does not fire.

I have this in the Page XAML:

...  
    <CollectionView ...  
        ...  
        <DataTemplate ...  
        ...  
            <Switch x:Name="xamlSwitch"   
                 Grid.Column="1"  
                 IsToggled="{Binding IsActive, Mode=OneTime}"  
                OnColor="{StaticResource AppPrimaryColor}"  
                ThumbColor="{StaticResource SecondaryColor}"  
                VerticalOptions="Center">  
                   <Switch.Behaviors>  
                        <behaviors:EventToCommandBehavior   
                             EventName="Toggled"    
                             Command="{Binding ToggledCommand}"   
                            CommandParameter="{Binding .}"  
                            x:DataType="viewModels:DevicesPageViewModel"   
                         />  
                   </Switch.Behaviors>  
             </Switch>  

In the DevicesPageViewModel:

public Command ToggledCommand { get; set; }  
   
public DevicesPageViewModel(){  
  
        ToggledCommand = new Command(async x => await ExecuteToggledCommand((UserDevice)x));          
  
}  
  
private async Task ExecuteToggledCommand(UserDevice x)  
{  
    ... code  
}  

I have tried (what feels like) a million combinations but nothing works! What am I doing wrong?

Edit: Full code for XAML and ViewModel

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:viewModels="clr-namespace:MyTest.ViewModels;assembly=MyTest"  
             xmlns:models="clr-namespace:MyTest.Models;assembly=MyTest"  
             xmlns:behaviors="http://xamarin.com/schemas/2020/toolkit"  
             x:Class="MyTest.Views.DevicesPage"  
             x:Name="pageName"  
             Title="My Devices">  
    <ContentPage.BindingContext>  
        <viewModels:DevicesPageViewModel />  
    </ContentPage.BindingContext>  
    <ContentPage.Content>  
          
        <AbsoluteLayout>  
            <StackLayout Margin="{StaticResource PageMargin}"   
                         Spacing="80"  
                x:Name="MainLayout"  
                AbsoluteLayout.LayoutBounds="0,0,1,1"  
                AbsoluteLayout.LayoutFlags="All" >  
                  
                <RefreshView IsRefreshing="{Binding IsRefreshing}"   
                         Command="{Binding RefreshItemsCommand}">  
                    <CollectionView x:Name="ItemsCollectionView"   
                                ItemsSource="{Binding Items}"  
                                RemainingItemsThreshold="{Binding ItemThreshold}"  
                                RemainingItemsThresholdReachedCommand="{Binding ItemThresholdReachedCommand}"  
                                Margin="12,12,12,0"  
                                >  
						<CollectionView.ItemTemplate>  
							<DataTemplate>  
								<!--SwipeView layout  
                                see https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/swipeview#custom-swipe-items for commands -->  
								<SwipeView >  
									<SwipeView.RightItems>  
                                        <SwipeItems Mode="Reveal">  
                                            <SwipeItemView>  
                                                <Grid WidthRequest="85" Margin="0,16,0,16" RowSpacing="0" RowDefinitions=".5*,.5*" ColumnSpacing="0">  
                                                    <BoxView Grid.RowSpan="2"  
															 BackgroundColor="{StaticResource SwipeItemAlert}"  
															 CornerRadius="0,8,0,8" />  
                                                    <Image	Grid.Row="0"  
                                                            Margin="0,0,0,-6"  
															Aspect="AspectFit"  
															HorizontalOptions="Center"  
															Source="icon_white_alert"  
                                                            HeightRequest="32"  
															VerticalOptions="End"/>  
                                                    <Label	Grid.Row="1"  
															Text="Report lost or stolen"  
                                                            Padding="6,6,6,0"  
															FontAttributes="Bold"  
															FontSize="{StaticResource SwipeItemFontSize}"  
															HorizontalOptions="Center"															  
															TextColor="White"  
                                                            HorizontalTextAlignment="Center"  
															VerticalOptions="Start" />  
                                                </Grid>  
                                            </SwipeItemView>  
                                            <SwipeItemView>  
                                                <Grid WidthRequest="85" Margin="0,16,0,16" RowSpacing="0" RowDefinitions="*,*">  
                                                    <BoxView Grid.RowSpan="2"  
															 BackgroundColor="{StaticResource SwipeItemDefault }"  
															 CornerRadius="0" />  
                                                    <Image	Grid.Row="0"   
                                                            Margin="0,0,0,-6"  
															Aspect="AspectFit"  
															HorizontalOptions="Center"  
															Source="icon_white_more"  
                                                            HeightRequest="32"  
															VerticalOptions="End"/>  
                                                    <Label	Grid.Row="1"  
															Text="More"  
                                                            Padding="6,6,6,0"  
															FontAttributes="Bold"  
															FontSize="{StaticResource SwipeItemFontSize}"  
															HorizontalOptions="Center"  
                                                            HorizontalTextAlignment="Center"  
															TextColor="White"  
															VerticalOptions="Start" />  
                                                </Grid>  
                                            </SwipeItemView>  
                                        </SwipeItems>  
									</SwipeView.RightItems>  
								  
								<!--Content layout-->  
									<StackLayout Margin="0, 16, 0, 16" >  
										<Frame  
										Padding="{StaticResource SpacingMedium}"  
										CornerRadius="10"  
										IsClippedToBounds="True"  
										HorizontalOptions="Center"  
										VerticalOptions="Center"  
										HasShadow="True"  
										BackgroundColor="{StaticResource SecondaryColor}">  
											<Grid ColumnDefinitions="Auto, *" ColumnSpacing="{StaticResource DefaultSpacing}">  
  
												<Image Source="{Binding ProductImage}"  
												   Aspect="AspectFill"  
												   HeightRequest="60"  
												   WidthRequest="60"  
                                                   BackgroundColor="Aquamarine">  
													<Image.Clip>  
														<EllipseGeometry  
														Center="30,30"  
														RadiusX="30"  
														RadiusY="30"/>  
													</Image.Clip>  
												</Image>  
  
												<StackLayout Grid.Column="1" Padding="0">  
													<Label Text="{Binding ProductName}"  
												   FontSize="18"  
												   FontAttributes="Bold" />  
													<Label Text="{Binding MaskedPAN, StringFormat='ID: {0}'}"  
												   FontAttributes="Bold" />  
													<Label Text="{Binding CardBalanceText}"  
												   VerticalOptions="End"  />  
  
													<Grid ColumnDefinitions="Auto, Auto" ColumnSpacing="6" >  
														<Label x:Name="switchStateLabel"   
                                                            Text="Active"  
														    VerticalOptions="Center" />  
														<Switch x:Name="xamlSwitch"   
														    Grid.Column="1"  
														    IsToggled="{Binding IsActive, Mode=OneTime}"  
														    OnColor="{StaticResource AppPrimaryColor}"  
														    ThumbColor="{StaticResource SecondaryColor}"  
														    VerticalOptions="Center">  
                                                            <Switch.Behaviors>  
                                                                <behaviors:EventToCommandBehavior   
                                                                    EventName="Toggled"    
                                                                    Command="{Binding   Path=BindingContext.ToggledCommand,   
                                                     Source={x:Reference   
                                                    Name=pageName}}"   
                                                                    CommandParameter="{Binding .}"  
                                                                    />  
                                                            </Switch.Behaviors>  
                                                        </Switch>  
                                                    </Grid>  
												</StackLayout>  
  
  
											</Grid>  
										</Frame>  
									</StackLayout>  
								</SwipeView>  
								  
							</DataTemplate>  
						</CollectionView.ItemTemplate>  
					</CollectionView>  
                </RefreshView>  
  
            </StackLayout>  
  
            <StackLayout  
        x:Name="aiLayout"  
        IsVisible="{Binding IsBusy}"  
        AbsoluteLayout.LayoutBounds="0,0,1,1"  
        AbsoluteLayout.LayoutFlags="All"  
        >  
                <Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">  
                    <!--Semi transparent overlay panel-->  
                    <BoxView BackgroundColor="White" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Opacity=".75"  />  
                      
                    <Frame  
                    CornerRadius="50"  
                    HeightRequest="48"  
                    WidthRequest="48"  
                        Padding="5"  
                    IsClippedToBounds="True"  
                    HorizontalOptions="Center"  
                    VerticalOptions="Center"  
                    HasShadow="True"  
                    BackgroundColor="{StaticResource SecondaryColor}">  
                        <ActivityIndicator   
                        x:Name="ai"              
                        IsRunning="{Binding IsBusy}"  
                        Color="{StaticResource AppPrimaryColor}"  
                        HorizontalOptions="CenterAndExpand"  
                        VerticalOptions="CenterAndExpand"  
                        IsVisible="{Binding IsBusy}"  
                        />  
                    </Frame>  
  
                </Grid>  
                  
            </StackLayout>  
  
        </AbsoluteLayout>  
          
    </ContentPage.Content>  
</ContentPage>  

ViewModel:

using System;  
using System.Collections.ObjectModel;  
using System.Diagnostics;  
using System.Linq;  
using System.Threading.Tasks;  
using MyTestAppApi;  
using MyTestAppApi.Models.ResponseData;  
using MyTest.Services;  
using Xamarin.Forms;  
  
namespace MyTest.ViewModels  
{  
    public class DevicesPageViewModel : BaseViewModel  
    {  
        #region Private Properties  
  
        private bool _isRefreshing;  
  
        private int _itemThreshold;  
  
        #endregion  
  
        #region Public Properties  
  
        public ObservableCollection<UserDevice> Items { get; set; }  
        public const string ScrollToPreviousLastItem = "Scroll_ToPrevious";  
  
  
        public bool IsRefreshing  
        {  
            get => _isRefreshing;  
            set => SetProperty(ref _isRefreshing, value);  
        }  
  
        public Command RefreshItemsCommand { get; set; }  
        public Command ItemThresholdReachedCommand { get; set; }  
        public Command LoadItemsCommand { get; set; }  
  
        public Command ToggledCommand { get; set; }  
  
        public int ItemThreshold  
        {  
            get => _itemThreshold;  
            set => SetProperty(ref _itemThreshold, value);  
        }  
  
        #endregion  
  
        #region Constructors  
  
        public DevicesPageViewModel()  
        {  
            ItemThreshold = 1;  
            Items = new ObservableCollection<UserDevice>();  
  
            LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());  
            ItemThresholdReachedCommand = new Command(async () => await ItemsThresholdReached());  
            RefreshItemsCommand = new Command(async () =>  
            {  
                IsRefreshing = false;  
                await ExecuteLoadItemsCommand();  
            });  
  
            ToggledCommand = new Command(async x => await ExecuteToggledCommand((UserDevice)x));  
  
        }  
  
  
        private async Task ExecuteToggledCommand(UserDevice x)  
        {  
            var myApi = new MyTestAppApiService(MyTestAppConfig.ApiBaseUrl);  
            var accessToken = await AccessToken();  
            myApi.UpdateDeviceStatus(accessToken, "?", "?");  
        }  
  
        #endregion  
  
        public async Task ItemsThresholdReached()  
        {  
            if (IsBusy)  
                return;  
  
            IsRefreshing = false;  
            IsBusy = true;  
  
            // Get next lot of data  
            try  
            {  
                var myApi = new MyTestAppApiService(MyTestAppConfig.ApiBaseUrl);  
                var accessToken = await AccessToken();  
                var rtnData = await myApi.GetUserDevicesAsync(accessToken, Items.Count);  
  
                foreach (var item in rtnData.Devices)  
                {  
                    Items.Add(item);  
                }  
                if (!Items.Any())  
                {  
                    ItemThreshold = -1;  
                }  
            }  
            catch (Exception ex)  
            {  
                Debug.WriteLine(ex);  
            }  
            finally  
            {  
                IsBusy = false;  
            }  
        }  
  
        public async Task ExecuteLoadItemsCommand()  
        {  
            if (IsBusy)  
                return;  
  
            IsRefreshing = false;  
            IsBusy = true;  
  
            try  
            {  
                Items.Clear();  
                var myApi = new MyTestAppApiService(MyTestAppConfig.ApiBaseUrl);  
                var accessToken = await AccessToken();  
                var rtnData = await myApi.GetUserDevicesAsync(accessToken, 0);  
  
                foreach (var item in rtnData.Devices)  
                {  
                    Items.Add(item);  
                }  
            }  
            catch (Exception ex)  
            {  
                Debug.WriteLine(ex);  
            }  
            finally  
            {  
                IsBusy = false;  
            }  
        }  
    }  
}  
  
Developer technologies .NET Xamarin
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Joe Manke 1,091 Reputation points
    2021-03-23T18:10:19.367+00:00

    Bindings inside a CollectionView DataTemplate are to the item from the ItemsSource. To bind to a command defined on the ViewModel that holds the ItemsSource, you need to specify a different source for the binding. The best way to do that is with the RelativeSource extension:

    <behaviors:EventToCommandBehavior   
        EventName="Toggled"    
        Command="{Binding ToggledCommand, Source={RelativeSource AncestoryType={x:Type viewModels:DevicesPageViewModel}}}"   
        CommandParameter="{Binding .}" />  
    

    https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/relative-bindings


  2. JessieZhang-MSFT 7,716 Reputation points Microsoft External Staff
    2021-03-24T03:23:29.17+00:00

    Hello,

    Welcome to our Microsoft Q&A platform!

    You can try the following code . It works on my app.

    <?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:behaviors="clr-namespace:CollectionViewDemos.Behaviors"
                 x:Class="CollectionViewDemos.Views.VerticalListPage"
                 x:Name="pageName"
                 Title="Vertical list (DataTemplate)">
        <StackLayout >
            <CollectionView ItemsSource="{Binding Monkeys}" VerticalOptions="Start">
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <Grid Padding="3">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="1*" />
                                <ColumnDefinition Width="1*" />
                                <ColumnDefinition Width="1*" />
                            </Grid.ColumnDefinitions>
    
                            <Switch x:Name="xamlSwitch"
                                    Grid.RowSpan="2"
                                    IsToggled="{Binding IsActive, Mode=OneTime}"
                                    VerticalOptions="Center">
                                <Switch.Behaviors>
                                    <behaviors:EventToCommandBehavior
                                  EventName="Toggled"
                                 Command="{Binding   Path=BindingContext.ToggledCommand,
                                                        Source={x:Reference
                                                       Name=pageName}}"
                                  CommandParameter="{Binding .}"
                                 />
                                </Switch.Behaviors>
                            </Switch>
    
    
                            <Label Grid.Column="1"
                                   Text="{Binding Name}"
                                   FontAttributes="Bold" />
                            <Label Grid.Row="1"
                                   Grid.Column="1"
                                   Text="{Binding Location}"
                                   FontAttributes="Italic"
                                   VerticalOptions="End" />
    
                        </Grid>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
            </StackLayout>
    </ContentPage>
    

    Note: We can define the x:Name for our page( x:Name="pageName") :

       <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:behaviors="clr-namespace:CollectionViewDemos.Behaviors"
             x:Class="CollectionViewDemos.Views.VerticalListPage"
             x:Name="pageName">
    

    And use the following code for Switch :

      <Switch x:Name="xamlSwitch"
                                Grid.RowSpan="2"
                                IsToggled="{Binding IsActive, Mode=TwoWay}"
                                VerticalOptions="Center">
                            <Switch.Behaviors>
                                <behaviors:EventToCommandBehavior
                              EventName="Toggled"
                             Command="{Binding   Path=BindingContext.ToggledCommand,
                                                    Source={x:Reference
                                                   Name=pageName}}"
                              CommandParameter="{Binding .}"
                             />
                            </Switch.Behaviors>
        </Switch>
    

    Update

    Try to change your code like this:

      ToggledCommand = new Command(( s) =>  ExecuteToggledCommand(s as UserDevice));
    

    And ExecuteToggledCommand is:

             private void ExecuteToggledCommand(UserDevice s)
        {
            if (s != null)
            {
                // add your code here
            }
    
        }
    

    Note: 1. You can also just need to change the following code:

            ToggledCommand = new Command(async x => await ExecuteToggledCommand((UserDevice)x));
    

    to

            ToggledCommand = new Command(async x => await ExecuteToggledCommand(x as Monkey));
    

    And ExecuteToggledCommand is :

            private async Task ExecuteToggledCommand(Monkey s)
        {
            if (s != null)
            {
    
            }
        }
    

    Best Regards,

    Jessie Zhang


    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.