software solutions for a mobile world

Silverlight Toolkit ToggleSwitch Databinding Bug

Andy Wigley

Are you using MVVM? Are you using the ToggleSwitch from the Silverlight Toolkit? Have you got a weird bug where a ToggleSwitch that has its IsChecked property databound to a bool or a bool? property of a ViewModel doesn’t always seem to display the correct ON or OFF value? It may be you have encountered a rather subtle bug in the ToggleButton control.

I have just been developing an app using MVVM and noticed a problem with the ToggleSwitch control from the Silverlight Toolkit for Windows Phone (November 2011 release).  I noticed that a ToggleSwitch that had its IsChecked property databound to a ViewModel class did not update the ToggleSwitch UI as expected – but only when the bound property on the viewmodel is changed from true to false and only when the ToggleSwitch is not visible. Puzzled? Let me explain…

This can be easily demonstrated by a slight variant on the well-known Databound App that you can create using the standard project templates. I added an additional property to the ItemViewModel class so alongside the existing LineOne, LineTwo and LineThree properties, it now has an additional property of type bool? called IsActive:

public class ItemViewModel : INotifyPropertyChanged
{
    public string LineOne  { ... }

    public string LineTwo  { ... } 
    
    public string LineThree { ... }
    
    private bool? _isActive;

    public bool? IsActive
    {
        get
        {
            return _isActive;
        }
        set
        {
            if (value != _isActive)
            {
                _isActive = value;
                NotifyPropertyChanged("IsActive");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    ...
}

Next, I changed the DataTemplate for the ListBox on the main page so that it displays a ToggleSwitch with its IsChecked property databound to the new IsActive property of my ViewModel. For interest, I also added a Checkbox databound in a similar way because ToggleSwitch inherits from CheckBox and I wanted to see if the problem existed with the CheckBox as well. So the DataTemplate looks like this:

            <ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="MainListBox_SelectionChanged">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                      <StackPanel Margin="0,0,0,17" Width="432">
                          <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                          <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                          <CheckBox  Content="Is Active" IsChecked="{Binding IsActive}" IsEnabled="False" Margin="0,-6,0,0"/>
                          <toolkit:ToggleSwitch Margin="0,-36,0,0" IsChecked="{Binding IsActive}" IsEnabled="False"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

 

Next i updated the code in the constructor of MainViewModel so that it sets ‘runtime one’ IsActive to false, ‘runtime two’ IsActive to true and ‘runtime three’ IsActive to null. When running the List looks like this, just as you would expect:

ToggleMainList

The CheckBox and the ToggleSwitch controls in the template display the UI you would expect, Unchecked/Off for runtime one, Checked/On for runtime two and Indeterminate/Off for Runtime three.

When you tap on an item in the ListBox, the standard Databound App navigates to DetailsPage.xaml, and in my version, I have added a ToggleSwitch to that page with its IsChecked property databound to the IsActive property.  I also added an ApplicationBar to that page with an ‘Edit’ button on it that when pressed navigates to another page called EditDetailsPage.xaml which is similar to DetailsPage.xaml but uses two-way databinding to allow you to edit the fields of the underlying data item. It looks like this:

ToggleEdit

[I won’t go into the simple code behind this – download the sample code that accompanies this post to see how this is implemented Smile].

Remember that when the app starts up, the ‘runtime one’ object has IsActive set to false, so the checkbox and ToggleSwitch in the full list on MainPage.xaml are ‘Off’. If you then tap on the Runtime One record in the ListBox, the app navigates to DetailsPage.xaml to show the details of that record. If I then tap on the application bar Edit button, the app navigates to EditDetailsPage.xaml. On that edit page, I have checked the CheckBox so thanks to two-way databinding, the IsActive property of ‘runtime one’ is now set to true. So of course, as we navigate back through the pages, the ToggleSwitch and Checkbox controls that are bound to that property will be checked, correct?

Correct. Everything is as it should be:

ToggleEditCorrect1ToggleEditCorrect2

So, no problem there. The ToggleSwitch on DetailsPage.xaml is correctly shown as ‘On’ (the left hand screenshot), and on MainPage.xaml, the CheckBox and the ToggleSwitch in the list for runtime one have correctly detected that IsActive property is now true, so are showing as Checked and On respectively.

Now we‘ll reverse that and set IsActive to false again. So tap on runtime one in the list and we navigate to the DetailsPage.xaml, then tap on the edit button in the appbar and we navigate to EditDetailspage.xaml. Tap the checkbox to clear it and then tap Save. This is where things go wrong Surprised smile. The ToggleSwitch controls both on DetailsPage.xaml and in the DataTemplate for the ListBox on MainPage.xaml do not pick up the fact that IsActive is now false – though the CheckBox controls *do* pick it up and display unchecked:

ToggleEditWrong1ToggleEditWrong2ToggleEditWrong3

As you can see, the Checkbox has been cleared on the first edit screen – this causes the IsActive property of the viewmodel to be set to false. Pressing the save button on that screen causes a navigation back to the View Details screen (middle screen shot) and as you can see, the ToggleSwitch on that screen hasn’t picked up the fact that the viewmodel has changed since it is still set to On. Another press of the Back key puts you back on the main page, where the ListBox displays both a CheckBox and a ToggleSwitch databound to the IsActive property and here the CheckBox is unset but the ToggleSwitch is still On.

Note that this problem only occurs when the databound ToggleSwitches are not visible. When the IsActive property of the viewmodel class is set, it fires PropertyChanged events in the normal way. In Windows Phone, as you navigate through pages, the control objects that are on the parent screens (such as MainPage.xaml and DetailsPage.xaml in this example) stay in memory but are not rendered to the screen. Although the TextBox and CheckBox controls respond correctly to changes in a databound property of an underlying viewmodel class, the ToggleSwitch does not – well at least not when a bool? type is changed from true to false!

In the download for this post, I’ve included another simple project that shows that there is no problem with a databound ToggleButton as long as it is visible.

The workaround for this? You have to force the ToggleButton to refresh its binding and this is best done in OnNavigatedTo. For example, in OnNavigatedTo on DetailsPage.xaml, add code such as:

protected override void OnNavigatedTo(NavigationEventArgs e)
{

    ...

    // Force the ToggleButton to refresh its binding
    ItemViewModel vm = this.DataContext as ItemViewModel;
    this.ToggleSwitch1.IsChecked = vm.IsActive;
}

For the ListBox on  MainPage.xaml, you could use a blunt stick approach and simply toggle the setting of the ItemsSource to force it to renew all its bindings:

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    // Toggle the ItemsSource setting to force the ToggleSwitches to rebind
    this.MainListBox.ItemsSource = null;
    this.MainListBox.ItemsSource = App.ViewModel.Items;
}

It’s ugly but effective. More elegant solutions would be possible using the VisualTreeHelper to identify the ToggleButton instances and to refresh them individually, but I leave that as an exercise for the reader Smile


Posted Jan 18 2012, 03:23 PM by Andy Wigley
Copyright © 2014 APPA Mundi Limited. All Rights Reserved. Terms of Use and Privacy Policy. OrcsWeb's Windows Cloud Server Hosting