2013-04-23 2 views
1

Я реализую ComboBox, который может быть обновлен пользователями с помощью кнопки. Я пытаюсь сделать так, чтобы ранее выбранный элемент автоматически переизбирался, если он все еще присутствует внутри ComboBox после обновления.WPF - Восстановить Предыдущий SelectedItem, когда ComboBox Изменения ItemSource

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(); 
} 

[отредактирован CURRENT VERSION] 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), если предыдущий выбранный элемент не может быть восстановлен.

ответ

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]; 

Update:

Код, который вы загрузили две проблемы.

  1. Значение Process имущества BrowserInstance объекта по умолчанию, который вы добавляете, если нет процесса null. Это приводит к NullReferenceException в коде сравнения, используемом SingleOrDefault.
    это исправить, изменив предшествующую if к

    if(selectedItem != null && selectedItem.Process != null) 
    
  2. В конце Populate метода вы поднять PropertyChanged событие для Items - для обновления значений в выпадающем списке - и для SelectedItem - для установки выбранного элемента к тому, который ранее был выбран пользователь.
    Проблема заключается в том, что WPF обновит SelectedItem с помощью null, когда PropertyChanged поднят для Items, так как он не отображает ранее выбранный элемент в списке новых элементов. Это эффективно перезаписывает новый выбранный элемент, который вы вычисляете в методе Populate.
    исправить это, не назначая новый выбранный элемент m_SelectedItem но selectedItem и присвоить это значение SelectedItem после повышения PropertyChanged события для Items:

    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; 
     } 
    } 
} 
+0

Вау спасибо! Каков наилучший способ реализации членов равенства в пользовательском классе? Мой класс BrowserInstance имеет только два поля: 'System.Diagnostics.Process' Process и' System.String Name' ... все, что мне нужно, чтобы сравнить процессы, но я не могу найти способ правильно проверить, если два процессы идентичны. –

+0

@ Zarathos: вам нужно будет переопределить 'Object.Equals' и' Object.GetHashCode'. Как именно вы выполняете сравнение, зависит от вас и вашей программы. Это зависит от того, как вы определяете равенство для BrowserInstance. Например, вы можете использовать логику из 'Recover'. –

+0

Mhhhh ... Я тестирую свой код, и я все еще вижу некоторые проблемы. Если я продолжаю нажимать кнопку Refresh, по неизвестной причине выбранный элемент ComboBox мигает (иногда он пуст, иногда нет). Я не могу понять причину. Я отредактировал вопрос с моим отредактированным кодом, следуя вашим предложениям. –

Смежные вопросы