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

WPF MVVM模式下ComboBox选择过滤Tab页DataGrid数据异常修复

问题原因

这两个问题和ICollectionView本身能力无关,是实现逻辑存在错误:

  • 第二个Tab的DataGrid直接绑定了AllBills,该属性在ViewModel构造阶段就加载了全量账单数据,所以应用启动时就会显示全部数据
  • 切换CollectionViewSource的Source属性后,没有触发对应集合属性的PropertyChanged通知,UI感知不到数据源变更,自然不会刷新展示过滤结果
  • 初始化顺序错误,构造函数读取ComboItems时_allBills还未完成数据库加载,会导致下拉框初始选项为空
  • 待付账单查询SQL逻辑写反,PaidOn != ''查询的是已支付账单,不符合待付账单的业务定义
  • 命名语义混乱,FilteredBills同时被第一个Tab的待付账单、第二个Tab的过滤结果复用,极易出现绑定错误

修复步骤

1. 修正XAML绑定

第二个Tab的DataGrid不要绑定全量数据源,改为绑定专门的搜索结果属性,同时给ComboBox加输入支持和实时更新触发:

<TabControl>
    <TabItem Header="Pending Bills">
        <DataGrid ItemsSource="{Binding PendingBills}" AutoGenerateColumns="False" CanUserAddRows="False">
            <DataGrid.Columns>
                <DataGridTextColumn
                        Header="Vendor"
                        Binding="{Binding Path=Party, Mode=OneWay}"
                        Width="275"
                        IsReadOnly="True" />
                <DataGridTextColumn
                        Header="Bill No."
                        Binding="{Binding Path=BillNo, Mode=OneWay}"
                        Width="275"
                        IsReadOnly="True" />
                <!-- 其余列保持原有逻辑不变 -->
            </DataGrid.Columns>
        </DataGrid>
    </TabItem>
    <TabItem Header="Filtered Bills">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <!-- 绑定搜索结果属性,不要绑定AllBills -->
            <DataGrid Grid.Row="0" ItemsSource="{Binding SearchResultBills}" AutoGenerateColumns="False" CanUserAddRows="False">
                <DataGrid.Columns>
                    <DataGridTextColumn
                        Header="Vendor"
                        Binding="{Binding Path=Party, Mode=OneWay}"
                        Width="275"
                        IsReadOnly="True" />
                    <DataGridTextColumn
                        Header="Bill No."
                        Binding="{Binding Path=BillNo, Mode=OneWay}"
                        Width="275"
                        IsReadOnly="True" />
                    <!-- 其余列保持原有逻辑不变 -->
                </DataGrid.Columns>
            </DataGrid>
            <StackPanel Grid.Row="1" Orientation="Horizontal">
                <ComboBox x:Name="cmbBox" 
                          ItemsSource="{Binding ComboItems}" 
                          SelectedItem="{Binding SelectedCBItem, UpdateSourceTrigger=PropertyChanged}"
                          IsEditable="True"
                          Width="220"/>
            </StackPanel>
        </Grid>
    </TabItem>
</TabControl>

2. 修正ViewModel逻辑

初始化时给第二个Tab的数据源赋值空集合,切换过滤结果时主动触发属性变更通知,增加数据刷新方法支持自动更新:

public class BillsViewModel : ViewModelBase
{
    private readonly Model _model = new Model();
    private readonly CollectionViewSource _cvsPending = new CollectionViewSource();
    private readonly CollectionViewSource _cvsSearchResult = new CollectionViewSource();

    public BillsViewModel()
    {
        // 提前加载全量数据,保证下拉框选项正常
        _ = _model.AllBills;
        // 初始化第一个Tab的待付账单数据源
        _cvsPending.Source = _model.PendingBills;
        // 第二个Tab初始赋值空集合,启动时不展示任何数据
        _cvsSearchResult.Source = new List<Bills>();
    }

    // 第一个Tab待付账单数据源
    public ICollectionView PendingBills => _cvsPending.View;

    // 第二个Tab搜索结果数据源
    public ICollectionView SearchResultBills => _cvsSearchResult.View;

    // 下拉框选项
    public List<string> ComboItems => _model.UniqueValues();

