user3863376
user3863376

Reputation: 63

Toggle Button Two Way Binding Not Working (Universal Windows Platform)

I am trying to bind the "IsChecked" property on the ToggleButton to "ModelView.IsEnabled".
"ModelView.IsEnabled" is always "false"
but somehow the ToggleButton can still show as "Checked".
Is there anything wrong with the binding?
enter image description here

XAML

...
<Page.Resources>
    <ModelView:ModelView x:Key="ModelView"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <ToggleButton IsChecked="{Binding Source={StaticResource ModelView}, Path=IsEnabled, Mode=TwoWay}">
        <TextBlock >UWP Toggle Button</TextBlock>
    </ToggleButton>
</Grid>
...

ModelView.cs

using...

namespace App2
{
    class ModelView : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public event EventHandler CanExecuteChanged;

        private bool _isEnabled;

        public bool IsEnabled
        {
            get {
                return _isEnabled;
            }
            set
            {
                _isEnabled = false;
                OnPropertyChanged("IsEnabled");
            }
        }

        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}

Upvotes: 2

Views: 5608

Answers (4)

P59
P59

Reputation: 11

I've run into the same problem, be it not with a ToggleButton, but with a TextBox, where I wanted to format the text the user had entered.

In your case you want to change the IsChecked property in your viewmodel and have it reflected in the User Interface straight away (so always be unchecked). The reason you want that is of absolutely no importance.

The problem is that with UWP the getter of your property gets called as you would expect when you click the ToggleButton. The normal action for the ToggleButton is to change from unchecked to checked (and vice versa) and that is what happens in your case. But then you expect that NotifyPropetyChanged signals the control in the UI. And that's where it goes wrong. The getter never gets called when the setter is executed (including NotifyPropertyChanged), so the UI doesn't reflect what you did in your setter. This is very different from what the TwoWay Binding used to do (and still does in WPF). So there is nothing wrong with your code, but it seems that the binding mechanism has changed, although Microsoft claims it didn't. If you would use x:Bind, it works fine, so hat might solve your problem.

To clarify things more I have taken your example and modified it slightly, to show the problem. I've put a ToggleButton on the page with a TwoWay binding to a viewmodel, exactly as you did. Clicking on the ToggleButton will switch its state from checked to unchecked and vice versa, even though the setter in my viewmodel Always sets the property to false (so unchecked). But I've also added a normal button, that I've bound to a command that also modifies the property that the ToggleButton is bound to. Clicking this button calls the setter on the property the ToggleButton is bound to. Of course the setter gets called just the same, but after that the binding to the ToggleButton gets called, so NotifyPropertyChanged in this case does cause a UI update.

If you use the debugger, you can see exactly what i mean. So your problem can be solved by using x:Bind, or by figuring out another way to update the UI, which you shouldn't have to do if Binding was still working as it used to. Maybe Microsoft has implemented some kind of optimization that now destroys classic Binding.

No special things, just a MainPage and a viewmodel.

My code for MainPage.xaml

<Page x:Class="App10.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:local="using:App10"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Page.Resources>
        <local:ViewModel x:Key="viewModel" />
    </Page.Resources>

    <Grid x:Name="mainGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Margin="10,20,10,0">
            <Button
                x:Name="Button"
                Content="UWP Normal button"
                Command="{Binding Source={StaticResource viewModel}, Path=SwitchIschecked}"
                HorizontalAlignment="Stretch" />
            <ToggleButton
                x:Name="toggleButton"
                Margin="0,10,0,0"
                HorizontalAlignment="Stretch"
                VerticalAlignment="Top"
                IsChecked="{Binding Source={StaticResource viewModel}, Path=IsChecked,
                                          Mode=TwoWay}">
                <TextBlock>UWP Toggle Button</TextBlock>

            </ToggleButton>
        </StackPanel>
    </Grid>
</Page>

The code for MainPage.xaml.cs

using Windows.UI.Xaml.Controls;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace App10
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();    
        }    
    }
}

