MVVM(Model-View-ViewModel)模式是一种设计模式,广泛应用于WPF、UWP、Xamarin等基于XAML的应用程序开发中
·
MVVM(Model-View-ViewModel)模式是一种设计模式,广泛应用于WPF、UWP、Xamarin等基于XAML的应用程序开发中,特别是在WpfPowerTester框架中(如你的ProjectView.xaml、ProjectViewModel和ProjectModel)。它通过分离关注点(Separation of Concerns)提高代码的可维护性、可测试性和可扩展性。以下是对MVVM模式的详细探索,包括其核心概念、在WpfPowerTester中的实现、优缺点、优化建议,以及如何进一步扩展以兼容WpfPowerTester框架。
一、MVVM模式的核心概念
MVVM模式将应用程序分为三个主要部分:
-
Model(模型):
- 定义:表示应用程序的数据和业务逻辑,通常是数据实体或与后端服务交互的层。
- 职责:
- 存储和管理数据(如
ProjectModel中的ProjectName、ProjectNum、ParamSetupModel)。 - 执行业务逻辑(如与硬件驱动交互、执行测试流程)。
- 不直接与视图交互,保持独立性。
- 存储和管理数据(如
- 示例:在WpfPowerTester中,
ProjectModel是Model,包含工程的属性(如ProjectName、ProjectDate)和测试逻辑(如通过TestFlowFactory执行TspFlow或ZthFlow)。
-
View(视图):
- 定义:用户界面,负责显示数据和接收用户输入,通常由XAML定义。
- 职责:
- 呈现数据(如
ProjectView.xaml中的ListBox显示工程列表)。 - 处理用户交互(如按钮点击、双击工程项),通过绑定触发ViewModel的命令。
- 不包含业务逻辑,仅通过数据绑定与ViewModel交互。
- 呈现数据(如
- 示例:
ProjectView.xaml通过ListBox绑定到ProjectViewModel.DisplayProjects,通过命令绑定(如AddProjectCommand)处理用户操作。
-
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优化性能,支持大量工程显示。
- 使用Prism的
- 示例代码:
<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。
- XAML绑定简化UI更新,
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中实现了高效的工程管理,满足复杂测试系统的需求,同时具有良好的扩展性和可维护性。
更多推荐
所有评论(0)