Почему элемент WPF ComboBox не обновляется?

Я не понимаю, почему в моем следующем примере поле со списком «Модель биллинга» не отображает свойство BillingModel.BillingModelDescription, когда это отображается в текстовом поле. После выбора клиента я хочу, чтобы в поле со списком отображалось текущее описание модели биллинга, но оно остается пустым. Текстовое поле, привязанное к тому же объекту, показывает описание. У меня есть набор возможных моделей в качестве ItemsSource, который работает нормально. Как обновить поле со списком Модель биллинга при выборе клиента?

Вот XAML:

<Window x:Class="WpfApplication7.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<StackPanel>
    <StackPanel Orientation="Horizontal">
        <Label Content="Client"/>
    <ComboBox ItemsSource="{Binding AllClientData}" DisplayMemberPath="EmployerStr"
              SelectedItem="{Binding SelectedClient}"
              Width="300"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
        <Label Content="Billing Model:"/>
    <ComboBox ItemsSource="{Binding AllBillingModels}" DisplayMemberPath="BillingModelDescription"
              SelectedItem="{Binding SelectedClient.BillingModel}"
              Width="300"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
        <Label Content="Billing Model" />
    <TextBox Text="{Binding SelectedClient.BillingModel.BillingModelDescription}" Width="200"/>
    </StackPanel>
</StackPanel>

