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 правильно перезагружает объекты через привязку. Однако как побочный эффект, это также сбрасывает выбранный пункт!

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

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

6
задан Dave Clemmer 19 September 2011 в 16:14
поделиться

2 ответа

Для этой цели в 1,0 существовал With.ConstructorArgument . В 2.0 синтаксис немного изменился: - With.Parameters.ConstrityArgument с ninject 2,0

См. раздел Ввод значения в введенную зависимость для получения более подробной информации и примеров использования контекста, провайдеров и аргументов для более корректной передачи подобных данных.

EDIT: Поскольку Стивен решил сделать вид, что мой комментарий не имеет значения, я бы лучше прояснить, что я говорю на некоторых примерах (для 2,0):

MyClass m = kernel.Get<MyClass>( new ConstructorArgument( "i", 2) );

что для моих глаз очень ясно и точно указывает, что происходит.

Если вы находитесь в положении, где вы можете определить параметр более глобальным образом, вы можете зарегистрировать провайдера и сделать это так:

class MyClassProvider : SimpleProvider<MyClass>
{
    protected override MyClass CreateInstance( IContext context )
    {
        return new MyClass( context.Kernel.Get<IService>(), CalculateINow() );
    }
}

И зарегистрировать его так:

x => x.Bind<MyClass>().ToProvider( new MyClassProvider() )

NB CalculateINow () бит - это то, куда вы бы положили логику, как в первом ответе.

Или сделать его более сложным, как это:

class MyClassProviderCustom : SimpleProvider<MyClass>
{
    readonly Func<int> _calculateINow;
    public MyClassProviderCustom( Func<int> calculateINow )
    {
        _calculateINow = calculateINow;
    }

    protected override MyClass CreateInstance( IContext context )
    {
        return new MyClass( context.Kernel.Get<IService>(), _calculateINow() );
    }
}

Какие вы бы зарегистрировали так:

x => x.Bind<MyClass>().ToProvider( new MyClassProviderCustom( (  ) => new Random( ).Next( 9 ) ) )

UPDATE: Новые механизмы, которые демонстрируют гораздо улучшенные модели с меньшим шаблоном, чем выше, воплощены в расширении Ninject.Extensions.Factory , см.: https://github.com/ninject/ninject.extensions.factory/wiki

Как указывалось ранее, если каждый раз нужно передавать разные параметры и в графике зависимостей имеется несколько уровней, может потребоваться выполнить что-то подобное .

Последнее соображение состоит в том, что, поскольку вы не указали Using < Behavior > , по умолчанию будет установлено значение по умолчанию, как указано/по умолчанию в параметрах ядра ( Transient Behavior в образце), что может привести к тому, что фабрика вычислит i на лету, [например, если объект кэшируется]

Теперь, чтобы прояснить некоторые другие моменты в комментариях, которые FUDed и блеск над. Некоторые важные вещи, чтобы рассмотреть об использовании DI, будь то Ninject или что-то еще:

  1. Есть как можно больше сделано конструктором инъекции, так что вам не нужно использовать контейнер конкретных атрибутов и трюков. Есть хорошая запись в блоге, которая называется Ваш IoC контейнер показывает .

  2. Минимизируйте код, идущий к контейнеру и запрашивающий вещи - в противном случае ваш код связан с а) определенным контейнером (который CSL может минимизировать) b) способом, которым весь ваш проект выложен. Есть хорошие записи в блоге, которые показывают, что CSL не делает то, что вы думаете, что он делает. Этот общий раздел называется Расположение обслуживания по сравнению с вводом зависимостей . ОБНОВЛЕНИЕ: Подробное и полное обоснование см. в http://blog.ploeh.dk/2011/07/28/CompositionRoot.aspx .

  3. Минимизируйте использование статики и одиночки

  4. Не предполагайте, что существует только один [глобальный] контейнер, и что это нормально, чтобы просто требовать его, когда вам это нужно, как хорошая глобальная переменная. Правильное использование нескольких модулей и Bind.ToProvider () предоставляет структуру для управления этим.Таким образом, каждая отдельная подсистема может работать сама по себе, и у вас не будет низкоуровневых компонентов, связанных с компонентами верхнего уровня, и т.д.

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

Теперь, если бы только Джоэл мог войти и действительно настроить меня на то, что хороший синтаксис и/или правильный способ сделать это!

ОБНОВЛЕНИЕ: Хотя этот ответ явно полезен из числа полученных голосов, я хотел бы сделать следующие рекомендации:

  • Вышеприведенное ощущение, как это немного датировано и, если честно, отражает много неполного мышления, которое почти чувствует себя неловко с момента чтения Dependency Injection in .net - Бегите и покупайте его сейчас - дело не только в DI, первая половина - это полное лечение всех связанных с архитектурой проблем от человека, который провел путь слишком много времени здесь, вися вокруг тэга инъекции зависимости.
  • Прочитайте Лучшие сообщения Марка Симана здесь, на сайте SO, прямо сейчас - вы узнаете ценные методы из каждой
-121--928912-

В соответствии с документацией по перегрузке конструктора StackTrace, которая делает исключение

StackTrace создается с помощью текущий поток вызывающего абонента, но не содержать имя файла, номер строки или сведения о столбцах.

Чтобы получить номера строк , необходимо использовать перегрузку, которая принимает bool, а также исключение .

Для номеров строк также требуются файлы символов (pdb). Файлы символов доступны как для отладки, так и для сборки версий .

-121--3302923-

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

Вы можете обновить наблюдаемую коллекцию в отдельном потоке с небольшим взломом .

4
ответ дан 17 December 2019 в 00:09
поделиться

Да, первый определенно лучше. Я бы никогда не пошел на второй метод. Но вы должны думать об использовании полиморфизма больше. Использование instanceof в таком тяжелом состоянии не является хорошим OO-дизайном.

-121--2964726-

Как сказал куби, пользуйтесь частным филиалом. Если вы вообще не хотите вставлять нарушенный код в репозиторий, вы можете отправить изменения по электронной почте как исправление (используйте git format-patch ), примените его дома и работайте оттуда (с git apply )

Также помните, что вы можете использовать git commit --amend , чтобы перезаписать «наполовину испеченную» фиксацию на правильную, считываемую как толкаемую к общему репо.

-121--4690730-

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

Быстрое макетирование:

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

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

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

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>

View/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; }
}

В случае отсутствия выбранного предмета в списке потребуется выполнить определенную обработку.
Здесь важно свойство IsSynchronesingWireCurrentItem , иначе оно не будет работать!
Кроме того, путь на главное окно должна осуществляться с помощью DI-фреймворка.

1
ответ дан 17 December 2019 в 00:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: