【问题标题】:WPF Combobox SelectedItem binding doesn't update from codeWPF Combobox SelectedItem 绑定不会从代码更新
【发布时间】:2017-06-21 13:00:32
【问题描述】:

我有这个组合框:

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

在启动时我只有一项:Browse...。选择它我可以浏览文件并将其路径添加到组合框。 AddItem 方法被调用
如果所选文件路径在 Items 中不存在,我添加并选择它(这是有效的)。
如果所选文件路径存在于 Items 中,我想自动选择它而不将其再次添加到列表中。这不起作用,Browse... 是可视化项目。
我已经尝试过使用 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 SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding Items}"/>

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

【问题讨论】:

  • “我已经尝试过使用 INotifyPropertyChanged。” -- 你做错了。向我们展示您是如何做到的,我们会帮助您解决问题。
  • OnPropertyChanged(nameof(SelectedItem)) 调用应该在 SelectedItem 属性设置器中,在 _selectedItem = value 之后
  • 在名为X 的属性更改其值之后立即,您必须调用OnPropertyChanged(nameof(X))。您必须Xset 块中执行此操作,之后给支持变量一个新值。没有例外。您必须对每个属性执行此操作,并且必须在 set 块中执行此操作,因此每次设置属性时都会发生此操作。这就是我们所说的“实现INotifyPropertyChanged”。
  • AddItem 中还有_selectedItem = item。将其替换为SelectedItem = item
  • 请注意,ItemsSource Binding 上的Mode=OneWay 已经是默认设置,因此是多余的。

标签: c# wpf combobox binding selecteditem


【解决方案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 在您退出将ASD 设置为ASD 的调用之前的SelectedItem 设置器

所以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 控件的实现特性驱动。这不是一个好主意。

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

【讨论】:

    【解决方案2】:

    您在设置 _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>
    

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

    确保你有一个清晰的代码结构,不要让事情变得过于复杂。

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

    【讨论】:

      猜你喜欢
      • 2011-11-01
      • 2015-06-28
      • 2010-10-24
      • 1970-01-01
      • 1970-01-01
      • 2017-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多