【问题标题】:How to bind ComboBox properly in WPF?如何在 WPF 中正确绑定 ComboBox?
【发布时间】:2014-06-19 15:50:18
【问题描述】:

我是 WPF 和 MVVM 的新手,我正在按照 MVVM 设计模式开发一个测试 WPF 应用程序。我的数据库有 2 个实体,卡片和部门。任何卡片只能有 1 个部门,因此是一对多的关系。

为了绑定到视图,我创建了以下 ViewModel:

public class CardViewModel : INotifyPropertyChanged
{
    public CardViewModel(Card card)
    {
        this.Card = card;

        SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
        builder.DataSource = ".\\SQLExpress";
        builder.InitialCatalog = "TESTDB";
        builder.IntegratedSecurity = true;

        SybaseDatabaseContext myDB = new SybaseDatabaseContext(builder.ConnectionString);

        var query = from d in myDB.Departments
                    select d;

        this.Departments = new ObservableCollection<Department>(query);
    }
    private Card _Card;
    private ObservableCollection<Department> _Departments;

    public Card Card
    {
        get { return _Card; }
        set
        {
            if (value != this._Card)
            {
                this._Card = value;
                SendPropertyChanged("Card");
            }
        }
    }

    public ObservableCollection<Department> Departments
    {
        get { return _Departments; }
        set
        {
            this._Departments = value;
            SendPropertyChanged("Departments");
        }
    }

    #region INPC
    // Logic for INotify interfaces that nootify WPF when change happens
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void SendPropertyChanged(String propertyName)
    {
        if ((this.PropertyChanged != null))
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion
}

CardForms 的 datacontext 当前正在被实例化 CardForm 的代码中设置为 CardViewModel 的一个实例,但我将创建一个 IoC 容器或依赖注入。

除了应该包含所有部门并且选择了 Card 实例中的当前部门 (card.Department) 的 ComboBox 之外,所有内容都正确绑定。这是 ComboBox 的 XAML:

<ComboBox Height="23" HorizontalAlignment="Left" Margin="350,64,0,0" 
          Name="comboBoxDepartment" VerticalAlignment="Top" Width="120"
          IsSynchronizedWithCurrentItem="True"
          ItemsSource="{Binding Path=Departments}" 
          DisplayMemberPath="DepartmentName"
          SelectedItem="{Binding Path=Card.Department, Mode=TwoWay}" />

部门显示在组合框中,但卡的当前部门不是,如果我尝试更改它,我会收到错误消息“无法添加具有已使用密钥的实体”。

所以,我的问题是,如何将这个组合框正确绑定到我的 ViewModel?

附:我知道在 ViewModel 中填充 ObservableCollection&lt;Department&gt; 可能不是正确的方法,但当时我想不出更好的方法。如果您对此也有任何建议,请告诉我。

另外,这是 Card 模型:

[Table(Name = "Card")]
public class Card : INotifyPropertyChanged, INotifyPropertyChanging
{
    private string _CardID;
    private string _Holder;
    private Int16? _DepartmentNo;

    [Column(UpdateCheck = UpdateCheck.WhenChanged)]
    public string CardID
    {
        get
        {
            return this._CardID;
        }
        set
        {
            if (value != this._CardID)
            {
                SendPropertyChanging();
                this._CardID = value;
                SendPropertyChanged("CardID");
            }
        }
    }

    [Column(UpdateCheck = UpdateCheck.WhenChanged)]
    public string Holder
    {
        get
        {
            return this._Holder;
        }
        set
        {
            if (value != this._Holder)
            {
                SendPropertyChanging();
                this._Holder = value;
                SendPropertyChanged("Holder");
            }
        }
    }

    [Column(CanBeNull = true, UpdateCheck = UpdateCheck.WhenChanged)]
    public Int16? DepartmentNo
    {
        get
        {
            return this._DepartmentNo;
        }
        set
        {
            if (value != this._DepartmentNo)
            {
                SendPropertyChanging();
                this._DepartmentNo = value;
                SendPropertyChanged("DepartmentNo");
            }
        }
    }

    private EntityRef<Department> department;
    [Association(Storage = "department", ThisKey = "DepartmentNo", OtherKey = "DepartmentNo", IsForeignKey = true)]
    public Department Department
    {
        get
        {
            return this.department.Entity;
        }
        set
        {
            Department previousValue = this.department.Entity;
            if (((previousValue != value)
                        || (this.department.HasLoadedOrAssignedValue == false)))
            {
                this.SendPropertyChanging();
                if ((previousValue != null))
                {
                    this.department.Entity = null;
                    previousValue.Cards.Remove(this);
                }
                this.department.Entity = value;
                if ((value != null))
                {
                    value.Cards.Add(this);
                    this._DepartmentNo = value.DepartmentNo;
                }
                else
                {
                    this._DepartmentNo = default(Nullable<short>);
                }
                this.SendPropertyChanged("Department");
            }
        }
    }

【问题讨论】:

