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

时间:2017-06-21 13:00:32

标签: c# wpf combobox binding selecteditem

我有这个组合框:

<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);
        }
    }
}

在启动时,我只有一个项目:浏览... 。选择它我可以浏览文件并将其路径添加到ComboBox。 AddItem 方法称为
如果选择的文件路径在项目中不存在,我添加并选择它(这是有效的) 如果中存在所选文件路径,我想自动选择它而不再将其添加到列表中。这不起作用,浏览... 是可视化项目 我已经尝试使用 INotifyPropertyChanged 我正在使用.NET 4.6.2。任何想法让它运作?

编辑4:准系统示例

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:

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

尝试:
- 选择FILE_A.txt
- 选择FILE_B.txt
- 再次选择FILE_A.txt

2 个答案:

答案 0 :(得分:1)

您之后未设置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: https://stackoverflow.com/questions/22285866/why-relaycommand
        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 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>

在我的情况下,每次都会更改值,但您应该能够修改代码以满足您的需求。

确保您拥有清晰的代码结构,并且不会使事情过于复杂。

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

答案 1 :(得分:1)

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

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设置器从将其设置为{的调用中退出之前更新SelectedItem {1}}。

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

这将解决这个问题:

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

这看起来更奇怪,但由于它不依赖于线程计时,它可能是一个更好的解决方案。另一方面,这是“viewmodel”代码,其细节由private void AddItem(string item) { // Harmless, if it's not actually there. Items.Remove(item); Items.Add(item); SelectedItem = item; } 控件的实现的特殊性驱动。那不是个好主意。

这应该可以在视图中完成(除了在这个人为的例子中我们的视图我们的视图模型)。