WPF Combobox SelectedItem 绑定不会从代码更新

WPF Combobox SelectedItem binding doesn't update from code

我有这个组合框:

<ComboBox Grid.Column="1" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Items, Mode=OneWay}" HorizontalAlignment="Stretch" VerticalAlignment="Center"/>

这是代码:

public class CustomComboBoxViewModel
    {
    private bool DiscardSelChanged { get; set; }
    public ObservableCollection<string> Items { get; set; }

    public string SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            if (!DiscardSelChanged)
                _selectedItem = value;
            bool old = DiscardSelChanged;
            DiscardSelChanged = false;
            if (!old)
                SelectionChanged?.Invoke(_selectedItem);
        }
    }

    public event Action<string> SelectionChanged;

    public void AddItem(string item)
    {
        var v = Items.Where(x => x.Equals(item)).FirstOrDefault();
        if (v != default(string))
        {
            SelectedItem = v;
        }
        else
        {
            DiscardSelChanged = true;
            _selectedItem = item;
            Items.Insert(0, item);
        }
    }
}

启动时我只有一项:浏览...。 select我可以浏览文件并将其路径添加到 ComboBox。 AddItem 方法被调用
如果 selected 文件路径在 Items 中不存在,我添加并 select 它(这是有效的)。
如果 selected 文件路径存在于 Items 中,我想自动 select 它而不将其再次添加到列表中。这不起作用,浏览... 是可视化项目。
我已经尝试使用 INotifyPropertyChanged.
我正在使用 .NET 4.6.2。有什么想法让它工作吗?

编辑4:barebone示例

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;

namespace WpfApp2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            Items = new ObservableCollection<string>();
            Items.Add(ASD);
        }
        private string ASD = @"BROWSE";
        private string _selectedItem;

        public string SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                _selectedItem = value;
                OnPropertyChanged(nameof(SelectedItem));
                UploadFileSelection_SelectionChanged();
            }
        }
        public ObservableCollection<string> Items { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        private void AddItem(string item)
        {
            var v = Items.Where(x => x.Equals(item)).FirstOrDefault();
            if (v != default(string))
                SelectedItem = v;
            else
            {
                Items.Add(item);
                SelectedItem = item;
            }
        }

        private void UploadFileSelection_SelectionChanged()
        {
            if (SelectedItem == ASD)
            {
                Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog()
                {
                    DefaultExt = ".*",
                    Filter = "* Files (*.*)|*.*"
                };
                bool? result = dlg.ShowDialog();

                if (result == true)
                    AddItem(dlg.FileName);
            }
        }

    }
}

组合框:

<ComboBox SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Items}"/>

尝试:
- select FILE_A.txt
- select FILE_B.txt
- select FILE_A.txt 再一次

您正在设置 _selectedItem 之后没有调用 OnPropertyChanged()。这就是它不起作用的原因。如果您想要一个清晰的代码解决方案,请考虑使用 OnPropertyChanged() 实现 属性,如下所示:

int _example;
public int Example
{
    get
    {
        return _example;
    }
    set
    {
        _example = value;
        OnPropertyChanged(nameof(Example);
    }
}

您的代码将不易出错。

尽可能简单:

public class ViewModel : INotifyPropertyChanged
{
    public ObservableCollection<string> Strings { get; set; }

    public ICommand AddAnotherStringCommand { get; set; }

    string _selectedItem;
    public string SelectedItem
    {
        get
        {
            return _selectedItem;
        }

        set
        {
            _selectedItem = value;
            OnPropertyChanged(nameof(this.SelectedItem));
        }
    }

    public int counter { get; set; } = 1;

    public ViewModel()
    {
        // RelayCommand from: 
        this.AddAnotherStringCommand = new RelayCommand<object>(AddAnotherString);
        this.Strings = new ObservableCollection<string>();
        this.Strings.Add("First item");
    }

    private void AddAnotherString(object notUsed = null)
    {
        this.Strings.Add(counter.ToString());
        counter++;
        this.SelectedItem = counter.ToString();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

主要Window:

<Window x:Class="Test.MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Test"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:ViewModel x:Name="ViewModel" />
    </Window.DataContext>
    <StackPanel>
        <ComboBox ItemsSource="{Binding Strings}" SelectedItem="{Binding SelectedItem}"/>
        <Button Content="Add another item" Command="{Binding AddAnotherStringCommand}" />
    </StackPanel>
</Window>

在我的例子中,值每次都会更改,但您应该能够修改代码以满足您的需要。

确保你有一个清晰的代码结构,不要把事情搞得太复杂。

如果您想要更具体的答案,您应该考虑向您展示完整的代码。

我试过你的例子。我用一个标志修复了重入问题(双浏览对话框):

private bool _browsing = false;
private void UploadFileSelection_SelectionChanged()
{
    if (_browsing)
    {
        return;
    }

    if (SelectedItem == ASD)
    {
        try
        {
            _browsing = true;
            Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog()
            {
                DefaultExt = ".*",
                Filter = "* Files (*.*)|*.*"
            };
            bool? result = dlg.ShowDialog();

            if (result == true)
                AddItem(dlg.FileName);
        }
        finally
        {
            _browsing = false;
        }
    }
}

这是穴居人的东西,但很管用。

您遇到的真正问题是 UploadFileSelection_SelectionChanged() 被调用,并在您退出 SelectedItem setter[=43= 之前更新 SelectedItem ] 来自将其设置为 ASD 的调用。

因此 AddItem() 中的 SelectedItem = v; 对组合框没有影响,因为组合框此时没有响应 PropertyChanged

这将解决这个问题:

private void AddItem(string item)
{
    var v = Items.FirstOrDefault(x => x.Equals(item));

    if (v != default(string))
    {
        //SelectedItem = v;
        Task.Run(() => SelectedItem = v);
    }
    else
    {
        Items.Add(item);
        SelectedItem = item;
    }
}

现在我们稍后再做。

但请注意,另一个分支确实有效,其中 item 是新添加到集合中的分支。您也可以通过删除 item 并再次添加来伪造它:

private void AddItem(string item)
{
    //  Harmless, if it's not actually there. 
    Items.Remove(item);

    Items.Add(item);
    SelectedItem = item;
}

这看起来更奇怪,但由于它不依赖于线程计时,所以它可能是更好的解决方案。另一方面,这是 "viewmodel" 代码,其细节由 ComboBox 控件实现的特殊性驱动。那不是一个好主意。

这可能应该在视图中完成(抛开这个人为的例子,我们的视图 我们的视图模型)。