WPF MVVM模式下如何将DataGrid选中行同步至TextBox与ViewModel
解决WPF MVVM中DataGrid选中行无法传递到ViewModel的问题
问题核心
你当前的TextBox直接绑定DataGrid的SelectedItem,这种方式绕开了ViewModel,导致ViewModel无法获取选中数据。正确的MVVM模式应该让DataGrid的选中项直接绑定到ViewModel的属性,再让TextBox绑定该属性。
修复步骤
修改DataGrid的SelectedItem绑定
给DataGrid添加SelectedItem绑定,关联到ViewModel中的CurrentCar属性,同时保持ItemsSource绑定不变。调整TextBox的绑定目标
将所有TextBox的绑定路径从SelectedItem.XXX改为CurrentCar.XXX,不再直接引用DataGrid的元素名。确保ViewModel和实体类实现通知接口
ViewModel需要实现INotifyPropertyChanged接口,CurrentCar属性变更时触发通知;Car实体类也需要实现该接口,保证TextBox修改后能同步回实体。
修改后的XAML代码
<StackPanel> <ScrollViewer Grid.Row="2" Grid.Column="1" Height="300" Width="auto"> <DataGrid x:Name="dtGrd" Width="800" AutoGenerateColumns="False" Background="Azure" ItemsSource="{Binding Path=Cars, Mode=TwoWay}" IsReadOnly="True" SelectionMode="Single" SelectedItem="{Binding CurrentCar, Mode=TwoWay}"> <!-- 新增SelectedItem绑定 --> <DataGrid.Columns> <DataGridTextColumn Header="Id" Width="auto" Binding="{Binding Path=Id}"/> <DataGridTextColumn Header="Brand" Width="auto" Binding="{Binding Path=Brand}"/> <DataGridTextColumn Header="Model" Width="auto" Binding="{Binding Path=Model}"/> <DataGridTextColumn Header="Fuel" Width="auto" Binding="{Binding Path=Fuel}"/> <DataGridTextColumn Header="MaxPassenger" Width="auto" Binding="{Binding Path=MaxPassenger}"/> <DataGridTextColumn Header="NumberOfDoors" Width="auto" Binding="{Binding Path=NumberOfDoors}"/> <DataGridTextColumn Header="AvailableType" Width="auto" Binding="{Binding Path=AvailableType}"/> <DataGridTextColumn Header="Price" Width="auto" Binding="{Binding Path=Price}"/> </DataGrid.Columns> </DataGrid> </ScrollViewer> </StackPanel> <Grid Margin="5" Height="150"> <Grid.ColumnDefinitions> <ColumnDefinition Width="225"/> <ColumnDefinition Width="225"/> <ColumnDefinition Width="225"/> <ColumnDefinition Width="225"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0" Orientation="Vertical"> <Label Content="ID" HorizontalAlignment="Center"/> <TextBox x:Name="txtBxId" Padding="5" Margin="5" Text="{Binding Path=CurrentCar.Id, Mode=TwoWay}"/> <Label Content="Brand" HorizontalAlignment="Center"/> <TextBox x:Name="txtBxBrand" Padding="5" Margin="5" Text="{Binding Path=CurrentCar.Brand, Mode=TwoWay}"/> </StackPanel> <StackPanel Grid.Column="1" Orientation="Vertical"> <Label Content="Model" HorizontalAlignment="Center"/> <TextBox x:Name="txtBxModel" Padding="5" Margin="5" Text="{Binding Path=CurrentCar.Model, Mode=TwoWay}"/> <Label Content="Fuel" HorizontalAlignment="Center"/> <TextBox x:Name="txtBxFuel" Padding="5" Margin="5" Text="{Binding Path=CurrentCar.Fuel, Mode=TwoWay}"/> </StackPanel> <StackPanel Grid.Column="2" Orientation="Vertical"> <Label Content="MaxPassanger" HorizontalAlignment="Center"/> <TextBox x:Name="txtBxMaxPassanger" Padding="5" Margin="5" Text="{Binding Path=CurrentCar.MaxPassenger, Mode=TwoWay}"/> <Label Content="NumberOfDoors" HorizontalAlignment="Center"/> <TextBox x:Name="txtBxNumberOfDoors" Padding="5" Margin="5" Text="{Binding Path=CurrentCar.NumberOfDoors, Mode=TwoWay}"/> </StackPanel> <StackPanel Grid.Column="3" Orientation="Vertical"> <Label Content="AvailableType" HorizontalAlignment="Center"/> <TextBox x:Name="txtBxAvailableType" Padding="5" Margin="5" Text="{Binding Path=CurrentCar.AvailableType, Mode=TwoWay}"/> <Label Content="Price" HorizontalAlignment="Center"/> <TextBox x:Name="txtBxPrice" Padding="5" Margin="5" Text="{Binding Path=CurrentCar.Price, Mode=TwoWay}"/> </StackPanel> </Grid>
ViewModel示例代码
using System.ComponentModel; using System.Collections.ObjectModel; public class CarViewModel : INotifyPropertyChanged { private Car _currentCar; public ObservableCollection<Car> Cars { get; set; } public Car CurrentCar { get => _currentCar; set { _currentCar = value; OnPropertyChanged(nameof(CurrentCar)); } } public CarViewModel() { // 初始化Cars集合,模拟数据 Cars = new ObservableCollection<Car> { new Car { Id = 1, Brand = "Toyota", Model = "Camry", Fuel = "Gasoline", MaxPassenger = 5, NumberOfDoors = 4, AvailableType = "Sedan", Price = 25000 }, new Car { Id = 2, Brand = "Honda", Model = "CR-V", Fuel = "Hybrid", MaxPassenger = 5, NumberOfDoors = 4, AvailableType = "SUV", Price = 30000 } }; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
Car实体类示例代码
using System.ComponentModel; public class Car : INotifyPropertyChanged { private int _id; private string _brand; private string _model; private string _fuel; private int _maxPassenger; private int _numberOfDoors; private string _availableType; private decimal _price; public int Id { get => _id; set { _id = value; OnPropertyChanged(nameof(Id)); } } public string Brand { get => _brand; set { _brand = value; OnPropertyChanged(nameof(Brand)); } } public string Model { get => _model; set { _model = value; OnPropertyChanged(nameof(Model)); } } public string Fuel { get => _fuel; set { _fuel = value; OnPropertyChanged(nameof(Fuel)); } } public int MaxPassenger { get => _maxPassenger; set { _maxPassenger = value; OnPropertyChanged(nameof(MaxPassenger)); } } public int NumberOfDoors { get => _numberOfDoors; set { _numberOfDoors = value; OnPropertyChanged(nameof(NumberOfDoors)); } } public string AvailableType { get => _availableType; set { _availableType = value; OnPropertyChanged(nameof(AvailableType)); } } public decimal Price { get => _price; set { _price = value; OnPropertyChanged(nameof(Price)); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
额外注意事项
- 确保View的DataContext已正确设置为ViewModel实例(可以在XAML的Window/UserControl标签中设置,或者在后台代码中赋值)
- 如果不需要TextBox修改后同步回DataGrid,可以把TextBox的
Mode改为OneWay,但保持CurrentCar的双向绑定以便ViewModel获取选中数据
内容的提问来源于stack exchange,提问作者CrossRaccoon
相关产品推荐
相关产品推荐

