[英]WPF - Restore Previous SelectedItem when ComboBox ItemSource Changes
I'm implementing a ComboBox that can be refreshed by users using a button. 我正在实现一个ComboBox,用户可以使用按钮刷新它。 I'm trying to make it so that the previously selected item is automatically reselected if still present inside the ComboBox after a refresh.
我正在尝试使其在刷新后仍然存在于ComboBox中时自动重新选择先前选择的项目。
MainWindow.xaml: 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: MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
DataContext = m_BrowserInstances = new BrowserInstancesViewModel();
}
private void OnClickButtonRefresh(Object sender, RoutedEventArgs e)
{
m_BrowserInstances.Populate();
}
[EDITED TO CURRENT VERSION] BrowserInstancesViewModel.cs: [编辑为当前版本] 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");
}
}
I can get back the previously selected item, but only sometimes. 我可以取回之前选择的项目,但有时只能。 Looks like the code is not working properly when I need to select a default value (Index 0) if the previous selected item cannot be recovered.
如果无法恢复上一个选定项目,我需要选择默认值(索引0)时,代码看起来不能正常工作。
You need to set m_SelectedItem
to the item found by SingleOrDefault(Recover(...))
. 您需要将
m_SelectedItem
设置为SingleOrDefault(Recover(...))
找到的项目。
Currently, you are setting it to the old instance. 目前,您将其设置为旧实例。 That instance no longer exists in the list and apparently your
BrowserInstance
class doesn't implement any equality members. 该实例不再存在于列表中,显然您的
BrowserInstance
类没有实现任何相等的成员。
Correct code based on your current code: 根据您当前的代码更正代码:
if(selectedItem != null)
m_SelectedItem = m_Items.SingleOrDefault(Recover(selectedItem));
if(m_SelectedItem == null)
m_SelectedItem = m_Items[0];
Update: 更新:
The code you uploaded has two problems. 您上传的代码有两个问题。
The value of the Process
property of the default BrowserInstance
object that you add if there is no process is null
. 如果没有进程,则添加的默认
BrowserInstance
对象的Process
属性的值为null
。 This leads to a NullReferenceException
in the comparison code used by SingleOrDefault
. 这导致
SingleOrDefault
使用的比较代码中出现NullReferenceException
。
Fix it by changing the preceding if
to 通过更改前面的
if
来修复它
if(selectedItem != null && selectedItem.Process != null)
At the end of the Populate
method you raise the PropertyChanged
event for Items
- to update the values in the combobox - and for SelectedItem
- to set the selected item to the one the user had previously selected. 在
Populate
方法的末尾,您可以为Items
引发PropertyChanged
事件 - 更新组合框中的值 - 以及SelectedItem
- 将所选项设置为用户先前选择的项。
The problem here is that WPF will update SelectedItem
with null
when PropertyChanged
is raised for Items
as it doesn't find the previously selected item in the new item list. 这里的问题是,当为
Items
引发PropertyChanged
时,WPF将使用null
更新SelectedItem
,因为它在新项目列表中找不到先前选择的项目。 This effectively overwrites the new selected item you computed in the Populate
method. 这有效地覆盖了您在
Populate
方法中计算的新选择项。
Fix it by not assigning the new selected item to m_SelectedItem
but to selectedItem
and assign that value to SelectedItem
after the PropertyChanged
event for Items
was raised: 修复它的方法是不将新选择的项目分配给
m_SelectedItem
而是分配给selectedItem
并在引发Items
的PropertyChanged
事件后将该值分配给SelectedItem
:
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; }
If you would properly implement equality for BrowserInstance
you could make use of the WPF feature that keeps the currently selected item. 如果要正确实现
BrowserInstance
相等性,则可以使用保留当前所选项的WPF功能。
The code of Populate
can be simplified like this: 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];
}
The equality implementation of BrowserInstance
looks like this: 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;
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.