我正在实现一个ComboBox,用户可以使用按钮刷新它。我正在努力使其在刷新后仍然存在于ComboBox中时自动重新选择之前选择的项目。
MainWindow.xaml:
<ComboBox Canvas.Left="10" Canvas.Top="10" DisplayMemberPath="Name" IsEnabled="{Binding Path=Enabled}" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Mode=TwoWay, Path=SelectedItem}" Width="379"/>
<Button Content="{x:Static p:Resources.TextRefresh}" Canvas.Right="10" Canvas.Top="10" Click="OnClickButtonRefresh" Width="75"/>
MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
DataContext = m_BrowserInstances = new BrowserInstancesViewModel();
}
private void OnClickButtonRefresh(Object sender, RoutedEventArgs e)
{
m_BrowserInstances.Populate();
}
[编辑到当前版本] BrowserInstancesViewModel.cs:
public sealed class BrowserInstancesViewModel : ViewModel
{
private Boolean m_Enabled;
public Boolean Enabled
{
get { return m_Enabled; }
}
private BrowserInstance m_SelectedItem;
public BrowserInstance SelectedItem
{
get { return m_SelectedItem; }
set
{
if (m_SelectedItem != value)
{
m_SelectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
}
private ObservableCollection<BrowserInstance> m_Items;
public ObservableCollection<BrowserInstance> Items
{
get { return m_Items; }
}
public BrowserInstancesViewModel()
{
Populate();
}
private static Func<BrowserInstance, Boolean> Recover(BrowserInstance selectedItem)
{
return x =>
{
Process currentProcess = x.Process;
Process selectedProcess = selectedItem.Process;
if (currentProcess.Id != selectedProcess.Id)
return false;
if (currentProcess.MainModule.BaseAddress != selectedProcess.MainModule.BaseAddress)
return false;
if (currentProcess.MainWindowTitle != selectedProcess.MainWindowTitle)
return false;
return true;
};
}
public void Populate()
{
BrowserInstance item = m_SelectedItem;
List<BrowserInstance> items = new List<BrowserInstance>();
foreach (Process process in Process.GetProcessesByName("chrome"))
items.Add(new BrowserInstance(process));
if (items.Count > 0)
{
m_Enabled = true;
m_Items = new ObservableCollection<BrowserInstance>(items.OrderBy(x => x.Process.Id));
if (item != null)
m_SelectedItem = m_Items.SingleOrDefault(Recover(item));
if (m_SelectedItem == null)
m_SelectedItem = m_Items[0];
}
else
{
m_Enabled = false;
m_Items = new ObservableCollection<BrowserInstance>();
m_Items.Add(new BrowserInstance());
m_SelectedItem = m_Items[0];
}
NotifyPropertyChanged("Enabled");
NotifyPropertyChanged("Items");
NotifyPropertyChanged("SelectedItem");
}
}
我可以取回之前选择的项目,但有时只能。如果无法恢复上一个选定项目,我需要选择默认值(索引0)时,代码看起来不能正常工作。
答案 0 :(得分:3)
您需要将m_SelectedItem
设置为SingleOrDefault(Recover(...))
找到的项目。
目前,您将其设置为旧实例。该实例不再存在于列表中,显然您的BrowserInstance
类没有实现任何相等的成员。
根据您当前的代码更正代码:
if(selectedItem != null)
m_SelectedItem = m_Items.SingleOrDefault(Recover(selectedItem));
if(m_SelectedItem == null)
m_SelectedItem = m_Items[0];
更新:
您上传的代码有两个问题。
如果没有进程,则添加的默认Process
对象的BrowserInstance
属性的值为null
。这导致NullReferenceException
使用的比较代码中的SingleOrDefault
通过将前面的if
更改为
if(selectedItem != null && selectedItem.Process != null)
在Populate
方法结束时,您为PropertyChanged
举起Items
事件 - 更新组合框中的值 - 以及SelectedItem
- 来将所选项目设置为用户之前选择的项目
这里的问题是,当SelectedItem
引发null
时,WPF将使用PropertyChanged
更新Items
,因为它在新项目列表中找不到先前选择的项目。这有效地覆盖了您在Populate
方法中计算的新选定项目
通过不将新选择的项目分配给m_SelectedItem
但是selectedItem
,并在SelectedItem
引发PropertyChanged
事件后将该值分配给Items
来修复此问题:< / p>
public void Populate()
{
BrowserInstance selectedItem = m_SelectedItem;
List<BrowserInstance> items = new List<BrowserInstance>();
foreach (Process process in Process.GetProcessesByName("chrome"))
items.Add(new BrowserInstance(process));
if (items.Count > 0)
{
m_Enabled = true;
m_Items = new ObservableCollection<BrowserInstance>(items.OrderBy(x => x.Process.Id));
if (selectedItem != null && selectedItem.Process != null)
selectedItem = m_Items.SingleOrDefault(x => (x.Process.Id == selectedItem.Process.Id) && (x.Process.MainModule.BaseAddress == selectedItem.Process.MainModule.BaseAddress));
if (selectedItem == null)
selectedItem = m_Items[0];
}
else
{
m_Enabled = false;
m_Items = new ObservableCollection<BrowserInstance>();
m_Items.Add(new BrowserInstance());
selectedItem = m_Items[0];
}
NotifyPropertyChanged("Enabled");
NotifyPropertyChanged("Items");
SelectedItem = selectedItem;
}
如果要正确实现BrowserInstance
的相等性,则可以使用保留当前所选项目的WPF功能。
Populate
的代码可以简化为:
public void Populate()
{
BrowserInstance selectedItem = m_SelectedItem;
List<BrowserInstance> items = new List<BrowserInstance>();
foreach (Process process in Process.GetProcessesByName("chrome"))
items.Add(new BrowserInstance(process));
m_Enabled = items.Any();
m_Items = new ObservableCollection<BrowserInstance>(items.OrderBy(x => x.Process.Id));
if(!m_Enabled)
m_Items.Add(new BrowserInstance());
NotifyPropertyChanged("Enabled");
NotifyPropertyChanged("Items");
if (SelectedItem == null)
SelectedItem = m_Items[0];
}
BrowserInstance
的平等实现如下:
public sealed class BrowserInstance : IEquatable<BrowserInstance>
{
// ...
public bool Equals(BrowserInstance other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
if (m_Process == null)
{
if (other.m_Process == null)
return true;
return false;
}
if (other.m_Process == null)
return false;
return m_Process.Id == other.m_Process.Id && m_Process.MainModule.BaseAddress == other.m_Process.MainModule.BaseAddress;
}
public override bool Equals(object obj)
{
return Equals(obj as BrowserInstance);
}
public override int GetHashCode()
{
unchecked
{
return m_Process != null ? ((m_Process.Id.GetHashCode() * 397) ^ m_Process.MainModule.BaseAddress.GetHashCode()) : 0;
}
}
}