简体   繁体   English

如何在WPF中正确绑定ComboBox?

[英]How to bind ComboBox properly in WPF?

I'm new to WPF and MVVM and I'm developing a test WPF application following the MVVM design pattern. 我是WPF和MVVM的新手,并且正在按照MVVM设计模式开发测试WPF应用程序。 My database has 2 entities, Cards and Departments. 我的数据库有2个实体,卡和部门。 Any card can have only 1 department, so it's a one-to-many relationship. 任何一张卡只能有1个部门,因此是一对多关系。

I've created the following ViewModel in order to bind to the view: 我创建了以下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
}

The CardForms' datacontext is currently being set to an instance of the CardViewModel in the code where the CardForm is being instantiated, but I'm going to create a IoC container or dependency injections down the line. CardForms的datacontext当前正在实例化CardForm的代码中设置为CardViewModel的实例,但是我将在此行下创建一个IoC容器或依赖项注入。

Everything binds correctly except for the ComboBox that should contain all departments and that has the current department in the Card instance selected (card.Department). 除了应该包含所有部门并且在Card实例中选择了当前部门(card.Department)的ComboBox之外,其他所有东西都正确绑定。 Here's the XAML for the 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}" />

The departments are displayed in the combobox, but the current department of the card isn't and if I try to change it I get and error saying "Cannot add an entity with a key that is already in use". 部门显示在组合框中,但是卡的当前部门不是,如果尝试更改它,则会出现错误,提示“无法使用已使用的密钥添加实体”。

So, my question is, how do I bind this combobox correctly to my ViewModel? 因此,我的问题是,如何将该组合框正确绑定到ViewModel?

PS I know populating the ObservableCollection<Department> in the ViewModel is probably not the right way to do it, but I could not think of a better way at the time. PS我知道在ViewModel中填充ObservableCollection<Department>可能不是正确的方法,但是我当时想不到更好的方法。 If you have any suggestions for this also, please let me know. 如果您对此有任何建议,请告诉我。

Additionally, this is the Card model: 此外,这是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");
            }
        }
    }

I edited the constructor in the CardViewModel to take the DataContext as a parameter and that did it. 我在CardViewModel编辑了构造函数,以将DataContext作为参数,然后做到了。 This is the new CardViewModel constructor: 这是新的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);
}

Had to do a bit of research on this myself. 不得不对此做一些研究。 Thought I would contribute with a self answered question, but found this open current question... 我以为我会回答一个自我回答的问题,但是发现了这个开放的当前问题...

The ComboBox is designed to be a kind of textbox that restricts it's possible values to the contents of a given list. ComboBox设计为一种文本框,将其可能的值限制为给定列表的内容。 The list is provided by the ItemsSource attribute. 该列表由ItemsSource属性提供。 The current value of the ComboBox is the SelectedValue property. ComboBox的当前值是SelectedValue属性。 Typically these attributes are bound to relevant properties of a corresponding ViewModel. 通常,这些属性绑定到相应ViewModel的相关属性。

The following example shows wired ComboBox together with a TextBox control used to redundantly view the current value of the ComboBox by sharing a view model property. 下面的示例显示了有线的ComboBox和TextBox控件,该TextBox用于通过共享视图模型属性来冗余地查看ComboBox的当前值。 (It is interesting to note that when TextBox changes the shared property to a value outside the scope of the ComboBox 's list of values, the ComboBox displays nothing.) (有趣的是,当TextBox将shared属性更改为ComboBox值列表范围之外的值时, ComboBox不会显示任何内容。)

Note: the following WPF/C# example does does use code-behind and so presents the ViewModel as merely the datacontext of the view and not a partial class of it, a current implementation constraint when using WPF with F#. 注意:以下WPF / C#示例确实使用了代码隐藏功能,因此将ViewModel仅仅表示为视图的数据上下文,而不是视图的partial class ,这是将WPF与F#一起使用时的当前实现约束。

WPF XAML 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# ViewModel C#ViewModel

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;
  }
}

By default, ToString() is used to interpret the objects in the list. 默认情况下,ToString()用于解释列表中的对象。 However, ComboBox offers DisplayMemberPath and SelectedValuePath attributes for specifying paths to specific object properties for corresponding displayed and stored values. 但是, ComboBox提供了DisplayMemberPathSelectedValuePath属性,用于为相应的显示和存储的值指定特定对象属性的路径。 These paths are relative to the list object element so a path of "Name" refers to Name on a list object item. 这些路径是相对于列表对象元素的,因此“名称”路径是指列表对象项上的名称。

The "Remarks" section of this MSDN link explains the interpretations of the IsEditable and IsReadOnly ComboBox properties. 此MSDN链接的“备注”部分说明了IsEditableIsReadOnly ComboBox属性的解释。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM