wpf 数据网格组合框出错 "Two-way binding requires Path or XPath"
Error with wpf datagrid combobox "Two-way binding requires Path or XPath"
我在尝试在 DataGrid 中使用 ComboBox 时收到可怕的“双向绑定需要 Path 或 XPath”错误。是的,我已经查看了几个现有的答案,但 none 似乎符合我的示例的风格。对于冗长的代码,我深表歉意,但我想对我的示例进行全面介绍。
SQL 服务器数据模型:
-- 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; }
}
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
public IContractViewModel ContractViewModel { get; set; }
public MainViewModel(IContractViewModel contractViewModel)
{
ContractViewModel = contractViewModel;
}
public async Task LoadAsync()
{
await ContractViewModel.LoadAsync();
}
}
ContractViewModel.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);
}
}
}
MainWindow.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;
}
}
MainWindow.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>
ContractsView.xaml
因为承运人列表在合同列表之外,所以我使用这个 answer 来 link 正确设置:
<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>
当我将下拉列表值更新为另一个值然后单击数据行时出现异常“双向绑定需要 Path 或 XPath”。
我做错了什么?
问题出在您的 ComboBox 的 SelectedItems 绑定上:
SelectedItem="{Binding Path=SelectedContract.CarrierName}">
DataContext已经设置为SelectedContract,这里不需要指定。并且您想绑定到 Carrier 对象本身,而不是 CarrierName 属性:
SelectedItem="{Binding Path=Carrier}">
附带说明一下,请记住,像这样直接绑定到数据层并不总是最好的主意。您上面的代码将在您更改它们时修改 Carrier 属性,但它不会将这些更改传播到您的 Carrier 实例中的 Contracts 列表,因此您的 ORM 很可能会在序列化时忽略任何更改到你的数据库。一般来说,您的数据层应该与您的视图模型中的更高层进行镜像,该层负责处理这些内容并且更适合视图。这是一个实施问题,我会留给您解决,因为它在很大程度上取决于您的体系结构。
我在尝试在 DataGrid 中使用 ComboBox 时收到可怕的“双向绑定需要 Path 或 XPath”错误。是的,我已经查看了几个现有的答案,但 none 似乎符合我的示例的风格。对于冗长的代码,我深表歉意,但我想对我的示例进行全面介绍。
SQL 服务器数据模型:
-- 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; }
}
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
public IContractViewModel ContractViewModel { get; set; }
public MainViewModel(IContractViewModel contractViewModel)
{
ContractViewModel = contractViewModel;
}
public async Task LoadAsync()
{
await ContractViewModel.LoadAsync();
}
}
ContractViewModel.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);
}
}
}
MainWindow.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;
}
}
MainWindow.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>
ContractsView.xaml
因为承运人列表在合同列表之外,所以我使用这个 answer 来 link 正确设置:
<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>
当我将下拉列表值更新为另一个值然后单击数据行时出现异常“双向绑定需要 Path 或 XPath”。
我做错了什么?
问题出在您的 ComboBox 的 SelectedItems 绑定上:
SelectedItem="{Binding Path=SelectedContract.CarrierName}">
DataContext已经设置为SelectedContract,这里不需要指定。并且您想绑定到 Carrier 对象本身,而不是 CarrierName 属性:
SelectedItem="{Binding Path=Carrier}">
附带说明一下,请记住,像这样直接绑定到数据层并不总是最好的主意。您上面的代码将在您更改它们时修改 Carrier 属性,但它不会将这些更改传播到您的 Carrier 实例中的 Contracts 列表,因此您的 ORM 很可能会在序列化时忽略任何更改到你的数据库。一般来说,您的数据层应该与您的视图模型中的更高层进行镜像,该层负责处理这些内容并且更适合视图。这是一个实施问题,我会留给您解决,因为它在很大程度上取决于您的体系结构。