And the code for ViewModel.cs

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace App10
{
    public class ViewModel : INotifyPropertyChanged
    {
        private bool _isChecked;

        // property for TwoWay binding with ToggleButton
        public bool IsChecked
        {
            get
            {
                return _isChecked;
            }
            set
            {
                // extra var just to check 'value'
                var _value = value;
                // now always set it to false
                _isChecked = false;
                // Try to pass value of _isChecked to user interface
                // because there is no check whether the value really
                // has changed
                // But this only works if the setter is not being called
                // directly from the control the property is bound to
                OnPropertyChanged();
            }
        }

        private ICommand _switchChecked;

        // ICommand for normal button, binding to Command
        // calls method to set Property for ToggleButton
        public ICommand SwitchIschecked
        {
            get
            {
                if ( _switchChecked == null )
                    _switchChecked = new ChangeChecked( new Action( ChangeVar ));
                return _switchChecked;
            }
            set
            {
                _switchChecked = value;
            }
        }

        // This will set the property for the ToggleButton
        private void ChangeVar()
        {
            IsChecked = !IsChecked;
        }


        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged( [CallerMemberName] string propertyName = null )
        {
            var handler = PropertyChanged;
            handler?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
        }


    }


    /// <summary>
    /// Quick class to implement ICommand
    /// </summary>
    class ChangeChecked : ICommand
    {
        Action _execute;
        public ChangeChecked( Action execute )
        {
            _execute = execute;
        }

        public event EventHandler CanExecuteChanged;

        public bool CanExecute( object parameter )
        {
            return true;
        }

        public void Execute( object parameter )
        {
            _execute();
        }
    }
}

Upvotes: 1

Serg Kryvonos
Serg Kryvonos

Reputation: 4677

IsEnabled property is indicating whether the user can interact with the control. IsPressed is readonly property. So IsChecked is probably what you need.

Upvotes: 0

jsanalytics
jsanalytics

Reputation: 13188

In your class ModelView, change IsEnabled from this:

 public bool IsEnabled
    {
        get {
            return _isEnabled;
        }
        set
        {
            _isEnabled = false;
            OnPropertyChanged("IsEnabled");
        }
    }

to this:

 public bool IsEnabled
    {
        get {
            return _isEnabled;
        }
        set
        {
            _isEnabled = value;
            OnPropertyChanged("IsEnabled");
        }
    }

enter image description here

EDIT: If i use _isEnabled = !value; as you suggested, it still works, with button and state now showing opposite values:

enter image description here

EDIT 2: Now, if you want to properly test your binding, then you could add an extra regular button and do this:

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        myModelView.IsEnabled = !myModelView.IsEnabled;
    }

so you can watch your ToggleButton switch between true and false every time you click Test Button. Please note that Test Button is not bound to anything, it's just for testing purposes. See corresponding XAML at the bottom.

enter image description here

The problem is that the way you're doing it, "forcing" IsEnabled to be always false, you're actually sabotaging your own code...:O)

And finally, it is not clear from your code when/where you're assigning your DataContext. Please see below how to do it.

XAML:

<Page.DataContext>
    <local:MyModelView/>
</Page.DataContext>

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <ToggleButton x:Name="toggleButton1" Content="ToggleButton" IsChecked="{Binding IsEnabled, Mode=TwoWay}" HorizontalAlignment="Center"/>
    <TextBlock x:Name="textBlock1" Text="{Binding IsEnabled}" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="126,0,201,286" />
    <Button x:Name="button1" Click="button1_Click" Margin="127,400,0,220" Content="Test Button" Height="35" />
</Grid>

Code-behind:

    private void Page_Loaded(object sender, RoutedEventArgs e)
    {
        myModelView = new MyModelView();
        this.DataContext = myModelView;
    }

Upvotes: 1

Ilan
Ilan

Reputation: 2782

Try this, it worked to me: 1. Xaml code changes:

    <Grid>
    <Grid.DataContext>
        <soHelpProject:MainViewModel/>
    </Grid.DataContext>
    <ToggleButton IsChecked="{Binding IsToggled, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
        <TextBlock >UWP Toggle Button</TextBlock>
    </ToggleButton>
</Grid>

regards,

Upvotes: 2

Related Questions