以下是对提供的 C# 代码(ProjectModel 类)的详细分析、注释和优化建议,重点关注代码结构、功能逻辑以及如何在 WPF 框架中进一步优化。代码是一个基于 WPF 和 Prism 框架的复杂模型类,主要用于管理工程测试相关的业务逻辑,涉及设备控制、数据采集、曲线显示等功能。由于代码较长,我将分模块进行分析,并提供详细的注释和优化建议。


代码结构和功能概述

ProjectModel 是一个基于 MVVM 模式的模型类,继承自 BindableBase(Prism 框架的基类,支持属性变更通知)。它用于表示一个工程(Project),包含工程的基本信息、通道配置、测试参数、运行状态、曲线数据管理等功能。主要功能包括:

  1. 工程信息管理

    • 包含工程的唯一标识 (Guid)、名称 (ProjectName)、创建日期 (ProjectDate)、编号 (ProjectNum) 等。
    • 管理工程的运行状态 (ProjectRunStatus) 和按钮可用性 (IsTestBtnEnable, IsRunBtnEnable 等)。
  2. 通道和参数管理

    • 使用 ObservableCollection<ChannelModel> 存储通道信息 (ChannelList)。
    • 使用 ObservableCollection<InputParamModel> 存储标准参数 (ParamList) 和停止条件参数 (StopParamList)。
    • 使用 ObservableCollection<OutputParamModel>ObservableCollection<OutputPCParamModel> 存储测试结果。
  3. 曲线显示和数据处理

    • 使用 ScottPlot 库管理实时曲线 (ChartValueList) 和历史曲线 (HistoryChartValueList)。
    • 支持多种曲线类型(如 VCE, TVJ, TC 等)以及拟合曲线 (Tvjfit)。
    • 提供锁定和拟合功能 (DoChartLockCommand, DoChartFitCommand)。
  4. 设备驱动和通信

    • 通过 ComInitDaq 管理串口通信和数据采集。
    • 提供 Startini, Stopini, Pause, Resume 等方法控制设备运行。
  5. 命令绑定

    • 使用 DelegateCommand(Prism 框架)绑定界面操作(如 TestButtonEnableCommand, RunCommand 等)。
  6. 区域 (Zone) 管理

    • 支持多个区域 (Zone1, Zone2, Zone3, Zone4) 的状态管理,防止区域冲突。
    • 提供并联区域支持 (ProjectSelectedParallelZone)。
  7. 数据保存和事件处理

    • 通过 Global.LocalDataAccess 保存测试数据到数据库。
    • 使用事件(如 StaticPropertyChanged)通知区域状态变化。

详细代码注释

以下是对主要代码段的详细注释,覆盖核心属性、方法和逻辑:

1. 属性部分
public class ProjectModel : BindableBase
{
    /// <summary>
    /// 唯一标识符,用于区分不同工程
    /// </summary>
    public Guid Guid { get; set; } = Guid.NewGuid();

    private DateTime _projectDate = DateTime.Now;
    /// <summary>
    /// 工程创建日期
    /// </summary>
    public DateTime ProjectDate
    {
        get { return _projectDate; }
        set { SetProperty(ref _projectDate, value); }
    }

    private int _projectNum;
    /// <summary>
    /// 工程编号,唯一标识一个工程
    /// </summary>
    public int ProjectNum
    {
        get { return _projectNum; }
        set { SetProperty(ref _projectNum, value); }
    }

    private bool _isProjectHide;
    /// <summary>
    /// 是否在搜索界面中隐藏,仅用于界面显示
    /// JsonIgnore 避免序列化
    /// </summary>
    [JsonIgnore]
    public bool IsProjectHide
    {
        get { return _isProjectHide; }
        set { SetProperty(ref _isProjectHide, value); }
    }

    private bool _isTestBtnEnable = true;
    /// <summary>
    /// 测试按钮是否可用,控制界面按钮状态
    /// </summary>
    [JsonIgnore]
    public bool IsTestBtnEnable
    {
        get { return _isTestBtnEnable; }
        set { SetProperty(ref _isTestBtnEnable, value); }
    }

    // ... 其他按钮状态属性 (IsRunBtnEnable, IsStopBtnEnable 等) 类似