And the code-behind (this is just an example, I am using MVVM etc in the full app, but this serves my puurpose to illustrate the issue):

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        AllClientData = new ObservableCollection<ClientRate>();
        AllBillingModels = new ObservableCollection<BillingModelType>();

        ClientRate uno = new ClientRate();
        uno.BillingModel = new BillingModelType();
        uno.BillingModel.BillingModelID = 3;
        uno.BillingModel.BillingModelDescription = "Free";
        uno.ID = 01;
        uno.EmployerName = "Employer1";

        ClientRate dos = new ClientRate();
        dos.BillingModel = new BillingModelType();
        dos.BillingModel.BillingModelID = 2;
        dos.BillingModel.BillingModelDescription = "Variable";
        dos.ID = 02;
        dos.EmployerName = "Employer2";

        ClientRate tre = new ClientRate();
        tre.BillingModel = new BillingModelType();
        tre.BillingModel.BillingModelID = 1;
        tre.BillingModel.BillingModelDescription = "Flat";
        tre.ID = 01;
        tre.EmployerName = "Employer3";

        AllClientData.Add(uno);
        AllClientData.Add(dos);
        AllClientData.Add(tre);

        BillingModelType one = new BillingModelType();
        one.BillingModelID = 1;
        one.BillingModelDescription = "Flat";

        BillingModelType two = new BillingModelType();
        two.BillingModelID = 2;
        two.BillingModelDescription = "Variable";

        BillingModelType three = new BillingModelType();
        three.BillingModelID = 3;
        three.BillingModelDescription = "Free";

        AllBillingModels.Add(one);
        AllBillingModels.Add(two);
        AllBillingModels.Add(three);

        InitializeComponent();
        this.DataContext = this;
    }

    private ObservableCollection<ClientRate> _allClientData;
    public ObservableCollection<ClientRate> AllClientData
    {
        get { return _allClientData; }
        set
        {
            if (_allClientData != value)
            {
                _allClientData = value;
                FirePropertyChanged("AllClientData");
            }
        }
    }

    private ClientRate _selectedClient;
    /// <summary>
    /// Gets/Sets Global SelectedClient object
    /// </summary>
    public ClientRate SelectedClient
    {
        get { return _selectedClient; }
        set
        {
            if (_selectedClient != value)
            {
                _selectedClient = value;
                FirePropertyChanged("SelectedClient");
            }
        }
    }

    //private BillingModelType _selectedBillingModel;
    //public BillingModelType SelectedBillingModel
    //{
    //    get
    //    {
    //        return _selectedBillingModel;
    //    }
    //    set
    //    {
    //        if (_selectedBillingModel != value)
    //        {
    //            _selectedBillingModel = value;
    //            FirePropertyChanged("SelectedBillingModel");
    //        }
    //    }
    //}

    private ObservableCollection<BillingModelType> _allBillingModels;
    /// <summary>
    /// Holds all possible billing model types 
    /// </summary>
    public ObservableCollection<BillingModelType> AllBillingModels
    {
        get { return _allBillingModels; }
        set
        {
            if (_allBillingModels != value)
            {
                _allBillingModels = value;
                FirePropertyChanged("AllBillingModels");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void FirePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

    }
}

public class BillingModelType
{
    /// <summary>
    /// Billing Model ID
    /// </summary>
    public int? BillingModelID { get; set; }
    /// <summary>
    /// Billing Model Description
    /// </summary>
    public string BillingModelDescription { get; set; }
}

public class ClientRate : INotifyPropertyChanged
{
    /// <summary>
    /// Employer name with Employer ID in parentheses
    /// </summary>
    public string EmployerStr { get { return EmployerName + " (" + ID + ")"; } }
    /// <summary>
    /// Employer ID
    /// </summary>
    public int? ID { get; set; }
    private string _EmployerName;
    /// <summary>
    /// Employer Official Name
    /// </summary>
    public string EmployerName
    {
        get { return _EmployerName; }
        set
        {
            if (_EmployerName != value)
            {
                _EmployerName = value;
                FirePropertyChanged("EmployerName");
            }
        }
    }

    private BillingModelType _billingModel;
    /// <summary>
    /// Rate Type  ID and Description
    /// </summary>
    public BillingModelType BillingModel
    {
        get { return _billingModel; }
        set
        {
            if (_billingModel != value)
            {
                _billingModel = value;
                FirePropertyChanged("BillingModel");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void FirePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

    }
}

person Theodosius Von Richthofen    schedule 10.07.2013    source источник
comment
у одного клиента один режим биллинга? тогда почему он отображается как выбираемое поле со списком? Это может быть простое текстовое поле, отображающее привязанный тип режима биллинга?   -  person Naresh    schedule 10.07.2013
comment
@Naresh похоже, что вы должны выбрать модель биллинга для каждого клиента.   -  person amitfr    schedule 10.07.2013
comment
@amitfr, тогда что он имеет в виду под: Как мне обновить поле со списком Billing Model при выборе клиента ?. Я думаю, у него есть несколько режимов биллинга, и у клиента может быть несколько из них в качестве вариантов? Я что-то упускаю?   -  person Naresh    schedule 10.07.2013
comment
да, как вы видите, я установил модель биллинга, т.е. Работодатель1 в коде. Это должно быть отображено в поле со списком. Но тогда я хочу иметь возможность выбрать другую модель биллинга, если захочу.   -  person Theodosius Von Richthofen    schedule 10.07.2013
comment
По сути, я хочу, чтобы в поле со списком отображались все возможные описания BillingModel и выбиралось описание, которое в настоящее время назначено для SelectedClient, а затем можно было изменить его на одну из других возможных BillingModel (и ее описание). Я думаю, что моя проблема возникает из-за того, что, хотя идентификатор и описание модели BillingModel, которую я установил в коде, такие же, как один из вариантов в ItemSource, на самом деле это не тот же объект?   -  person Theodosius Von Richthofen    schedule 10.07.2013


Ответы (2)


Я исправил эту проблему, переопределив Equals () в моем классе BillingModelType. Как я подозревал, проблема заключалась в том, что BillingModel не был тем же самым экземпляром BillingModel, который использовался в списке возможных вариантов выбора. Я просто добавил:

public override bool Equals(object obj)
    {
        if (obj == null || !(obj is BillingModelType))
            return false;
        return ((BillingModelType)obj).BillingModelID == this.BillingModelID;
    }

public override int GetHashCode()
    {
        return this.BillingModelID.GetHashCode();
    }

в класс для BillingModelTypes, и все хорошо. Благодарим Рэйчел Лим, поскольку я нашел здесь ее блог об этой проблеме: http://rachel53461.wordpress.com/2011/08/20/comboboxs-selecteditem-not-displaying/#comments

person Theodosius Von Richthofen    schedule 10.07.2013
comment
Рэйчел также предлагает использовать SelectedValue и SelectedValuePath для установки SelectedItem по значению, а не по элементу, но я не знал, как их правильно использовать. Кто-нибудь может мне сказать? - person Theodosius Von Richthofen; 10.07.2013
comment
Привет, Тео, чтобы использовать SelectedValue и SelectedValuePath, вы сначала должны установить SelectedValuePath в строку, равную вашему Идентификатору элемента (в вашем случае SelectedValuePath="BillingModelID"), затем вы должны привязать SelectedValue к Id выбранного элемента (SelectedValue="{Binding SelectedClient.BillingModelID}"). Для этого вам, вероятно, придется немного изменить вашу SelectedClient модель, поскольку я не думаю, что привязка к SelectedValue="{Binding SelectedClient.BillingModel.BillingModelID}" будет работать правильно. Хотя я мог ошибаться, так что не помешало бы попробовать. - person Rachel; 12.07.2013
comment
Привет, Рэйчел, но как изменить SelectedClient, чтобы все это работало? Как вы заявили, SelectedValue не работает ни с привязкой к SelectedClient.BillingModelID, ни с SelectedClient.BillingModel.BillingModelID). Думаю, я до сих пор не понимаю, ПОЧЕМУ это не сработает в существующем виде. - person Theodosius Von Richthofen; 12.07.2013
comment
Вам нужно будет изменить SelectedClient, чтобы свойство BillingModel сравнивалось по значению, а не по ссылке. Если вы переключите его на свойство Id (int), оно будет сравниваться по значению, а не по ссылке, поэтому элемент с идентификатором 1 будет считаться таким же, как любой BillingModel с Id, равным 1. Вы правы в своем ответьте здесь, что он сравнивает SelectedClient.BillingModel по ссылке, и точный экземпляр элемента не существует в вашем списке AllBillingModels. - person Rachel; 12.07.2013
comment
Если вы не хотите изменять объект SelectedClient так, чтобы он содержал свойство int BillingId, то лучше всего просто переопределить .Equals(), как будто у вас есть - person Rachel; 12.07.2013

Я не уверен, что вы ожидали, но вы добавляете AllBillingModels все модели биллинга (one two three), и все они отображаются в поле со списком (плоские, переменные и бесплатные)

РЕДАКТИРОВАТЬ:

Хорошо, если вы все еще здесь, у меня есть хорошая идея
для вашего ComboBox Xaml:

<StackPanel Orientation="Horizontal">
        <Label Content="Billing Model:"/>
        <ComboBox ItemsSource="{Binding AllBillingModels}" DisplayMemberPath="BillingModelDescription"
          SelectedItem="{Binding SelectedClient.BillingModel}"
                  Text="{Binding SelectedClient.BillingModel.BillingModelDescription}"
          Width="300"/>
</StackPanel>

Это обновит тест в поле со списком.

person amitfr    schedule 10.07.2013
comment
да, они отображаются в поле со списком, но я хочу, чтобы значение, присвоенное, например, Employer1, было выбрано в поле со списком, когда я выбираю Employer1 в указанном выше поле со списком. Когда я выбираю Emploer1, в комбинированном списке должно быть выбрано «Бесплатно». Вы видите, что это отображается в текстовом поле - person Theodosius Von Richthofen; 10.07.2013
comment
@TheoTheSuperCat см. Мой отредактированный ответ, я думаю, он красивее. - person amitfr; 11.07.2013
comment
В моем примере это сработало. В моем реальном приложении это решение, похоже, не работает, потому что у меня уже есть текст, привязанный к BillingModelDescription, как это, чтобы отображать многоточия, если текст слишком длинный для поля со списком: ‹ComboBox.ItemTemplate› ‹DataTemplate› ‹TextBlock TextTrimming = CharacterEllipsis Text = {Binding BillingModelDescription} / ›‹/DataTemplate›‹ /ComboBox.ItemTemplate › - person Theodosius Von Richthofen; 11.07.2013
comment
Я надеюсь, что кто-то сможет понять, как использовать SelectedValue и SelectedValuePath для этого, это, казалось бы, лучший способ - person Theodosius Von Richthofen; 11.07.2013