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

如何基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 18:47:23