    private int _projectRunStatus;
    /// <summary>
    /// 工程运行状态
    /// 0: 空闲 (IDLE), 1: 运行 (Running), 2: 暂停 (Paused), 3: 停止 (Stopped)
    /// </summary>
    [JsonIgnore]
    public int ProjectRunStatus
    {
        get { return _projectRunStatus; }
        set { SetProperty(ref _projectRunStatus, value); }
    }

    private string _projectSelectedZone;
    /// <summary>
    /// 当前选择的运行区域 (Zone1, Zone2, Zone3, Zone4 等)
    /// </summary>
    [JsonIgnore]
    public string ProjectSelectedZone
    {
        get { return _projectSelectedZone; }
        set { SetProperty(ref _projectSelectedZone, value); }
    }

    private string _projectName;
    /// <summary>
    /// 工程名称
    /// </summary>
    public string ProjectName
    {
        get { return _projectName; }
        set { SetProperty(ref _projectName, value); }
    }

    private ObservableCollection<ChannelModel> _channelList = new ObservableCollection<ChannelModel>();
    /// <summary>
    /// 工程包含的通道列表,每个通道对应一个测试单元
    /// </summary>
    public ObservableCollection<ChannelModel> ChannelList
    {
        get { return _channelList; }
        set { SetProperty(ref _channelList, value); }
    }

    private ObservableCollection<InputParamModel> _paramList = new ObservableCollection<InputParamModel>();
    /// <summary>
    /// 工程的标准参数模型集合,用于配置测试条件
    /// </summary>
    public ObservableCollection<InputParamModel> ParamList
    {
        get { return _paramList; }
        set { SetProperty(ref _paramList, value); }
    }

    private ObservableCollection<OutputParamModel> _resultList = new ObservableCollection<OutputParamModel>();
    /// <summary>
    /// 工程测试结果集合
    /// </summary>
    [JsonIgnore]
    public ObservableCollection<OutputParamModel> ResultList
    {
        get { return _resultList; }
        set { SetProperty(ref _resultList, value); }
    }

    private ObservableCollection<OutputPCParamModel> _pcResultList = new ObservableCollection<OutputPCParamModel>();
    /// <summary>
    /// PC 测试结果集合,专用于 PC 测试模式
    /// </summary>
    [JsonIgnore]
    public ObservableCollection<OutputPCParamModel> PCResultList
    {
        get { return _pcResultList; }
        set { SetProperty(ref _pcResultList, value); }
    }

    private ParamSetupModel _paramSetupModel = new ParamSetupModel();
    /// <summary>
    /// 工程的设置参数模型,包含测试模式、停止条件等
    /// </summary>
    public ParamSetupModel ParamSetupModel
    {
        get { return _paramSetupModel; }
        set { SetProperty(ref _paramSetupModel, value); }
    }
}

优化建议

  1. 属性初始化:所有属性都使用了 private 字段和 SetProperty(Prism 的方法)实现 INotifyPropertyChanged。建议使用 C# 6.0+ 的自动属性初始化来简化代码,例如:

    public Guid Guid { get; set; } = Guid.NewGuid();
    public DateTime ProjectDate { get => _projectDate; set => SetProperty(ref _projectDate, value); } = DateTime.Now;
    

    这可以减少冗余的字段声明。

  2. 按钮状态管理:多个按钮状态属性 (IsTestBtnEnable, IsRunBtnEnable 等) 可以合并为一个枚举或状态机,避免分散的布尔值管理。例如:

    public enum ButtonState { Enabled, Disabled, Hidden }
    public Dictionary<string, ButtonState> ButtonStates { get; } = new Dictionary<string, ButtonState>
    {
        { "Test", ButtonState.Enabled },
        { "Run", ButtonState.Enabled },
        // ...
    };
    
  3. 序列化优化:使用 [JsonIgnore] 避免序列化某些属性,但可以考虑将需要序列化的属性集中到一个 DTO(Data Transfer Object)类中,分离 ViewModel 和数据模型逻辑。

  4. 属性分组:属性数量较多,建议按功能分组(如基本信息、按钮状态、曲线显示、区域状态等),并在代码中添加区域注释(如 #region)提高可读性。

2. 曲线显示部分
private ObservableCollection<(ObservableCollection<double>, ObservableCollection<double>, string)> _chartValueList
    = new ObservableCollection<(ObservableCollection<double>, ObservableCollection<double>, string)>();
/// <summary>
/// 实时曲线数据集合,存储 X 坐标、Y 坐标和图例名称
/// </summary>
[JsonIgnore]
public ObservableCollection<(ObservableCollection<double>, ObservableCollection<double>, string)> ChartValueList
{
    get { return _chartValueList; }
    set { SetProperty(ref _chartValueList, value); }
}

private bool _tvj;
/// <summary>
/// 是否显示 TVJ 曲线
/// </summary>
[JsonIgnore]
public bool Tvj
{
    get { return _tvj; }
    set { SetProperty(ref _tvj, value); }
}

// ... 其他曲线属性 (Vce, TC, Vgeheat 等) 类似

private void DoChartShowCommand(object obj)
{
    // 关闭测量尺
    Global.EventAggregator.GetEvent<FitStationLogShow>().Publish(false);
    Global.EventAggregator.GetEvent<CloseRuleEvent>().Publish(false);

    string chartName = obj as string;
    if (string.IsNullOrEmpty(ProjectName))
        return;

    // 解析图表名称,如 "VCE_CycNum_V"
    string[] names = chartName.ToString().Split(new char[] { '_' });
    this.XLabel = names[1];
    this.YLabel = names[2];
    if (names[0] == "VCE")
    {
        if (ParamSetupModel.ProjectMethod == "K系数")
            this.XLabel = names[1].Split("/")[1];
        else
            this.XLabel = names[1].Split("/")[0];
    }

    // 设置图例名称
    string name = names[0];
    selectedline = name;

    // 清空曲线数据
    if (Monitor.TryEnter(chartShowLock, 1000))
    {
        ChartValueList.Clear();
        Monitor.Exit(chartShowLock);
    }

    // 根据运行状态和选择的曲线类型显示数据
    if (ProjectRunStatus == 0 && !string.IsNullOrEmpty(selectedline) && SelectedChartStationList.Count > 0)
    {
        ChartValueList.Clear();
        if (!(selectedline == "VCE" || selectedline == "TVJ" || selectedline == "TC"))
        {
            switch (ParamSetupModel.ProjectMethod)
            {
                case "PC":
                    foreach (string key in HistoryChartValueList.Keys)
                    {
                        if (SelectedChartStationList.Contains(key))
                        {
                            var _value = HistoryChartValueList[key];
                            foreach (var t in _value)
                            {
                                if (t.Item3.Split('_')[2] == selectedline)
                                {
                                    ChartValueList.Add((t.Item1, t.Item2, t.Item3));
                                }
                            }
                        }
                    }
                    break;
                case "热阻":
                    foreach (var ch in ChannelList)
                    {
                        string selectedA = $"A_{ch.ChannelID}";
                        string selectedB = $"B_{ch.ChannelID}";
                        if (ch.DeviceModelA != null && SelectedChartStationList.Contains(selectedA))
                        {
                            var t = tuple.FirstOrDefault(t => t.Item3 == $"A_{ch.ChannelID}_{selectedline}");
                            if (t != null)
                                ChartValueList.Add((t.Item1, t.Item2, t.Item3));
                        }
                        if (ch.DeviceModelB != null && SelectedChartStationList.Contains(selectedB))
                        {
                            var t = tuple.FirstOrDefault(t => t.Item3 == $"B_{ch.ChannelID}_{selectedline}");
                            if (t != null)
                                ChartValueList.Add((t.Item1, t.Item2, t.Item3));
                        }
                    }
                    break;
            }
        }
        else
        {
            foreach (var ch in ChannelList)
            {
                string selectedA = $"A_{ch.ChannelID}";
                string selectedB = $"B_{ch.ChannelID}";
                if (ch.DeviceModelA != null && SelectedChartStationList.Contains(selectedA))
                {
                    var t = outline.FirstOrDefault(t => t.Item3 == $"A_{ch.ChannelID}_{selectedline}");
                    if (t != null)
                        ChartValueList.Add((t.Item1, t.Item2, t.Item3));
                }
                if (ch.DeviceModelB != null && SelectedChartStationList.Contains(selectedB))
                {
                    var t = outline.FirstOrDefault(t => t.Item3 == $"B_{ch.ChannelID}_{selectedline}");
                    if (t != null)
                        ChartValueList.Add((t.Item1, t.Item2, t.Item3));
                }
            }
        }
    }
}

优化建议

  1. 曲线数据结构优化

    • 当前使用 ObservableCollection<(ObservableCollection<double>, ObservableCollection<double>, string)> 存储曲线数据,结构复杂且性能开销较大。建议定义一个专用的 ChartData 类:
      public class ChartData
      {
          public ObservableCollection<double> XValues { get; set; }
          public ObservableCollection<double> YValues { get; set; }
          public string Legend { get; set; }
      }
      public ObservableCollection<ChartData> ChartValueList { get; set; } = new ObservableCollection<ChartData>();
      
      这可以提高代码可读性和类型安全性。
  2. 线程安全

    • 使用 Monitor.TryEnter 确保线程安全,但 chartShowLock 的使用范围较小,建议将所有数据操作都放入锁中,避免潜在的并发问题。
    • 考虑使用 ConcurrentObservableCollection(自定义或第三方库)来简化线程安全处理。
  3. 曲线选择逻辑优化

    • DoChartShowCommand 中的逻辑较为复杂,包含多个嵌套循环和条件判断。建议将曲线选择逻辑抽取为独立的方法,例如:
      private void UpdateChartData(string chartName)
      {
          var names = chartName.Split('_');
          XLabel = names[1];
          YLabel = names[2];
          if (names[0] == "VCE")
              XLabel = ParamSetupModel.ProjectMethod == "K系数" ? names[1].Split('/')[1] : names[1].Split('/')[0];
      
          selectedline = names[0];
          ChartValueList.Clear();
      
          if (ProjectRunStatus == 0 && !string.IsNullOrEmpty(selectedline) && SelectedChartStationList.Any())
          {
              if (IsCommonCurve(selectedline))
                  UpdateCommonCurveData();
              else
                  UpdateCustomCurveData();
          }
      }
      
      private bool IsCommonCurve(string line) => line is "VCE" or "TVJ" or "TC";
      
  4. 事件发布优化

    • 使用 Global.EventAggregator 发布事件,但事件名称(如 FitStationLogShow, CloseRuleEvent)可以定义为常量或枚举,避免硬编码。
3. 命令处理部分
public DelegateCommand<object> TestButtonEnableCommand { get; set; }
public DelegateCommand<object> RunCommand { get; set; }
// ... 其他命令

public ProjectModel()
{
    TestButtonEnableCommand = new DelegateCommand<object>(DoTestButtonEnableCommand);
    RunCommand = new DelegateCommand<object>(DoRunCommand);
    // ... 初始化其他命令
}

private void DoTestButtonEnableCommand(object obj)
{
    if (Boolean.Parse(obj.ToString()))
    {
        this.IsRunBtnEnable = false;
        this.IsStopBtnEnable = false;
        this.IsPauseBtnEnable = false;
        this.IsResumeBtnEnable = false;
        this.IsTestBtnEnable = false;
    }
    else
    {
        this.IsRunBtnEnable = true;
        this.IsStopBtnEnable = true;
        this.IsPauseBtnEnable = true;
        this.IsResumeBtnEnable = true;
        this.IsTestBtnEnable = true;
    }
}

private void DoRunCommand(object obj)
{
    if (obj == null) return;

    Cleanbutton();
    ProjectModel projectModel = obj as ProjectModel;

    // 检查区域是否可用
    var zone = projectModel.ProjectSelectedZone;
    string zonep = ParallelZONE(projectModel.ProjectSelectedParallelZone);
    if (!string.IsNullOrEmpty(zonep))
    {
        if (!zonep.Contains(zone))
        {
            Global.LogAndPop($"【{projectModel.ProjectName}】并联区域不包含运行区", Level.ERROR, LogType.Pop);
            return;
        }
        // ... 区域检查逻辑
    }

    Task.Run(() =>
    {
        // 设置委托
        WorkstationModel.ResultAction = new Action<object>(result =>
        {
            Application.Current.Dispatcher.Invoke(() =>
            {
                projectModel?.ResultList?.Clear();
                projectModel?.PCResultList?.Clear();
            });
            // ... 处理测试结果
        });

        // 启动流程
        projectModel.ChartValueList.Clear();
        selectedline = string.Empty;
        Selectedfixline = string.Empty;
        tuple.Clear();
        outline.Clear();
        tvjfitline.Clear();
        object objplan = new object();
        Startini(ref objplan, projectModel, DriveCOM);
    });
}

优化建议

  1. 命令参数类型安全

    • 当前命令使用 DelegateCommand<object>,参数类型不明确,容易导致运行时错误。建议为特定命令定义强类型参数,例如:
      public DelegateCommand<ProjectModel> RunCommand { get; set; }
      RunCommand = new DelegateCommand<ProjectModel>(DoRunCommand);
      
  2. 异步命令

    • DoRunCommand 使用 Task.Run 执行异步操作,但未处理异常。建议使用 Prism 的 DelegateCommand 异步支持:
      public DelegateCommand<ProjectModel> RunCommand { get; set; }
      RunCommand = new DelegateCommand<ProjectModel>(async projectModel => await DoRunCommandAsync(projectModel));
      
      private async Task DoRunCommandAsync(ProjectModel projectModel)
      {
          try
          {
              // 异步逻辑
              await Task.Run(() => Startini(ref objplan, projectModel, DriveCOM));
          }
          catch (Exception ex)
          {
              Global.LogAndPop($"运行失败: {ex.Message}", Level.ERROR, LogType.Pop);
          }
      }
      
  3. 命令复用

    • 多个命令(如 DoRunCommand, DoTestJoinLineCommand)有相似的区域检查逻辑,可以抽取为公共方法:
      private bool CanRunZone(ProjectModel projectModel, out string errorMessage)
      {
          errorMessage = string.Empty;
          var zone = projectModel.ProjectSelectedZone;
          if (string.IsNullOrEmpty(zone))
          {
              errorMessage = "工作区不能为空";
              return false;
          }
          // ... 其他区域检查逻辑
          return true;
      }
      
  4. 按钮状态管理

    • Cleanbutton 方法重置所有曲线相关的布尔值,建议将其改为状态机模式,统一管理按钮和曲线状态。
4. 设备驱动和通信
private void Startini(ref object obj, ProjectModel projectModel, ComInit drive)
{
    ComInit _temp_1 = drive;
    ADriveall aDriveall = new ADriveall(_temp_1, Daq);
    var zone = projectModel.ProjectSelectedZone;
    aDriveall.zoneseting.Id = zone;
    aDriveall.zoneseting.Name = projectModel.ProjectName;

    List<WorkstationModel> workstationList = new List<WorkstationModel>();
    foreach (var ch in projectModel.ChannelList)
    {
        WorkstationModel workstation = new WorkstationModel
        {
            ZoneName = zone,
            StationName = "A_" + ch.ChannelID,
            IsUsed = ch.DeviceModelA != null,
            Temp4716 = ch.Temp4716A,
            StopParamList = ch.AStopParamList,
            M = ch.DeviceModelA == null ? "0" : string.IsNullOrEmpty(ch.DeviceModelA.DeviceM) ? "0" : (double.Parse(ch.DeviceModelA.DeviceM) / 1000).ToString(),
            B = ch.DeviceModelA == null ? "0" : string.IsNullOrEmpty(ch.DeviceModelA.DeviceB) ? "0" : (double.Parse(ch.DeviceModelA.DeviceB) / 1000).ToString(),
            Inom = ch.DeviceModelA == null ? "0" : ch.DeviceModelA.DeviceInom
        };
        if (workstation.IsUsed && !Fit_Channls.ContainsKey(workstation.StationName))
        {
            Fit_Channls.Add(workstation.StationName, false);
            FitDAte_Channls.Add(workstation.StationName, false);
        }
        workstationList.Add(workstation);
        // ... 类似处理 B 通道
    }

    aDriveall.SetSeting(projectModel.ParamList, workstationList);
    aDriveall.WorkstationModel = WorkstationModel;
    aDriveall.EndResult = EndResult;
    aDriveall.OutputRunStateModel = OutputRunStateModel;
    aDriveall.OuterEquipmentModel = OuterEquipmentModel;

    switch (projectModel.ParamSetupModel.ProjectMethod)
    {
        case "K系数":
            Tsp _Tsp = new Tsp();
            obj = _Tsp;
            _Tsp.aDrive = aDriveall;
            OpenCom(_temp_1);
            _Tsp.Start();
            break;
        case "热阻":
            Zth _Zth = new Zth();
            _Zth.TCMeas = projectModel.ParamSetupModel.ProjectTcMeas;
            _Zth.aDrive = aDriveall;
            obj = _Zth;
            OpenCom(_temp_1);
            _Zth.Start();
            break;
        case "PC":
            PCs _PCs = new PCs();
            PCSecInfo pCSecInfo = new PCSecInfo
            {
                FixedCondition = projectModel.ParamSetupModel.ProjectMode.Contains("Ic"),
                FixedDeltaTVJ = projectModel.ParamSetupModel.ProjectMode.Contains("Tvj"),
                // ... 其他配置
            };
            _PCs.pCSecInfo = pCSecInfo;
            _PCs.aDrive = aDriveall;
            obj = _PCs;
            OpenCom(_temp_1);
            _PCs.Start();
            break;
    }
}

优化建议

  1. 驱动初始化分离

    • Startini 方法负责设备初始化和流程启动,逻辑复杂。建议将设备配置和流程启动分离:
      private ADriveall InitializeDrive(ProjectModel projectModel, ComInit drive)
      {
          var aDriveall = new ADriveall(drive, Daq);
          aDriveall.zoneseting.Id = projectModel.ProjectSelectedZone;
          aDriveall.zoneseting.Name = projectModel.ProjectName;
          // ... 配置逻辑
          return aDriveall;
      }
      
      private void StartProcess(ref object obj, ProjectModel projectModel, ADriveall aDriveall)
      {
          switch (projectModel.ParamSetupModel.ProjectMethod)
          {
              case "K系数":
                  var tsp = new Tsp { aDrive = aDriveall };
                  obj = tsp;
                  tsp.Start();
                  break;
              // ... 其他 case
          }
      }
      
  2. 异常处理

    • 驱动方法(如 OpenCom, Start) 缺乏异常处理,可能导致设备通信失败时程序崩溃。建议添加 try-catch:
      private void OpenCom(ComInit drive)
      {
          if (_isComOpen) return;
          try
          {
              drive.Open();
              _isComOpen = true;
          }
          catch (Exception ex)
          {
              Global.LogAndPop($"串口打开失败: {ex.Message}", Level.ERROR, LogType.Pop);
          }
      }
      
  3. 设备状态管理

    • 使用 _isComOpen 管理串口状态,但状态管理分散。建议引入一个设备状态类:
      public class DeviceState
      {
          public bool IsComOpen { get; set; }
          public ComInit Drive { get; set; }
          public Daq DataAcquisition { get; set; }
      }
      
  4. 并联区域逻辑

    • ParallelZONE 方法将并联区域名称转换为标准格式,建议使用枚举或常量:
      public enum ZoneCombination
      {
          None,
          Zone1_Zone2,
          Zone2_Zone3,
          Zone1_Zone2_Zone3
      }
      
5. 数据保存和事件处理
EndResult.ResultAction = new Action<object>(result =>
{
    var pList = FileHelper.LoadProjectList(Global.SysSettingModel.ProjectPath);
    if (projectModel.ParamSetupModel.ProjectMethod == "K系数")
    {
        List<OutputParamModel> outputParamModels = result as List<OutputParamModel>;
        foreach (var ch in projectModel.ChannelList)
        {
            if (ch.DeviceModelA != null)
            {
                DeviceModel device = Global.DeviceList.FirstOrDefault(d => d.Guid == ch.DeviceModelA.Guid);
                device.DeviceM = outputParamModels.FirstOrDefault(op => op.Name == $"A_{ch.ChannelID}_M")?.Value;
                device.DeviceB = outputParamModels.FirstOrDefault(op => op.Name == $"A_{ch.ChannelID}_B")?.Value;
                ch.DeviceModelA = device;
                // ... 更新其他工程的设备信息
            }
        }
        FileHelper.SaveDeviceList(Global.SysSettingModel.DevicePath, Global.DeviceList);
        FileHelper.SaveProjectList(Global.SysSettingModel.ProjectPath, pList);
    }
});

优化建议

  1. 数据访问层封装

    • 数据保存逻辑(如 FileHelper.SaveDeviceList)分散在多个地方,建议封装为一个数据访问服务:
      public interface IDataAccessService
      {
          Task SaveDeviceListAsync(string path, IEnumerable<DeviceModel> devices);
          Task SaveProjectListAsync(string path, IEnumerable<ProjectModel> projects);
      }
      
  2. 事件驱动优化

    • 使用 Action<object> 传递数据,类型不安全。建议定义强类型事件参数:
      public class TestResultEventArgs : EventArgs
      {
          public string ProjectMethod { get; set; }
          public List<OutputParamModel> Results { get; set; }
      }
      
  3. 异步数据保存

    • 数据保存操作可能是 I/O 密集型,建议使用异步方法:
      await FileHelper.SaveDeviceListAsync(Global.SysSettingModel.DevicePath, Global.DeviceList);
      

WPF 框架优化建议

  1. MVVM 模式优化

    • 当前 ProjectModel 既是 ViewModel 又是数据模型,建议分离:
      • 创建 Project 数据模型类,存储纯数据(如 Guid, ProjectName, ChannelList)。
      • ProjectModel 作为 ViewModel,仅包含界面逻辑和命令。
    • 使用 Prism 的 IDialogService 优化对话框逻辑,例如在 DoRunCommand 中显示历史数据对话框。
  2. 数据绑定优化

    • 确保所有界面相关属性都实现 INotifyPropertyChanged,并在 ChartValueList 等集合属性更改时触发通知。
    • 使用 CollectionViewSource 在界面上过滤和排序 ChannelListResultList
  3. UI 线程安全

    • 所有界面更新(如 ChartValueList.Clear())都应通过 Dispatcher.Invoke 确保在 UI 线程执行。当前代码已部分实现,但需全面检查。
  4. Prism 事件优化

    • 使用 Prism 的 EventAggregator 发布事件,但事件名称分散且容易出错。建议定义事件类:
      public class FitStationLogShowEvent : PubSubEvent<bool> { }
      
  5. 依赖注入

    • 使用 Prism 的 IContainerProvider 注入 ComInit, Daq, IDataAccessService 等依赖,避免直接在类中实例化:
      public ProjectModel(IContainerProvider container)
      {
          _drive = container.Resolve<ComInit>();
          _daq = container.Resolve<Daq>();
      }
      
  6. UI 组件优化

    • 使用 ScottPlot.WPF 控件显示曲线,建议封装为自定义控件,简化绑定逻辑:
      public class ChartControl : UserControl
      {
          public ObservableCollection<ChartData> ChartData
          {
              get => (ObservableCollection<ChartData>)GetValue(ChartDataProperty);
              set => SetValue(ChartDataProperty, value);
          }
          public static readonly DependencyProperty ChartDataProperty = 
              DependencyProperty.Register(nameof(ChartData), typeof(ObservableCollection<ChartData>), typeof(ChartControl));
      }
      

总结

优点

  • 代码结构清晰,使用 Prism 框架实现了 MVVM 模式。
  • 支持复杂的测试逻辑,涵盖设备控制、数据采集、曲线显示等功能。
  • 使用线程锁(Monitor)和 Dispatcher 确保线程安全。

改进方向

  1. 代码分离:将数据模型、ViewModel 和服务逻辑分离,提高可维护性。
  2. 类型安全:使用强类型参数和事件,减少运行时错误。
  3. 异步编程:全面引入异步方法,优化 I/O 和设备操作性能。
  4. 状态管理:使用状态机或枚举统一管理按钮和运行状态。
  5. 依赖注入:通过 Prism 的 DI 容器管理依赖,降低耦合。

通过以上优化,可以显著提高代码的可读性、可维护性和性能,同时更好地利用 WPF 和 Prism 框架的特性。如果需要更具体的实现或进一步的 WPF 组件设计,请提供更多上下文,我可以提供更详细的代码示例!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