c# - wpf 数据网格组合框“双向绑定需要路径或 XPath”时出错

尝试在 DataGrid 中使用 ComboBox 时,我收到可怕的“双向绑定需要路径或 XPath”错误。是的,我已经看过几个现有的答案,但似乎没有一个与我的例子的风格相匹配。对于冗长的代码,我很抱歉,但我想对我的示例进行彻底的介绍。

SQL Server 数据模型:

-- list of dropdown values lives in here, specifically the CarrierName
create table Carrier
(
    CarrierId int identity(1,1) primary key,
    CarrierName nvarchar(500) not null,
    CarrierType nvarchar(500) not null
);

-- this is what my datagrid will be looking at
create table Contract
(
    ContractId int identity(1,1) primary key,
    CarrierId int foreign key references Carrier(CarrierId)
    -- other attributes removed for example
);

通过 EF Core 中的 Scaffold-DbContext 命令生成的模型:

public partial class Carrier
{
    public Carrier()
    {
        Contracts = new HashSet<Contract>();
    }
    public int CarrierId { get; set; }
    public string CarrierName { get; set; }
    public string CarrierType { get; set; }
    public virtual ICollection<Contract> Contracts { get; set; }
}

public partial class Contract
{
    public Contract() { }
    public int ContractId { get; set; }
    public int CarrierId { get; set; }
    public virtual Carrier Carrier { get; set; }
}

主视图模型.cs

public class MainViewModel : ViewModelBase
{
    public IContractViewModel ContractViewModel { get; set; }
    public MainViewModel(IContractViewModel contractViewModel)
    {
        ContractViewModel = contractViewModel;
    }
    public async Task LoadAsync()
    {
        await ContractViewModel.LoadAsync();
    }
}

合同视图模型.cs

public class ContractViewModel : ViewModelBase, IContractViewModel
{
    private readonly IRepository _repository;
    private CoreContract _selectedContract;

    public ObservableCollection<Contract> Contracts { get; set; }
    public ObservableCollection<Carrier> Carriers { get; set; }

    public Contract SelectedContract
    {
        get { return _selectedContract; }
        set { _selectedContract = value; }
    }

    public ContractViewModel(IRepository repository)
    {
        Contracts = new ObservableCollection<Contract>();
        Carriers = new ObservableCollection<Carrier>();
        _repository = repository ?? throw new ArgumentNullException(nameof(repository));
    }

    public async Task LoadAsync()
    {
        Contracts.Clear();
        foreach (var contract in await _repository.GetAllContractsAsync())
        {
            Contracts.Add(contract);
        }

        Carriers.Clear();
        foreach(var carrier in await _repository.GetAllCarriersAsync())
        {
            Carriers.Add(carrier);
        }
    }
}

主窗口.cs

public partial class MainWindow : Window
{
    private readonly MainViewModel _viewModel;
    public MainWindow(MainViewModel mainViewModel)
    {
        InitializeComponent();
        this.Loaded += async (s, e) => await _viewModel.LoadAsync();
        _viewModel = mainViewModel;
        DataContext = _viewModel;
    }
}

主窗口。xaml

<Window xmlns:Views="clr-namespace:COREContracts.Views"  
        x:Class="COREContracts.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:COREContracts"
        mc:Ignorable="d">

        <Views:ContractsView DataContext="{Binding ContractViewModel}"/>
</Window>

合同视图。xaml

因为运营商列表不在合同列表中,所以我使用了这个 https://stackoverflow.com/a/5085644/9882482 来正确链接:

<UserControl x:Class="COREContracts.Views.ContractsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             Name="root">
    <DataGrid Name="Contracts" 
            ItemsSource="{Binding Contracts}"
            SelectedItem="{Binding SelectedContract}"
            AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Contract Id" Binding="{Binding Path=ContractId}"/>
            <DataGridTemplateColumn Header="Carrier Name">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Path=Carrier.CarrierName}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <ComboBox ItemsSource="{Binding Path=DataContext.Carriers, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" 
                                    SelectedItem="{Binding Path=SelectedContract.CarrierName}">
                            <ComboBox.ItemTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding Path=CarrierName}" />
                                </DataTemplate>
                            </ComboBox.ItemTemplate>
                        </ComboBox>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</UserControl>

当我将下拉列表 value 更新为另一个 value 然后单击数据行时,会出现“双向绑定需要路径或 XPath”异常。

我在这里做错了什么?

回答1

问题在于您的 ComboBox 的 SelectedItems 绑定:

SelectedItem="{Binding Path=SelectedContract.CarrierName}">

DataContext 已设置为 SelectedContract,因此无需在此处指定。并且您想绑定到 Carrier 对象本身,而不是 CarrierName 属性:

SelectedItem="{Binding Path=Carrier}">

作为旁注,请记住,像这样直接绑定到您的数据层并不总是最好的主意。您上面的代码将在您更改 Carrier 属性时对其进行修改,但它不会将这些更改传播到您的 Carrier 实例中的 Contracts 列表,因此您的 ORM 很有可能在序列化时忽略任何更改到您的数据库。一般来说,您的数据层应该与视图模型中的更高层进行镜像,该层负责处理这些内容并且更适合视图。这是一个实现问题,我将留给您解决,因为它在很大程度上取决于您的架构。

相似文章

scrapy - AttributeError: 'set' 对象没有属性 'getall'

我有一个scrapy项目,我试图以有限的经验完成它。我已经完成了nextPage和innerPage操作。我的代码分别搜索广告并从中一个一个地抓取数据。之后,它移动到下一页。我用一两个变量检查了所有步...

最新文章