尝试在 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 很有可能在序列化时忽略任何更改到您的数据库。一般来说,您的数据层应该与视图模型中的更高层进行镜像,该层负责处理这些内容并且更适合视图。这是一个实现问题,我将留给您解决,因为它在很大程度上取决于您的架构。