简体   繁体   中英

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.

  1. 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){

}
  1. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM