示波器加入源适应性界面
This commit is contained in:
+1
-1
@@ -176,7 +176,7 @@ namespace ACP
|
|||||||
//注册弹窗
|
//注册弹窗
|
||||||
containerRegistry.RegisterDialog<MessageBoxView, MessageBoxViewModel>("MessageBox");
|
containerRegistry.RegisterDialog<MessageBoxView, MessageBoxViewModel>("MessageBox");
|
||||||
// 注册通知管理器
|
// 注册通知管理器
|
||||||
INotificationManager NotificationManager = new NotificationManager();
|
INotificationManager NotificationManager = new NotificationManager();
|
||||||
containerRegistry.RegisterInstance<INotificationManager>(NotificationManager);
|
containerRegistry.RegisterInstance<INotificationManager>(NotificationManager);
|
||||||
// 注册仓储
|
// 注册仓储
|
||||||
containerRegistry.RegisterScoped(typeof(SqlSugarRepository<>));
|
containerRegistry.RegisterScoped(typeof(SqlSugarRepository<>));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using DeviceCommand.Devices;
|
using DeviceCommand.Devices;
|
||||||
|
using Microsoft.Win32;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
@@ -11,6 +12,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
using UIShare.GlobalVariable;
|
using UIShare.GlobalVariable;
|
||||||
using UIShare.ViewModelBase;
|
using UIShare.ViewModelBase;
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
private readonly DeviceManager _dm;
|
private readonly DeviceManager _dm;
|
||||||
private readonly GlobalInfo _globalInfo;
|
private readonly GlobalInfo _globalInfo;
|
||||||
private Chroma61800? _dev;
|
private Chroma61800? _dev;
|
||||||
|
private TektronixMSO? _scope;
|
||||||
private CancellationTokenSource? _cmdCts;
|
private CancellationTokenSource? _cmdCts;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -40,6 +43,92 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
public bool IsBusy { get => _isBusy; set => SetProperty(ref _isBusy, value); }
|
public bool IsBusy { get => _isBusy; set => SetProperty(ref _isBusy, value); }
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 示波器(TektronixMSO)屏幕抓图
|
||||||
|
private string _scopeName = "TektronixMSO";
|
||||||
|
/// <summary>示波器设备名称(DeviceMap 键名)</summary>
|
||||||
|
public string ScopeName { get => _scopeName; set => SetProperty(ref _scopeName, value); }
|
||||||
|
|
||||||
|
private bool _isScopeConnected;
|
||||||
|
/// <summary>示波器连接状态</summary>
|
||||||
|
public bool IsScopeConnected { get => _isScopeConnected; set => SetProperty(ref _isScopeConnected, value); }
|
||||||
|
|
||||||
|
private string _scopeImageDir = @"D:\acp\源测试\波形截图";
|
||||||
|
/// <summary>波形图片存储位置(抓取的屏幕 PNG 保存目录),抓图/浏览选择时自动持久化</summary>
|
||||||
|
public string ScopeImageDir { get => _scopeImageDir; set => SetProperty(ref _scopeImageDir, value); }
|
||||||
|
|
||||||
|
private BitmapImage? _scopeImage;
|
||||||
|
/// <summary>最近一次抓取的示波器屏幕图片(供界面 Image 控件显示)</summary>
|
||||||
|
public BitmapImage? ScopeImage { get => _scopeImage; set => SetProperty(ref _scopeImage, value); }
|
||||||
|
|
||||||
|
private bool _hasScopeImage;
|
||||||
|
/// <summary>是否已有截图(控制图片区占位提示的显隐)</summary>
|
||||||
|
public bool HasScopeImage { get => _hasScopeImage; set => SetProperty(ref _hasScopeImage, value); }
|
||||||
|
|
||||||
|
private string _scopeImageHint = "";
|
||||||
|
/// <summary>最近抓取的图片文件提示</summary>
|
||||||
|
public string ScopeImageHint { get => _scopeImageHint; set => SetProperty(ref _scopeImageHint, value); }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 示波器设置(采集 / 水平 / 通道 / 测量,对应 TektronixMSO 已有 SCPI 方法)
|
||||||
|
/// <summary>采集模式选项 (ACQuire:MODe,显示名与 TekAcquisitionMode 枚举一致)</summary>
|
||||||
|
public IReadOnlyList<string> ScopeAcquireModeOptions { get; } =
|
||||||
|
new[] { "SAMple", "PEAKdetect", "HIRes", "AVErage", "ENVelope" };
|
||||||
|
|
||||||
|
/// <summary>测量类型选项 (MEASUrement:TYPe,显示名与 TekMeasurementType 枚举一致)</summary>
|
||||||
|
public IReadOnlyList<string> ScopeMeasTypeOptions { get; } =
|
||||||
|
new[] { "AMPLitude", "FREQuency", "MEAN", "PK2PK", "MAXimum", "MINimum" };
|
||||||
|
|
||||||
|
/// <summary>通道选择列表 (对应通道号 1~4)</summary>
|
||||||
|
public IReadOnlyList<string> ScopeChannelOptions { get; } = new[] { "CH1", "CH2", "CH3", "CH4" };
|
||||||
|
|
||||||
|
private string _scopeAcquireMode = "SAMple";
|
||||||
|
/// <summary>采集模式 (ACQuire:MODe)</summary>
|
||||||
|
public string ScopeAcquireMode { get => _scopeAcquireMode; set => SetProperty(ref _scopeAcquireMode, value); }
|
||||||
|
|
||||||
|
private double _scopeTimeScale = 0.001;
|
||||||
|
/// <summary>水平刻度待设值 (HORizontal:SCAle, s/Div)</summary>
|
||||||
|
public double ScopeTimeScale { get => _scopeTimeScale; set => SetProperty(ref _scopeTimeScale, value); }
|
||||||
|
|
||||||
|
private long _scopeRecordLength = 6250000;
|
||||||
|
/// <summary>记录长度待设值 (HORizontal:RECOrdlength, 采样点数)</summary>
|
||||||
|
public long ScopeRecordLength { get => _scopeRecordLength; set => SetProperty(ref _scopeRecordLength, value); }
|
||||||
|
|
||||||
|
private string _sampleRateText = "";
|
||||||
|
/// <summary>采样率查询结果显示 (HORizontal:SAMPLERate?, Sa/s)</summary>
|
||||||
|
public string SampleRateText { get => _sampleRateText; set => SetProperty(ref _sampleRateText, value); }
|
||||||
|
|
||||||
|
private int _selectedScopeChannelIndex;
|
||||||
|
/// <summary>当前编辑通道索引 (0~3 → CH1~CH4)</summary>
|
||||||
|
public int SelectedScopeChannelIndex { get => _selectedScopeChannelIndex; set => SetProperty(ref _selectedScopeChannelIndex, value); }
|
||||||
|
|
||||||
|
private double _channelScale = 100;
|
||||||
|
/// <summary>通道垂直刻度待设值 (CH:SCAle, V/Div)</summary>
|
||||||
|
public double ChannelScale { get => _channelScale; set => SetProperty(ref _channelScale, value); }
|
||||||
|
|
||||||
|
private double _channelPosition;
|
||||||
|
/// <summary>通道垂直位置待设值 (CH:POSition, Div)</summary>
|
||||||
|
public double ChannelPosition { get => _channelPosition; set => SetProperty(ref _channelPosition, value); }
|
||||||
|
|
||||||
|
private int _measurementNumber = 1;
|
||||||
|
/// <summary>测量项编号 (MEASUrement:MEASn)</summary>
|
||||||
|
public int MeasurementNumber { get => _measurementNumber; set => SetProperty(ref _measurementNumber, value); }
|
||||||
|
|
||||||
|
private string _measurementType = "FREQuency";
|
||||||
|
/// <summary>测量类型 (MEASUrement:TYPe)</summary>
|
||||||
|
public string MeasurementType { get => _measurementType; set => SetProperty(ref _measurementType, value); }
|
||||||
|
|
||||||
|
private string _measurementSource = "CH1";
|
||||||
|
/// <summary>测量源 (MEASUrement:SOUrce1, 如 CH1/CH2)</summary>
|
||||||
|
public string MeasurementSource { get => _measurementSource; set => SetProperty(ref _measurementSource, value); }
|
||||||
|
|
||||||
|
private string _measResultText = "";
|
||||||
|
/// <summary>测量查询结果显示</summary>
|
||||||
|
public string MeasResultText { get => _measResultText; set => SetProperty(ref _measResultText, value); }
|
||||||
|
|
||||||
|
/// <summary>当前编辑通道号 (1~4)</summary>
|
||||||
|
private int ScopeChannelNo => SelectedScopeChannelIndex + 1;
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region 输入参数(三相输出设置)
|
#region 输入参数(三相输出设置)
|
||||||
private double _voltage = 220;
|
private double _voltage = 220;
|
||||||
/// <summary>待设置的交流电压 (V)</summary>
|
/// <summary>待设置的交流电压 (V)</summary>
|
||||||
@@ -212,6 +301,9 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
/// <summary>当前波形说明(随文件保存,载入后自动填充)</summary>
|
/// <summary>当前波形说明(随文件保存,载入后自动填充)</summary>
|
||||||
public string SynthPresetNote { get => _synthPresetNote; set => SetProperty(ref _synthPresetNote, value); }
|
public string SynthPresetNote { get => _synthPresetNote; set => SetProperty(ref _synthPresetNote, value); }
|
||||||
|
|
||||||
|
/// <summary>当前已打开的波形文件完整路径(载入或另存后设置);null 表示尚未与磁盘文件绑定</summary>
|
||||||
|
private string? _currentSynthPath;
|
||||||
|
|
||||||
private int _selectedSynthPresetIndex = -1;
|
private int _selectedSynthPresetIndex = -1;
|
||||||
/// <summary>当前选中的合成波形预设索引(-1 表示未选择)</summary>
|
/// <summary>当前选中的合成波形预设索引(-1 表示未选择)</summary>
|
||||||
public int SelectedSynthPresetIndex
|
public int SelectedSynthPresetIndex
|
||||||
@@ -260,6 +352,9 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
/// <summary>当前测试项说明(随文件保存,载入后自动填充)</summary>
|
/// <summary>当前测试项说明(随文件保存,载入后自动填充)</summary>
|
||||||
public string PresetNote { get => _presetNote; set => SetProperty(ref _presetNote, value); }
|
public string PresetNote { get => _presetNote; set => SetProperty(ref _presetNote, value); }
|
||||||
|
|
||||||
|
/// <summary>当前已打开的测试项文件完整路径(载入或另存后设置);null 表示尚未与磁盘文件绑定</summary>
|
||||||
|
private string? _currentPresetPath;
|
||||||
|
|
||||||
private int _selectedPresetIndex = -1;
|
private int _selectedPresetIndex = -1;
|
||||||
/// <summary>当前选中的测试项索引(-1 表示未选择)</summary>
|
/// <summary>当前选中的测试项索引(-1 表示未选择)</summary>
|
||||||
public int SelectedPresetIndex
|
public int SelectedPresetIndex
|
||||||
@@ -305,6 +400,7 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
public ICommand EnterListModeCommand { get; }
|
public ICommand EnterListModeCommand { get; }
|
||||||
public ICommand LoadPresetCommand { get; }
|
public ICommand LoadPresetCommand { get; }
|
||||||
public ICommand SavePresetCommand { get; }
|
public ICommand SavePresetCommand { get; }
|
||||||
|
public ICommand SavePresetAsCommand { get; }
|
||||||
public ICommand DeletePresetCommand { get; }
|
public ICommand DeletePresetCommand { get; }
|
||||||
public ICommand CopyPhaseToThreeCommand { get; }
|
public ICommand CopyPhaseToThreeCommand { get; }
|
||||||
public ICommand DownloadThreePhaseListCommand { get; }
|
public ICommand DownloadThreePhaseListCommand { get; }
|
||||||
@@ -314,7 +410,28 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
public ICommand ResetSynthHarmonicsCommand { get; }
|
public ICommand ResetSynthHarmonicsCommand { get; }
|
||||||
public ICommand LoadSynthPresetCommand { get; }
|
public ICommand LoadSynthPresetCommand { get; }
|
||||||
public ICommand SaveSynthPresetCommand { get; }
|
public ICommand SaveSynthPresetCommand { get; }
|
||||||
|
public ICommand SaveSynthPresetAsCommand { get; }
|
||||||
public ICommand DeleteSynthPresetCommand { get; }
|
public ICommand DeleteSynthPresetCommand { get; }
|
||||||
|
public ICommand CaptureScopeImageCommand { get; }
|
||||||
|
public ICommand BrowseScopeImageDirCommand { get; }
|
||||||
|
public ICommand ScopeAcquireRunCommand { get; }
|
||||||
|
public ICommand ScopeAcquireStopCommand { get; }
|
||||||
|
public ICommand QueryAcquireStateCommand { get; }
|
||||||
|
public ICommand ApplyAcquireModeCommand { get; }
|
||||||
|
public ICommand SetScopeTimeScaleCommand { get; }
|
||||||
|
public ICommand SetRecordLengthCommand { get; }
|
||||||
|
public ICommand QuerySampleRateCommand { get; }
|
||||||
|
public ICommand ChannelDisplayOnCommand { get; }
|
||||||
|
public ICommand ChannelDisplayOffCommand { get; }
|
||||||
|
public ICommand SetChannelScaleCommand { get; }
|
||||||
|
public ICommand SetChannelPositionCommand { get; }
|
||||||
|
public ICommand AddMeasurementCommand { get; }
|
||||||
|
public ICommand QueryMeasCurrentCommand { get; }
|
||||||
|
public ICommand QueryMeasMeanCommand { get; }
|
||||||
|
public ICommand QueryMeasMaxCommand { get; }
|
||||||
|
public ICommand QueryScopeIdnCommand { get; }
|
||||||
|
public ICommand ClearScopeErrorsCommand { get; }
|
||||||
|
public ICommand ReadScopeErrorsCommand { get; }
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public PowerAdaptabilityViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
public PowerAdaptabilityViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
@@ -330,6 +447,9 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
EnsureSynthStorage();
|
EnsureSynthStorage();
|
||||||
RefreshSynthPresets();
|
RefreshSynthPresets();
|
||||||
|
|
||||||
|
// 恢复上次设置的波形图片存储位置
|
||||||
|
LoadScopeImageDir();
|
||||||
|
|
||||||
#region 命令初始化
|
#region 命令初始化
|
||||||
QueryIdnCommand = new DelegateCommand(async () =>
|
QueryIdnCommand = new DelegateCommand(async () =>
|
||||||
await Exec(async () => Log("IDN: " + await _dev!.查询设备信息Async(Ct()))));
|
await Exec(async () => Log("IDN: " + await _dev!.查询设备信息Async(Ct()))));
|
||||||
@@ -411,14 +531,18 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
|
|
||||||
ListEditPhaseIndex = 0; // 切到 A 相显示(若原本就是 0,setter 不触发刷新,故下方手动刷新)
|
ListEditPhaseIndex = 0; // 切到 A 相显示(若原本就是 0,setter 不触发刷新,故下方手动刷新)
|
||||||
RaiseListSequenceChanged();
|
RaiseListSequenceChanged();
|
||||||
PresetName = p.Name; // 填充名称/说明,便于修改后覆盖保存
|
PresetName = p.Name; // 名称框回显当前文件名
|
||||||
PresetNote = p.Note;
|
PresetNote = p.Note;
|
||||||
|
_currentPresetPath = PresetPathOf(p.Name); // 记住当前打开的文件,此后“保存”只覆盖它
|
||||||
Log($"已载入测试项:{p.Name}(次数={(p.Count == 0 ? "无限" : p.Count.ToString())} 触发={p.Trigger} 时基={p.TimeBase})");
|
Log($"已载入测试项:{p.Name}(次数={(p.Count == 0 ? "无限" : p.Count.ToString())} 触发={p.Trigger} 时基={p.TimeBase})");
|
||||||
});
|
});
|
||||||
|
|
||||||
// 保存当前界面的 LIST 配置为测试项文件(写入 D:\acp\源测试\{名称}.json)
|
// 保存:覆盖当前打开的测试项文件(未打开文件时按“另存为”新建)
|
||||||
SavePresetCommand = new DelegateCommand(SaveCurrentPreset);
|
SavePresetCommand = new DelegateCommand(SaveCurrentPreset);
|
||||||
|
|
||||||
|
// 另存为:以“名称”框内容为新文件名新建文件,不修改当前打开的文件
|
||||||
|
SavePresetAsCommand = new DelegateCommand(SaveCurrentPresetAs);
|
||||||
|
|
||||||
// 删除当前选中的测试项文件(带二次确认)
|
// 删除当前选中的测试项文件(带二次确认)
|
||||||
DeletePresetCommand = new DelegateCommand(DeleteCurrentPreset);
|
DeletePresetCommand = new DelegateCommand(DeleteCurrentPreset);
|
||||||
|
|
||||||
@@ -517,10 +641,145 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
Log("SYNTHESIS 谐波已重置:振幅/相位两框均填入 50 个 0;点「下发并启动 SYNTH」写入仪器");
|
Log("SYNTHESIS 谐波已重置:振幅/相位两框均填入 50 个 0;点「下发并启动 SYNTH」写入仪器");
|
||||||
});
|
});
|
||||||
|
|
||||||
// 合成波形的载入 / 保存 / 删除(与 LIST 测试项同机制,文件存于 D:\acp\源测试\合成波形)
|
// 合成波形的载入 / 保存 / 另存为 / 删除(与 LIST 测试项同机制,文件存于 D:\acp\源测试\合成波形)
|
||||||
LoadSynthPresetCommand = new DelegateCommand(LoadCurrentSynthPreset);
|
LoadSynthPresetCommand = new DelegateCommand(LoadCurrentSynthPreset);
|
||||||
SaveSynthPresetCommand = new DelegateCommand(SaveCurrentSynthPreset);
|
SaveSynthPresetCommand = new DelegateCommand(SaveCurrentSynthPreset);
|
||||||
|
SaveSynthPresetAsCommand = new DelegateCommand(SaveCurrentSynthPresetAs);
|
||||||
DeleteSynthPresetCommand = new DelegateCommand(DeleteCurrentSynthPreset);
|
DeleteSynthPresetCommand = new DelegateCommand(DeleteCurrentSynthPreset);
|
||||||
|
|
||||||
|
// 示波器抓图:抓取当前屏幕保存为 PNG 到存储位置,成功后自动加载显示
|
||||||
|
CaptureScopeImageCommand = new DelegateCommand(async () => await ExecScope(async () =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(ScopeImageDir))
|
||||||
|
{
|
||||||
|
Log("请先设置波形图片存储位置");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveScopeImageDir(); // 记住本次使用的存储位置
|
||||||
|
Directory.CreateDirectory(ScopeImageDir);
|
||||||
|
string file = Path.Combine(ScopeImageDir, $"屏幕_{DateTime.Now:yyyyMMdd_HHmmss_fff}.png");
|
||||||
|
|
||||||
|
await _scope!.保存屏幕截图(file, Ct()); // SAVe:IMAGe → *OPC? → READFile → 写盘
|
||||||
|
|
||||||
|
LoadScopeImage(file);
|
||||||
|
Log($"示波器屏幕图片已抓取:{file}");
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 浏览选择波形图片存储位置(选择后立即记住)
|
||||||
|
BrowseScopeImageDirCommand = new DelegateCommand(() =>
|
||||||
|
{
|
||||||
|
var dlg = new OpenFolderDialog
|
||||||
|
{
|
||||||
|
Title = "选择波形图片存储位置",
|
||||||
|
InitialDirectory = Directory.Exists(ScopeImageDir) ? ScopeImageDir : PresetStorageDir,
|
||||||
|
};
|
||||||
|
if (dlg.ShowDialog() != true) return; // 用户取消
|
||||||
|
|
||||||
|
ScopeImageDir = dlg.FolderName;
|
||||||
|
SaveScopeImageDir();
|
||||||
|
Log($"波形图片存储位置已设置:{ScopeImageDir}");
|
||||||
|
});
|
||||||
|
|
||||||
|
// —— 采集控制 ——
|
||||||
|
ScopeAcquireRunCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => { await _scope!.设置采集状态(true, Ct()); Log("示波器采集已启动 (RUN)"); }));
|
||||||
|
|
||||||
|
// 停止采集定格画面后抓图,是最稳定的截图方式(截图抓的是当前屏幕画面)
|
||||||
|
ScopeAcquireStopCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => { await _scope!.设置采集状态(false, Ct()); Log("示波器采集已停止 (STOP),画面已定格;可点「抓取示波器屏幕图片」截取稳定波形"); }));
|
||||||
|
|
||||||
|
QueryAcquireStateCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => Log("采集状态: " + await _scope!.查询采集状态(Ct()))));
|
||||||
|
|
||||||
|
ApplyAcquireModeCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () =>
|
||||||
|
{
|
||||||
|
if (!Enum.TryParse<TekAcquisitionMode>(ScopeAcquireMode, out var mode))
|
||||||
|
{
|
||||||
|
Log($"未知采集模式:{ScopeAcquireMode}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _scope!.设置采集模式(mode, Ct());
|
||||||
|
Log($"采集模式已设置:{mode}");
|
||||||
|
}));
|
||||||
|
|
||||||
|
// —— 水平系统 ——
|
||||||
|
SetScopeTimeScaleCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => { await _scope!.设置水平刻度(ScopeTimeScale, Ct()); Log($"水平刻度={ScopeTimeScale} s/Div"); }));
|
||||||
|
|
||||||
|
SetRecordLengthCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => { await _scope!.设置记录长度(ScopeRecordLength, Ct()); Log($"记录长度={ScopeRecordLength} 点"); }));
|
||||||
|
|
||||||
|
QuerySampleRateCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () =>
|
||||||
|
{
|
||||||
|
double sr = await _scope!.查询采样率(Ct());
|
||||||
|
SampleRateText = $"{sr:0.###} Sa/s";
|
||||||
|
Log($"采样率={sr} Sa/s");
|
||||||
|
}));
|
||||||
|
|
||||||
|
// —— 通道设置 ——
|
||||||
|
ChannelDisplayOnCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => { await _scope!.设置通道显示开关(ScopeChannelNo, true, Ct()); Log($"{ScopeChannelOptions[SelectedScopeChannelIndex]} 显示已开启"); }));
|
||||||
|
|
||||||
|
ChannelDisplayOffCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => { await _scope!.设置通道显示开关(ScopeChannelNo, false, Ct()); Log($"{ScopeChannelOptions[SelectedScopeChannelIndex]} 显示已关闭"); }));
|
||||||
|
|
||||||
|
SetChannelScaleCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => { await _scope!.设置通道垂直刻度(ScopeChannelNo, ChannelScale, Ct()); Log($"{ScopeChannelOptions[SelectedScopeChannelIndex]} 垂直刻度={ChannelScale} V/Div"); }));
|
||||||
|
|
||||||
|
SetChannelPositionCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => { await _scope!.设置通道垂直位置(ScopeChannelNo, ChannelPosition, Ct()); Log($"{ScopeChannelOptions[SelectedScopeChannelIndex]} 垂直位置={ChannelPosition} Div"); }));
|
||||||
|
|
||||||
|
// —— 自动测量:开启测量项 + 类型/源一次完成(4/5/6 系必须先 ADDNew 动态创建) ——
|
||||||
|
AddMeasurementCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () =>
|
||||||
|
{
|
||||||
|
if (!Enum.TryParse<TekMeasurementType>(MeasurementType, out var type))
|
||||||
|
{
|
||||||
|
Log($"未知测量类型:{MeasurementType}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _scope!.开启测量项(MeasurementNumber, Ct());
|
||||||
|
await _scope.设置测量类型(MeasurementNumber, type, Ct());
|
||||||
|
await _scope.设置测量源(MeasurementNumber, MeasurementSource, Ct());
|
||||||
|
Log($"测量项 MEAS{MeasurementNumber} 已开启:{type} @ {MeasurementSource}");
|
||||||
|
}));
|
||||||
|
|
||||||
|
QueryMeasCurrentCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () =>
|
||||||
|
{
|
||||||
|
double v = await _scope!.查询测量项当前值(MeasurementNumber, Ct());
|
||||||
|
MeasResultText = $"MEAS{MeasurementNumber} 当前值: {v}";
|
||||||
|
Log(MeasResultText);
|
||||||
|
}));
|
||||||
|
|
||||||
|
QueryMeasMeanCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () =>
|
||||||
|
{
|
||||||
|
double v = await _scope!.查询测量项全局平均值(MeasurementNumber, Ct());
|
||||||
|
MeasResultText = $"MEAS{MeasurementNumber} 全局均值: {v}";
|
||||||
|
Log(MeasResultText);
|
||||||
|
}));
|
||||||
|
|
||||||
|
QueryMeasMaxCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () =>
|
||||||
|
{
|
||||||
|
double v = await _scope!.查询测量项全局最大值(MeasurementNumber, Ct());
|
||||||
|
MeasResultText = $"MEAS{MeasurementNumber} 全局最大: {v}";
|
||||||
|
Log(MeasResultText);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// —— 设备辅助 ——
|
||||||
|
QueryScopeIdnCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => Log("示波器 IDN: " + await _scope!.查询设备标识(Ct()))));
|
||||||
|
|
||||||
|
ClearScopeErrorsCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => { await _scope!.清除错误队列Async(Ct()); Log("示波器错误队列已清除 (*CLS)"); }));
|
||||||
|
|
||||||
|
ReadScopeErrorsCommand = new DelegateCommand(async () =>
|
||||||
|
await ExecScope(async () => Log("示波器错误队列: " + await _scope!.读取所有错误消息Async(Ct()))));
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
Initialize();
|
Initialize();
|
||||||
@@ -555,6 +814,30 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
Log(found != null
|
Log(found != null
|
||||||
? $"已关联设备 [{DeviceName}],连接:{(IsConnected ? "已连接" : "未连接")}"
|
? $"已关联设备 [{DeviceName}],连接:{(IsConnected ? "已连接" : "未连接")}"
|
||||||
: "未找到 Chroma61800 设备,请先在设置界面添加并启用该设备");
|
: "未找到 Chroma61800 设备,请先在设置界面添加并启用该设备");
|
||||||
|
|
||||||
|
// 示波器实例:查找方式与交流源一致(DeviceMap 优先,回退全局 HardwarePool)
|
||||||
|
TektronixMSO? scopeFound = null;
|
||||||
|
string? sfn = null;
|
||||||
|
|
||||||
|
foreach (var kv in _dm.DeviceMap)
|
||||||
|
{
|
||||||
|
if (kv.Value is TektronixMSO s) { scopeFound = s; sfn = kv.Key; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scopeFound == null)
|
||||||
|
{
|
||||||
|
foreach (var kv in _globalInfo.HardwarePool)
|
||||||
|
{
|
||||||
|
if (kv.Value.IsValueCreated && kv.Value.Value is TektronixMSO s) { scopeFound = s; sfn = kv.Key; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_scope = scopeFound;
|
||||||
|
ScopeName = sfn ?? "TektronixMSO (未找到)";
|
||||||
|
IsScopeConnected = _scope?.IsConnected ?? false;
|
||||||
|
|
||||||
|
if (scopeFound != null)
|
||||||
|
Log($"已关联示波器 [{ScopeName}],连接:{(IsScopeConnected ? "已连接" : "未连接")}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||||
@@ -594,9 +877,14 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>从磁盘重新加载测试项列表,刷新下拉框并保持选中项有效。</summary>
|
/// <summary>从磁盘重新加载测试项列表,刷新下拉框并按名称恢复选中项。</summary>
|
||||||
private void RefreshPresets()
|
private void RefreshPresets()
|
||||||
{
|
{
|
||||||
|
// 刷新前记住选中项名称:列表重建后索引会漂移,按名称恢复最可靠
|
||||||
|
string? selectedName = (_selectedPresetIndex >= 0 && _selectedPresetIndex < _presets.Count)
|
||||||
|
? _presets[_selectedPresetIndex].Name
|
||||||
|
: null;
|
||||||
|
|
||||||
var list = new List<ListPreset>();
|
var list = new List<ListPreset>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -608,7 +896,7 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
{
|
{
|
||||||
var json = File.ReadAllText(file);
|
var json = File.ReadAllText(file);
|
||||||
var item = JsonConvert.DeserializeObject<ListPreset>(json);
|
var item = JsonConvert.DeserializeObject<ListPreset>(json);
|
||||||
if (item != null) list.Add(item);
|
if (item != null) list.Add(NormalizePresetName(item, file));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -627,51 +915,72 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
RaisePropertyChanged(nameof(PresetOptions));
|
RaisePropertyChanged(nameof(PresetOptions));
|
||||||
RaisePropertyChanged(nameof(PresetStorageHint));
|
RaisePropertyChanged(nameof(PresetStorageHint));
|
||||||
|
|
||||||
if (_selectedPresetIndex >= _presets.Count)
|
SelectedPresetIndex = selectedName != null
|
||||||
SelectedPresetIndex = -1;
|
? _presets.FindIndex(p => p.Name == selectedName)
|
||||||
|
: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>把一个测试项写入 D:\acp\源测试\{清洗后名称}.json(缩进格式化,便于人工查看)。</summary>
|
/// <summary>
|
||||||
private void WritePresetFile(ListPreset preset)
|
/// 以磁盘文件名校正测试项名称:文件被外部改名后内容中的 Name 仍是旧名,
|
||||||
|
/// 统一改为文件名(不含扩展名),保证“名称=文件名”不变式,载入/保存/删除都按同一名称寻址。
|
||||||
|
/// </summary>
|
||||||
|
private static ListPreset NormalizePresetName(ListPreset item, string file)
|
||||||
|
{
|
||||||
|
string stem = Path.GetFileNameWithoutExtension(file);
|
||||||
|
return string.Equals(item.Name, stem, StringComparison.Ordinal)
|
||||||
|
? item
|
||||||
|
: new ListPreset(stem, item.Note, item.Count, item.Phases, item.Trigger, item.TimeBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>按名称拼出测试项文件完整路径(写入、另存、删除共用同一规则)。</summary>
|
||||||
|
private static string PresetPathOf(string name) => Path.Combine(PresetStorageDir, SanitizeFileName(name) + PresetFileExt);
|
||||||
|
|
||||||
|
/// <summary>把一个测试项写入 D:\acp\源测试\{清洗后名称}.json(缩进格式化,便于人工查看)。</summary>
|
||||||
|
private static void WritePresetFile(ListPreset preset) => WritePresetFile(preset, PresetPathOf(preset.Name));
|
||||||
|
|
||||||
|
/// <summary>把测试项写入指定文件(覆盖写入)。</summary>
|
||||||
|
private static void WritePresetFile(ListPreset preset, string path)
|
||||||
{
|
{
|
||||||
string path = Path.Combine(PresetStorageDir, SanitizeFileName(preset.Name) + PresetFileExt);
|
|
||||||
string json = JsonConvert.SerializeObject(preset, Formatting.Indented);
|
string json = JsonConvert.SerializeObject(preset, Formatting.Indented);
|
||||||
File.WriteAllText(path, json);
|
File.WriteAllText(path, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>用当前界面参数 + 三相配置快照构建测试项对象(内容名称始终与文件名一致)。</summary>
|
||||||
|
private ListPreset SnapshotPreset(string name) => new(
|
||||||
|
name,
|
||||||
|
PresetNote ?? "",
|
||||||
|
ListCount,
|
||||||
|
new[] { _phaseConfigs[0].Clone(), _phaseConfigs[1].Clone(), _phaseConfigs[2].Clone() },
|
||||||
|
ListTrigger,
|
||||||
|
ListTimeBase);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保存当前界面的 LIST 配置为测试项文件(名称取自“测试项名称”输入框)。
|
/// 保存:覆盖写入“当前打开的文件”,不改变文件名(名称框内容不参与)。
|
||||||
/// <para>快照当前三相序列配置 + 执行次数/触发/时基 + 说明,写入后刷新列表并选中该项。</para>
|
/// <para>尚未打开任何文件时,退化为“另存为”,用名称框内容新建文件。</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SaveCurrentPreset()
|
private void SaveCurrentPreset()
|
||||||
{
|
{
|
||||||
string name = (PresetName ?? "").Trim();
|
if (string.IsNullOrEmpty(_currentPresetPath))
|
||||||
if (string.IsNullOrEmpty(name))
|
|
||||||
{
|
{
|
||||||
Log("请先在“测试项名称”填写要保存的名称");
|
SaveCurrentPresetAs();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 内容名称以当前文件名为准,保证“名称=文件名”不变式
|
||||||
|
string name = Path.GetFileNameWithoutExtension(_currentPresetPath);
|
||||||
|
string typed = (PresetName ?? "").Trim();
|
||||||
|
if (!string.IsNullOrEmpty(typed) && !string.Equals(typed, name, StringComparison.Ordinal))
|
||||||
|
Log($"名称框内容 [{typed}] 未用于保存:保存只覆盖当前文件,如需换名请用“另存为”");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(PresetStorageDir))
|
Directory.CreateDirectory(PresetStorageDir);
|
||||||
Directory.CreateDirectory(PresetStorageDir);
|
WritePresetFile(SnapshotPreset(name), _currentPresetPath);
|
||||||
|
|
||||||
// 用当前界面参数 + 三相配置快照构建测试项
|
|
||||||
var phases = new[]
|
|
||||||
{
|
|
||||||
_phaseConfigs[0].Clone(),
|
|
||||||
_phaseConfigs[1].Clone(),
|
|
||||||
_phaseConfigs[2].Clone(),
|
|
||||||
};
|
|
||||||
var preset = new ListPreset(name, PresetNote ?? "", ListCount, phases, ListTrigger, ListTimeBase);
|
|
||||||
WritePresetFile(preset);
|
|
||||||
|
|
||||||
RefreshPresets();
|
RefreshPresets();
|
||||||
int idx = _presets.FindIndex(p => p.Name == name);
|
SelectedPresetIndex = _presets.FindIndex(p => p.Name == name);
|
||||||
if (idx >= 0) SelectedPresetIndex = idx;
|
PresetName = name; // 名称框回显当前文件名
|
||||||
|
Log($"测试项已保存(覆盖):{_currentPresetPath}");
|
||||||
Log($"测试项已保存:{Path.Combine(PresetStorageDir, SanitizeFileName(name) + PresetFileExt)}");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -679,6 +988,45 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 另存为:弹出文件对话框选择 .json 保存位置,成功后“当前打开文件”切换到新文件。
|
||||||
|
/// <para>存入预设目录(D:\acp\源测试)内会自动出现在下拉列表;存到目录外则仅本次会话可用“保存”覆盖,
|
||||||
|
/// 对话框自身的“是否替换”提示已覆盖同名覆盖确认。</para>
|
||||||
|
/// </summary>
|
||||||
|
private void SaveCurrentPresetAs()
|
||||||
|
{
|
||||||
|
var dlg = new SaveFileDialog
|
||||||
|
{
|
||||||
|
Title = "测试项另存为",
|
||||||
|
Filter = "JSON 文件 (*.json)|*.json",
|
||||||
|
DefaultExt = ".json",
|
||||||
|
AddExtension = true,
|
||||||
|
InitialDirectory = PresetStorageDir,
|
||||||
|
FileName = SanitizeFileName((PresetName ?? "").Trim()),
|
||||||
|
};
|
||||||
|
if (dlg.ShowDialog() != true) return; // 用户取消
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string path = dlg.FileName;
|
||||||
|
string name = Path.GetFileNameWithoutExtension(path); // 内容名称始终与文件名一致
|
||||||
|
WritePresetFile(SnapshotPreset(name), path);
|
||||||
|
|
||||||
|
_currentPresetPath = path; // 另存为成功后,后续“保存”直接修改这个新文件
|
||||||
|
RefreshPresets();
|
||||||
|
SelectedPresetIndex = _presets.FindIndex(p => p.Name == name);
|
||||||
|
PresetName = name;
|
||||||
|
|
||||||
|
Log(Path.GetDirectoryName(path) is string dir && string.Equals(dir, PresetStorageDir, StringComparison.OrdinalIgnoreCase)
|
||||||
|
? $"测试项已另存为:{path}"
|
||||||
|
: $"测试项已另存为:{path}(在预设目录之外,不会出现在下拉列表,“保存”仍可覆盖此文件)");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"另存测试项失败:{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 删除当前选中的测试项文件(带二次确认),删除后刷新列表。
|
/// 删除当前选中的测试项文件(带二次确认),删除后刷新列表。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -703,6 +1051,9 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
File.Delete(path);
|
File.Delete(path);
|
||||||
|
|
||||||
|
if (string.Equals(path, _currentPresetPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
_currentPresetPath = null; // 当前打开的文件已被删除,解除绑定
|
||||||
|
|
||||||
SelectedPresetIndex = -1;
|
SelectedPresetIndex = -1;
|
||||||
RefreshPresets();
|
RefreshPresets();
|
||||||
Log($"已删除测试项:{path}");
|
Log($"已删除测试项:{path}");
|
||||||
@@ -740,9 +1091,14 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>从磁盘重新加载合成波形预设列表,刷新下拉框并保持选中项有效。</summary>
|
/// <summary>从磁盘重新加载合成波形预设列表,刷新下拉框并按名称恢复选中项。</summary>
|
||||||
private void RefreshSynthPresets()
|
private void RefreshSynthPresets()
|
||||||
{
|
{
|
||||||
|
// 刷新前记住选中项名称:列表重建后索引会漂移,按名称恢复最可靠
|
||||||
|
string? selectedName = (_selectedSynthPresetIndex >= 0 && _selectedSynthPresetIndex < _synthPresets.Count)
|
||||||
|
? _synthPresets[_selectedSynthPresetIndex].Name
|
||||||
|
: null;
|
||||||
|
|
||||||
var list = new List<SynthPreset>();
|
var list = new List<SynthPreset>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -754,7 +1110,7 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
{
|
{
|
||||||
var json = File.ReadAllText(file);
|
var json = File.ReadAllText(file);
|
||||||
var item = JsonConvert.DeserializeObject<SynthPreset>(json);
|
var item = JsonConvert.DeserializeObject<SynthPreset>(json);
|
||||||
if (item != null) list.Add(item);
|
if (item != null) list.Add(NormalizeSynthPresetName(item, file));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -773,18 +1129,39 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
RaisePropertyChanged(nameof(SynthPresetOptions));
|
RaisePropertyChanged(nameof(SynthPresetOptions));
|
||||||
RaisePropertyChanged(nameof(SynthStorageHint));
|
RaisePropertyChanged(nameof(SynthStorageHint));
|
||||||
|
|
||||||
if (_selectedSynthPresetIndex >= _synthPresets.Count)
|
SelectedSynthPresetIndex = selectedName != null
|
||||||
SelectedSynthPresetIndex = -1;
|
? _synthPresets.FindIndex(p => p.Name == selectedName)
|
||||||
|
: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>把一个合成波形预设写入 {SynthStorageDir}\{清洗后名称}.json(缩进格式化)。</summary>
|
/// <summary>以磁盘文件名校正波形名称(同 <see cref="NormalizePresetName"/> 的“名称=文件名”不变式)。</summary>
|
||||||
private void WriteSynthPresetFile(SynthPreset preset)
|
private static SynthPreset NormalizeSynthPresetName(SynthPreset item, string file)
|
||||||
|
{
|
||||||
|
string stem = Path.GetFileNameWithoutExtension(file);
|
||||||
|
return string.Equals(item.Name, stem, StringComparison.Ordinal)
|
||||||
|
? item
|
||||||
|
: new SynthPreset(stem, item.Note, item.Compose, item.Frequency, item.Fundamental,
|
||||||
|
item.Dc, item.StartPhase, item.AmplitudeText, item.PhaseText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>按名称拼出合成波形文件完整路径(写入、另存、删除共用同一规则)。</summary>
|
||||||
|
private static string SynthPathOf(string name) => Path.Combine(SynthStorageDir, SanitizeFileName(name) + PresetFileExt);
|
||||||
|
|
||||||
|
/// <summary>把一个合成波形预设写入 {SynthStorageDir}\{清洗后名称}.json(缩进格式化)。</summary>
|
||||||
|
private static void WriteSynthPresetFile(SynthPreset preset) => WriteSynthPresetFile(preset, SynthPathOf(preset.Name));
|
||||||
|
|
||||||
|
/// <summary>把合成波形预设写入指定文件(覆盖写入)。</summary>
|
||||||
|
private static void WriteSynthPresetFile(SynthPreset preset, string path)
|
||||||
{
|
{
|
||||||
string path = Path.Combine(SynthStorageDir, SanitizeFileName(preset.Name) + PresetFileExt);
|
|
||||||
string json = JsonConvert.SerializeObject(preset, Formatting.Indented);
|
string json = JsonConvert.SerializeObject(preset, Formatting.Indented);
|
||||||
File.WriteAllText(path, json);
|
File.WriteAllText(path, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>用当前界面参数快照构建波形预设对象(内容名称始终与文件名一致)。</summary>
|
||||||
|
private SynthPreset SnapshotSynthPreset(string name) => new(
|
||||||
|
name, SynthPresetNote ?? "", SynthCompose, SynthFrequency,
|
||||||
|
SynthFundamental, SynthDc, SynthStartPhase, SynthAmplitudeText, SynthPhaseText);
|
||||||
|
|
||||||
/// <summary>载入选中波形:把文件参数一次性回填到界面各 SYNTHESIS 输入框。</summary>
|
/// <summary>载入选中波形:把文件参数一次性回填到界面各 SYNTHESIS 输入框。</summary>
|
||||||
private void LoadCurrentSynthPreset()
|
private void LoadCurrentSynthPreset()
|
||||||
{
|
{
|
||||||
@@ -803,35 +1180,37 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
SynthPhaseText = p.PhaseText;
|
SynthPhaseText = p.PhaseText;
|
||||||
SynthPresetName = p.Name;
|
SynthPresetName = p.Name;
|
||||||
SynthPresetNote = p.Note;
|
SynthPresetNote = p.Note;
|
||||||
|
_currentSynthPath = SynthPathOf(p.Name); // 记住当前打开的文件,此后“保存”只覆盖它
|
||||||
Log($"已载入波形:{p.Name}(格式={p.Compose} 基频={p.Frequency}Hz 基波={p.Fundamental}V)");
|
Log($"已载入波形:{p.Name}(格式={p.Compose} 基频={p.Frequency}Hz 基波={p.Fundamental}V)");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保存当前界面的 SYNTHESIS 参数快照为波形文件(名称取自“波形名称”输入框)。
|
/// 保存:覆盖写入“当前打开的文件”,不改变文件名(名称框内容不参与)。
|
||||||
|
/// <para>尚未打开任何文件时,退化为“另存为”,用名称框内容新建文件。</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SaveCurrentSynthPreset()
|
private void SaveCurrentSynthPreset()
|
||||||
{
|
{
|
||||||
string name = (SynthPresetName ?? "").Trim();
|
if (string.IsNullOrEmpty(_currentSynthPath))
|
||||||
if (string.IsNullOrEmpty(name))
|
|
||||||
{
|
{
|
||||||
Log("请先在“波形名称”填写要保存的名称");
|
SaveCurrentSynthPresetAs();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 内容名称以当前文件名为准,保证“名称=文件名”不变式
|
||||||
|
string name = Path.GetFileNameWithoutExtension(_currentSynthPath);
|
||||||
|
string typed = (SynthPresetName ?? "").Trim();
|
||||||
|
if (!string.IsNullOrEmpty(typed) && !string.Equals(typed, name, StringComparison.Ordinal))
|
||||||
|
Log($"名称框内容 [{typed}] 未用于保存:保存只覆盖当前文件,如需换名请用“另存为”");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(SynthStorageDir))
|
Directory.CreateDirectory(SynthStorageDir);
|
||||||
Directory.CreateDirectory(SynthStorageDir);
|
WriteSynthPresetFile(SnapshotSynthPreset(name), _currentSynthPath);
|
||||||
|
|
||||||
var preset = new SynthPreset(name, SynthPresetNote ?? "", SynthCompose, SynthFrequency,
|
|
||||||
SynthFundamental, SynthDc, SynthStartPhase, SynthAmplitudeText, SynthPhaseText);
|
|
||||||
WriteSynthPresetFile(preset);
|
|
||||||
|
|
||||||
RefreshSynthPresets();
|
RefreshSynthPresets();
|
||||||
int idx = _synthPresets.FindIndex(p => p.Name == name);
|
SelectedSynthPresetIndex = _synthPresets.FindIndex(p => p.Name == name);
|
||||||
if (idx >= 0) SelectedSynthPresetIndex = idx;
|
SynthPresetName = name; // 名称框回显当前文件名
|
||||||
|
Log($"波形已保存(覆盖):{_currentSynthPath}");
|
||||||
Log($"波形已保存:{Path.Combine(SynthStorageDir, SanitizeFileName(name) + PresetFileExt)}");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -839,6 +1218,45 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 另存为:弹出文件对话框选择 .json 保存位置,成功后“当前打开文件”切换到新文件。
|
||||||
|
/// <para>存入预设目录(D:\acp\源测试\合成波形)内会自动出现在下拉列表;存到目录外则仅本次会话可用“保存”覆盖,
|
||||||
|
/// 对话框自身的“是否替换”提示已覆盖同名覆盖确认。</para>
|
||||||
|
/// </summary>
|
||||||
|
private void SaveCurrentSynthPresetAs()
|
||||||
|
{
|
||||||
|
var dlg = new SaveFileDialog
|
||||||
|
{
|
||||||
|
Title = "波形另存为",
|
||||||
|
Filter = "JSON 文件 (*.json)|*.json",
|
||||||
|
DefaultExt = ".json",
|
||||||
|
AddExtension = true,
|
||||||
|
InitialDirectory = SynthStorageDir,
|
||||||
|
FileName = SanitizeFileName((SynthPresetName ?? "").Trim()),
|
||||||
|
};
|
||||||
|
if (dlg.ShowDialog() != true) return; // 用户取消
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string path = dlg.FileName;
|
||||||
|
string name = Path.GetFileNameWithoutExtension(path); // 内容名称始终与文件名一致
|
||||||
|
WriteSynthPresetFile(SnapshotSynthPreset(name), path);
|
||||||
|
|
||||||
|
_currentSynthPath = path; // 另存为成功后,后续“保存”直接修改这个新文件
|
||||||
|
RefreshSynthPresets();
|
||||||
|
SelectedSynthPresetIndex = _synthPresets.FindIndex(p => p.Name == name);
|
||||||
|
SynthPresetName = name;
|
||||||
|
|
||||||
|
Log(Path.GetDirectoryName(path) is string dir && string.Equals(dir, SynthStorageDir, StringComparison.OrdinalIgnoreCase)
|
||||||
|
? $"波形已另存为:{path}"
|
||||||
|
: $"波形已另存为:{path}(在预设目录之外,不会出现在下拉列表,“保存”仍可覆盖此文件)");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"另存波形失败:{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>删除当前选中的波形文件(带二次确认),删除后刷新列表。</summary>
|
/// <summary>删除当前选中的波形文件(带二次确认),删除后刷新列表。</summary>
|
||||||
private void DeleteCurrentSynthPreset()
|
private void DeleteCurrentSynthPreset()
|
||||||
{
|
{
|
||||||
@@ -861,6 +1279,9 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
File.Delete(path);
|
File.Delete(path);
|
||||||
|
|
||||||
|
if (string.Equals(path, _currentSynthPath, StringComparison.OrdinalIgnoreCase))
|
||||||
|
_currentSynthPath = null; // 当前打开的文件已被删除,解除绑定
|
||||||
|
|
||||||
SelectedSynthPresetIndex = -1;
|
SelectedSynthPresetIndex = -1;
|
||||||
RefreshSynthPresets();
|
RefreshSynthPresets();
|
||||||
Log($"已删除波形:{path}");
|
Log($"已删除波形:{path}");
|
||||||
@@ -873,6 +1294,84 @@ namespace PowerAdaptabilityModule.ViewModels
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 示波器抓图(TektronixMSO)与存储位置持久化
|
||||||
|
/// <summary>
|
||||||
|
/// 示波器命令执行包装:与 Exec 同一防重入/异常日志机制,仅把设备校验对象换成示波器实例。
|
||||||
|
/// </summary>
|
||||||
|
private async Task ExecScope(Func<Task> action)
|
||||||
|
{
|
||||||
|
if (_scope == null) { Log("错误:未找到 TektronixMSO 示波器实例,请先在设置界面添加并启用该设备"); return; }
|
||||||
|
if (IsBusy) return;
|
||||||
|
|
||||||
|
IsBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
IsScopeConnected = _scope.IsConnected;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { Log("示波器命令超时或已取消"); }
|
||||||
|
catch (Exception ex) { Log($"示波器错误:{ex.Message}"); }
|
||||||
|
finally { IsBusy = false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 把抓取到的 PNG 加载为 BitmapImage 并刷新界面显示。
|
||||||
|
/// <para>BitmapCacheOption.OnLoad 读完即释放文件句柄,不占用图片文件;Freeze 后跨线程安全。</para>
|
||||||
|
/// </summary>
|
||||||
|
private void LoadScopeImage(string file)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bmp = new BitmapImage();
|
||||||
|
bmp.BeginInit();
|
||||||
|
bmp.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bmp.UriSource = new Uri(file, UriKind.Absolute);
|
||||||
|
bmp.EndInit();
|
||||||
|
bmp.Freeze();
|
||||||
|
|
||||||
|
ScopeImage = bmp;
|
||||||
|
HasScopeImage = true;
|
||||||
|
ScopeImageHint = $"最近抓取:{Path.GetFileName(file)}";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"加载截图失败:{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>示波器截图目录配置文件路径(%AppData%\ACP\ScopeCaptureDir.json)。</summary>
|
||||||
|
private static string ScopeDirConfigPath => Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ACP", "ScopeCaptureDir.json");
|
||||||
|
|
||||||
|
/// <summary>启动时恢复上次设置的波形图片存储位置(配置缺失/损坏时保持默认目录)。</summary>
|
||||||
|
private void LoadScopeImageDir()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(ScopeDirConfigPath))
|
||||||
|
{
|
||||||
|
var dir = JsonConvert.DeserializeObject<string>(File.ReadAllText(ScopeDirConfigPath));
|
||||||
|
if (!string.IsNullOrWhiteSpace(dir)) ScopeImageDir = dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* 配置缺失或损坏时静默使用默认目录 */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>把当前波形图片存储位置写入配置(抓图或浏览选择后调用)。</summary>
|
||||||
|
private void SaveScopeImageDir()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(ScopeDirConfigPath)!);
|
||||||
|
File.WriteAllText(ScopeDirConfigPath, JsonConvert.SerializeObject(ScopeImageDir));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"保存截图目录设置失败:{ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region 辅助
|
#region 辅助
|
||||||
/// <summary>创建 10 秒超时的取消令牌(命令用)。</summary>
|
/// <summary>创建 10 秒超时的取消令牌(命令用)。</summary>
|
||||||
private CancellationToken Ct() => (_cmdCts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
private CancellationToken Ct() => (_cmdCts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<Grid Margin="8">
|
<Grid Margin="8">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="360" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- ============ 左侧:Chroma 命令面板 ============ -->
|
<!-- ============ 左侧:Chroma 命令面板 ============ -->
|
||||||
@@ -184,11 +185,15 @@
|
|||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="名称" Style="{StaticResource ParamLabel}" />
|
<TextBlock Text="名称" Style="{StaticResource ParamLabel}" />
|
||||||
<TextBox Style="{StaticResource NumInput}" Width="150"
|
<TextBox Style="{StaticResource NumInput}" Width="150"
|
||||||
ToolTip="保存时作为文件名(写入 D:\acp\源测试);选中已有测试项会自动填充,可改名另存"
|
ToolTip="另存为对话框的默认文件名(写入 D:\acp\源测试);载入文件后自动回显当前文件名,保存不改动文件名"
|
||||||
Text="{Binding PresetName, UpdateSourceTrigger=PropertyChanged}" />
|
Text="{Binding PresetName, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
<Button Content="保存" Command="{Binding SavePresetCommand}"
|
<Button Content="保存" Command="{Binding SavePresetCommand}"
|
||||||
Style="{StaticResource CmdBtn}"
|
Style="{StaticResource CmdBtn}"
|
||||||
|
ToolTip="覆盖保存当前打开的测试项文件(不改变文件名);未打开文件时按“另存为”新建"
|
||||||
Background="#388E3C" Foreground="White" />
|
Background="#388E3C" Foreground="White" />
|
||||||
|
<Button Content="另存为" Command="{Binding SavePresetAsCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
ToolTip="弹出文件对话框选择 .json 位置另存;存入 D:\acp\源测试 才会出现在下拉列表" />
|
||||||
<Button Content="删除" Command="{Binding DeletePresetCommand}"
|
<Button Content="删除" Command="{Binding DeletePresetCommand}"
|
||||||
Style="{StaticResource WarnBtn}"
|
Style="{StaticResource WarnBtn}"
|
||||||
ToolTip="删除下拉框当前选中的测试项文件(带二次确认)" />
|
ToolTip="删除下拉框当前选中的测试项文件(带二次确认)" />
|
||||||
@@ -340,11 +345,15 @@
|
|||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="名称" Style="{StaticResource ParamLabel}" />
|
<TextBlock Text="名称" Style="{StaticResource ParamLabel}" />
|
||||||
<TextBox Style="{StaticResource NumInput}" Width="150"
|
<TextBox Style="{StaticResource NumInput}" Width="150"
|
||||||
ToolTip="保存时作为文件名(写入 D:\acp\源测试\合成波形);选中已有波形会自动填充,可改名另存"
|
ToolTip="另存为对话框的默认文件名(写入 D:\acp\源测试\合成波形);载入文件后自动回显当前文件名,保存不改动文件名"
|
||||||
Text="{Binding SynthPresetName, UpdateSourceTrigger=PropertyChanged}" />
|
Text="{Binding SynthPresetName, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
<Button Content="保存" Command="{Binding SaveSynthPresetCommand}"
|
<Button Content="保存" Command="{Binding SaveSynthPresetCommand}"
|
||||||
Style="{StaticResource CmdBtn}"
|
Style="{StaticResource CmdBtn}"
|
||||||
|
ToolTip="覆盖保存当前打开的波形文件(不改变文件名);未打开文件时按“另存为”新建"
|
||||||
Background="#388E3C" Foreground="White" />
|
Background="#388E3C" Foreground="White" />
|
||||||
|
<Button Content="另存为" Command="{Binding SaveSynthPresetAsCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
ToolTip="弹出文件对话框选择 .json 位置另存;存入 D:\acp\源测试\合成波形 才会出现在下拉列表" />
|
||||||
<Button Content="删除" Command="{Binding DeleteSynthPresetCommand}"
|
<Button Content="删除" Command="{Binding DeleteSynthPresetCommand}"
|
||||||
Style="{StaticResource WarnBtn}"
|
Style="{StaticResource WarnBtn}"
|
||||||
ToolTip="删除下拉框当前选中的波形文件(带二次确认)" />
|
ToolTip="删除下拉框当前选中的波形文件(带二次确认)" />
|
||||||
@@ -426,6 +435,224 @@
|
|||||||
</GroupBox>
|
</GroupBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- ============ 右侧:示波器抓波面板 ============ -->
|
||||||
|
<ScrollViewer Grid.Column="1"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
HorizontalScrollBarVisibility="Disabled">
|
||||||
|
<StackPanel>
|
||||||
|
|
||||||
|
<GroupBox Header="示波器屏幕抓图(TektronixMSO)"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4">
|
||||||
|
<!-- 示波器连接状态 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,2,0,4">
|
||||||
|
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="#F44336" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsScopeConnected}" Value="True">
|
||||||
|
<Setter Property="Background" Value="#4CAF50" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Text="{Binding ScopeName}"
|
||||||
|
FontSize="12" Foreground="#616161"
|
||||||
|
VerticalAlignment="Center" TextTrimming="CharacterEllipsis" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 波形图片存储位置 -->
|
||||||
|
<TextBlock Text="波形图片存储位置"
|
||||||
|
FontSize="11" Foreground="#616161" Margin="4,0,4,2" />
|
||||||
|
<Grid Margin="0,0,0,2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox Style="{StaticResource NumInput}" Width="Auto"
|
||||||
|
ToolTip="抓取的屏幕图片 PNG 保存目录;也可直接在框内修改,抓图/浏览时自动记住"
|
||||||
|
Text="{Binding ScopeImageDir, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<Button Grid.Column="1" Content="浏览…" Command="{Binding BrowseScopeImageDirCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" Margin="4,0,0,0"
|
||||||
|
ToolTip="弹出文件夹选择对话框选择存储位置" />
|
||||||
|
</Grid>
|
||||||
|
<Button Content="抓取示波器屏幕图片" Command="{Binding CaptureScopeImageCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
Background="#388E3C" Foreground="White"
|
||||||
|
HorizontalAlignment="Left" Margin="0,4"
|
||||||
|
ToolTip="抓取示波器当前屏幕保存为 PNG(SAVe:IMAGe),成功后自动加载显示" />
|
||||||
|
|
||||||
|
<!-- 波形图片显示 -->
|
||||||
|
<Border BorderBrush="#DDDDDD" BorderThickness="1"
|
||||||
|
Background="#FFFFFF" CornerRadius="2" Margin="0,4,0,0">
|
||||||
|
<Grid Height="230">
|
||||||
|
<Image Source="{Binding ScopeImage}" Stretch="Uniform" Margin="4"
|
||||||
|
Visibility="{Binding HasScopeImage, Converter={StaticResource BoolToVis}}" />
|
||||||
|
<TextBlock Text="暂无波形图片
抓取成功后自动显示"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
Foreground="#BDBDBD" FontSize="13" TextAlignment="Center">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding HasScopeImage}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Text="{Binding ScopeImageHint}"
|
||||||
|
FontSize="11" Foreground="#757575"
|
||||||
|
Margin="4,4,4,2" TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 采集控制:运行/停止采集,定格画面后抓图最稳定 -->
|
||||||
|
<GroupBox Header="采集控制" Margin="0,8,0,0"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<Button Content="运行 (RUN)" Command="{Binding ScopeAcquireRunCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
Background="#388E3C" Foreground="White" />
|
||||||
|
<Button Content="停止 (STOP)" Command="{Binding ScopeAcquireStopCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"
|
||||||
|
ToolTip="停止采集定格当前画面,再点「抓取示波器屏幕图片」即可截到稳定波形" />
|
||||||
|
<Button Content="查询状态" Command="{Binding QueryAcquireStateCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="采集模式" Style="{StaticResource ParamLabel}" />
|
||||||
|
<ComboBox Width="120" Height="32" FontSize="12" Margin="4,0"
|
||||||
|
ToolTip="ACQuire:MODe,采样/峰值检测/高分辨率/平均/包络"
|
||||||
|
ItemsSource="{Binding ScopeAcquireModeOptions}"
|
||||||
|
SelectedItem="{Binding ScopeAcquireMode}" />
|
||||||
|
<Button Content="应用" Command="{Binding ApplyAcquireModeCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 水平系统 -->
|
||||||
|
<GroupBox Header="水平系统" Margin="0,8,0,0"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="刻度(s/Div)" Style="{StaticResource ParamLabel}" />
|
||||||
|
<TextBox Style="{StaticResource NumInput}" Width="90"
|
||||||
|
ToolTip="HORizontal:SCAle,每格时间,单位 s,如 0.001"
|
||||||
|
Text="{Binding ScopeTimeScale, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<Button Content="设置" Command="{Binding SetScopeTimeScaleCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="记录长度" Style="{StaticResource ParamLabel}" />
|
||||||
|
<TextBox Style="{StaticResource NumInput}" Width="90"
|
||||||
|
ToolTip="HORizontal:RECOrdlength,采样点数,如 6250000"
|
||||||
|
Text="{Binding ScopeRecordLength, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<Button Content="设置" Command="{Binding SetRecordLengthCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<Button Content="查询采样率" Command="{Binding QuerySampleRateCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}" Width="150"
|
||||||
|
Text="{Binding SampleRateText, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 通道设置 -->
|
||||||
|
<GroupBox Header="通道设置" Margin="0,8,0,0"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="通道" Style="{StaticResource ParamLabel}" />
|
||||||
|
<ComboBox Width="80" Height="32" FontSize="12" Margin="4,0"
|
||||||
|
ItemsSource="{Binding ScopeChannelOptions}"
|
||||||
|
SelectedIndex="{Binding SelectedScopeChannelIndex}" />
|
||||||
|
<Button Content="显示开" Command="{Binding ChannelDisplayOnCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
<Button Content="显示关" Command="{Binding ChannelDisplayOffCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="刻度(V/Div)" Style="{StaticResource ParamLabel}" />
|
||||||
|
<TextBox Style="{StaticResource NumInput}" Width="90"
|
||||||
|
ToolTip="CH:SCAle,每格电压,单位 V,如 100"
|
||||||
|
Text="{Binding ChannelScale, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<Button Content="设置" Command="{Binding SetChannelScaleCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="位置(Div)" Style="{StaticResource ParamLabel}" />
|
||||||
|
<TextBox Style="{StaticResource NumInput}" Width="90"
|
||||||
|
ToolTip="CH:POSition,垂直位置偏移,单位格,如 0"
|
||||||
|
Text="{Binding ChannelPosition, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<Button Content="设置" Command="{Binding SetChannelPositionCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 自动测量 -->
|
||||||
|
<GroupBox Header="自动测量" Margin="0,8,0,0"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="测量项" Style="{StaticResource ParamLabel}" />
|
||||||
|
<TextBox Style="{StaticResource NumInput}" Width="52"
|
||||||
|
ToolTip="MEASUrement:MEASn 的编号 n"
|
||||||
|
Text="{Binding MeasurementNumber, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<ComboBox Width="110" Height="32" FontSize="12" Margin="4,0"
|
||||||
|
ToolTip="MEASUrement:TYPe,幅度/频率/平均/峰峰值/最大/最小"
|
||||||
|
ItemsSource="{Binding ScopeMeasTypeOptions}"
|
||||||
|
SelectedItem="{Binding MeasurementType}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="测量源" Style="{StaticResource ParamLabel}" />
|
||||||
|
<TextBox Style="{StaticResource NumInput}" Width="90"
|
||||||
|
ToolTip="MEASUrement:SOUrce1,如 CH1 / CH2 / MATH1"
|
||||||
|
Text="{Binding MeasurementSource, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<Button Content="开启测量" Command="{Binding AddMeasurementCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
ToolTip="ADDNew 创建测量项并设置类型与源(4/5/6 系必须先动态创建)" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<Button Content="当前值" Command="{Binding QueryMeasCurrentCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
ToolTip="本次采集的测量平均值" />
|
||||||
|
<Button Content="全局均值" Command="{Binding QueryMeasMeanCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
<Button Content="全局最大" Command="{Binding QueryMeasMaxCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
</StackPanel>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}" Width="300" HorizontalAlignment="Left"
|
||||||
|
Margin="0,2" Text="{Binding MeasResultText, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 设备辅助 -->
|
||||||
|
<GroupBox Header="设备辅助" Margin="0,8,0,0"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="4,4">
|
||||||
|
<Button Content="查询 IDN" Command="{Binding QueryScopeIdnCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}" />
|
||||||
|
<Button Content="清除错误" Command="{Binding ClearScopeErrorsCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
ToolTip="*CLS 清除所有状态寄存器与错误/事件队列" />
|
||||||
|
<Button Content="读取错误" Command="{Binding ReadScopeErrorsCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
ToolTip="ALLEv? 读取并清空示波器错误/事件队列,命令不生效时先看这里" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
Reference in New Issue
Block a user