使用MVVM绑定WPF DataGridComboBoxColumn

29

我已经查看了不同问题的答案,但是没有成功将这些答案中的内容映射到我试图解决的问题上。我已经将它简化为以下代码(代表我试图实现的结果),基本上想要在行未被编辑时呈现Person.TitleId对应的Title.TitleText,并正确地绑定下拉列表,使其在下拉列表中显示TitleText并在更改选项时将相关的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列表? - H.B.
另外,从您使用“Title”的方式来看,该类似乎是多余的,最好用“Dictionary<int,string>”替换。 - H.B.
@H.B. - 根据代码,网格不显示初始值的文本表示(即Person.TitleId的适当TitleText),下拉列表正确地填充了{先生,夫人,小姐},选择下拉列表中的项目会导致在网格中显示TestMVVM.Models.Title(TestMVVM.Models是解决方案中的命名空间,我为简洁起见将其剥离)。 - Rob
@H.B. - “Title”是我“真实”代码的简化。我将真实代码缩减到这个样本案例(在我的“真实”代码中,Title类具有更多属性,虽然为了网格的目的我可以将其简化为Dictionary<int, string>,但我不想这样做,因为这会增加代码量),以查看是否可以自己解决问题,而不涉及任何额外的问题,如数据库访问。鉴于最小化案例是在stackoverflow上发布的最佳案例,这就是我所做的 =) - Rob
@Rob,我已经成功添加了标题的文本表示,但是我仍然有一个问题,即“TitleId”未被填充。您是否考虑将“Person”类中的“TitleId”更改为“Title”引用的选项? - Snowbear
显示剩余3条评论
2个回答

39

这里有一个能工作的代码。关键点是使用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>

难以置信,我刚刚写了几乎完全相同的东西,好吧,这一轮算你赢。 - H.B.
这在我的简化案例中完美地运作,我将花些时间将其集成到我的实际代码中,看看它是否能够在那里工作(我相信它会!)。您能否解释一下它是如何/为什么工作的呢? :) - Rob
3
SelectedValuePath是设置ComboBox中所选项对应的对象内部成员的路径,而DisplayMemberPath则是设置类内部应该被展示的成员的路径。SelectedValueBinding将所选值绑定到指定绑定中的属性。这有点令人困惑,但如果你多用几次就会明白了。E: https://dev59.com/hm865IYBdhLWcg3wd-ce - H.B.
我已经将它集成到我的“实际”代码中,它运行得非常好,谢谢!=) - Rob
我希望我能多次点赞。感谢你提供简洁的工作示例,@Snowbear。 - Paul Prewett
谢谢,楼主和你救了我的一天! 哦,今天是星期五 - 啤酒时间 :) - CeOnSql

2

@SnowBear的答案对我很有帮助。但我想澄清绑定方面的一个细节。

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

SelectedValueBinding="{Binding TitleId}"

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

因为SelectedValueBinding属性出现在DataGridComboBoxColumn上,所以它将绑定到包含DataGrid的ItemsSource。 在这种情况下,它绑定到Person对象的Contacts集合。

在我的情况下,DataGrid的DataSource集合被赋予了一个与ComboBox的ItemSource集合的ValuePath命名不同的属性。 因此,我的SelectedValueBinding的值被绑定到与ComboBox的SelectedValuePath中命名的属性不同的属性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接