【问题标题】:WPF - Restore Previous SelectedItem when ComboBox ItemSource ChangesWPF - 当 ComboBox ItemSource 更改时恢复上一个 SelectedItem
【发布时间】:2013-04-16 16:48:48
【问题描述】:

我正在实现一个可以由用户使用按钮刷新的 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)时,代码似乎无法正常工作。

【问题讨论】:

    标签: c# wpf xaml data-binding mvvm


    【解决方案1】:

    您需要将m_SelectedItem 设置为SingleOrDefault(Recover(...)) 找到的项目。

    目前,您将其设置为旧实例。该实例不再存在于列表中,显然您的 BrowserInstance 类没有实现任何相等成员。

    根据您当前的代码正确代码:

    if(selectedItem != null)
        m_SelectedItem = m_Items.SingleOrDefault(Recover(selectedItem));
    if(m_SelectedItem == null)
        m_SelectedItem = m_Items[0];
    

    更新:

    您上传的代码有两个问题。

    1. 如果没有进程,您添加的默认BrowserInstance 对象的Process 属性的值是null。这导致SingleOrDefault 使用的比较代码中出现NullReferenceException
      通过将前面的 if 更改为

      来修复它
      if(selectedItem != null && selectedItem.Process != null)
      
    2. Populate 方法结束时,您为Items 引发PropertyChanged 事件 - 以更新组合框中的值 - 并为SelectedItem - 将所选项目设置为用户之前拥有的项目已选中。
      这里的问题是,当为PropertyChanged 引发Items 时,WPF 将使用null 更新SelectedItem,因为它在新项目列表中找不到先前选择的项目。这有效地覆盖了您在 Populate 方法中计算的新选定项。
      通过不将新选定项分配给 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;
      }
      

    如果您能正确实现 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;
            }
        }
    }
    

    【讨论】:

    • 哇,谢谢!在自定义类上实现平等成员的最佳方法是什么?我的 BrowserInstance 类只有两个字段:System.Diagnostics.Process Process 和 System.String Name... 我所要做的就是比较进程,但我找不到正确检查两个进程是否相同的方法。
    • @Zarathos:您需要覆盖 Object.EqualsObject.GetHashCode。如何准确地进行比较取决于您和您的程序。这取决于您如何为 BrowserInstance 定义相等性。例如,您可以使用来自Recover 的逻辑。
    • Mhhhh... 我正在测试我的代码,但仍然发现一些问题。如果我继续按下刷新按钮,由于未知原因,ComboBox 所选项目会闪烁(有时它是空的,有时不是)。我无法理解原因。我按照您的建议用我编辑的代码编辑了这个问题。
    • @Zarathos:你没有按照我展示的方式编辑它。请注意我的代码中selectedItemm_SelectedItem 之间的区别。 selectedItem 取自您之前版本的代码,包含在用户单击“刷新”之前已选择的项目。
    • 咳咳,抱歉。好的,我现在正确修改了它。但是当我有一个选定的项目并且我很快单击刷新按钮时,仍然会发生一些错误。它应该始终保持当前的...但实际上有时 SelectedItem 为空,并且没有从 ComboBox 中选择任何值。看起来在 SingleOrDefault 之后发生了一些不好的事情,我无法选择列表中的第一个项目,或者我从中获得的所选项目是错误的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-26
    • 2011-02-04
    • 2011-01-10
    • 1970-01-01
    • 1970-01-01
    • 2011-04-27
    相关资源
    最近更新 更多