Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RadioButton binding is (incorrectly) reset when switching between Tabs or ViewModels #5612

Open
joneuhauser opened this issue Mar 4, 2021 · 3 comments

Comments

@joneuhauser
Copy link

joneuhauser commented Mar 4, 2021

Hi,

WPF shows the same behavior (https://stackoverflow.com/questions/8568861/when-viewmodel-is-changed-to-a-different-instance-a-bound-radiobutton-is-reset-t), but I still think this is a bug.

MainWindow.xaml

<Window.DataTemplates>
        <DataTemplate DataType="{x:Type local:TabViewModel}">
            <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
                <RadioButton IsChecked="{Binding !RadioButtonProperty}" Content="Value: false"/>
                <RadioButton IsChecked="{Binding RadioButtonProperty}" Margin="5, 0" Content="Value: true"/>
            </StackPanel>
        </DataTemplate>
    </Window.DataTemplates>
    <TabControl>
        <TabItem Content="{Binding VM1}" Header="Tab 1"></TabItem>
        <TabItem Content="{Binding VM2}" Header="Tab 2"></TabItem>
    </TabControl>

MainWindow.xaml.cs

 public class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel();
        }

        private void InitializeComponent()
        {
            AvaloniaXamlLoader.Load(this);
        }
    }
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public class MainWindowViewModel : ViewModelBase
    {
        public TabViewModel VM1 { get; set; }
        public TabViewModel VM2 { get; set; }
        public MainWindowViewModel()
        {
            VM1 = new TabViewModel();
            VM2 = new TabViewModel();
        }
    }
    public class TabViewModel : ViewModelBase
    {
        bool _RadioButtonProperty;

        public bool RadioButtonProperty
        {
            get
            {
                return _RadioButtonProperty;
            }
            set
            {
                if (_RadioButtonProperty == value)
                    return;

                _RadioButtonProperty = value;
                OnPropertyChanged();
            }
        }
    }

Steps to reproduce:

  1. Run app
  2. Switch radio button on the first tab to True
  3. Go to the second tab
  4. Go back to the first tab

Expected outcome: RadioButton in the first tab is still True
Observed outcome: RadioButton is reset to False

Not reproduced with a CheckBox.

I don't require Group names to reproduce, so I doubt #2717 or #2903 are related.

Avalonia 0.9.12 and 0.10.0 on Windows 10 and Ubuntu 20.04

If this is not a bug, how can I work around it?

@chkr1011
Copy link

chkr1011 commented Oct 6, 2022

I have the same issue. This is really annoying.

Is there at least a workaround for this? Probably styling a checkbox to look like a radio button? But this would require implementing the single selection feature.

@timunie
Copy link
Contributor

timunie commented Oct 7, 2022

A styled CheckBox should work. As the property is negated, once you click on first check box, the second should update. But anyway, if you have some time to investigate the issue further, try to clone the latest source and see if you can find a fix for it. Probably the states have to be refreshed just before showing up.

@WCKYWCKF
Copy link

WCKYWCKF commented Nov 26, 2024

Today, while solving a problem for a fellow member of the Ursa community, I unexpectedly provided a workaround for the issue (not an actual fix).

The following is a reference code that I hope can help the community partners who encounter this problem.

View

        <TabControl VerticalAlignment="Center"
                    SelectionChanged="ProactivelyUpdateRadioButtonIsChecked">
            <TabControl.ContentTemplate>
                <DataTemplate x:DataType="vm:T11">
                    <StackPanel>
                        <RadioButton
                            IsChecked="{Binding P1}"
                            GroupName="{Binding GroupName}"
                            Loaded="RegisteredRadioButtonIsCheckedProactiveUpdates">
                            P1
                        </RadioButton>
                        <RadioButton
                            IsChecked="{Binding P2}"
                            GroupName="{Binding GroupName}"
                            Loaded="RegisteredRadioButtonIsCheckedProactiveUpdates">
                            P2
                        </RadioButton>
                        <RadioButton
                            IsChecked="{Binding P3}"
                            GroupName="{Binding GroupName}"
                            Loaded="RegisteredRadioButtonIsCheckedProactiveUpdates">
                            P3
                        </RadioButton>
                    </StackPanel>
                </DataTemplate>
            </TabControl.ContentTemplate>
            <TabItem Header="1" Content="{Binding T1}">

            </TabItem>
            <TabItem Header="2" Content="{Binding T2}">
            </TabItem>
        </TabControl>

View.cs

    private List<RadioButton> RadioButtons = new List<RadioButton>();

    private void RegisteredRadioButtonIsCheckedProactiveUpdates(object? sender, RoutedEventArgs e)
    {
        if (sender is not RadioButton c) return;
        RadioButtons.Add(c);
        c.Unloaded += UnRegisteredRadioButtonIsCheckedProactiveUpdates;
    }

    private void UnRegisteredRadioButtonIsCheckedProactiveUpdates(object? sender, RoutedEventArgs e)
    {
        if (sender is not RadioButton c) return;
        RadioButtons.Remove(c);
        c.Unloaded -= UnRegisteredRadioButtonIsCheckedProactiveUpdates;
    }

    private void ProactivelyUpdateRadioButtonIsChecked(object? sender, SelectionChangedEventArgs e)
    {
        foreach (var radioButton in RadioButtons)
        {
            if (radioButton.DataContext is not T11 vm) return;
            switch (radioButton.Content?.ToString())
            {
                case nameof(vm.P1): radioButton.IsChecked = vm.P1; break;
                case nameof(vm.P2): radioButton.IsChecked = vm.P2; break;
                case nameof(vm.P3): radioButton.IsChecked = vm.P3; break;
                default: break;
            }
        }
    }

ViewModel

public partial class MainWindowViewModel : ViewModelBase
{
    [Reactive] private T11 _t1 = new();
    [Reactive] private T11 _t2 = new();
}

public partial class T11 : ViewModelBase
{
    private bool _p1;
    private bool _p2;
    private bool _p3;
    [Reactive] private string _groupName = Guid.NewGuid().ToString();

    public bool P1
    {
        get => AllIsFalse ? _latestValueTuple.Item1 : _p1;
        set => this.RaiseAndSetIfChanged(ref _p1, value);
    }

    public bool P2
    {
        get => AllIsFalse ? _latestValueTuple.Item2 : _p2;
        set => this.RaiseAndSetIfChanged(ref _p2, value);
    }

    public bool P3
    {
        get => AllIsFalse ? _latestValueTuple.Item3 : _p3;
        set => this.RaiseAndSetIfChanged(ref _p3, value);
    }

    private bool AllIsFalse => _p1 == false && _p2 == false && _p3 == false;

    private (bool, bool, bool) _latestValueTuple;

    public T11()
    {
        this.PropertyChanged += (sender, args) =>
        {
            if (!AllIsFalse) return;
            switch (args.PropertyName)
            {
                case nameof(P1):
                    _latestValueTuple.Item1 = true;
                    _latestValueTuple.Item2 = false;
                    _latestValueTuple.Item3 = false;
                    break;
                case nameof(P2):
                    _latestValueTuple.Item1 = false;
                    _latestValueTuple.Item2 = true;
                    _latestValueTuple.Item3 = false;
                    break;
                case nameof(P3):
                    _latestValueTuple.Item1 = false;
                    _latestValueTuple.Item2 = false;
                    _latestValueTuple.Item3 = true;
                    break;
                default:
                    break;
            }
        };
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants