Связывание ячеек Silverlight со списками объектов - работает, но безобразно - PullRequest
2 голосов
/ 24 января 2009

Я занимаюсь разработкой бизнес-приложения с использованием Silverlight для пользовательского интерфейса и веб-службы WCF для серверной части. В базе данных у меня есть несколько таблиц поиска. Когда служба WCF возвращает бизнес-объект, одно из свойств содержит всю строку из таблицы поиска, а не только внешний ключ, поэтому в пользовательском интерфейсе я могу отображать такие вещи, как описание из таблицы поиска, без повторного вызова оказание услуг. В данный момент я пытаюсь предоставить комбинированный список, связанный со всем списком значений поиска, и правильно его обновить. Бизнес-объект, с которым я имею дело в этом примере, называется Session, а поиск называется SessionType.

Ниже приведено определение комбинированного списка. DataContext устанавливается на экземпляр Session. Я устанавливаю ItemTemplate, потому что в выпадающем списке отображается больше, чем просто список строк.

<ComboBox 
x:Name="SessionTypesComboBox"
ItemTemplate="{StaticResource SessionTypeDataTemplate}"
ItemsSource="{Binding Source={StaticResource AllSessionTypes}}"
SelectedItem="{Binding Path=SessionType, Mode=TwoWay}"
/>

И бизнес-объект, и таблица поиска загружаются асинхронно через веб-сервис. Если я больше ничего не сделаю, список со списком будет заполнен SessionTypes, но он не будет показывать начальное значение SessionType из Session. Однако сессия будет обновлена ​​с правильным SessionType, если выбор в выпадающем списке изменен.

Кажется, что происходит то, что привязка SelectedItem не может сопоставить SessionType в Session с его эквивалентом в списке SessionType. Значения объекта совпадают, но ссылки не совпадают.

Обходной путь, который я нашел, состоит в том, чтобы загрузить Session и список SessionTypes, а затем обновить текущий SessionType для Session соответствующим соответствующим из списка SesstionTypes. Если я это сделаю, то выпадающий список отображается правильно. Однако для меня это имеет неприятный запах кода. Поскольку все загружается асинхронно, я должен определить, когда все доступно. Вот как я это делаю:

В коде моего пользовательского элемента управления Silverlight:

// incremented every time we get data back during initial form load.
private volatile int m_LoadSequence = 0;

...

// Loaded event, called when the form is er... loaded.
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    // load session types
    var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
    if (sessionTypes != null)
    {
        sessionTypes.DataLoadCompleted += (s, ea) =>
        {
            IncrementLoadSequence();
        };
        sessionTypes.LoadAsync();
    }

    // start loading another lookup table, same as above
    // omitted for clarity

    // set our DataContect to our business object (passed in when form was created)
    this.LayoutRoot.DataContext = this.m_Session;
    IncrementLoadSequence();
}

// This is the smelly part. This gets called by OnBlahCompleted events as web service calls return.
private void IncrementLoadSequence()
{
    // check to see if we're expecting any more service calls to complete.
    if (++m_LoadSequence < 3)
        return;

    // set lookup values on m_Session to the correct one in SessionType list.

    // Get SessionType list from page resources
    var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;

    // Find the matching SessionType based on ID
    this.m_Session.SessionType = sessionTypes.Where((st) => { return st.SessionTypeID == this.m_Session.SessionType.SessionTypeID; }).First();

    // (other lookup table omitted for clarity)
}

Так что в основном у меня есть счетчик, который увеличивается каждый раз, когда я получаю данные от веб-службы. Так как я ожидаю 3 вещи (основной бизнес-объект + 2 таблицы поиска), когда этот счетчик достигает 3, я сопоставляю ссылки.

Мне это кажется очень глупым. Я бы предпочел, чтобы в выпадающем списке были указаны ValueMemberPath и SelectedValue, чтобы сопоставить выбранный элемент с одним в списке.

Может кто-нибудь увидеть более чистый способ сделать это? Такая ситуация очень распространена в бизнес-приложениях, поэтому я уверен, что должен быть хороший способ сделать это.

Ответы [ 5 ]

2 голосов
/ 26 января 2009

Джеф,

Чтобы подтвердить, я понимаю вашу проблему: инфраструктура привязки данных, похоже, не распознает, что два объекта вы считаете «равными» равны на самом деле равны - следовательно, исходное значение SelectedItem не установлено неправильно, поскольку привязка данных не находит объект с равными ссылками в вашей коллекции StaticResource, который соответствует Session.SessionType. Вы можете обойти это, «сгладив» ссылки (т. Е. Вы заставляете Session.SessionType быть равными ссылкам в коде Where((st)...First().

У нас была похожая проблема .

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * '' * '' * '* * * * * В некотором смысле имеет смысл, что Silverlight не будет автоматически "уравнивать" два объекта из разных "источников" только потому, что вы знают, что они представляют одни и те же данные. Как вы сказали " Значения объекта одинаковы, но ссылки не ". Но как сделать так, чтобы привязка данных приравнивала их?

Вещи, которые мы подумали / попробовали:

  • реализация .Equals () для класса (SessionType в вашем случае)

  • оператор реализации == для класса (SessionType в вашем случае)

  • реализация IEquatable в классе (SessionType в вашем случае)

  • создание коллекции только String с и привязка к строковому свойству

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

Я согласен с вами по поводу запаха кода, и подозреваю, что должно быть лучшим решением. Пока что все наши отладки в методах доступа к свойствам и no-op IValueConverter s не нашли решения - но если мы это сделаем, я тоже опубликую их здесь.

1 голос
/ 24 января 2009

Вам лучше привязать к ObservableCollection, чем использовать какой-либо другой код (часть модели представления MVVM - неплохой выбор) для его обновления в фоновом режиме. Таким образом, вы получаете отделение от пользовательского интерфейса, и гораздо проще обрабатывать обновления, поскольку пользовательский интерфейс просто привязан.

1 голос
/ 24 января 2009

Я не уверен, что полностью понимаю проблему (это рано :)) Но разве вы не можете просто перевести все необходимые вещи за один звонок? (даже если вам нужно обернуть 3 в новый класс DTO), вы можете просто обновить текущий тип сеанса, используя событие complete. Это все еще не идеально, но, по крайней мере, вам не нужно держать счетчики.

Я бы также переместил всю эту логику в ViewModel и просто привязал к ней, но это только я :)

0 голосов
/ 18 марта 2010

geofftnz , Вы нашли какое-нибудь хорошее решение для этого?

CraigD , Я сомневаюсь, что переопределение Equals и т. Д. Является хорошим решением. Во-первых, это должно быть сделано внутри сгенерированного прокси-класса SessionType, поэтому эти изменения будут потеряны при каждом обновлении ссылки на службу. Во-вторых, уведомление в установщике SessionType (здесь SessionType - это тот же сгенерированный клиентский прокси-класс) использует вызов ReferenceEquals ... так что это еще одно место, где можно потрогать сгенерированный код! Хорошо, первое, что можно сделать через частичный класс SessionType, созданный вручную (и поэтому не будет потерян после обновления), но второе, безусловно, нельзя сделать таким же образом.

0 голосов
/ 26 января 2009

Спасибо за ответы, все вышеперечисленное было полезно! Я двигаюсь в направлении MVVM, а также объединяю несколько сервисных вызовов в один (также сокращает затраты на передачу данных в оба конца). Похоже, что пока я буду придерживаться повторной ссылки поиска - если я найду лучший способ, я также опубликую его.

...