MVVM(Model-View-ViewModel)模式是一种设计模式,广泛应用于WPF、UWP、Xamarin等基于XAML的应用程序开发中,特别是在WpfPowerTester框架中(如你的ProjectView.xaml、ProjectViewModel和ProjectModel)。它通过分离关注点(Separation of Concerns)提高代码的可维护性、可测试性和可扩展性。以下是对MVVM模式的详细探索,包括其核心概念、在WpfPowerTester中的实现、优缺点、优化建议,以及如何进一步扩展以兼容WpfPowerTester框架。


一、MVVM模式的核心概念

MVVM模式将应用程序分为三个主要部分:

  1. Model(模型):

    • 定义:表示应用程序的数据和业务逻辑,通常是数据实体或与后端服务交互的层。
    • 职责:
      • 存储和管理数据(如ProjectModel中的ProjectName、ProjectNum、ParamSetupModel)。
      • 执行业务逻辑(如与硬件驱动交互、执行测试流程)。
      • 不直接与视图交互,保持独立性。
    • 示例:在WpfPowerTester中,ProjectModel是Model,包含工程的属性(如ProjectName、ProjectDate)和测试逻辑(如通过TestFlowFactory执行TspFlow或ZthFlow)。
  2. View(视图):

    • 定义:用户界面,负责显示数据和接收用户输入,通常由XAML定义。
    • 职责:
      • 呈现数据(如ProjectView.xaml中的ListBox显示工程列表)。
      • 处理用户交互(如按钮点击、双击工程项),通过绑定触发ViewModel的命令。
      • 不包含业务逻辑,仅通过数据绑定与ViewModel交互。
    • 示例:ProjectView.xaml通过ListBox绑定到ProjectViewModel.DisplayProjects,通过命令绑定(如AddProjectCommand)处理用户操作。
  3. ViewModel(视图模型):

    • 定义:视图和模型之间的中介,负责处理视图的逻辑和状态。
    • 职责:
      • 提供视图所需的数据(如DisplayProjects、FilteredProjectCount)。
      • 实现命令(如DelegateCommand)处理用户交互。
      • 与Model交互,更新数据并通知视图(如通过INotifyPropertyChanged)。
    • 示例:ProjectViewModel转换Global.ProjectList为DisplayProjects,实现筛选逻辑(如SearchByMethodCommand)。
MVVM的关联机制
  • 数据绑定:View通过XAML绑定到ViewModel的属性(如{Binding DisplayProjects}),ViewModel通过INotifyPropertyChanged通知视图更新。
  • 命令绑定:View通过命令绑定(如Command="{Binding AddProjectCommand}")调用ViewModel的方法。
  • 依赖注入:ViewModel和Model通过Prism的IContainerProvider注入服务(如HardwareDriverManager、DaqService)。
  • 事件通知:Model通过事件(如DataCollected)通知ViewModel更新数据,ViewModel通过属性通知View刷新。

二、MVVM在WpfPowerTester中的实现

结合你的ProjectView.xaml、ProjectViewModel和ProjectModel,以下是MVVM模式在WpfPowerTester中的具体实现:

1. Model:ProjectModel
  • 职责:
    • 存储工程数据(如ProjectName、ProjectNum、ParamSetupModel)。
    • 与硬件交互,通过HardwareDriverManager和DaqService执行测试流程。
    • 管理测试状态(如ProjectRunStatus)和数据(如ChartValueList)。
  • 实现:
    • ProjectModel实现INotifyPropertyChanged(通过Prism的BindableBase),支持数据绑定。
    • 通过RunCommand调用TestFlowFactory.CreateTestFlow,根据ParamSetupModel.ProjectMethod创建TspFlow、ZthFlow或PcFlow。
    • 监听HardwareDriverManager.DataCollected事件,更新ChartValueList。
  • 示例代码:
    private async Task DoRunCommandAsync(object obj)
    {
        var projectModel = obj as ProjectModel;
        if (!_zoneManager.IsZoneAvailable(projectModel.ProjectSelectedZone, projectModel.ProjectName, out string errorMessage))
        {
            Global.LogAndPop(errorMessage, Level.ERROR, LogType.Pop);
            return;
        }
        _currentFlow = TestFlowFactory.CreateTestFlow(projectModel.ParamSetupModel.ProjectMethod, projectModel, _driverManager);
        await _currentFlow.StartAsync();
    }
    
2. ViewModel:ProjectViewModel
  • 职责:
    • 管理视图状态(如SearchText、FilteredProjectCount)。
    • 提供命令(如AddProjectCommand、DeleteProjectCommand)。
    • 转换Global.ProjectList为DisplayProjects,简化视图绑定。
  • 实现:
    • 使用ObservableCollection<ProjectDisplayModel>存储显示数据,监听Global.ProjectList变化。
    • 实现筛选逻辑(如UpdateFilteredProjects),根据SearchText、SelectedMethod和日期范围过滤工程。
    • 通过Prism的DelegateCommand处理用户交互(如创建、删除工程)。
  • 示例代码:
    private void UpdateFilteredProjects()
    {
        DisplayProjects.Clear();
        var projects = Global.ProjectList.AsEnumerable();
        if (!string.IsNullOrEmpty(SearchText))
            projects = projects.Where(p => p.ProjectName.Contains(SearchText, StringComparison.OrdinalIgnoreCase));
        foreach (var project in projects)
        {
            DisplayProjects.Add(new ProjectDisplayModel
            {
                Guid = project.Guid,
                ProjectName = project.ProjectName,
                ProjectDate = project.ProjectDate.ToString("yy-MM-dd HH:mm:ss")
            });
        }
        FilteredProjectCount = DisplayProjects.Count;
    }
    
3. View:ProjectView.xaml
  • 职责:
    • 显示工程列表(ListBox绑定到DisplayProjects)。
    • 提供筛选控件(TextBox、RadioButton、DatePicker)。
    • 触发命令(如双击触发EditProjectCommand)。
  • 实现:
    • 使用Prism的ViewModelLocator.AutoWireViewModel="True"绑定到ProjectViewModel。
    • 数据绑定到DisplayProjects,命令绑定到AddProjectCommand等。
    • 使用VirtualizingStackPanel优化性能,支持大量工程显示。
  • 示例代码:
    <ListBox ItemsSource="{Binding DisplayProjects}" VirtualizingStackPanel.IsVirtualizing="True">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid x:Name="grid" Height="156" Width="156" Margin="4">
                    <TextBlock Text="{Binding ProjectName}" Foreground="#555" FontSize="14"/>
                    <Grid.InputBindings>
                        <MouseBinding MouseAction="LeftDoubleClick" 
                                      Command="{Binding DataContext.EditProjectCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
                                      CommandParameter="{Binding}"/>
                    </Grid.InputBindings>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    
4. Prism框架支持
  • 依赖注入:通过IContainerProvider注入HardwareDriverManager、DaqService等服务。
  • 模块化:ProjectView作为Prism模块注册到主窗口的区域。
  • 事件聚合:使用IEventAggregator处理跨模块通信(如硬件状态更新)。

三、MVVM的优缺点

1. 优点
  • 分离关注点:
    • View(UI)、ViewModel(逻辑)、Model(数据)分离,降低耦合。
    • 便于团队协作:UI设计师专注于XAML,前端开发专注于ViewModel,后端开发专注于Model。
  • 可测试性:
    • ViewModel和Model不依赖UI,可通过单元测试验证逻辑。
    • 示例:测试ProjectViewModel.UpdateFilteredProjects是否正确过滤工程。
  • 可维护性:
    • 代码模块化,易于修改和扩展(如添加新测试方法)。
    • 示例:通过TestFlowFactory支持新的测试流程。
  • 可重用性:
    • ViewModel和Model可复用于不同视图(如Web或移动端)。
    • 示例:ProjectModel可用于命令行测试工具。
  • 数据绑定:
    • XAML绑定简化UI更新,INotifyPropertyChanged确保视图实时刷新。
    • 示例:DisplayProjects变化自动更新ListBox。
2. 缺点
  • 学习曲线:
    • MVVM涉及绑定、命令、Prism等概念,对初学者较复杂。
    • 解决:通过Prism的模板和文档降低学习成本。
  • 性能开销:
    • 数据绑定和通知可能在大数据量(如大量工程)时影响性能。
    • 解决:使用虚拟化(如VirtualizingStackPanel)和异步操作。
  • 复杂性增加:
    • 小型项目中,MVVM可能引入不必要的复杂性。
    • 解决:根据项目规模选择轻量级实现(如简化ViewModel)。
  • 调试难度:
    • 绑定错误或命令失效难以追踪。
    • 解决:使用日志(如ILogger)和调试工具(如Snoop)。

四、MVVM在WpfPowerTester中的优化建议

结合WpfPowerTester框架和你的代码,以下是MVVM模式的优化建议:

1. 性能优化
  • 虚拟化:已实现的ListBox和VirtualizingStackPanel有效减少渲染开销,可进一步优化绑定:
    // 在ProjectViewModel中缓存复杂计算
    private void UpdateFilteredProjects()
    {
        var filtered = Global.ProjectList
            .Where(p => string.IsNullOrEmpty(SearchText) || p.ProjectName.Contains(SearchText, StringComparison.OrdinalIgnoreCase))
            .Select(p => new ProjectDisplayModel { /* 简化属性 */ })
            .ToList(); // 缓存结果
        DisplayProjects.Clear();
        foreach (var item in filtered) DisplayProjects.Add(item);
    }
    
  • 异步加载:对于大量工程,使用异步加载数据:
    private async Task UpdateFilteredProjectsAsync()
    {
        await Task.Run(() =>
        {
            var filtered = Global.ProjectList
                .Where(p => string.IsNullOrEmpty(SearchText) || p.ProjectName.Contains(SearchText, StringComparison.OrdinalIgnoreCase))
                .Select(p => new ProjectDisplayModel { /* 属性 */ })
                .ToList();
            Dispatcher.CurrentDispatcher.Invoke(() =>
            {
                DisplayProjects.Clear();
                foreach (var item in filtered) DisplayProjects.Add(item);
                FilteredProjectCount = DisplayProjects.Count;
            });
        });
    }
    
2. 可维护性优化
  • 集中资源管理:
    • 将样式(如NormalButtonStyle)和资源(如Resources.xaml)集中到全局ResourceDictionary:
      <Application.Resources>
          <ResourceDictionary>
              <ResourceDictionary.MergedDictionaries>
                  <ResourceDictionary Source="Resources.xaml"/>
                  <ResourceDictionary Source="Styles.xaml"/>
              </ResourceDictionary.MergedDictionaries>
          </ResourceDictionary>
      </Application.Resources>
      
  • 命令封装:
    • 使用枚举替代字符串参数(如TestMethod),提高类型安全:
      public enum TestMethod { ThermalResistance, PowerCycling, KCoefficient }
      public DelegateCommand<TestMethod> AddProjectCommand { get; }
      
3. 可扩展性优化
  • 动态测试方法:
    • 通过配置文件动态加载测试方法:
      public class TestMethodConfig
      {
          public string Name { get; set; }
          public string FlowType { get; set; }
      }
      
      public ObservableCollection<TestMethodConfig> AvailableTestMethods { get; } = LoadTestMethods();
      
      private static ObservableCollection<TestMethodConfig> LoadTestMethods()
      {
          var configs = JsonSerializer.Deserialize<List<TestMethodConfig>>(File.ReadAllText("TestMethods.json"));
          return new ObservableCollection<TestMethodConfig>(configs);
      }
      
    • 更新XAML:
      <ItemsControl ItemsSource="{Binding AvailableTestMethods}">
          <ItemsControl.ItemTemplate>
              <DataTemplate>
                  <Button Content="{Binding Name}" Command="{Binding DataContext.AddProjectCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
                          CommandParameter="{Binding}"/>
              </DataTemplate>
          </ItemsControl.ItemTemplate>
      </ItemsControl>
      
  • 多语言支持:
    • 实现运行时语言切换:
      public class LanguageService
      {
          public void ChangeLanguage(string culture)
          {
              var dict = new ResourceDictionary { Source = new Uri($"Resources.{culture}.xaml", UriKind.Relative) };
              Application.Current.Resources.MergedDictionaries[0] = dict;
          }
      }
      
