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

WPF MVVM项目中DataGridComboBox列绑定行专属ObservableCollection可行吗?

Absolutely! This is a super common scenario in WPF MVVM, and there are a couple of clean, maintainable ways to pull it off. Let me walk you through a reliable implementation step by step.

Step 1: Define Your Model Classes

First, make sure your data models implement INotifyPropertyChanged so property updates propagate to the UI correctly. We'll need two models: one for your main equipment list rows, and one for the serial numbers.

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class CaseCardEquipmentList : INotifyPropertyChanged
{
    private string _opEquipIDNum;
    public string OPEquipIDNum
    {
        get => _opEquipIDNum;
        set { _opEquipIDNum = value; OnPropertyChanged(); }
    }

    private SerialNumList _selectedSerialNum;
    public SerialNumList SelectedSerialNum
    {
        get => _selectedSerialNum;
        set { _selectedSerialNum = value; OnPropertyChanged(); }
    }

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

public class SerialNumList : INotifyPropertyChanged
{
    private string _equipID;
    public string EquipID // Matches OPEquipIDNum from CaseCardEquipmentList
    {
        get => _equipID;
        set { _equipID = value; OnPropertyChanged(); }
    }

    private string _serialNumber;
    public string SerialNumber
    {
        get => _serialNumber;
        set { _serialNumber = value; OnPropertyChanged(); }
    }

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

Step 2: Set Up the ViewModel

Your view model will hold the main equipment collection (bound to the DataGrid) and the full list of serial numbers. We'll use this full list to filter options for each row's ComboBox.

using System.Collections.ObjectModel;

public class MainViewModel : INotifyPropertyChanged
{
    public ObservableCollection<CaseCardEquipmentList> EquipmentList { get; set; }
    public ObservableCollection<SerialNumList> AllSerialNumbers { get; set; }

    public MainViewModel()
    {
        // Initialize sample data (replace with your actual data loading logic)
        AllSerialNumbers = new ObservableCollection<SerialNumList>
        {
            new SerialNumList { EquipID = "EQ001", SerialNumber = "SN-0001" },
            new SerialNumList { EquipID = "EQ001", SerialNumber = "SN-0002" },
            new SerialNumList { EquipID = "EQ002", SerialNumber = "SN-0003" },
            new SerialNumList { EquipID = "EQ002", SerialNumber = "SN-0004" }
        };

        EquipmentList = new ObservableCollection<CaseCardEquipmentList>
        {
            new CaseCardEquipmentList { OPEquipIDNum = "EQ001" },
            new CaseCardEquipmentList { OPEquipIDNum = "EQ002" }
        };
    }

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

Step 3: Create a Filter Converter

We'll use a MultiValueConverter to take two inputs: the full list of serial numbers, and the OPEquipIDNum from the current row. The converter will return only the serial numbers that match the row's equipment ID.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;

public class SerialNumFilterConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        // Validate input values
        if (values[0] is ObservableCollection<SerialNumList> allSerials && values[1] is string equipId)
        {
            return allSerials.Where(s => s.EquipID == equipId).ToList();
        }
        // Return empty list if inputs are invalid
        return new List<SerialNumList>();
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        // No need for ConvertBack in this scenario
        throw new NotImplementedException();
    }
}

Step 4: Configure the DataGrid in XAML

Finally, wire up the DataGrid and ComboBox column in your XAML. We'll use MultiBinding to pass the necessary data to our converter.

First, declare the converter in your window/user control resources:

<Window.Resources>
    <local:SerialNumFilterConverter x:Key="SerialNumFilterConverter"/>
</Window.Resources>

Then define the DataGrid with the ComboBox template column:

<DataGrid 
    ItemsSource="{Binding EquipmentList}" 
    AutoGenerateColumns="False"
    CanUserAddRows="False"
    Margin="10">
    <DataGrid.Columns>
        <!-- Display the equipment ID -->
        <DataGridTextColumn 
            Header="设备ID" 
            Binding="{Binding OPEquipIDNum}" 
            Width="100"/>

        <!-- ComboBox column for serial numbers -->
        <DataGridTemplateColumn Header="序列号" Width="150">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox>
                        <!-- Bind filtered serial numbers using the converter -->
                        <ComboBox.ItemsSource>
                            <MultiBinding Converter="{StaticResource SerialNumFilterConverter}">
                                <!-- Bind to the full serial number list from the view model -->
                                <Binding Path="DataContext.AllSerialNumbers" RelativeSource="{RelativeSource AncestorType={x:Type Window}}"/>
                                <!-- Bind to the current row's equipment ID -->
                                <Binding Path="OPEquipIDNum"/>
                            </MultiBinding>
                        </ComboBox.ItemsSource>
                        <!-- Bind selected serial number to the row's model -->
                        <ComboBox.SelectedItem>
                            <Binding Path="SelectedSerialNum" Mode="TwoWay"/>
                        </ComboBox.SelectedItem>
                        <!-- Show the serial number text in the ComboBox -->
                        <ComboBox.DisplayMemberPath>SerialNumber</ComboBox.DisplayMemberPath>
                    </ComboBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

Bonus: Dynamic Updates with CollectionViewSource

If you need the ComboBox options to automatically update when the full AllSerialNumbers collection changes (e.g., new serial numbers are added), replace the converter approach with CollectionViewSource for live filtering:

<DataGridTemplateColumn Header="序列号" Width="150">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <DataTemplate.Resources>
                <CollectionViewSource x:Key="FilteredSerials" Source="{Binding DataContext.AllSerialNumbers, RelativeSource={RelativeSource AncestorType=Window}}">
                    <CollectionViewSource.Filter>
                        <MultiBinding Converter="{StaticResource SerialNumMatchConverter}">
                            <Binding Path="."/> <!-- Current serial number item -->
                            <Binding Path="OPEquipIDNum" RelativeSource="{RelativeSource AncestorType=DataGridRow}"/> <!-- Row's equipment ID -->
                        </MultiBinding>
                    </CollectionViewSource.Filter>
                </CollectionViewSource>
            </DataTemplate.Resources>
            <ComboBox 
                ItemsSource="{Binding Source={StaticResource FilteredSerials}}"
                SelectedItem="{Binding SelectedSerialNum, Mode=TwoWay}"
                DisplayMemberPath="SerialNumber"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

You'll need a simple converter that returns bool to match serial numbers:

public class SerialNumMatchConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values[0] is SerialNumList serial && values[1] is string equipId)
        {
            return serial.EquipID == equipId;
        }
        return false;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 04:20:21