  • 你能粘贴Card代码吗?
  • @kmatyaszek 当然,但这只是带有 getter 和 setter 的属性。它实现了 INotifyPropertyChanged。
  • @Sheridan 我已经编辑了问题以解释差异,请取消标记以便人们回答。
  • 可能是因为我正在创建 2x DataContexts。一个用于模型,另一个用于从数据库中检索所有部门。我将尝试从同一个 ComboBox 中检索所有必须在 ComboBox 中的部门。
  • 是的,就是这样。 DataContext 不能有 2 个不同的实例。我删除了ViewModel 中的声明,并将其作为参考传递。

标签: c# wpf xaml mvvm combobox


【解决方案1】:

我在CardViewModel 中编辑了构造函数,以将DataContext 作为参数并做到了。这是新的CardViewModel 构造函数:

public CardViewModel(Card card, SybaseDatabaseContext myDB)
{
    this.Card = card;

    var query = from d in myDB.Departments
                select d;

    this.Departments = new ObservableCollection<Department>(query);
}

【讨论】:

    【解决方案2】:

    必须自己对此进行一些研究。以为我会贡献一个自我回答的问题,但发现这个开放的当前问题......

    ComboBox 被设计成一种文本框,将它的可能值限制为给定列表的内容。该列表由ItemsSource 属性提供。 ComboBox 的当前值是 SelectedValue 属性。通常,这些属性绑定到相应 ViewModel 的相关属性。

    以下示例显示了有线 ComboBox 和 TextBox 控件,用于通过共享视图模型属性冗余查看 ComboBox 的当前值。 (有趣的是,当 TextBox 将共享属性更改为 ComboBox 值列表范围之外的值时,ComboBox 什么也不显示。)

    注意:以下 WPF/C# 示例确实使用了代码隐藏,因此仅将 ViewModel 显示为视图的数据上下文,而不是它的 partial class,这是将 WPF 与 F# 一起使用时的当前实现约束。

    WPF XAML

    <Window 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:m="clr-namespace:WpfApplication1"
      Title="MainWindow" Height="350" Width="525">
      <Window.DataContext>
        <m:MainWindowVM />
      </Window.DataContext>
      <StackPanel>
        <TextBox Text="{Binding SelectedString}" />
        <ComboBox ItemsSource="{Binding MyList}" SelectedValue="{Binding SelectedString}" />
      </StackPanel>
    </Window>
    

    C# 视图模型

    using System.Collections.Generic;
    using System.ComponentModel;
    namespace WpfApplication1
    {
      public class MainWindowVM : INotifyPropertyChanged
      {
        string selectedString;
        void NotifyPropertyChanged(string propertyName)
        {
          if (PropertyChanged == null) return;
          PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        public string SelectedString
        {
          get { return selectedString; }
          set
          {
            selectedString = value;
            NotifyPropertyChanged("SelectedString");
          }
        }
        public List<string> MyList
        {
          get { return new List<string> { "The", "Quick", "Brown", "Fox" }; }
        }
        public event PropertyChangedEventHandler PropertyChanged;
      }
    }
    

    默认情况下,ToString() 用于解释列表中的对象。但是,ComboBox 提供了DisplayMemberPathSelectedValuePath 属性,用于指定相应显示和存储值的特定对象属性的路径。这些路径是相对于列表对象元素的,因此“名称”路径指的是列表对象项上的名称。

    this MSDN link 的“备注”部分解释了 IsEditableIsReadOnly ComboBox 属性的解释。

    【讨论】:

    • List&lt;T&gt; 对 WPF 不太友好。添加或删除元素时,它不会向视图发送通知。你应该使用ObservableCollection&lt;T&gt;。此外,这是一个将组合框绑定到字符串列表而不是绑定到对象集合的特定组合框的通用示例。
    • 是的,我知道。我将从列表本身中删除更改通知。在我的搜索中,我发现了很多关于边缘案例和技巧的信息,但关于基础知识的信息却很少。这是一个复杂的控件,有五个以“Select”开头的属性,以及围绕 IsEditable 和 IsReadOnly 设置的其他复杂性。
    猜你喜欢
    • 2012-06-18
    • 1970-01-01
    • 2011-10-28
    • 1970-01-01
    • 1970-01-01
    • 2011-11-01
    • 2015-04-07
    • 1970-01-01
    • 2020-10-10
    相关资源
    最近更新 更多