4. 用户体验优化
  • 实时反馈:
    • 显示筛选进度或加载动画:
      <ProgressBar IsIndeterminate="True" Visibility="{Binding IsFiltering, Converter={StaticResource BooleanToVisibilityConverter}}"/>
      
      private bool _isFiltering;
      public bool IsFiltering
      {
          get => _isFiltering;
          set => SetProperty(ref _isFiltering, value);
      }
      
      private async Task UpdateFilteredProjectsAsync()
      {
          IsFiltering = true;
          await Task.Run(() => { /* 筛选逻辑 */ });
          IsFiltering = false;
      }
      
  • 错误提示:
    • 使用ValidationRules验证TextBox输入:
      <TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}">
          <TextBox.Text>
              <Binding Path="SearchText" UpdateSourceTrigger="PropertyChanged">
                  <Binding.ValidationRules>
                      <local:MaxLengthRule MaxLength="50"/>
                  </Binding.ValidationRules>
              </Binding>
          </TextBox.Text>
      </TextBox>
      
      public class MaxLengthRule : ValidationRule
      {
          public int MaxLength { get; set; }
          public override ValidationResult Validate(object value, CultureInfo cultureInfo)
          {
              if (value is string str && str.Length > MaxLength)
                  return new ValidationResult(false, $"输入长度不能超过{MaxLength}个字符");
              return ValidationResult.ValidResult;
          }
      }
      
5. 测试优化
  • 单元测试:
    • 为ProjectViewModel编写测试用例:
      [TestClass]
      public class ProjectViewModelTests
      {
          [TestMethod]
          public void UpdateFilteredProjects_FiltersBySearchText()
          {
              var viewModel = new ProjectViewModel(new MockHardwareDriverManager(), new MockDaqService(), new MockZoneManager(), new MockLogger());
              Global.ProjectList.Add(new ProjectModel(null, null, null, null) { ProjectName = "TestProject" });
              viewModel.SearchText = "Test";
              Assert.AreEqual(1, viewModel.DisplayProjects.Count);
          }
      }
      

五、扩展代码以兼容WpfPowerTester

以下是进一步扩展代码的建议,确保与WpfPowerTester框架的硬件交互、测试流程和数据可视化无缝集成。

1. 增强ProjectModel的测试流程

扩展ProjectModel以支持更复杂的测试流程(如多通道同步测试):

public async Task DoRunCommandAsync(object obj)
{
    var projectModel = obj as ProjectModel;
    if (!_zoneManager.IsZoneAvailable(projectModel.ProjectSelectedZone, projectModel.ProjectName, out string errorMessage))
    {
        _logger.Error("工作区不可用:{Error}", errorMessage);
        Global.LogAndPop(errorMessage, Level.ERROR, LogType.Pop);
        return;
    }

    if (!await _driverManager.InitializeAsync())
    {
        _logger.Error("硬件驱动初始化失败");
        Global.LogAndPop("硬件驱动初始化失败", Level.ERROR, LogType.Pop);
        return;
    }

    _zoneManager.OccupyZone(projectModel.ProjectSelectedZone, projectModel.ProjectName);
    projectModel.ProjectRunStatus = (int)ProjectStatus.Running;

    try
    {
        _currentFlow = TestFlowFactory.CreateTestFlow(projectModel.ParamSetupModel.ProjectMethod, projectModel, _driverManager);
        _logger.Information("开始测试流程:{Method}", projectModel.ParamSetupModel.ProjectMethod);
        await Task.WhenAll(projectModel.ChannelList
            .Where(c => c.DeviceModelA != null || c.DeviceModelB != null)
            .Select(c => _currentFlow.StartAsync(c.ChannelName)));
        projectModel.ProjectRunStatus = (int)ProjectStatus.Completed;
    }
    catch (Exception ex)
    {
        _logger.Error(ex, "测试流程失败:{Method}", projectModel.ParamSetupModel.ProjectMethod);
        Global.LogAndPop($"运行失败:{ex.Message}", Level.ERROR, LogType.Pop);
        projectModel.ProjectRunStatus = (int)ProjectStatus.Idle;
    }
    finally
    {
        _zoneManager.ReleaseZone(projectModel.ProjectSelectedZone);
        projectModel.ProjectSelectedZoneIsEnable = true;
    }
}
2. 增强ChartService

支持实时和历史数据可视化:

public class ChartService
{
    private readonly ILogger<ChartService> _logger;

    public ChartService(ILogger<ChartService> logger)
    {
        _logger = logger;
    }

    public void UpdateChartData(ProjectModel project, string parameter, ObservableCollection<string> channels)
    {
        var time = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
        foreach (var channel in channels)
        {
            var data = project.ChartValueList.FirstOrDefault(x => x.Parameter == parameter && x.Channels.Contains(channel));
            if (data == default)
            {
                data = (new ObservableCollection<double>(), new ObservableCollection<double>(), parameter);
                project.ChartValueList.Add(data);
            }
            data.Values.Add(project.WorkstationModel.GetLatestValue(channel, parameter));
            data.Times.Add(time);
            _logger.Information("更新图表数据,通道:{Channel},参数:{Parameter}", channel, parameter);
        }
    }
}
3. 添加编辑窗口

实现ProjectEditView和ProjectEditViewModel:

public class ProjectEditViewModel : BindableBase
{
    private ProjectModel _project;
    public ProjectModel Project
    {
        get => _project;
        set => SetProperty(ref _project, value);
    }

    public DelegateCommand SaveCommand { get; }

    public ProjectEditViewModel()
    {
        SaveCommand = new DelegateCommand(ExecuteSave);
    }

    private void ExecuteSave()
    {
        // 保存Project到Global.ProjectList
    }
}
<UserControl x:Class="PowerCycling.Views.ProjectEditView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True">
    <StackPanel>
        <TextBox Text="{Binding Project.ProjectName}"/>
        <ComboBox ItemsSource="{Binding DataContext.AvailableTestMethods, RelativeSource={RelativeSource AncestorType=UserControl}}"
                  SelectedItem="{Binding Project.ParamSetupModel.ProjectMethod}"/>
        <Button Content="保存" Command="{Binding SaveCommand}"/>
    </StackPanel>
</UserControl>
4. Prism模块注册

在App.xaml.cs中注册模块:

public partial class App : PrismApplication
{
    protected override Window CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterSingleton<ILoggerFactory, LoggerFactory>();
        containerRegistry.RegisterSingleton<HardwareDriverManager>();
        containerRegistry.RegisterSingleton<DaqService>();
        containerRegistry.RegisterSingleton<ZoneManager>();
        containerRegistry.RegisterSingleton<ChartService>();
        containerRegistry.Register<ProjectViewModel>();
        containerRegistry.Register<ProjectEditViewModel>();
    }

    protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
    {
        moduleCatalog.AddModule<ProjectModule>();
    }
}

六、总结

1. MVVM模式的核心
  • Model:ProjectModel管理工程数据和测试逻辑。
  • View:ProjectView.xaml通过绑定显示数据和触发命令。
  • ViewModel:ProjectViewModel处理筛选、命令和数据转换。
  • 关联:通过Prism的依赖注入、数据绑定和事件机制连接。
2. WpfPowerTester中的实现
  • 集成硬件驱动(HardwareDriverManager、DaqService)。
  • 支持多种测试流程(Tsp、Zth、PCs)。
  • 使用Prism实现模块化、依赖注入和事件聚合。
3. 优化和扩展
  • 性能:虚拟化和异步加载。
  • 可维护性:集中资源管理和类型安全命令。
  • 可扩展性:动态测试方法和多语言支持。
  • 用户体验:实时反馈和错误验证。

通过以上探索和代码,MVVM模式在WpfPowerTester中实现了高效的工程管理,满足复杂测试系统的需求,同时具有良好的扩展性和可维护性。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