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
控件实现的特殊性驱动。那不是一个好主意。
这可能应该在视图中完成(抛开这个人为的例子,我们的视图 是 我们的视图模型)。
我有这个组合框:
<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
控件实现的特殊性驱动。那不是一个好主意。
这可能应该在视图中完成(抛开这个人为的例子,我们的视图 是 我们的视图模型)。