Привязка данных к ObservableCollection в другом UserControl - как сохранить текущий выбор? - PullRequest
1 голос
/ 19 марта 2010

Объем вопросов расширен на 2010-03-25

В итоге я выяснил свою проблему, но вот новая проблема, возникшая в результате решения исходного вопроса, потому что я хочу быть в состоянии наградить кого-то за вознаграждение !!!

Как только я выяснил свою проблему, я вскоре обнаружил, что при обновлении ObservableCollection содержимое ComboBox с базой данных переполняется, но большинство выборок были убраны.

Я предполагаю, что в этом случае MVVM затруднит мне запоминание последнего выбранного элемента. У меня есть идея, но она кажется немного неприятной. Я присужду награду тому, кто придет с хорошим решением для этого!

Вопрос переписан 2010-03-24

У меня есть два элемента UserControl, один из которых представляет собой диалог с TabControl, а другой - тот, который появляется внутри указанного TabControl. Я просто назову их CandyDialog и CandyNameViewer для простоты. Есть также класс управления данными под названием Tracker , который управляет хранением информации, который для всех намерений и целей просто предоставляет открытое свойство, которое является ObservableCollection.

Я отображаю CandyNameViewer в CandyDialog через код, например:

private void CandyDialog_Loaded( object sender, RoutedEventArgs e)
{
  _candyviewer = new CandyViewer();
  _candyviewer.DataContext = _tracker;
  candy_tab.Content = _candyviewer;
}

XAML CandyViewer выглядит следующим образом (отредактировано для kaxaml):

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <DataTemplate x:Key="CandyItemTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="120"></ColumnDefinition>
                    <ColumnDefinition Width="150"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <TextBox Grid.Column="0" Text="{Binding CandyName}" Margin="3"></TextBox>
                <!-- just binding to DataContext ends up using InventoryItem as parent, so we need to get to the UserControl -->
                <ComboBox Grid.Column="1" SelectedItem="{Binding SelectedCandy, Mode=TwoWay}" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.CandyNames}" Margin="3"></ComboBox>
            </Grid>
        </DataTemplate>
    </Page.Resources>

    <Grid>
        <ListBox DockPanel.Dock="Top" ItemsSource="{Binding CandyBoxContents, Mode=TwoWay}" ItemTemplate="{StaticResource CandyItemTemplate}" />
    </Grid>
</Page>

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

Проблема, с которой я столкнулся, заключается в том, что при изменении ObservableCollection из модели эти изменения не отражаются в пользовательском UserControl! У меня никогда не было этой проблемы раньше; все мои предыдущие использования ObservableCollection обновлялись нормально, хотя в тех случаях я не связывал базы данных между сборками. Хотя в настоящее время я только добавляю и удаляю названия конфет в / из ObservableCollection, позднее я, скорее всего, также разрешу переименовывать со стороны модели.

Что-то я не так сделал? Есть хороший способ отладить это? Рид Копси указывает здесь , что возможно связывание данных между UserControl. К сожалению, моя любимая статья Bea Stollnitz об отладке привязки данных WPF не предлагает ничего, что я мог бы использовать для этой конкретной проблемы.

Ответы [ 5 ]

2 голосов
/ 29 марта 2010

У меня была такая же проблема с отключением ComboBox при изменении их DataContext. У меня есть относительно простое решение, которое использует класс, который я написал под названием «ComboBoxFixer». После внедрения вы можете решить эту проблему, просто заменив это:

<ComboBox ItemsSource="..." SelectedItem="..." />

с этим:

<ComboBox ItemsSource="..." my:ComboBoxFixer.SelectedItem="..." />

Объяснение проблемы

Причина, по которой ваши ComboBoxы появляются пустыми, заключается в том, что привязка SelectedItems оценивается, когда ItemsSource не установлен. Это можно исправить, отложив передачу данных в SelectedItems и обратно до тех пор, пока не будет завершено связывание всех других данных.

Как реализовать ComboBoxFixer

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

public class DependencyPropertySynchronizer
{
  public DispatcherPriority Priority { get; set; }
  public DependencyProperty AutoSyncProperty { get; set; }

  public DependencyProperty Register(...
  public DependencyProperty RegisterAttached(...
  public DependencyPropertyKey RegisterReadOnly(...
  public DependencyPropertyKey RegisterAttachedReadOnly(...
}

И обычно используется так:

public SomeClass : DependencyObject
{
  static DependencyPropertySynchronizer sync =
    new DependencyPropertySynchronizer
    {
      Priority = DispatcherPriority.ApplicationIdle
    };

  public static readonly DependencyProperty HappinessProperty =
   sync.RegisterAttached("Happiness", typeof(int), typeof(SomeClass));

  public static readonly DependencyProperty JoyProperty =
   sync.RegisterAttached("Joy", typeof(int), typeof(SomeClass));
}

Приведенный выше код приведет к тому, что присоединенные свойства Happiness и Joy любого данного объекта останутся синхронизированными: всякий раз, когда установлено либо Happiness, либо Joy, для другого будет установлено значение DispatcherPriority.ApplicationIdle. DependencyPropertySynchronizer реализован с использованием скрытого вложенного свойства, в котором хранится последнее значение, установленное в любом свойстве, и координируется планирование обновлений. Вы также можете синхронизировать с существующим свойством, установив AutoSyncProperty.

Используя этот класс, мой класс ComboBoxFixer очень прост:

public class ComboBoxFixer : DependencyObject
{
  static DependencyPropertySynchronizer sync =
    new DependencyPropertySynchronizer
    {
      Priority = DispatcherPriority.ApplicationIdle,
      AutoSyncProperty = Selector.SelectedItemProperty,
    };

  public static readonly DependencyProperty SelectedItemProperty =
    sync.RegisterAttached("SelectedItem", typeof(object), typeof(ComboBoxFixer),
    new FrameworkPropertyMetadata
    {
      BindsTwoWayByDefault = true,
    });

  public static object GetSelectedItem(...  // normal attached property stuff
  public static void SetSelectedItem(...
}

Как это работает

При каждом изменении my: ComboBoxFixer.SelectedItem синхронизатор обновляет Selector.SelectedItem с приоритетом ApplicationIdle или наоборот.

Поток данных:

ViewModel property
   <- bound from ->
      my:ComboBoxFixer.SelectedItem
         <- synced with ->
            ComboBox.SelectedItem

Дополнительные примечания

В определенных обстоятельствах, если вы на самом деле переключаете ItemsSource, возможно, для SelectedItem будет установлено значение null более недавно, чем правильное значение. Эту проблему можно решить, добавив функцию проверки в DependencyObjectSynchronizer, а затем используйте ее для игнорирования нулевых значений во время синхронизации.

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

Ну, теперь я чувствую себя глупо. Мой код был плохо продуман, и только сегодня я понял, что сделал неправильно. Я дважды установил DataContext, каждый на другой объект, и по какой-то причине один из них определил CandyNames как список !!! Argh ...

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

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

Я не Bea Stolnitz (к сожалению, поскольку она потрясающая), но мне интересно, возможно ли, что из-за тайн выражения связей с привязкой к данным в XAML (что НЕ удивительно в малейшей степени, IMO) комбобокс теряет след тот факт, что свойство, с которым оно связано, на самом деле является коллекцией ObservableCollection. Возможно, вы захотите попробовать выставить ObservableCollection, скажем, из вашей модели данных или из какого-либо другого слоя, с которым выпадающий список может быть привязан напрямую (а не через элемент управления вкладками). Возможно, вам придется использовать немного C # для этого, а не XAML, и это может оскорбить некоторых пуристов MVVM, но многие часы и дни, которые я потратил на устранение неполадок, подобных этой, вызвали у меня некоторое отвращение для XAML и MVVM. По моему мнению, если я не могу заставить сложный сценарий привязки данных работать в течение часа или около того, возможно, пришло время попробовать другой подход.

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

Ваш xaml сбивает меня с толку, но я думаю, что вам нужно привязать TextBox следующим образом: {Binding SelectedCandy.CandyName}

Наличие ObservableCollection сообщает WPF, когда элементы добавляются или удаляются , поэтомуудалите или добавьте CandyNames из вашей модели, вы должны увидеть соответствующее обновление в выпадающем списке.Но если вы измените CandyName, то WPF требует, чтобы вы внедрили INotifyPropertyChanged, чтобы дать привязке WPF заголовок

, поэтому все довольно просто (если я понимаю вашу модель)

public class Candy : INotifyPropertyChanged
{
  string _CandyName;
  public string CandyName
  {
    get { return _CandyName;}
    set { _CandyName = value; OnPropertyChanged("CandyName");
  }

  public event PropertyChangedEventHandler OnPropertyChanged;

  void OnPropertyChanged(string prop)
  {
    if (PropertyChanged != null) PropertyChanged(this,new PropertyChangedEventArgs(prop));
  }
}
0 голосов
/ 20 марта 2010

Звучит так, как будто ваш объект в ObservableCollection не хочет изменений в отображении (или ObservableCollection не хочет изменений ???). Если бы сначала я использовал интерфейс INotifyPropertyChanged в моей объектной модели.

...