WPF MVVM ComboBox SelectedItem не распространяется из ViewModel в View

У меня есть ComboBox, связанный с MVVM.

<ComboBox
  ItemsSource = "{Binding RootPathItems, Mode=OneTime}" 
  DisplayMemberPath = "DisplayName"
  SelectedItem = "{Binding SelectedRootPathItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
  IsSynchronizedWithCurrentItem = "True">
</ComboBox>

Все элементы в ComboBox уникальны в памяти и создаются при запуске окна.

Когда пользователь меняет выбор в раскрывающемся списке, вызывается установщик свойств ViewModel, и установщику передается правильный объект. Затем я запускаю какое-то синхронное действие на основе выбора, а затем хочу изменить выбор на выбор по умолчанию.

private RootPathItem _selectedRootPathItem;
public RootPathItem SelectedRootPathItem
{
    get => _selectedRootPathItem;
    set
    {
      if (_selectedRootPathItem != value)
      {
        _selectedRootPathItem = value;          
        this.OnPropertyChanged();          
        SomeAction();
      }
    }
}

... 
//in SomeAction():
this.SelectedRootPathItem = _nothingComboBoxItem;

Внутренние компоненты .Net снова вызовут метод получения свойств и получат _nothingComboBoxItem, но пользовательский интерфейс останется на ранее выбранном элементе и не переключится на значение по умолчанию.

Я тоже попробовал связать SelectedIndex с тем же эффектом.

Я предполагаю, что это не работает, потому что я все еще нахожусь в стеке вызовов установки свойств, когда устанавливаю новый элемент, но я на самом деле не знаю, что здесь происходит не так.

🤔 А знаете ли вы, что...
C# поддерживает асинхронное и параллельное программирование с помощью ключевых слов async и await.


86
2

Ответы:

Решено

Что ж, просматривая вашу базу кода и проверяя ее, мне кажется, что вы правы насчет теории «все еще в настройке свойств». Кажется, ComboBox не обновит представление, если вы не добавите строку this.SelectedRootPathItem = _nothingComboBoxItem в задачу.

И я также видел, что вы пытались использовать поведение взаимодействия, чтобы подписаться на событие изменения выбора ComboBox, что я считаю гораздо более понятным подходом, чем выполнение множества действий в средстве установки свойств. Что случилось с этой идеей? Я вижу, что код xaml закомментирован.

Я вернул код поведения взаимодействия в xaml (без CommandParameter), удалил строку _rootPathItemSelectionChangedCommand.Execute(null); в установщике, и он работает безупречно.

Ксамл:

    <ComboBox Grid.Column = "0" Grid.Row = "0" VerticalAlignment = "Center" Margin = "10"
              x:Name = "CBRootPathComboBox"
              ItemsSource = "{Binding RootPathItems, Mode=OneTime}" 
              DisplayMemberPath = "DisplayName"
              SelectedItem = "{Binding SelectedRootPathItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName = "SelectionChanged">
                <i:InvokeCommandAction Command = "{Binding RootPathItemSelectionChangedCommand}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </ComboBox>

И в MainViewModel просто не выполняйте команду:

    public RootPathItem SelectedRootPathItem
    {
        get => _selectedRootPathItem;
        set
        {
            if (_selectedRootPathItem != value)
            {
                _selectedRootPathItem = value;
                this.OnPropertyChanged();
            }
        }
    }

Это должно сработать!


Выйти из стека вызовов можно с помощью диспетчера или async/await. Только не делайте этого в самом установщике. Вы можете использовать обработчик событий:

public ViewModel()
{
    ...
    this.PropertyChanged += HandleSetValue;
}

private void HandleSetValue(object? sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == nameof(SelectedRootPathItem))
    {
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
            SomeAction);
    }
}

public RootPathItem SelectedRootPathItem
{
    get => _selectedRootPathItem;
    set
    {
        if (_selectedRootPathItem != value)
        {
            _selectedRootPathItem = value;
            this.OnPropertyChanged();
        }
    }
}