简体   繁体   English

WPF - 当ComboBox ItemSource更改时还原以前的SelectedItem

[英]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. 您上传的代码有两个问题。

  1. 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) 
  2. 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并在引发ItemsPropertyChanged事件后将该值分配给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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM