2009-09-27 3 views
3

У меня есть следующий XAML разметку:WPF ComboBox привязки поведение

<TextBox x:Name="MyTextBox" Text="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber, UpdateSourceTrigger=PropertyChanged}" /> 
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Products}" DisplayMemberPath="ProductName" 
    SelectedValue="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber}" 
    SelectedValuePath="ProductNumber" /> 

Мой Посмотреть DataContext-х связан с ViewModel, содержащей государственную собственность под названием SelectedCustomer. Объекты Customer содержат свойство FavouriteProduct объектов Product и Product, содержащих общедоступные свойства ProductNumber и ProductName.

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

Рассмотрим следующий список продуктов ([Номер продукта], [Имя продукта]):

  1. Fanta
  2. Пепси
  3. Coca Cola
  4. Sprite
  5. Вода

Теперь скажем, что функция Избранного клиента te продукт Coca Cola (должен быть разработчиком). Поэтому, когда окно открывается, TextBox читает 3, а ComboBox читает Coca Cola. Прекрасный. Теперь измените номер продукта в TextBox на 2. ComboBox обновляет его значение для Pepsi. Теперь попробуйте изменить номер продукта в TextBox на что-нибудь большее, чем число для Coca Cola (3). Не очень мило. При выборе 4 (Sprite) или 5 (Water) ComboBox возвращается обратно в Coca Cola. Таким образом, поведение похоже, что все, что находится ниже элемента, которое вы открываете ширину окна из списка в ItemSource, не работает. Установите его в 1 (Fanta), и никто из других не работает. Установите его на 5 (Вода), и все они работают. Может ли это иметь отношение к некоторой инициализации для ComboBox? Потенциальная ошибка? Любопытно, если кто-либо еще видел это поведение.

UPDATE:

После прочтения ответа Майка Брауна я создал свойство для SelectedProduct и SelectedProductNumber. Проблема, с которой я сталкиваюсь, заключается в том, что как только вы выберете что-то из ComboBox, вы окажетесь в бесконечном цикле, в котором свойства сохраняют updatign друг друга. Я неправильно выполнил обработчик OnPropertyChanged или есть что-то, чего я не вижу? Вот фрагмент кода из моего ViewModel:

private int _SelectedProductNumber = -1; 
     public int SelectedProductNumber 
     { 
      get 
      { 
       if (_SelectedProductNumber == -1 && SelectedCustomer.Product != null) 
        _SelectedProductNumber = SelectedCustomer.Product.ProductNumber; 
       return _SelectedProductNumber; 
      } 
      set 
      { 
       _SelectedProductNumber = value; 
       OnPropertyChanged("SelectedProductNumber"); 
       _SelectedProduct = ProductList.FirstOrDefault(s => s.ProductNumber == value); 
      } 
     } 

     private Product _SelectedProduct; 
     public Product SelectedProduct 
     { 
      get 
      { 
       if (_SelectedProduct == null) 
        _SelectedProduct = SelectedCustomer.Product; 
       return _SelectedProduct; 
      } 
      set 
      { 
       _SelectedProduct = value; 
       OnPropertyChanged("SelectedProduct"); 
       _SelectedProductNumber = value.ProductNumber; 
      } 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 

     protected virtual void OnPropertyChanged(string property) 
     { 
      if (PropertyChanged != null) 
       PropertyChanged(this, new PropertyChangedEventArgs(property)); 
     } 

UPDATE 2

Я изменил реализацию немного в настоящее время путем обновления SelectedCustomer.FavouriteProduct от обоих свойств, а затем, используя, что при чтении их значения. Теперь это работает, но я не уверен, что это «правильный путь».

private int _SelectedProductNumber = 0; 
public int SelectedProductNumber 
{ 
    get 
    { 
     if (SelectedCustomer.Product != null) 
      _SelectedProductNumber = SelectedCustomer.Product.ProductNumber; 
     return _SelectedProductNumber; 
    } 
    set 
    { 
     _SelectedProductNumber = value; 
     SelectedCustomer.FavouriteProduct = ProductList.FirstOrDefault(s => s.ProductNumber == value); 
     OnPropertyChanged("SelectedProductNumber"); 
     OnPropertyChanged("SelectedProduct"); 
    } 
} 

private Product _SelectedProduct; 
public Product SelectedProduct 
{ 
    get 
    { 
     if (SelectedCustomer.Product != null) 
      _SelectedProduct = SelectedCustomer.Product; 
     return _SelectedProduct; 
    } 
    set 
    { 
     _SelectedProduct = value; 
     SelectedCustomer.FavouriteProduct = value; 
     OnPropertyChanged("SelectedProduct"); 
     OnPropertyChanged("SelectedProductNumber"); 
    } 
} 
+0

Ошибка была на мне ... Я обновил свой ответ с помощью правильного кода. –

+0

Чтобы гарантировать, что вы не активируете измененные события, когда свойство фактически не изменилось, используйте блок 'if (_selectedProduct! = Value) {/*...*/}'. –

+0

Ваше новое решение совершенно справедливо (на самом деле оно позволяет избежать повторяющейся информации, которая отлично). Что странно для меня, однако, вы все еще получаете переполнение стека. Я поместил код в VS, и он отлично работает для меня. Не уверен, что происходит с вашей стороны. Является ли ваше изменение на selectedcustomer.favoriteproduct, также предлагая обновление для двух других свойств? В любом случае, я обновил свой пример кода с версией, которая работает на моей машине (TM) –

ответ

0

Try: SelectedItem = "{Binding Path = YourPath, Mode = TwoWay"} вместо установки SelectedValue и SelectedValuePath.

Возможно также работать с SelectedValue, не забудьте Mode = TwoWay, так как это не значение по умолчанию.

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

Вот пример: http://blogs.microsoft.co.il/blogs/tomershamam/archive/2008/03/28/63397.aspx

Уведомление мастер связывания имеет вид {Binding} или {Binding} SourceCollection и детали связывания имеет вид {Binding} или {Binding SourceCollection}.

Для этой работы вам необходимо обернуть коллекцию объектом, который сохраняет выбранный элемент. WPF имеет один из этих встроенных: ObjectDataProvider.

Пример: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/068977c9-95a8-4b4a-9d38-b0cc36d06446

+0

Я не вижу, как я могу использовать SelectedItem для установки выбранного продукта на основе ProductNumber –

+0

Отредактированный ответ для решения проблемы. –

1

Ваша цель не слишком ясно, так что я написал folloiwng так поддержать или варианты я могу видеть.

Чтобы сохранить два элемента, присоединенные к одному элементу в синхронизации вы можете установить в IsSynchronizedWithCurrentItem="True" на вашем выпадающий список, как показано ниже:

<TextBox x:Name="MyTextBox" Text="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber, UpdateSourceTrigger=PropertyChanged}" /> 
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Products}" DisplayMemberPath="ProductName" 
    SelectedValue="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber}" 
    IsSynchronizedWithCurrentItem="True" 
    SelectedValuePath="ProductNumber" /> 

Это будет означать все, что в текущем окне, связанного с тем же фон объекта будет держать в синхронизировать и не давать странное поведение, которое вы видите.

Эта цитата форме это больше MSDN article описывает эффект:

IsSynchronizedWithCurrentItem атрибут имеет важное значение в том, что, когда изменения выбора, то есть то, что изменяет «текущий элемент», насколько в окно. Это сообщает движку WPF, что этот объект будет использоваться для изменения текущего пункта . Без этого атрибута текущий элемент в DataContext не будет изменен , поэтому ваши текстовые поля будут считать, что он по-прежнему находится на первом элементе .

Тогда установка Mode=TwoWay как предложено другой ответ будет только убедиться, что как при обновлении текстового поля основной объект будет обновляться и при обновлении объекта в текстовом поле обновляется.

Это делает текстовое поле редактирования выбранных элементов текст, а не выбрать пункт в combolist с текстом согласования (который является альтернатива думает, что вы, возможно, пытаетесь достичь?)

Для достижения синхронизированного эффекта выбора возможно, стоит установить IsEditable="True" в поле со списком, чтобы пользователи могли вводить элементы и отбрасывать текстовое поле. Альтернативно, если вам нужны две коробки, замените текстовое поле на второе поле со списком IsSynchronizedWithCurrentItem="True" и IsEditable="True", а затем нарисуйте его как текстовое поле.

+0

Это выглядело очень многообещающим, но, к сожалению, я по-прежнему получаю такое же поведение. TextBox обновляется, когда я выбираю что-то из ComboBox, но другой способ работает неправильно. Я думаю, что, возможно, все пойдет не так. –

+0

Вы ищете текст в текстовом поле, чтобы изменить выбор или изменить/отредактировать текст выбранного элемента? Я обновил ответ на оба решения. – John

+0

Я стараюсь дать пользователю два варианта выбора продукта. Либо выбирая продукт из ComboBox, либо введя номер продукта. Я могу сделать ComboBox доступным для редактирования, но это не позволит мне вводить номера продуктов. Я еще не изучал использование отдельного ComboBox с параметром IsSynchronizedWithCurrentItem. –

1

Что вы хотите сделать, это разоблачить отдельные свойства на ViewModel для выбранного в данный момент продукта и выбранного в настоящее время номера продукта. Когда выбранный продукт изменен, обновите номер продукта и наоборот.Таким образом, ваш ViewModel должен выглядеть как этот

public class MyViewModel:INotifyPropertyChanged 
{ 
    private Product _SelectedProduct; 
    public Product SelectedProduct 
    { 
     get { return _SelectedProduct; } 
     set 
     { 
      _SelectedProduct = value; 
      PropertyChanged(this, new PropertyChangedEventArgs("SelectedProduct")); 
      _SelectedProductID = _SelectedProduct.ID; 
      PropertyChanged(this, new PropertyChangedEventArgs("SelectedProductID")); 
     } 
    } 
    private int _SelectedProductID; 
    public int SelectedProductID 
    { 
     get { return _SelectedProductID; } 
     set 
     { 
      _SelectedProductID = value; 
      PropertyChanged(this, new PropertyChangedEventArgs("SelectedProductID")); 
      _SelectedProduct = _AvailableProducts.FirstOrDefault(p => p.ID == value); 
      PropertyChanged(this,new PropertyChangedEventArgs("SelectedProduct")); 
     } 
    } 
    private IEnumerable<Product> _AvailableProducts = GetAvailableProducts(); 

    private static IEnumerable<Product> GetAvailableProducts() 
    { 
     return new List<Product> 
        { 
         new Product{ID=1, ProductName = "Coke"}, 
         new Product{ID = 2, ProductName="Sprite"}, 
         new Product{ID = 3, ProductName = "Vault"}, 
         new Product{ID=4, ProductName = "Barq's"} 
        }; 
    } 

    public IEnumerable<Product> AvailableProducts 
    { 
     get { return _AvailableProducts; } 
    } 
    private Customer _SelectedCustomer; 
    public Customer SelectedCustomer 
    { 
     get { return _SelectedCustomer; } 
     set 
     { 
      _SelectedCustomer = value; 
      PropertyChanged(this, new PropertyChangedEventArgs("SelectedCustomer")); 
      SelectedProduct = value.FavoriteProduct; 
     } 
    } 
    public event PropertyChangedEventHandler PropertyChanged; 
} 

Так что теперь ваш XAML связывается с соответствующими свойствами и ViewModel отвечает за syncrhronization

<TextBox 
    x:Name="MyTextBox" 
    Text="{Binding Path=SelectedProductID, UpdateSourceTrigger=PropertyChanged}" /> 
<ComboBox 
    x:Name="MyComboBox" 
    ItemsSource="{Binding AvailableProducts}" 
    DisplayMemberPath="ProductName" 
    SelectedItem="{Binding SelectedProduct}" /> 

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

+0

Я думал, что мне, возможно, придется сделать что-то подобное. Я добавил соответствующие свойства, но у меня проблема между свойствами ProductId и Product. Как только вы меняете любой из них, вы попадаете в бесконечный цикл и, наконец, получаете исключение StackOverflow;) Возможно, из-за того, как я реализую обработчик (я довольно новичок в WPF). Я обновлю вопрос с помощью кода. –

+0

Извините, что мне было противно ... вместо того, чтобы обновлять противоположное свойство, просто обновите поле поддержки и измените свойство вызова ... Я обновлю код с правильной реализацией. –

+1

Хорошо сделано ... это должно работать для вас сейчас ... они должны добавить специальный значок для ответов, которые приводят к переполнению стека;) –

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