如何基于MVVM实现ItemControl分类参数传递与对应商品展示?
解决MVVM架构下分类切换商品不更新的问题
当前代码存在三个核心问题:View直接操作数据库违反MVVM分离原则、分类按钮未绑定带参数的命令、商品查询条件硬编码无法动态更新。以下是分步解决方案:
1. 实现ViewModel基础类(属性通知)
创建ViewModelBase实现INotifyPropertyChanged,确保属性变化时View能自动更新:
using System.ComponentModel; using System.Runtime.CompilerServices; public class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
2. 编写CatalogViewModel(核心业务逻辑)
将分类加载、商品查询、分类切换逻辑全部移到ViewModel中,实现命令绑定和动态数据更新:
using System.Collections.Generic; using System.Windows.Input; using System.Linq; public class CatalogViewModel : ViewModelBase { private List<CatModel> _categoryList; private int _selectedCategoryId; private List<GoodsModel> _goodsList; private ICommand _switchCategoryCommand; // 分类列表绑定属性 public List<CatModel> CategoryList { get => _categoryList; set { _categoryList = value; OnPropertyChanged(); } } // 选中分类ID,变化时自动加载对应商品 public int SelectedCategoryId { get => _selectedCategoryId; set { _selectedCategoryId = value; OnPropertyChanged(); LoadGoodsByCategory(); } } // 商品列表绑定属性 public List<GoodsModel> GoodsList { get => _goodsList; set { _goodsList = value; OnPropertyChanged(); } } // 分类切换命令 public ICommand SwitchCategoryCommand => _switchCategoryCommand ??= new RelayCommand<int>(SwitchCategory); public CatalogViewModel() { LoadCategories(); // 默认加载第一个分类的商品 if (CategoryList?.Any() == true) { SelectedCategoryId = CategoryList.First().ID; } } // 加载分类数据 private void LoadCategories() { var items = new List<CatModel>(); using (var db = new DB()) { db.openConnection(); var cmd = new MySqlCommand("SELECT ID, Title FROM Category WHERE Cat_Name IN ('Handles_CN', 'Pencils_CN', 'Colour_pencils_CN', 'Brushes_CN')", db.GetConnection()); using (var rd = cmd.ExecuteReader()) { while (rd.Read()) { items.Add(new CatModel { ID = rd.GetInt32(0), Cat_Name = rd.GetString(1) }); } } } CategoryList = items; } // 根据分类ID加载对应商品(使用参数化查询避免SQL注入) private void LoadGoodsByCategory() { var items = new List<GoodsModel>(); using (var db = new DB()) { db.openConnection(); var cmd = new MySqlCommand("SELECT ID, Title, Description, Count, Cost FROM Goods WHERE Cat = @CategoryId", db.GetConnection()); cmd.Parameters.AddWithValue("@CategoryId", SelectedCategoryId); using (var rd = cmd.ExecuteReader()) { while (rd.Read()) { items.Add(new GoodsModel { ID = rd.GetInt32(0), Title = rd.GetString(1), Desc = rd.GetString(2), Count = rd.GetInt32(3), Cost = rd.GetInt32(4) }); } } } GoodsList = items; } // 带参数的RelayCommand实现 public class RelayCommand<T> : ICommand { private readonly Action<T> _execute; private readonly Func<T, bool> _canExecute; public event EventHandler CanExecuteChanged; public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) { return _canExecute == null || _canExecute((T)parameter); } public void Execute(object parameter) { _execute((T)parameter); } public void RaiseCanExecuteChanged() { CanExecuteChanged?.Invoke(this, EventArgs.Empty); } } } // 保留你的模型类 public class CatModel { public int ID { get; set; } public string Cat_Name { get; set; } } public class GoodsModel { public int ID { get; set; } public string Title { get; set; } public string Desc { get; set; } public int Count { get; set; } public int Cost { get; set; } }
3. 修改View的XAML和代码
XAML部分(绑定ViewModel和命令)
设置DataContext为ViewModel,绑定分类列表、商品列表,给分类按钮绑定带参数的命令:
<UserControl x:Class="Stationery.CatalogView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:local="clr-namespace:Stationery"> <UserControl.DataContext> <local:CatalogViewModel /> </UserControl.DataContext> <materialDesign:Card Grid.Column="0" materialDesign:ElevationAssist.Elevation="Dp3" Margin="50 0 0 0"> <StackPanel> <StackPanel> <WrapPanel Orientation="Horizontal"> <Image Source="F:\C#\Stationery\Stationery\IMG\Pen.png" Width="50"/> <Button Content="Пишущие принадлежности" FontSize="18" FontWeight="700" Style="{StaticResource MaterialDesignOutlinedButton}" BorderThickness="0" Foreground="Black" Command="{Binding NavigationToCatalogListCommand}"/> </WrapPanel> <!-- 分类列表绑定 --> <ItemsControl ItemsSource="{Binding CategoryList}"> <ItemsControl.ItemTemplate> <DataTemplate> <Button Content="{Binding Cat_Name}" HorizontalAlignment="Left" Style="{StaticResource MaterialDesignOutlinedButton}" BorderThickness="0" Foreground="#444444" <!-- 绑定ViewModel的分类切换命令,传递分类ID作为参数 --> Command="{Binding DataContext.SwitchCategoryCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding ID}"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> <!-- 商品展示区域绑定商品列表 --> <StackPanel> <ItemsControl ItemsSource="{Binding GoodsList}"> <ItemsControl.ItemTemplate> <DataTemplate> <materialDesign:Card Margin="20 10 20 10"> <DockPanel> <Image Source="F:\C#\Stationery\Stationery\IMG\Pen.png" Height="100" Width="100" Margin="0 0 50 0"/> <StackPanel HorizontalAlignment="Left" VerticalAlignment="Center" > <DockPanel> <TextBlock FontSize="20" FontWeight="600" Text="{Binding Title}"/> <StackPanel Margin="20 10 0 0"> <TextBlock Text="В наличии" Foreground="Chartreuse" /> <TextBlock Text="{Binding Count}" /> </StackPanel> </DockPanel> <TextBlock FontSize="18" FontWeight="400" MaxWidth="250" MaxHeight="50" Margin="0 0 200 0" Text="{Binding Desc}" /> </StackPanel> <TextBlock Foreground="#444444" Margin="50 0 0 0" VerticalAlignment="Center" FontSize="26" FontWeight="Bold" Text="{Binding Cost}" /> </DockPanel> </materialDesign:Card> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </StackPanel> </materialDesign:Card> </UserControl>
Code-behind部分(简化View逻辑)
删除构造函数中的数据库操作,只保留初始化:
public partial class CatalogView : UserControl { public CatalogView() { InitializeComponent(); } }
关键注意事项
- 使用参数化查询避免SQL注入风险,禁止直接拼接SQL语句
- 通过
RelativeSource让子元素绑定到ViewModel的命令,确保命令能正确触发 - 分类ID变化时自动触发商品加载,通过属性通知实现View的自动更新
- 所有数据逻辑集中在ViewModel,View仅负责UI展示,完全符合MVVM架构要求
内容的提问来源于stack exchange,提问作者Fainer Game
相关产品推荐
相关产品推荐

