使用 MVVM 绑定 WPF DataGridComboBoxColumn

Posted

技术标签:

【中文标题】使用 MVVM 绑定 WPF DataGridComboBoxColumn【英文标题】:Binding a WPF DataGridComboBoxColumn with MVVM 【发布时间】:2011-07-27 22:18:52 【问题描述】:

我查看了variousquestions 的答案,但没有设法将答案中的内容映射到我试图解决的问题。我已将其缩减为以下代码(代表我试图实现的结果),并且基本上希望能够在未编辑行时将Person.TitleId 呈现为其对应的Title.TitleText,并正确绑定下拉列表,以便在下拉列表中显示TitleTexts,并在更改时将关联的TitleId 写回Person 记录。

简而言之,我在<DataGridComboBoxColumn> 中输入了什么来实现这一点?

App.xaml.cs

protected override void OnStartup(StartupEventArgs e)

    base.OnStartup(e);

    var viewModel = new ViewModels.MainWindowViewModel();
    var mainWindow = new MainWindow();
    mainWindow.DataContext = viewModel;
    mainWindow.ShowDialog();

MainWindow.xaml

<Grid>
    <DataGrid AutoGenerateColumns="False" ItemsSource="Binding Path=Contacts">
        <DataGrid.Columns>
            <DataGridComboBoxColumn Header="Title" SelectedItemBinding="Binding Person">
                <DataGridComboBoxColumn.ElementStyle>
                    <Style TargetType="ComboBox">
                        <Setter Property="ItemsSource" Value="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type Window, Path=DataContext.Titles"/>
                        <Setter Property="IsReadOnly" Value="True"/>
                    </Style>
                </DataGridComboBoxColumn.ElementStyle>
                <DataGridComboBoxColumn.EditingElementStyle>
                    <Style TargetType="ComboBox">
                        <Setter Property="ItemsSource" Value="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type Window, Path=DataContext.Titles"/>
                        <Setter Property="DisplayMemberPath" Value="TitleText" />
                    </Style>
                </DataGridComboBoxColumn.EditingElementStyle>
            </DataGridComboBoxColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

Person.cs

public class Person

    public int TitleId  get; set; 
    public string LastName  get; set; 
    public string FirstName  get; set; 

Title.cs

public struct Title

    public Title(int titleId, string titleText)
        : this()
    
        TitleId = titleId;
        TitleText = titleText;
    

    public string TitleText  get; private set; 
    public int TitleId  get; private set; 

    public static List<Title> GetAvailableTitles()
    
        var titles = new List<Title>();

        titles.Add(new Title(1, "Mr"));
        titles.Add(new Title(2, "Miss"));
        titles.Add(new Title(3, "Mrs"));

        return titles;
    

MainWindowViewModel.cs

public class MainWindowViewModel : ViewModelBase

    private ObservableCollection<Person> contacts;
    private List<Title> titles;

    public MainWindowViewModel()
    
        titles = Title.GetAvailableTitles();

        Contacts = new ObservableCollection<Person>();
        Contacts.Add(new Person()  FirstName = "Jane", LastName = "Smith", TitleId = 2 );
    

    public List<Title> Titles
    
        get  return titles; 
    

    public ObservableCollection<Person> Contacts
    
        get  return contacts; 
        set
        
            if (contacts != value)
            
                contacts = value;
                this.OnPropertyChanged("Contacts");
            
        
    

ViewModelBase.cs

public class ViewModelBase : INotifyPropertyChanged

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

    public event PropertyChangedEventHandler PropertyChanged;

【问题讨论】:

如果您能概述该“计划”的哪些部分不起作用/哪些部分实际起作用,这将有所帮助。例如你能找到 ComboBox 的 ItemsSource 列表吗? 另外:从您使用Title 的方式来看,该类似乎是多余的,最好用Dictionary&lt;int,string&gt; 代替。 @H.B. - 正如代码所示,网格不显示初始值的文本表示(即 Person.TitleId 的适当 TitleText),下拉列表会正确填充 Mr,Mrs,Miss 并在下拉菜单导致 TestMVVM.Models.Title 显示在网格中(TestMVVM.Models 是解决方案中的命名空间,为简洁起见,我将其删除)。 @H.B. - “标题”是我的“真实”代码的简化。我已将 real 代码简化为这个示例案例(在我的“真实”代码中,Title 类具有更多属性,而我可以将其提炼为 Dictionary&lt;int, string&gt;网格,我宁愿不要,因为它的代码更多)看看我是否可以自己解决它而没有任何无关的问题,例如数据库访问。鉴于最小的情况是在***上发布的最佳情况,这就是我所做的=) 哦,抱歉,我没有意识到你已经压缩了它,因为它仍然有点大。 【参考方案1】:

这是一个工作代码。这里的关键是使用SelectedValueBinding 而不是SelecteItemBinding

<DataGridComboBoxColumn Header="Title" 
                        SelectedValueBinding="Binding TitleId"
                        SelectedValuePath="TitleId"
                        DisplayMemberPath="TitleText"
                        >
    <DataGridComboBoxColumn.ElementStyle>
        <Style TargetType="ComboBox">
            <Setter Property="ItemsSource" Value="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type Window, Path=DataContext.Titles"/>
        </Style>
    </DataGridComboBoxColumn.ElementStyle>
    <DataGridComboBoxColumn.EditingElementStyle>
        <Style TargetType="ComboBox">
            <Setter Property="ItemsSource" Value="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type Window, Path=DataContext.Titles"/>
        </Style>
    </DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>

【讨论】:

难以置信,我只是在写几乎完全相同的东西,好吧,这一轮给你。 这在我简化的情况下非常有效,我将花一些时间将它集成到我的实际代码中,看看它是否在那里工作(我相信它会!)。您是否有机会解释它如何/为什么起作用? :) SelectedValuePath 设置选定对象内部成员的路径,该对象代表 ComboBox 的选定项,另一方面,DisplayMemberPath 设置应显示的类内部成员的路径. SelectedValueBinding 会将选定的值绑定到绑定中指定的属性。这有点令人困惑,但如果你使用它几次它是有道理的。邮箱:***.com/questions/3797034/… 我已将它集成到我的“实际”代码中,它非常有用,谢谢! =) 我希望我能多次对此表示赞同。感谢您提供简洁的工作示例,@Snowbear。【参考方案2】:

@SnowBear 的回答对我来说效果很好。但我想澄清一下绑定的细节。

在@Rob 的示例中,Title 和 Person 类都使用 TitleID。 因此,在@SnowBear 的回答中,在绑定中:

SelectedValueBinding="Binding TitleId"

对我来说,绑定了哪个类和属性并不是很明显。

因为 SelectedValueBinding 属性出现在 DataGridComboBoxColumn 上,所以它绑定到包含 DataGrid 的 ItemsSource。在本例中是 Person 对象的 Contacts 集合。

在我的例子中,DataGrid 的 DataSource 集合的属性与 ComboBox 的 ItemSource 集合的 ValuePath 不同。因此,我的 SelectedValueBinding 的值绑定到了与 ComboBox 的 SelectedValuePath 中命名的属性不同的属性。

【讨论】:

以上是关于使用 MVVM 绑定 WPF DataGridComboBoxColumn的主要内容,如果未能解决你的问题,请参考以下文章

简单的 WPF + MVVM 绑定

无法使用 MVVM 将 WPF ChartPlotter 绑定到视图

WPF MVVM 绑定错误

使用 MVVM 绑定 WPF DataGridComboBoxColumn

WPF MVVM 数据绑定问题

DataGridTemplateColumn 绑定在 WPF 中使用文本框和 MVVM