Phill
Phill

Reputation: 427

Switch Toggled event fires when toggled programmatically

<StackLayout BackgroundColor="White">
    <ListView x:Name="ListViewMenu" ItemsSource="{Binding Menus}"
          HasUnevenRows="True"
          BackgroundColor="White"
          SeparatorVisibility="None"
          VerticalOptions="FillAndExpand"
          ItemTapped="Handle_ItemTapped"
          ItemSelected="Handle_ItemSelected"
          IsGroupingEnabled = "true"
          SeparatorColor="White">
        <ListView.GroupHeaderTemplate>
            <DataTemplate>
                <ViewCell>
                    <ViewCell.View>
                        <StackLayout BackgroundColor="LightSkyBlue" HeightRequest="25">
                            <Label Text="{Binding Key}"  FontAttributes="Bold"  LineBreakMode="NoWrap" Margin="10,0,0,0">
                            </Label>
                        </StackLayout>
                    </ViewCell.View>
                </ViewCell>
            </DataTemplate>
        </ListView.GroupHeaderTemplate>
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <ViewCell.View>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"></RowDefinition>
                                <RowDefinition Height="Auto"></RowDefinition>
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="6*"></ColumnDefinition>
                                <ColumnDefinition Width="Auto"></ColumnDefinition>
                            </Grid.ColumnDefinitions>
                            <Label Text="{Binding article_description}"
                                       FontAttributes="Bold" FontSize="13"  Margin="10,5,0,-6" Grid.Row="0" LineBreakMode="NoWrap"/>
                            <Label Text="{Binding dish_name}" 
                                   FontSize="13" Margin="10,0,0,2" Grid.Row="1" Grid.Column="0"/>
                            <Label Grid.Row="0" Grid.Column="0" x:Name="LabelReserved"  Text="{Binding reserved}" IsVisible="false" LineBreakMode="NoWrap"/> 
                            <Switch Grid.Row="0" Grid.RowSpan="2" Grid.Column="1"  HorizontalOptions="Start" VerticalOptions="Center" IsEnabled="False" Toggled="SwitchMenu_OnToggled" >
                                <Switch.Triggers>
                                    <DataTrigger TargetType="Switch" Binding="{Binding Source={x:Reference LabelReserved},
                                   Path=Text.Length}" Value="7">
                                        <Setter Property="IsToggled" Value="true" />
                                    </DataTrigger>
                                </Switch.Triggers>
                            </Switch>
                        </Grid>
                    </ViewCell.View>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</StackLayout>

Model

I have the listview above, that has a switch, I have a trigger to toggle the switch if the labelreserved has a legth of 7, but I dont want the Toggled event to fire, only when the user clicks on the switch. Is possible to do what I want?

Upvotes: 1

Views: 1564

Answers (3)

George
George

Reputation: 176

This question is old, but I found a simple (somewhat incorrect) solution that I used to save a lot of extra work from the accepted answer. When I programmatically toggle the switch, I set the Tag property to true.

ToggleSwitch.Tag = true;
ToggleSwitch.IsOn = true (or false);

Then in my Toggled event I check if the Tag property is null and perform my action. That way the only time the code is executed is when the user toggles the switch.

private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
    ToggleSwitch toggleSwitch = (ToggleSwitch)e.OriginalSource;
    if (toggleSwitch.Tag == null)
    {
        // Switch was toggled by user, do some action.
    }
    else
    {
        // Switch was toggled programmatically, set Tag null.
        toggleSwitch.Tag = null;
    }
}

Upvotes: 0

Sharada
Sharada

Reputation: 13591

Note: This solution was a bit of an experiment on my part - so I would recommend that, if you decide to implement this, use it with caution.

Basically, the intent is to create a view-only solution that is able to track how the IsToggled property is being set - whether it is through trigger, binding context or, tap action (something similar to property context that is maintained for BindingContext on BindableObject)

Assuming, we have a custom-event that is triggered only when user taps the switch - this problem should be solved. Simply adding a tap-recognizer to Switch didn't seem to work. And we are left with 2 options:

  1. Create your own custom control that provides functionality similar to Switch, but only triggers the toggle event when there is a tap gesture captured - would highly recommend this option.

  2. Or, you can extend existing Switch control with a custom event, and tracks how the IsToggled property is set - by providing its own set of bindable properties.

For example:

public class CustomSwitch : Switch
{
    internal enum ToggledSetFlags
    {
        None = 0,
        FromCode = 1 << 0,
    }

    ToggledSetFlags _toggleSetStatus = ToggledSetFlags.None;
    public event EventHandler<ToggledEventArgs> UserToggled;

    public static readonly BindableProperty ToggledStateFromCodeProperty =
        BindableProperty.Create(
            "ToggledStateFromCode", typeof(bool), typeof(CustomSwitch),
            defaultBindingMode: BindingMode.TwoWay,
            defaultValue: default(bool), propertyChanged: OnToggledStateFromCodeChanged);

    public bool ToggledStateFromCode
    {
        get { return (bool)GetValue(ToggledStateFromCodeProperty); }
        set { SetValue(ToggledStateFromCodeProperty, value); }
    }

    private static void OnToggledStateFromCodeChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((CustomSwitch)bindable).OnToggledStateFromCodeChangedImpl((bool)oldValue, (bool)newValue);
    }

    protected virtual void OnToggledStateFromCodeChangedImpl(bool oldValue, bool newValue)
    {
        if (ToggledStateFromCode != IsToggled)
        {
            _toggleSetStatus = ToggledSetFlags.FromCode;
            IsToggled = ToggledStateFromCode;
        }
    }

    protected override void OnPropertyChanged(string propertyName = null)
    {
        base.OnPropertyChanged(propertyName);

        if (propertyName == nameof(IsToggled))
        {
            ToggledStateFromCode = IsToggled;
            if (_toggleSetStatus == ToggledSetFlags.None)
                UserToggled?.Invoke(this, new ToggledEventArgs(IsToggled));
            else
                _toggleSetStatus = ToggledSetFlags.None;
        }
    }
}

And, sample usage would look like:

<local:CustomSwitch 
       Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" 
       HorizontalOptions="Start" VerticalOptions="Center" 
       ToggledStateFromCode="{Binding IsSwitchOn}" 
       UserToggled="SwitchMenu_OnToggled">
    <Switch.Triggers>
        <DataTrigger TargetType="{x:Type local:CustomSwitch}" 
                     Binding="{Binding Source={x:Reference LabelReserved},
                     Path=Text.Length}" Value="7">
            <Setter Property="ToggledStateFromCode" Value="true" />
        </DataTrigger>
    </Switch.Triggers>
</local:CustomSwitch>

EDIT 1: Had missed to add else in OnPropertyChanged method while reverting custom toggled property status. Also, added check to set flag only if current toggle value is different.

EDIT 2: Converted bindable property to track state instead of state-less as before. This ensures the ability to handle binding changes without firing the event.

Upvotes: 3

Ahmad ElMadi
Ahmad ElMadi

Reputation: 2617

You can prevent the user from toggling the switch by disabling the Switch by using the property IsEnabled and set it to false .

Upvotes: 0

Related Questions