WPF ComboBox сбрасывает выбранный элемент при изменении элемента-источника

рассмотрим следующий XAML:

<ComboBox Name="CompanyComboBox" 
    HorizontalAlignment="Stretch"
    ItemsSource="{Binding Path=GlobalData.Companies}" 
    SelectedValuePath="Id"
    SelectedValue="{Binding Customer.CompanyId, ValidatesOnDataErrors=True}"
    DisplayMemberPath="Name" />

GlobalData.Companies коллекция (IEnumerable<Company>) компаний; эта коллекция может быть перезагружена в фоновом режиме (она загружается с веб-сервиса). Когда это происходит, ComboBox правильно перезагружает элементы через привязку. Однако в качестве побочного эффекта, он также сбрасывает выбранный товар!

Я использовал отражатель для проверки источников комбинированных ящиков, и, по-видимому, это предназначено для поведения.

есть ли какой-либо "хороший" способ обойти это? Я хочу добиться того, что если пользователь выбирает "компанию А" и перезагружает список компаний после этого, то "компания а" остается выбранной (предполагая, что она находится в новом списке).

4 ответов


возможно, вы можете использовать ObservableCollection<Company> вместо IEnumerable<Company>? Затем при изменении фона вы добавляете / удаляете только новые / отсутствующие в новом списке элементы, выбранный элемент должен оставаться, если он не был удален изменением.

Вы можете обновите свою наблюдаемую коллекцию в отдельном потоке с помощью небольшого hack-around.


попробуйте использовать следующий код. Включите следующее свойство в поле со списком

IsSynchronizedWithCurrentItem="True"


Хм, я не знаю, является ли это" хорошим " способом, но если вы можете получить доступ к выбранному элементу до перезагрузки, вы можете сохранить его (или его ключ или что-то еще) и выбрать его программно снова после перезагрузки.

быстрый макет:

var selectedItem = myCombo.SelectedItem;
DoReload();
myCombo.SelectedItem = selectedItem;

но я предполагаю, что вы имеете в виду другой способ, чем эта ручная работа?
Надеюсь, это все равно поможет...

обновление
Хорошо, я вижу, из фонового потока.
Вы используете ICollectionView, чтобы связать ваш combobox тоже? Если это так, вы можете использовать свойство CurrentItem для сохранения ссылки. Я сделал быстрый макет, и это работает на моей установке. это предполагает, что у вас есть ссылка на ваш UI:

XAML

<Grid VerticalAlignment="Top">  
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <ComboBox ItemsSource="{Binding Items}" IsSynchronizedWithCurrentItem="True" Grid.Column="0" Grid.Row="0" DisplayMemberPath="Name"/>
    <Button Command="{Binding UpdateCommand}" Grid.Column="1" Grid.Row="0">Update</Button>
</Grid>

Просмотр / ViewModel

public partial class Window1 : Window {
   public Window1() {
        InitializeComponent();
        this.DataContext = new ViewModel(this);
   }
}

public class ViewModel
{
    private readonly Window1 window;
    private ObservableCollection<Item> items;
    private ICollectionView view;

    public ViewModel(Window1 window) {
        this.window = window;
        items = new ObservableCollection<Item>
            {
                new Item("qwerty"),
                new Item("hello"),
                new Item("world"),
            };

        view = CollectionViewSource.GetDefaultView(items);
    }

    public ObservableCollection<Item> Items { get { return items; } }

    public ICommand UpdateCommand {
        get { return new RelayCommand(DoUpdate); }
    }

    public Item SelectedItem { get; set; }

    private void DoUpdate(object obj) {
        var act = new Func<List<Item>>(DoUpdateAsync);
        act.BeginInvoke(CallBack, act);
    }

    private List<Item> DoUpdateAsync() {
        return new List<Item> {
                new Item("hello"),
                new Item("world"),
                new Item("qwerty"),
            };
    }

    private void CallBack(IAsyncResult result) {
        try {
            var act = (Func<List<Item>>)result.AsyncState;
            var list = act.EndInvoke(result);

            window.Dispatcher.Invoke(new Action<List<Item>>(delegate(List<Item> lst) {
                                                                    var current = lst.Single(i => i.Name == ((Item)view.CurrentItem).Name);
                                                                    Items.Clear();
                                                                    lst.ForEach(Items.Add);
                                                                    view.MoveCurrentTo(current);
                                                                }), list);

        } catch(Exception exc){ Debug.WriteLine(exc); }
    }
}

public class Item {
    public Item(string name) {
        Name = name; 
    }
    public string Name { get; set; }
}

вам нужно будет сделать некоторую обработку в случае, если выбранный элемент больше не находится в списке.
The IsSynchronizedWithCurrentItem собственность здесь важно, иначе это не сработает!
Кроме того, ссылка на главное окно должна быть сделана с помощью DI-framework.


Как указал Якодер, это связано с равенством объектов. Пока вы привязываете SelectedValue вместо SelectedItem, вы можете определить ItemsSource как анонимную коллекцию типов. Тогда эта проблема не возникнет (и это также быстрее, если вам нужно прочитать значения из базы данных).