460 lines
16 KiB
C#
460 lines
16 KiB
C#
|
|
using System.IO;
|
|||
|
|
using System.Collections.ObjectModel;
|
|||
|
|
using System.IO.Ports;
|
|||
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|||
|
|
using CommunityToolkit.Mvvm.Input;
|
|||
|
|
using Microsoft.Extensions.Options;
|
|||
|
|
using Microsoft.Win32;
|
|||
|
|
using SSPCTester.Devices.Config;
|
|||
|
|
using SSPCTester.Devices.Interfaces;
|
|||
|
|
using SSPCTester.UI.Services;
|
|||
|
|
|
|||
|
|
namespace SSPCTester.UI.ViewModels;
|
|||
|
|
|
|||
|
|
/// <summary>系统设置页。</summary>
|
|||
|
|
public partial class SettingsViewModel : ObservableObject
|
|||
|
|
{
|
|||
|
|
private readonly UiLogSink _log;
|
|||
|
|
private readonly ISspc _sspc;
|
|||
|
|
private readonly IPowerSupply _power;
|
|||
|
|
private readonly ILoad _load;
|
|||
|
|
private readonly IDaq _daq;
|
|||
|
|
private readonly DeviceOptions _devOpts;
|
|||
|
|
private readonly StorageOptions _storeOpts;
|
|||
|
|
private readonly ReportOptions _reportOpts;
|
|||
|
|
private readonly UserSettingsStore _settingsStore;
|
|||
|
|
private Action? _closeAction;
|
|||
|
|
|
|||
|
|
public SettingsViewModel(
|
|||
|
|
IOptions<DeviceOptions> devOpts,
|
|||
|
|
IOptions<StorageOptions> storeOpts,
|
|||
|
|
IOptions<ReportOptions> reportOpts,
|
|||
|
|
ISspc sspc, IPowerSupply power, ILoad load, IDaq daq, UiLogSink log,
|
|||
|
|
UserSettingsStore settingsStore)
|
|||
|
|
{
|
|||
|
|
_devOpts = devOpts.Value;
|
|||
|
|
_storeOpts = storeOpts.Value;
|
|||
|
|
_reportOpts = reportOpts.Value;
|
|||
|
|
_sspc = sspc; _power = power; _load = load; _daq = daq; _log = log;
|
|||
|
|
_settingsStore = settingsStore;
|
|||
|
|
NormalizeDaqChannels();
|
|||
|
|
LoadDaqChannelEditors();
|
|||
|
|
RefreshPorts();
|
|||
|
|
_sspc.ConnectionChanged += (_, _) => OnPropertyChanged(nameof(SspcStatus));
|
|||
|
|
_power.ConnectionChanged += (_, _) => OnPropertyChanged(nameof(PowerStatus));
|
|||
|
|
_load.ConnectionChanged += (_, _) => OnPropertyChanged(nameof(LoadStatus));
|
|||
|
|
_daq.ConnectionChanged += (_, _) => OnPropertyChanged(nameof(DaqStatus));
|
|||
|
|
_ = RefreshDaqDevices();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public DeviceOptions Devices => _devOpts;
|
|||
|
|
public StorageOptions Storage => _storeOpts;
|
|||
|
|
public ReportOptions Report => _reportOpts;
|
|||
|
|
public ObservableCollection<string> PortNames { get; } = new();
|
|||
|
|
public ObservableCollection<DaqDeviceDescriptor> DaqDevices { get; } = new();
|
|||
|
|
public ObservableCollection<DaqChannelEditor> DaqChannelEditors { get; } = new();
|
|||
|
|
public IReadOnlyList<int> DaqChannelCounts { get; } = new[] { 1, 2, 4, 8 };
|
|||
|
|
public IReadOnlyList<string> DaqInputRanges { get; } = new[] { "PlusMinus5V", "PlusMinus1V" };
|
|||
|
|
public IReadOnlyList<string> DaqRoles { get; } = Enum.GetNames<DaqChannelRole>();
|
|||
|
|
public IReadOnlyList<LoadQueryMode> LoadQueryModes { get; } = Enum.GetValues<LoadQueryMode>();
|
|||
|
|
public IReadOnlyList<TerminatorOption> TerminatorOptions { get; } = new[]
|
|||
|
|
{
|
|||
|
|
new TerminatorOption("LF (\\n)", "\n"),
|
|||
|
|
new TerminatorOption("CRLF (\\r\\n)", "\r\n"),
|
|||
|
|
new TerminatorOption("CR (\\r)", "\r")
|
|||
|
|
};
|
|||
|
|
public int SelectedDaqLogicalId
|
|||
|
|
{
|
|||
|
|
get => _devOpts.Daq.LogicalDeviceId;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (_devOpts.Daq.LogicalDeviceId == value) return;
|
|||
|
|
_devOpts.Daq.LogicalDeviceId = value;
|
|||
|
|
OnPropertyChanged();
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
_settingsStore.ScheduleSave();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string SspcStatus => _sspc.IsConnected ? "已连接" : "未连接";
|
|||
|
|
public string PowerStatus => _power.IsConnected ? "已连接" : "未连接";
|
|||
|
|
public string LoadStatus => _load.IsConnected ? "已连接" : "未连接";
|
|||
|
|
public string DaqStatus => _daq.IsConnected ? "已连接" : "未连接";
|
|||
|
|
|
|||
|
|
public void SetCloseAction(Action closeAction) => _closeAction = closeAction;
|
|||
|
|
public void ScheduleSave()
|
|||
|
|
{
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
_settingsStore.ScheduleSave();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task SaveSettingsAsync()
|
|||
|
|
{
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
await _settingsStore.SaveAsync();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[RelayCommand]
|
|||
|
|
private async Task Close()
|
|||
|
|
{
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
await _settingsStore.SaveAsync();
|
|||
|
|
_closeAction?.Invoke();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[RelayCommand]
|
|||
|
|
private void RefreshPorts()
|
|||
|
|
{
|
|||
|
|
string? selected = _devOpts.Sspc.PortName;
|
|||
|
|
PortNames.Clear();
|
|||
|
|
foreach (string port in SerialPort.GetPortNames().OrderBy(x => x))
|
|||
|
|
PortNames.Add(port);
|
|||
|
|
if (!string.IsNullOrWhiteSpace(selected) && !PortNames.Contains(selected))
|
|||
|
|
PortNames.Add(selected);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[RelayCommand]
|
|||
|
|
private async Task SaveAndReconnect()
|
|||
|
|
{
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
await _settingsStore.SaveAsync();
|
|||
|
|
await Reconnect("SSPC", _sspc);
|
|||
|
|
await Reconnect("电源", _power);
|
|||
|
|
await Reconnect("负载", _load);
|
|||
|
|
await TestDaq();
|
|||
|
|
RaiseStatuses();
|
|||
|
|
_log.Add(UiLogLevel.Success, "硬件配置已保存并重新连接。");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[RelayCommand]
|
|||
|
|
private async Task TestSspc() { await Reconnect("SSPC", _sspc); OnPropertyChanged(nameof(SspcStatus)); }
|
|||
|
|
[RelayCommand]
|
|||
|
|
private async Task TestPower()
|
|||
|
|
{
|
|||
|
|
await Reconnect("电源", _power);
|
|||
|
|
if (_power.IsConnected)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var id = await _power.QueryIdentityAsync();
|
|||
|
|
_log.Add(UiLogLevel.Success, $"UDP5080 设备标识:{id}");
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
_log.Add(UiLogLevel.Warning, $"UDP5080 已连接,但读取设备标识失败:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
OnPropertyChanged(nameof(PowerStatus));
|
|||
|
|
}
|
|||
|
|
[RelayCommand]
|
|||
|
|
private async Task TestLoad()
|
|||
|
|
{
|
|||
|
|
await Reconnect("负载", _load);
|
|||
|
|
if (_load.IsConnected)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var id = await _load.QueryIdentityAsync();
|
|||
|
|
var reading = await _load.ReadPowerAsync();
|
|||
|
|
_log.Add(UiLogLevel.Success, $"IT8702P 设备标识:{id};V={reading.Volts:F3}V I={reading.Amps:F3}A P={reading.Watts:F3}W");
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
_log.Add(UiLogLevel.Warning, $"IT8702P 已连接,但读取标识或测量值失败:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
OnPropertyChanged(nameof(LoadStatus));
|
|||
|
|
}
|
|||
|
|
[RelayCommand]
|
|||
|
|
private async Task TestDaq()
|
|||
|
|
{
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
try { await _daq.DisconnectAsync(); } catch { }
|
|||
|
|
await RefreshDaqDevices();
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
await _daq.ConnectAsync();
|
|||
|
|
await _settingsStore.SaveAsync();
|
|||
|
|
_log.Add(UiLogLevel.Success, "PCIe8586 连接成功。");
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
_log.Add(UiLogLevel.Error, $"PCIe8586 连接失败:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
OnPropertyChanged(nameof(DaqStatus));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[RelayCommand]
|
|||
|
|
private async Task RefreshDaqDevices()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var devices = await _daq.EnumerateDevicesAsync();
|
|||
|
|
if (devices.Count > 0)
|
|||
|
|
{
|
|||
|
|
int selectedId = _devOpts.Daq.LogicalDeviceId;
|
|||
|
|
DaqDevices.Clear();
|
|||
|
|
foreach (var device in devices) DaqDevices.Add(device);
|
|||
|
|
if (devices.Any(x => x.LogicalId == selectedId))
|
|||
|
|
_devOpts.Daq.LogicalDeviceId = selectedId;
|
|||
|
|
else
|
|||
|
|
SelectedDaqLogicalId = devices[0].LogicalId;
|
|||
|
|
OnPropertyChanged(nameof(SelectedDaqLogicalId));
|
|||
|
|
}
|
|||
|
|
else if (!DaqDevices.Any(x => x.LogicalId == _devOpts.Daq.LogicalDeviceId))
|
|||
|
|
{
|
|||
|
|
DaqDevices.Add(new DaqDeviceDescriptor(
|
|||
|
|
_devOpts.Daq.LogicalDeviceId, null, "当前配置的 PCIe8586", _devOpts.Daq.UseMock));
|
|||
|
|
}
|
|||
|
|
_log.Add(devices.Count > 0 ? UiLogLevel.Success : UiLogLevel.Error,
|
|||
|
|
devices.Count > 0 ? $"发现 {devices.Count} 台 PCIe8586。" : "未发现 PCIe8586。");
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
if (!DaqDevices.Any(x => x.LogicalId == _devOpts.Daq.LogicalDeviceId))
|
|||
|
|
DaqDevices.Add(new DaqDeviceDescriptor(
|
|||
|
|
_devOpts.Daq.LogicalDeviceId, null, "当前配置的 PCIe8586", _devOpts.Daq.UseMock));
|
|||
|
|
_log.Add(UiLogLevel.Error, $"枚举 PCIe8586 失败:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public double DaqActualSampleRateHz => _devOpts.Daq.ActualSampleRateHz;
|
|||
|
|
public string DaqValidationMessage
|
|||
|
|
{
|
|||
|
|
get
|
|||
|
|
{
|
|||
|
|
if (_devOpts.Daq.UseMock)
|
|||
|
|
return "当前为 Mock 模式,可直接验证继电器动作和曲线流程。";
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
if (_devOpts.Daq.ChannelCount is not (1 or 2 or 4 or 8))
|
|||
|
|
return "启用通道数只能为 1、2、4 或 8。";
|
|||
|
|
var enabled = _devOpts.Daq.Channels.Where(x => x.PhysicalChannel < _devOpts.Daq.ChannelCount).ToArray();
|
|||
|
|
if (enabled.Length == 0)
|
|||
|
|
return "没有可用的启用通道。";
|
|||
|
|
var outputVoltage = enabled.Where(x =>
|
|||
|
|
string.Equals(x.Role, nameof(DaqChannelRole.OutputVoltage), StringComparison.OrdinalIgnoreCase)).ToArray();
|
|||
|
|
if (outputVoltage.Length != 1)
|
|||
|
|
return $"必须配置且只能配置一个 OutputVoltage 角色,当前配置了 {outputVoltage.Length} 个。";
|
|||
|
|
var voltageChannel = outputVoltage[0];
|
|||
|
|
if (!voltageChannel.IsCalibrated)
|
|||
|
|
return $"CH{voltageChannel.PhysicalChannel} OutputVoltage 尚未勾选已标定。";
|
|||
|
|
if (!double.IsFinite(voltageChannel.Scale) || voltageChannel.Scale == 0)
|
|||
|
|
return $"CH{voltageChannel.PhysicalChannel} Scale 无效。";
|
|||
|
|
if (!double.IsFinite(voltageChannel.Offset))
|
|||
|
|
return $"CH{voltageChannel.PhysicalChannel} Offset 无效。";
|
|||
|
|
return "采集配置可用于曲线测试。";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
public void NotifyDaqSettingsChanged()
|
|||
|
|
{
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
RefreshDaqEditorEnabledStates();
|
|||
|
|
OnPropertyChanged(nameof(DaqActualSampleRateHz));
|
|||
|
|
OnPropertyChanged(nameof(DaqValidationMessage));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[RelayCommand]
|
|||
|
|
private async Task DisconnectSspc()
|
|||
|
|
{
|
|||
|
|
await _sspc.DisconnectAsync();
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
await _settingsStore.SaveAsync();
|
|||
|
|
OnPropertyChanged(nameof(SspcStatus));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task Reconnect(string name, IDeviceConnection device)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
await device.DisconnectAsync();
|
|||
|
|
bool connected = await device.ConnectAsync();
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
await _settingsStore.SaveAsync();
|
|||
|
|
if (connected && device.IsConnected)
|
|||
|
|
{
|
|||
|
|
_log.Add(UiLogLevel.Success, $"{name} 连接成功。");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
_log.Add(UiLogLevel.Warning, $"{name} 连接未成功,设备保持未连接。");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
_log.Add(UiLogLevel.Error, $"{name} 连接失败:{ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void RaiseStatuses()
|
|||
|
|
{
|
|||
|
|
OnPropertyChanged(nameof(SspcStatus));
|
|||
|
|
OnPropertyChanged(nameof(PowerStatus));
|
|||
|
|
OnPropertyChanged(nameof(LoadStatus));
|
|||
|
|
OnPropertyChanged(nameof(DaqStatus));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[RelayCommand]
|
|||
|
|
private void BrowseProjects()
|
|||
|
|
{
|
|||
|
|
string? selected = SelectFolder("选择项目数据目录", _storeOpts.ProjectsRoot);
|
|||
|
|
if (string.IsNullOrWhiteSpace(selected)) return;
|
|||
|
|
_storeOpts.ProjectsRoot = selected;
|
|||
|
|
OnPropertyChanged(nameof(Storage));
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
_settingsStore.ScheduleSave();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[RelayCommand]
|
|||
|
|
private void BrowseReports()
|
|||
|
|
{
|
|||
|
|
string? selected = SelectFolder("选择报告保存目录", _reportOpts.SavePath);
|
|||
|
|
if (string.IsNullOrWhiteSpace(selected)) return;
|
|||
|
|
_reportOpts.SavePath = selected;
|
|||
|
|
OnPropertyChanged(nameof(Report));
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
_settingsStore.ScheduleSave();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[RelayCommand]
|
|||
|
|
private void OpenProjectsDirectory() => OpenDirectory(_storeOpts.ProjectsRoot);
|
|||
|
|
|
|||
|
|
[RelayCommand]
|
|||
|
|
private void OpenReportsDirectory() => OpenDirectory(_reportOpts.SavePath);
|
|||
|
|
|
|||
|
|
private static string? SelectFolder(string description, string currentPath)
|
|||
|
|
{
|
|||
|
|
var dialog = new OpenFolderDialog
|
|||
|
|
{
|
|||
|
|
Title = description,
|
|||
|
|
InitialDirectory = Directory.Exists(currentPath) ? currentPath : null
|
|||
|
|
};
|
|||
|
|
return dialog.ShowDialog() == true ? dialog.FolderName : null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static void OpenDirectory(string path)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(path)) return;
|
|||
|
|
Directory.CreateDirectory(path);
|
|||
|
|
System.Diagnostics.Process.Start("explorer.exe", path);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void NormalizeDaqChannels()
|
|||
|
|
{
|
|||
|
|
_devOpts.Daq.Channels = DaqChannelOptionsNormalizer.Normalize(_devOpts.Daq.Channels);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void LoadDaqChannelEditors()
|
|||
|
|
{
|
|||
|
|
DaqChannelEditors.Clear();
|
|||
|
|
NormalizeDaqChannels();
|
|||
|
|
foreach (var channel in _devOpts.Daq.Channels)
|
|||
|
|
{
|
|||
|
|
var editor = new DaqChannelEditor
|
|||
|
|
{
|
|||
|
|
PhysicalChannel = channel.PhysicalChannel,
|
|||
|
|
Role = channel.Role,
|
|||
|
|
Name = channel.Name,
|
|||
|
|
Scale = channel.Scale,
|
|||
|
|
Offset = channel.Offset,
|
|||
|
|
IsCalibrated = channel.IsCalibrated,
|
|||
|
|
IsEnabled = channel.PhysicalChannel < _devOpts.Daq.ChannelCount
|
|||
|
|
};
|
|||
|
|
editor.Bind(channel, OnDaqChannelEditorChanged);
|
|||
|
|
DaqChannelEditors.Add(editor);
|
|||
|
|
}
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnDaqChannelEditorChanged()
|
|||
|
|
{
|
|||
|
|
ApplyDaqChannelEditorsToOptions();
|
|||
|
|
_settingsStore.ScheduleSave();
|
|||
|
|
OnPropertyChanged(nameof(DaqValidationMessage));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void RefreshDaqEditorEnabledStates()
|
|||
|
|
{
|
|||
|
|
foreach (var editor in DaqChannelEditors)
|
|||
|
|
editor.IsEnabled = editor.PhysicalChannel < _devOpts.Daq.ChannelCount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ApplyDaqChannelEditorsToOptions()
|
|||
|
|
{
|
|||
|
|
if (DaqChannelEditors.Count == 0)
|
|||
|
|
{
|
|||
|
|
NormalizeDaqChannels();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_devOpts.Daq.Channels = DaqChannelOptionsNormalizer.Normalize(
|
|||
|
|
DaqChannelEditors.Select(x => new DaqChannelOptions
|
|||
|
|
{
|
|||
|
|
PhysicalChannel = x.PhysicalChannel,
|
|||
|
|
Role = x.Role,
|
|||
|
|
Name = x.Name,
|
|||
|
|
Scale = x.Scale,
|
|||
|
|
Offset = x.Offset,
|
|||
|
|
IsCalibrated = x.IsCalibrated
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public sealed record TerminatorOption(string Name, string Value);
|
|||
|
|
|
|||
|
|
public sealed partial class DaqChannelEditor : ObservableObject
|
|||
|
|
{
|
|||
|
|
private DaqChannelOptions? _source;
|
|||
|
|
private Action? _changed;
|
|||
|
|
|
|||
|
|
public int PhysicalChannel { get; init; }
|
|||
|
|
public string ChannelName => $"CH{PhysicalChannel}";
|
|||
|
|
|
|||
|
|
[ObservableProperty] private string _role = nameof(DaqChannelRole.Unconfigured);
|
|||
|
|
[ObservableProperty] private string _name = "";
|
|||
|
|
[ObservableProperty] private double _scale = 1;
|
|||
|
|
[ObservableProperty] private double _offset;
|
|||
|
|
[ObservableProperty] private bool _isCalibrated;
|
|||
|
|
[ObservableProperty] private bool _isEnabled;
|
|||
|
|
|
|||
|
|
public void Bind(DaqChannelOptions source, Action changed)
|
|||
|
|
{
|
|||
|
|
_source = source;
|
|||
|
|
_changed = changed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
partial void OnRoleChanged(string value)
|
|||
|
|
{
|
|||
|
|
if (_source == null) return;
|
|||
|
|
_source.Role = value;
|
|||
|
|
_changed?.Invoke();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
partial void OnNameChanged(string value)
|
|||
|
|
{
|
|||
|
|
if (_source == null) return;
|
|||
|
|
_source.Name = value;
|
|||
|
|
_changed?.Invoke();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
partial void OnScaleChanged(double value)
|
|||
|
|
{
|
|||
|
|
if (_source == null) return;
|
|||
|
|
_source.Scale = value;
|
|||
|
|
_changed?.Invoke();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
partial void OnOffsetChanged(double value)
|
|||
|
|
{
|
|||
|
|
if (_source == null) return;
|
|||
|
|
_source.Offset = value;
|
|||
|
|
_changed?.Invoke();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
partial void OnIsCalibratedChanged(bool value)
|
|||
|
|
{
|
|||
|
|
if (_source == null) return;
|
|||
|
|
_source.IsCalibrated = value;
|
|||
|
|
_changed?.Invoke();
|
|||
|
|
}
|
|||
|
|
}
|