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

The TreeView seems to have an adverse effect on command binding #17387

Closed
chenjing1294 opened this issue Oct 31, 2024 · 8 comments
Closed

The TreeView seems to have an adverse effect on command binding #17387

chenjing1294 opened this issue Oct 31, 2024 · 8 comments

Comments

@chenjing1294
Copy link

Describe the bug

When I upgraded from 11.1.4 to 11.2.0, the command binding on the button failed. The following sample code can reproduce the problem.

11.1.4 good

1.mp4

11.2.0 Have a problem

When "a" is selected, the button is still unavailable

2.mp4

To Reproduce

The following sample code can reproduce the problem.

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:windows="clr-namespace:AvaloniaApplication1"
        x:Class="AvaloniaApplication1.MainWindow"
        Title="Avalonia Quickstart" Width="600" Height="450"
        x:DataType="windows:MainViewModel">
  <Window.DataContext>
    <windows:MainViewModel />
  </Window.DataContext>
  <Grid ColumnDefinitions="1*,Auto">
    <TreeView Grid.Column="0"
              SelectedItem="{Binding SelectedConnectionViewModel, Mode=TwoWay}"
              ItemsSource="{Binding ConnectionViewModels, Mode=OneWay}">
      <TreeView.ItemTemplate>
        <DataTemplate DataType="windows:ConnectionViewModel">
          <TextBlock Text="{Binding Title, Mode=OneWay}" />
        </DataTemplate>
      </TreeView.ItemTemplate>
    </TreeView>
    <Button Grid.Column="1" Content="ClickMe"
            Command="{Binding CreateSlave, Mode=OneWay}">
      <Button.CommandParameter>
        <MultiBinding Mode="OneWay">
          <Binding Path="SelectedConnectionViewModel" Mode="OneWay" />
          <Binding Path="SelectedConnectionViewModel.IsOpened" Mode="OneWay" />
        </MultiBinding>
      </Button.CommandParameter>
    </Button>
  </Grid>
</Window>
using System.Collections.ObjectModel;
using Avalonia.Controls;

namespace AvaloniaApplication1;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        if (this.DataContext is MainViewModel mainViewModel)
        {
            mainViewModel.ConnectionViewModels.Add(new ConnectionViewModel()
            {
                Title = "a",
                IsOpened = false,
            });
            mainViewModel.ConnectionViewModels.Add(new ConnectionViewModel()
            {
                Title = "b",
                IsOpened = true,
            });
        }
    }
}

public class MainViewModel : ViewModelBase
{
    public ObservableCollection<ConnectionViewModel> ConnectionViewModels { get; } = new ObservableCollection<ConnectionViewModel>();
    private ConnectionViewModel _selectedConnectionViewModel;

    public ConnectionViewModel SelectedConnectionViewModel
    {
        get => _selectedConnectionViewModel;
        set => SetField(ref _selectedConnectionViewModel, value, nameof(SelectedConnectionViewModel));
    }


    public void CreateSlave(object parameter)
    {
        if (parameter is ReadOnlyCollection<object> args && args[0] is ConnectionViewModel connectionViewModel && args[1] is bool opened)
        {
        }
    }

    public bool CanCreateSlave(object parameter)
    {
        if (parameter is ReadOnlyCollection<object> args && args[0] is ConnectionViewModel _ && args[1] is bool opened)
        {
            return !opened;
        }

        return false;
    }
}

public class ConnectionViewModel : ViewModelBase
{
    private string _title;

    public string Title
    {
        get => _title;
        set => SetField(ref _title, value, nameof(Title));
    }

    private bool _isOpened;

    public bool IsOpened
    {
        get => _isOpened;
        set => SetField(ref _isOpened, value, nameof(_isOpened));
    }
}

Expected behavior

No response

Avalonia version

11.2.0

OS

No response

Additional context

No response

@rabbitism
Copy link
Contributor

Should you add a converter in multibinding?

@IanRawley
Copy link
Contributor

At a guess a MultiBinding does not produce a ReadOnlyCollection or anything that inherits from that, so your CanCreateSlave() method is always returning false.

@chenjing1294
Copy link
Author

@rabbitism @IanRawley If you don't add a Converter, the ReadOnlyCollection is passed when the CanCreateSlave method is called. The problem is that in 11.1.4, when I select different TreeItems, the CanCreateSlave method is called, but in 11.2.0, the CanCreateSlave method is never called.

@IanRawley
Copy link
Contributor

You need to annotate your CanExecute equivalent method with the property or properties that can trigger changes.
See: https://docs.avaloniaui.net/docs/guides/data-binding/how-to-bind-to-a-command-without-reactiveui#trigger-can-execute

It seems MultiBindings don't automatically trigger re-evaluations of CanExecute for method bound commands. Binding to a single Property does though. So the problem isn't TreeView, it's MultiBinding.

@chenjing1294
Copy link
Author

I now understand that it is not a problem with TreeView, but a problem with MultiBinding or Command. Even if I don't use annotation, the Can method should be triggered because the properties have changed.

@IanRawley
Copy link
Contributor

I've figured out the problem here. When a MultiBinding doesn't have a converter, the associated MultiBindingExpressions simply publishes a wrapper ReadOnlyCollection around its internal values array. The collection itself isn't Observable, and never actually itself changes, so while the values contained have changed the list itself is the same. As a result, the Binding system just silently ignores it because from its point of view nothing has changed.

@IanRawley
Copy link
Contributor

This was introduced by #16219
Previously a MultiBinding would instantiate a new collection of values every time it needed to publish a value without a converter. The new MultiBindingExpression instead reuses the same ReadOnlyCollection, and so will effectively only ever publish a value once.

@chenjing1294
Copy link
Author

@IanRawley I understand. Thank you very much for your answer.

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

4 participants