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
相关产品推荐
相关产品推荐

