You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF DataGrid绑定ViewModel中DataTable时SelectedItem绑定异常及绑定机制咨询

WPF DataGrid with DataTable: Binding Mechanisms & Fixes

Hey there! Coming from WinForms to WPF, it's totally normal to hit these binding gotchas with DataGrid and DataTable—they work quite differently under the hood. Let's break down your questions and fix this properly, while keeping MVVM principles intact.

First, Let's Clarify the Binding Mechanism

When you bind a DataTable to WPF's DataGrid, the control doesn't treat your Booking objects as direct row/column data. Instead:

  • Each row in the DataGrid maps to a DataRowView (a wrapper around a DataRow from your DataTable).
  • When you assign a Booking object to a DataTable cell, the cell stores the object, but the DataGrid defaults to calling ToString() on it for display. Since you haven't overridden ToString() (or the DisplayMemberPath setting isn't targeting the right object), you see the full type name myNamespace.Models.Booking.

Answers to Your Questions

Q1: What's actually stored in the DataGrid cells? Why can't I cast the selected item to Booking?

  • Cells do store your Booking object, but the SelectedItem of the DataGrid is the entire DataRowView (the row wrapper), not the individual cell's Booking. That's why you get a casting error when trying to convert it to Booking—you're trying to turn a row wrapper into a single cell object.
  • The confusion comes from WinForms' behavior, where you might directly access cell values more intuitively. In WPF, you need to dig into the DataRowView to get your Booking from the correct column.

Why isn't DisplayMemberPath="Id" working?

DisplayMemberPath tells the DataGrid which property of the row object to display. Your row objects are DataRowView, not Booking—so the DataGrid looks for an Id property on DataRowView (which doesn't exist), hence it ignores the setting and falls back to ToString().


Fixes: Two Approaches

Approach 1: Keep Using DataTable (Quick Adjustments)

If you want to stick with your current DataTable setup, here's how to fix the display and selection:

1. Fix Display to Show Booking.Id

Instead of relying on DisplayMemberPath, define explicit columns in XAML and use a value converter to extract the Id from the Booking object:

<!-- First, declare the converter in your resources -->
<Window.Resources>
    <local:BookingToIdConverter x:Key="BookingToIdConverter" />
</Window.Resources>

<DataGrid x:Name="grdInstructors" 
          ItemsSource="{Binding DtBookings}" 
          SelectedCell="{Binding SelectedCell}"
          AutoGenerateColumns="False">
    <!-- Add columns for each time slot (adjust headers and indices to match your DataTable) -->
    <DataGrid.Columns>
        <DataGridTextColumn Header="Instructor" Binding="{Binding [0]}" /> <!-- Assuming column 0 is instructor name -->
        <DataGridTextColumn Header="Slot 1" Binding="{Binding [1], Converter={StaticResource BookingToIdConverter}}" />
        <DataGridTextColumn Header="Slot 2" Binding="{Binding [2], Converter={StaticResource BookingToIdConverter}}" />
        <!-- Add more columns for other time slots -->
    </DataGrid.Columns>
</DataGrid>

Create the converter class:

public class BookingToIdConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is Booking booking)
            return booking.Id.ToString(); // Or just booking.Id if it's a string
        return string.Empty;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // Not needed for display-only, throw or return null
        throw new NotImplementedException();
    }
}

2. Fix Selection to Get the Booking Object

Bind to SelectedCell (instead of SelectedItem) to get the specific cell, then extract the Booking from the DataRowView:

private DataGridCellInfo _selectedCell;
public DataGridCellInfo SelectedCell
{
    get => _selectedCell;
    set
    {
        _selectedCell = value;
        OnPropertyChanged(); // Make sure your ViewModel implements INotifyPropertyChanged

        if (_selectedCell.IsValid)
        {
            // Get the row wrapper
            var rowView = _selectedCell.Item as DataRowView;
            if (rowView != null)
            {
                // Get the column index from the selected cell
                var columnIndex = _selectedCell.Column.DisplayIndex;
                // Extract the Booking from the cell
                var selectedBooking = rowView[columnIndex] as Booking;
                
                // Now you can work with selectedBooking!
            }
        }
    }
}

DataTable is great for ADO.NET scenarios, but it's not the most MVVM-friendly choice for WPF. Instead, create a custom row model to represent each instructor's schedule, then use an ObservableCollection:

1. Create a Row ViewModel

public class InstructorScheduleRow : INotifyPropertyChanged
{
    // Instructor info for the row header
    private Instructor _instructor;
    public Instructor Instructor
    {
        get => _instructor;
        set
        {
            _instructor = value;
            OnPropertyChanged();
        }
    }

    // Properties for each time slot
    private Booking _slot1;
    public Booking Slot1
    {
        get => _slot1;
        set
        {
            _slot1 = value;
            OnPropertyChanged();
        }
    }

    private Booking _slot2;
    public Booking Slot2
    {
        get => _slot2;
        set
        {
            _slot2 = value;
            OnPropertyChanged();
        }
    }

    // Add more Slot properties for other time slots...

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. Update Your ViewModel

public ObservableCollection<InstructorScheduleRow> InstructorSchedules { get; set; }

public YourViewModel()
{
    InstructorSchedules = new ObservableCollection<InstructorScheduleRow>();
}

void DisplayBookings()
{
    // First, initialize rows for each instructor
    foreach (var instructor in YourInstructorList)
    {
        InstructorSchedules.Add(new InstructorScheduleRow { Instructor = instructor });
    }

    // Assign bookings to the correct row and slot
    foreach (Booking booking in bookings)
    {
        var row = InstructorSchedules.First(r => r.Instructor.Id == booking.InstructorId);
        // Map SlotTimeId to the corresponding property
        switch (booking.SlotTimeId)
        {
            case 0: row.Slot1 = booking; break;
            case 1: row.Slot2 = booking; break;
            // Add cases for other SlotTimeIds
        }
    }
}

3. Bind in XAML

<DataGrid x:Name="grdInstructors" 
          ItemsSource="{Binding InstructorSchedules}" 
          SelectedCell="{Binding SelectedCell}"
          AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Instructor" Binding="{Binding Instructor.Name}" />
        <DataGridTextColumn Header="Slot 1" Binding="{Binding Slot1.Id}" />
        <DataGridTextColumn Header="Slot 2" Binding="{Binding Slot2.Id}" />
        <!-- Add more columns for other slots -->
    </DataGrid.Columns>
</DataGrid>

Now, when you select a cell, you can get the Booking directly from the InstructorScheduleRow:

private DataGridCellInfo _selectedCell;
public DataGridCellInfo SelectedCell
{
    get => _selectedCell;
    set
    {
        _selectedCell = value;
        OnPropertyChanged();

        if (_selectedCell.IsValid)
        {
            var row = _selectedCell.Item as InstructorScheduleRow;
            if (row != null)
            {
                // Get the Booking based on the selected column
                Booking selectedBooking = null;
                switch (_selectedCell.Column.Header.ToString())
                {
                    case "Slot 1": selectedBooking = row.Slot1; break;
                    case "Slot 2": selectedBooking = row.Slot2; break;
                    // Add cases for other columns
                }
                
                // Use selectedBooking as needed
            }
        }
    }
}

Key Takeaways

  • DataTable + DataGrid in WPF uses DataRowView as row objects, not your domain models directly.
  • DisplayMemberPath targets row-level properties, not cell-level objects.
  • For proper MVVM compliance, using custom view models and ObservableCollection is far more maintainable than DataTable.

内容的提问来源于stack exchange,提问作者Ray

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 19:07:48