    private string _selectedCBItem;
    public string SelectedCBItem
    {
        get => _selectedCBItem;
        set
        {
            _selectedCBItem = value;
            OnPropertyChanged(nameof(SelectedCBItem));
            
            // 选中项为空时清空结果
            if (string.IsNullOrWhiteSpace(value))
            {
                _cvsSearchResult.Source = new List<Bills>();
            }
            else
            {
                // 按选中供应商过滤数据
                _cvsSearchResult.Source = _model.GetBillsByParty(value);
            }
            // 关键:通知UI搜索结果数据源变更,触发DataGrid刷新
            OnPropertyChanged(nameof(SearchResultBills));
        }
    }

    // 数据变更时调用该方法即可自动刷新两个Tab的展示内容
    public void RefreshAllData()
    {
        _model.ReloadData();
        _cvsPending.Source = _model.PendingBills;
        _cvsSearchResult.Source = new List<Bills>();
        OnPropertyChanged(nameof(PendingBills));
        OnPropertyChanged(nameof(SearchResultBills));
        OnPropertyChanged(nameof(ComboItems));
        _selectedCBItem = string.Empty;
        OnPropertyChanged(nameof(SelectedCBItem));
    }
}

3. 修正Model层逻辑

调整命名语义,修正SQL查询条件,增加资源释放和数据重载逻辑:

public class Model
{
    private List<Bills> _allBills;
    private List<Bills> _pendingBills;

    public List<Bills> AllBills
    {
        get
        {
            if (_allBills == null) LoadAllBillsFromDatabase();
            return _allBills;
        }
    }

    public List<Bills> PendingBills
    {
        get
        {
            if (_pendingBills == null) LoadPendingBillsFromDatabase();
            return _pendingBills;
        }
    }

    public void ReloadData()
    {
        _allBills = null;
        _pendingBills = null;
        _ = AllBills;
        _ = PendingBills;
    }

    private void LoadAllBillsFromDatabase()
    {
        // using语法自动释放数据库资源,避免内存泄漏
        using var m_dbConnection = new SQLiteConnection(@"Data Source=Bills.db;");
        m_dbConnection.Open();
        const string sql = "Select * From billdata";
        using var sqlCom = new SQLiteCommand(sql, m_dbConnection);
        using var sda = new SQLiteDataAdapter(sqlCom);
        var dt = new DataTable();
        sda.Fill(dt);
        _allBills = new List<Bills>();
        
        foreach (DataRow row in dt.Rows)
        {
            _allBills.Add(new Bills()
            {
                Id = Convert.ToInt32(row["Id"]),
                Party = row["Party"].ToString(),
                BillNo = row["BillNo"].ToString(),
                // 其余字段按原有逻辑赋值即可
            });
        }
    }

    private void LoadPendingBillsFromDatabase()
    {
        using var m_dbConnection = new SQLiteConnection(@"Data Source=Bills.db;");
        m_dbConnection.Open();
        // 修正SQL:待付账单为支付日期为空的记录
        const string sql = "Select * From billdata where PaidOn IS NULL OR PaidOn = ''";
        using var sqlCom = new SQLiteCommand(sql, m_dbConnection);
        using var sda = new SQLiteDataAdapter(sqlCom);
        var dt = new DataTable();
        sda.Fill(dt);
        _pendingBills = new List<Bills>();

        foreach (DataRow row in dt.Rows)
        {
            _pendingBills.Add(new Bills()
            {
                Id = Convert.ToInt32(row["Id"]),
                Party = row["Party"].ToString(),
                BillNo = row["BillNo"].ToString(),
                // 其余字段按原有逻辑赋值即可
            });
        }
    }

    public List<string> UniqueValues()
    {
        return AllBills.Select(s => s.Party).Distinct().OrderBy(s => s).ToList();
    }

    public List<Bills> GetBillsByParty(string filterValue)
    { 
        if (string.IsNullOrWhiteSpace(filterValue)) return new List<Bills>();
        // 忽略大小写匹配,避免输入大小写不一致导致过滤失败
        return AllBills.FindAll(s => s.Party.Equals(filterValue, StringComparison.OrdinalIgnoreCase)).ToList();
    }
}

优化提示
  • 所有OnPropertyChanged调用建议用nameof(属性名)代替硬编码字符串,避免拼写错误导致通知失效
  • 如果需要支持ComboBox输入模糊匹配,可以直接给CollectionViewSource配置Filter事件,不用每次重新赋值Source
  • 数据库操作建议加异常处理,避免数据库文件损坏、连接失败时程序直接崩溃

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 20:09:11