feat: 添加ello和ttl
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"IndentSize": 4,
|
||||
"IndentWithTabs": null,
|
||||
"AttributesTolerance": 5,
|
||||
"KeepFirstAttributeOnSameLine": false,
|
||||
"MaxAttributeCharactersPerLine": 80,
|
||||
"MaxAttributesPerLine": 0,
|
||||
"NewlineExemptionElements": "RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter",
|
||||
"SeparateByGroups": true,
|
||||
"AttributeIndentation": 0,
|
||||
"AttributeIndentationStyle": "Spaces",
|
||||
"RemoveDesignTimeReferences": false,
|
||||
"EnableAttributeReordering": true,
|
||||
"AttributeOrderingRuleGroups": [
|
||||
"x:Class",
|
||||
"xmlns, xmlns:x",
|
||||
"xmlns:*",
|
||||
"x:Key, Key, x:Name, Name, x:Uid, Uid, Title",
|
||||
"Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom",
|
||||
"Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight",
|
||||
"Classes, Theme, Styles",
|
||||
"Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex",
|
||||
"*:*, *",
|
||||
"PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint",
|
||||
"mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText",
|
||||
"Storyboard.*, From, To, Duration"
|
||||
],
|
||||
"FirstLineAttributes": "",
|
||||
"OrderAttributesByName": true,
|
||||
"IgnoreDesignTimeReferencePrefix": false,
|
||||
"PutEndingBracketOnNewLine": false,
|
||||
"RemoveEndingTagOfEmptyElement": true,
|
||||
"SpaceBeforeClosingSlash": false,
|
||||
"RootElementLineBreakRule": "Default",
|
||||
"ReorderVSM": "Last",
|
||||
"ReorderGridChildren": false,
|
||||
"ReorderCanvasChildren": false,
|
||||
"ReorderSetters": "None",
|
||||
"FormatMarkupExtension": false,
|
||||
"NoNewLineMarkupExtensions": "x:Bind, Binding",
|
||||
"ThicknessSeparator": "Comma",
|
||||
"ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin",
|
||||
"FormatOnSave": true,
|
||||
"SaveAndCloseOnFormat": true,
|
||||
"CommentPadding": 2,
|
||||
"SuppressProcessing": false
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<TargetFrameworks>net9.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
<Separator />
|
||||
<MenuItem Header="退出" Click="OnExitClick" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="设置">
|
||||
<MenuItem Header="EasyTTL 设置" Click="OnEasyTtlSettingsClick" />
|
||||
<MenuItem Header="ELLO 设置" Click="OnElloSettingsClick" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="显示设置">
|
||||
<MenuItem Header="显示图例">
|
||||
<MenuItem.Icon>
|
||||
@@ -280,7 +284,11 @@
|
||||
</TabItem>
|
||||
<TabItem Header="激光器">
|
||||
<ScrollViewer Padding="8">
|
||||
<views:LaserSettingsView DataContext="{Binding LaserSettings}" />
|
||||
<StackPanel Spacing="12">
|
||||
<views:EasyTtlView DataContext="{Binding EasyTtl}" />
|
||||
<Separator />
|
||||
<views:ElloFilterView DataContext="{Binding Ello}" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem Header="数据处理">
|
||||
|
||||
@@ -24,6 +24,24 @@ public partial class MainWindow : Window
|
||||
|
||||
private void OnExitClick(object? sender, RoutedEventArgs e) => Close();
|
||||
|
||||
private async void OnEasyTtlSettingsClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new Views.EasyTtlSettingsDialog
|
||||
{
|
||||
DataContext = Vm.EasyTtl
|
||||
};
|
||||
await dialog.ShowDialog(this);
|
||||
}
|
||||
|
||||
private async void OnElloSettingsClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new Views.ElloSettingsDialog
|
||||
{
|
||||
DataContext = Vm.Ello
|
||||
};
|
||||
await dialog.ShowDialog(this);
|
||||
}
|
||||
|
||||
private void OnYAxisUnitClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is MenuItem mi && mi.Header is string unit)
|
||||
@@ -282,4 +300,3 @@ public partial class MainWindow : Window
|
||||
.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EasyTTL.Lib\EasyTTL.Lib.csproj" />
|
||||
<ProjectReference Include="..\Filter.Ello\Filter.Ello.csproj" />
|
||||
<ProjectReference Include="..\Spec.Sea\Spec.Sea.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Serilog;
|
||||
|
||||
namespace RamanApp.Services;
|
||||
|
||||
public sealed class EasyTtlSettingsStore
|
||||
{
|
||||
private readonly string _path;
|
||||
|
||||
public EasyTtlSettingsStore(string? path = null)
|
||||
{
|
||||
_path = path ?? System.IO.Path.Combine(AppContext.BaseDirectory, "settings", "easyttl.json");
|
||||
}
|
||||
|
||||
public string Path => _path;
|
||||
|
||||
public EasyTtlSettingsFile Load()
|
||||
{
|
||||
if (!File.Exists(_path))
|
||||
{
|
||||
Log.Information("EasyTTL settings file not found path={Path}; using defaults", _path);
|
||||
return new EasyTtlSettingsFile();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(_path);
|
||||
var settings = JsonSerializer.Deserialize(json, EasyTtlJsonContext.Default.EasyTtlSettingsFile);
|
||||
Log.Information("EasyTTL settings loaded path={Path} channelCount={Count}",
|
||||
_path, settings?.Channels.Count ?? 0);
|
||||
return settings ?? new EasyTtlSettingsFile();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "EasyTTL settings load failed path={Path}; using defaults", _path);
|
||||
return new EasyTtlSettingsFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(EasyTtlSettingsFile settings)
|
||||
{
|
||||
string tmpPath = _path + ".tmp";
|
||||
try
|
||||
{
|
||||
var dir = System.IO.Path.GetDirectoryName(_path);
|
||||
if (!string.IsNullOrWhiteSpace(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
var json = JsonSerializer.Serialize(settings, EasyTtlJsonContext.Default.EasyTtlSettingsFile);
|
||||
File.WriteAllText(tmpPath, json);
|
||||
File.Move(tmpPath, _path, true);
|
||||
Log.Information("EasyTTL settings saved path={Path} channelCount={Count}",
|
||||
_path, settings.Channels.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TryDelete(tmpPath);
|
||||
Log.Warning(ex, "EasyTTL settings save failed path={Path}", _path);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryDelete(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort cleanup only.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EasyTtlSettingsFile
|
||||
{
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
public List<EasyTtlChannelSettings> Channels { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class EasyTtlChannelSettings
|
||||
{
|
||||
public int Channel { get; set; }
|
||||
|
||||
public string? Alias { get; set; }
|
||||
|
||||
public bool? ShowInLaserPanel { get; set; }
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonSerializable(typeof(EasyTtlSettingsFile))]
|
||||
internal sealed partial class EasyTtlJsonContext : JsonSerializerContext;
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Serilog;
|
||||
|
||||
namespace RamanApp.Services;
|
||||
|
||||
public sealed class ElloSettingsStore
|
||||
{
|
||||
private readonly string _path;
|
||||
|
||||
public ElloSettingsStore(string? path = null)
|
||||
{
|
||||
_path = path ?? System.IO.Path.Combine(AppContext.BaseDirectory, "settings", "ello.json");
|
||||
}
|
||||
|
||||
public ElloSettingsFile Load()
|
||||
{
|
||||
if (!File.Exists(_path))
|
||||
return new ElloSettingsFile();
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(_path);
|
||||
return JsonSerializer.Deserialize(json, ElloJsonContext.Default.ElloSettingsFile)
|
||||
?? new ElloSettingsFile();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "ELLO settings load failed path={Path}; using defaults", _path);
|
||||
return new ElloSettingsFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(ElloSettingsFile settings)
|
||||
{
|
||||
string tmpPath = _path + ".tmp";
|
||||
try
|
||||
{
|
||||
var dir = System.IO.Path.GetDirectoryName(_path);
|
||||
if (!string.IsNullOrWhiteSpace(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
var json = JsonSerializer.Serialize(settings, ElloJsonContext.Default.ElloSettingsFile);
|
||||
File.WriteAllText(tmpPath, json);
|
||||
File.Move(tmpPath, _path, true);
|
||||
Log.Information("ELLO settings saved path={Path} deviceCount={DeviceCount}",
|
||||
_path, settings.Devices.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
TryDelete(tmpPath);
|
||||
Log.Warning(ex, "ELLO settings save failed path={Path}", _path);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryDelete(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best effort cleanup only.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ElloSettingsFile
|
||||
{
|
||||
public int Version { get; set; } = 2;
|
||||
|
||||
public List<ElloDeviceSettings> Devices { get; set; } = [];
|
||||
|
||||
public string? PortName { get; set; }
|
||||
|
||||
public string? Address { get; set; }
|
||||
|
||||
public int? SlotCount { get; set; }
|
||||
|
||||
public List<ElloSlotSettings>? Slots { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ElloDeviceSettings
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string? PortName { get; set; }
|
||||
|
||||
public string Address { get; set; } = "0";
|
||||
|
||||
public int SlotCount { get; set; } = 6;
|
||||
|
||||
public bool ShowInLaserPanel { get; set; } = true;
|
||||
|
||||
public List<ElloSlotSettings> Slots { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class ElloSlotSettings
|
||||
{
|
||||
public int Slot { get; set; }
|
||||
|
||||
public string? Alias { get; set; }
|
||||
|
||||
public bool? ShowInLaserPanel { get; set; }
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
[JsonSerializable(typeof(ElloSettingsFile))]
|
||||
internal sealed partial class ElloJsonContext : JsonSerializerContext;
|
||||
@@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using EasyTTL.Lib;
|
||||
using RamanApp.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace RamanApp.ViewModels;
|
||||
|
||||
public partial class EasyTtlViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
private const int FixedFrequencyHz = 1;
|
||||
private const double FixedDutyPercent = 100;
|
||||
private const string SimulatedPortName = "SIM-EASYTTL";
|
||||
|
||||
private EasyTtlDevice? _device;
|
||||
private readonly bool _simulate;
|
||||
private readonly EasyTtlSettingsStore _settingsStore;
|
||||
private bool _suppressHardwareUpdates;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<string> PortNames { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? SelectedPortName { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsConnected { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool OutputEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<EasyTtlChannelViewModel> Channels { get; set; } =
|
||||
[
|
||||
new(1, "CH1"),
|
||||
new(2, "CH2"),
|
||||
new(3, "CH3"),
|
||||
new(4, "CH4"),
|
||||
new(5, "CH5"),
|
||||
new(6, "CH6"),
|
||||
];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string StatusText { get; set; } = "未连接";
|
||||
|
||||
public EasyTtlViewModel(bool simulate = false, EasyTtlSettingsStore? settingsStore = null)
|
||||
{
|
||||
_simulate = simulate;
|
||||
_settingsStore = settingsStore ?? new EasyTtlSettingsStore();
|
||||
LoadSavedSettings();
|
||||
RefreshPorts();
|
||||
foreach (var channel in Channels)
|
||||
channel.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(EasyTtlChannelViewModel.IsEnabled))
|
||||
{
|
||||
Log.Information("EasyTTL logical channel state changed channel={Channel} enabled={Enabled}",
|
||||
channel.Channel, channel.IsEnabled);
|
||||
}
|
||||
else if (e.PropertyName == nameof(EasyTtlChannelViewModel.HardwareEnabled))
|
||||
{
|
||||
ApplyHardwareChannel(channel);
|
||||
}
|
||||
else if (e.PropertyName == nameof(EasyTtlChannelViewModel.Alias))
|
||||
{
|
||||
SaveSettings();
|
||||
Log.Information("EasyTTL channel alias changed channel={Channel} alias={Alias}",
|
||||
channel.Channel, channel.Alias);
|
||||
}
|
||||
else if (e.PropertyName == nameof(EasyTtlChannelViewModel.ShowInLaserPanel))
|
||||
{
|
||||
SaveSettings();
|
||||
Log.Information("EasyTTL channel laser panel visibility changed channel={Channel} show={Show}",
|
||||
channel.Channel, channel.ShowInLaserPanel);
|
||||
}
|
||||
};
|
||||
|
||||
if (_simulate)
|
||||
StatusText = "模拟模式";
|
||||
Log.Information("EasyTTL initialized simulate={Simulate} selectedPort={SelectedPort}", _simulate, SelectedPortName);
|
||||
}
|
||||
|
||||
private void LoadSavedSettings()
|
||||
{
|
||||
var settings = _settingsStore.Load();
|
||||
foreach (var saved in settings.Channels)
|
||||
{
|
||||
var channel = Channels.FirstOrDefault(c => c.Channel == saved.Channel);
|
||||
if (channel == null)
|
||||
continue;
|
||||
|
||||
channel.Alias = string.IsNullOrWhiteSpace(saved.Alias)
|
||||
? $"CH{channel.Channel}"
|
||||
: saved.Alias.Trim();
|
||||
channel.ShowInLaserPanel = saved.ShowInLaserPanel ?? true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
var settings = new EasyTtlSettingsFile
|
||||
{
|
||||
Channels = Channels
|
||||
.Select(channel => new EasyTtlChannelSettings
|
||||
{
|
||||
Channel = channel.Channel,
|
||||
Alias = channel.Alias,
|
||||
ShowInLaserPanel = channel.ShowInLaserPanel
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
|
||||
_settingsStore.Save(settings);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RefreshPorts()
|
||||
{
|
||||
var previous = SelectedPortName;
|
||||
PortNames.Clear();
|
||||
if (_simulate)
|
||||
{
|
||||
PortNames.Add(SimulatedPortName);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var port in SerialPort.GetPortNames())
|
||||
PortNames.Add(port);
|
||||
}
|
||||
|
||||
SelectedPortName = previous != null && PortNames.Contains(previous)
|
||||
? previous
|
||||
: PortNames.Count > 0 ? PortNames[0] : null;
|
||||
|
||||
Log.Information("EasyTTL ports refreshed simulate={Simulate} count={Count} selected={SelectedPort}",
|
||||
_simulate, PortNames.Count, SelectedPortName ?? "-");
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Connect()
|
||||
{
|
||||
if (IsConnected || string.IsNullOrWhiteSpace(SelectedPortName))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Log.Information("EasyTTL connecting port={PortName} simulate={Simulate}", SelectedPortName, _simulate);
|
||||
if (!_simulate)
|
||||
{
|
||||
_device = new EasyTtlDevice(SelectedPortName);
|
||||
_device.Open();
|
||||
Log.Information("EasyTTL serial opened port={PortName}", SelectedPortName);
|
||||
}
|
||||
|
||||
IsConnected = true;
|
||||
SetHardwareUiOff();
|
||||
|
||||
if (!_simulate)
|
||||
{
|
||||
Log.Information("EasyTTL safe init start: DISABLE then ALL OFF");
|
||||
_device!.Disable();
|
||||
_device.AllOff();
|
||||
for (int channel = 1; channel <= 6; channel++)
|
||||
_device.SetChannel(channel, FixedFrequencyHz, (float)FixedDutyPercent);
|
||||
Log.Information("EasyTTL safe init completed frequency={FrequencyHz} duty={DutyPercent:0.##}",
|
||||
FixedFrequencyHz, FixedDutyPercent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information("EasyTTL simulated safe init completed");
|
||||
}
|
||||
|
||||
StatusText = _simulate ? "模拟已连接" : $"已连接 {SelectedPortName}";
|
||||
Log.Information("EasyTTL connected port={PortName} simulate={Simulate}; output disabled and all channels off",
|
||||
SelectedPortName, _simulate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_device?.Dispose();
|
||||
_device = null;
|
||||
IsConnected = false;
|
||||
StatusText = $"连接失败: {ex.Message}";
|
||||
Log.Warning(ex, "EasyTTL connect failed port={PortName}", SelectedPortName);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_simulate && _device != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Information("EasyTTL disconnect safe shutdown start: DISABLE then ALL OFF");
|
||||
_device.Disable();
|
||||
_device.AllOff();
|
||||
Log.Information("EasyTTL disconnect safe shutdown completed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "EasyTTL disconnect safe shutdown failed");
|
||||
}
|
||||
}
|
||||
|
||||
_device?.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_device = null;
|
||||
IsConnected = false;
|
||||
SetHardwareUiOff();
|
||||
StatusText = "未连接";
|
||||
Log.Information("EasyTTL disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyHardwareChannel(EasyTtlChannelViewModel channel)
|
||||
{
|
||||
if (_suppressHardwareUpdates) return;
|
||||
if (!_simulate && _device == null)
|
||||
{
|
||||
RevertHardwareChannel(channel);
|
||||
Log.Warning("EasyTTL channel change ignored because device is not connected channel={Channel} requestedOn={On}",
|
||||
channel.Channel, channel.HardwareEnabled);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Log.Information("EasyTTL channel command start channel={Channel} requestedOn={On} frequency={FrequencyHz} duty={DutyPercent:0.##} simulate={Simulate}",
|
||||
channel.Channel, channel.HardwareEnabled, FixedFrequencyHz, FixedDutyPercent, _simulate);
|
||||
|
||||
if (!_simulate)
|
||||
{
|
||||
_device!.SetChannel(channel.Channel, FixedFrequencyHz, (float)FixedDutyPercent);
|
||||
if (channel.HardwareEnabled)
|
||||
_device.ChannelOn(channel.Channel);
|
||||
else
|
||||
_device.ChannelOff(channel.Channel);
|
||||
}
|
||||
|
||||
var onChannels = Channels.Where(c => c.HardwareEnabled).Select(c => c.Channel).ToArray();
|
||||
StatusText = onChannels.Length == 0
|
||||
? "全部通道 OFF"
|
||||
: $"ON: CH{string.Join(", CH", onChannels)}";
|
||||
Log.Information("EasyTTL hardware channel changed channel={Channel} on={On} frequency={FrequencyHz} duty={DutyPercent:0.##} simulate={Simulate}",
|
||||
channel.Channel, channel.HardwareEnabled, FixedFrequencyHz, FixedDutyPercent, _simulate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText = $"设置失败: {ex.Message}";
|
||||
RevertHardwareChannel(channel);
|
||||
Log.Warning(ex, "EasyTTL apply channel failed");
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnOutputEnabledChanged(bool value)
|
||||
{
|
||||
if (_suppressHardwareUpdates) return;
|
||||
if (!_simulate && _device == null)
|
||||
{
|
||||
RevertOutputEnabled(value);
|
||||
Log.Warning("EasyTTL output change ignored because device is not connected requestedEnabled={Enabled}", value);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Log.Information("EasyTTL output command start enabled={Enabled} simulate={Simulate}", value, _simulate);
|
||||
if (!_simulate)
|
||||
{
|
||||
if (value)
|
||||
_device!.Enable();
|
||||
else
|
||||
_device!.Disable();
|
||||
}
|
||||
|
||||
StatusText = _simulate
|
||||
? value ? "模拟输出已使能" : "模拟输出已禁用"
|
||||
: value ? "输出已使能" : "输出已禁用";
|
||||
Log.Information("EasyTTL output enabled={Enabled} simulate={Simulate}", value, _simulate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText = $"输出设置失败: {ex.Message}";
|
||||
RevertOutputEnabled(value);
|
||||
Log.Warning(ex, "EasyTTL output toggle failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void SetHardwareUiOff()
|
||||
{
|
||||
_suppressHardwareUpdates = true;
|
||||
try
|
||||
{
|
||||
OutputEnabled = false;
|
||||
foreach (var channel in Channels)
|
||||
channel.HardwareEnabled = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressHardwareUpdates = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void RevertHardwareChannel(EasyTtlChannelViewModel channel)
|
||||
{
|
||||
_suppressHardwareUpdates = true;
|
||||
try
|
||||
{
|
||||
channel.HardwareEnabled = !channel.HardwareEnabled;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressHardwareUpdates = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void RevertOutputEnabled(bool attemptedValue)
|
||||
{
|
||||
_suppressHardwareUpdates = true;
|
||||
try
|
||||
{
|
||||
OutputEnabled = !attemptedValue;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressHardwareUpdates = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disconnect();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class EasyTtlChannelViewModel(int channel, string alias) : ObservableObject
|
||||
{
|
||||
public int Channel { get; } = channel;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Alias { get; set; } = alias;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowInLaserPanel { get; set; } = true;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool HardwareEnabled { get; set; }
|
||||
|
||||
public string DisplayName => string.IsNullOrWhiteSpace(Alias)
|
||||
? $"CH{Channel}"
|
||||
: Alias;
|
||||
|
||||
partial void OnAliasChanged(string value)
|
||||
{
|
||||
OnPropertyChanged(nameof(DisplayName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,659 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Filter.Ello;
|
||||
using Raman.Core.Filters;
|
||||
using RamanApp.Services;
|
||||
using Serilog;
|
||||
|
||||
namespace RamanApp.ViewModels;
|
||||
|
||||
public partial class ElloFilterViewModel : ObservableObject, IAsyncDisposable
|
||||
{
|
||||
internal const string SimulatedPortName = "SIM-ELLO";
|
||||
|
||||
private readonly bool _simulate;
|
||||
private readonly ElloSettingsStore _settingsStore;
|
||||
private bool _suppressSave;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<ElloDeviceViewModel> Devices { get; set; } = [];
|
||||
|
||||
public ElloFilterViewModel(bool simulate = false, ElloSettingsStore? settingsStore = null)
|
||||
{
|
||||
_simulate = simulate;
|
||||
_settingsStore = settingsStore ?? new ElloSettingsStore();
|
||||
|
||||
bool hasSavedConnections = LoadSavedSettings();
|
||||
if (Devices.Count == 0)
|
||||
{
|
||||
AddDevice(new ElloDeviceViewModel(_simulate, SaveSettings, CreateDefaultDevice(1)));
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
Log.Information("ELLO initialized simulate={Simulate} deviceCount={DeviceCount}",
|
||||
_simulate, Devices.Count);
|
||||
|
||||
if (hasSavedConnections)
|
||||
StartAutoConnect();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AddDevice()
|
||||
{
|
||||
AddDevice(new ElloDeviceViewModel(_simulate, SaveSettings, CreateDefaultDevice(Devices.Count + 1)));
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task RemoveDeviceAsync(ElloDeviceViewModel? device, CancellationToken cancellationToken)
|
||||
{
|
||||
if (device == null)
|
||||
return;
|
||||
|
||||
await device.DisconnectAsync(cancellationToken);
|
||||
await device.DisposeAsync();
|
||||
Devices.Remove(device);
|
||||
SaveSettings();
|
||||
Log.Information("ELLO device removed name={Name}", device.Name);
|
||||
}
|
||||
|
||||
private bool LoadSavedSettings()
|
||||
{
|
||||
var settings = _settingsStore.Load();
|
||||
var devices = settings.Devices.Count > 0
|
||||
? settings.Devices
|
||||
: LoadLegacySingleDevice(settings);
|
||||
bool hasSavedConnections = devices.Any(device => !string.IsNullOrWhiteSpace(device.PortName));
|
||||
|
||||
_suppressSave = true;
|
||||
try
|
||||
{
|
||||
foreach (var device in devices)
|
||||
AddDevice(new ElloDeviceViewModel(_simulate, SaveSettings, device));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressSave = false;
|
||||
}
|
||||
|
||||
return hasSavedConnections;
|
||||
}
|
||||
|
||||
private static System.Collections.Generic.List<ElloDeviceSettings> LoadLegacySingleDevice(ElloSettingsFile settings)
|
||||
{
|
||||
if (settings.PortName == null && settings.Address == null && settings.SlotCount == null && settings.Slots == null)
|
||||
return [];
|
||||
|
||||
return
|
||||
[
|
||||
new ElloDeviceSettings
|
||||
{
|
||||
Name = "ELLO1",
|
||||
PortName = settings.PortName,
|
||||
Address = string.IsNullOrWhiteSpace(settings.Address) ? "0" : settings.Address,
|
||||
SlotCount = settings.SlotCount is 4 or 6 ? settings.SlotCount.Value : 6,
|
||||
ShowInLaserPanel = true,
|
||||
Slots = settings.Slots ?? []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private ElloDeviceSettings CreateDefaultDevice(int index)
|
||||
{
|
||||
var ports = GetAvailablePorts(_simulate);
|
||||
return new ElloDeviceSettings
|
||||
{
|
||||
Name = $"ELLO{index}",
|
||||
PortName = ports.FirstOrDefault(),
|
||||
Address = Math.Clamp(index - 1, 0, 15).ToString("X"),
|
||||
SlotCount = 6,
|
||||
ShowInLaserPanel = true,
|
||||
Slots = Enumerable.Range(1, 6)
|
||||
.Select(slot => new ElloSlotSettings
|
||||
{
|
||||
Slot = slot,
|
||||
Alias = $"轮{slot}",
|
||||
ShowInLaserPanel = true
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private void AddDevice(ElloDeviceViewModel device)
|
||||
{
|
||||
Devices.Add(device);
|
||||
}
|
||||
|
||||
private void StartAutoConnect()
|
||||
{
|
||||
foreach (var device in Devices.Where(device => !string.IsNullOrWhiteSpace(device.SelectedPortName)))
|
||||
_ = AutoConnectAsync(device);
|
||||
}
|
||||
|
||||
private static async Task AutoConnectAsync(ElloDeviceViewModel device)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Information("ELLO auto connect queued name={Name} port={PortName} address={Address}",
|
||||
device.Name, device.SelectedPortName, device.Address);
|
||||
await device.ConnectAsync(CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "ELLO auto connect failed name={Name} port={PortName} address={Address}",
|
||||
device.Name, device.SelectedPortName, device.Address);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string[] GetAvailablePorts(bool simulate)
|
||||
{
|
||||
if (simulate)
|
||||
return [SimulatedPortName];
|
||||
|
||||
return SerialPort.GetPortNames();
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
if (_suppressSave)
|
||||
return;
|
||||
|
||||
_settingsStore.Save(new ElloSettingsFile
|
||||
{
|
||||
Version = 2,
|
||||
Devices = Devices.Select(device => device.ToSettings()).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
foreach (var device in Devices.ToArray())
|
||||
await device.DisposeAsync();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ElloDeviceViewModel : ObservableObject, IAsyncDisposable
|
||||
{
|
||||
private readonly bool _simulate;
|
||||
private readonly Action _save;
|
||||
private bool _suppressSave;
|
||||
private IFilterWheelDevice? _device;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<string> PortNames { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Name { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? SelectedPortName { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Address { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<int> SlotCountOptions { get; set; } = [4, 6];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int SlotCount { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowInLaserPanel { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<ElloSlotViewModel> Slots { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<ElloSlotViewModel> PanelSlots { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsConnected { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsMoving { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int? CurrentSlot { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string CurrentSlotText { get; set; } = "未连接";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowCurrentSlotText { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string CurrentSlotCountText { get; set; } = "-";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string StatusText { get; set; } = "未连接";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string ConnectionHintText { get; set; } = "未连接";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowConnectionHint { get; set; } = true;
|
||||
|
||||
public ElloDeviceViewModel(bool simulate, Action save, ElloDeviceSettings settings)
|
||||
{
|
||||
_simulate = simulate;
|
||||
_save = save;
|
||||
|
||||
_suppressSave = true;
|
||||
try
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(settings.Name) ? "ELLO" : settings.Name.Trim();
|
||||
SelectedPortName = settings.PortName;
|
||||
Address = string.IsNullOrWhiteSpace(settings.Address) ? "0" : settings.Address.Trim().ToUpperInvariant();
|
||||
SlotCount = settings.SlotCount is 4 or 6 ? settings.SlotCount : 6;
|
||||
ShowInLaserPanel = settings.ShowInLaserPanel;
|
||||
|
||||
RebuildSlots(settings.Slots);
|
||||
RefreshPorts();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressSave = false;
|
||||
}
|
||||
|
||||
if (_simulate)
|
||||
StatusText = "模拟模式";
|
||||
|
||||
UpdateConnectionHint();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RefreshPorts()
|
||||
{
|
||||
var previous = SelectedPortName;
|
||||
PortNames.Clear();
|
||||
foreach (var port in ElloFilterViewModel.GetAvailablePorts(_simulate))
|
||||
PortNames.Add(port);
|
||||
|
||||
SelectedPortName = previous != null && PortNames.Contains(previous)
|
||||
? previous
|
||||
: PortNames.Count > 0 ? PortNames[0] : null;
|
||||
|
||||
Log.Information("ELLO ports refreshed name={Name} simulate={Simulate} count={Count} selected={SelectedPort}",
|
||||
Name, _simulate, PortNames.Count, SelectedPortName ?? "-");
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (IsConnected)
|
||||
return;
|
||||
if (string.IsNullOrWhiteSpace(SelectedPortName))
|
||||
{
|
||||
StatusText = "未选择串口";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var normalizedAddress = NormalizeAddress(Address);
|
||||
Log.Information("ELLO connecting name={Name} port={PortName} address={Address} slotCount={SlotCount} simulate={Simulate}",
|
||||
Name, SelectedPortName, normalizedAddress, SlotCount, _simulate);
|
||||
|
||||
if (!_simulate)
|
||||
{
|
||||
_device = new SerialFilterWheelDevice(
|
||||
new SerialFilterOptions { PortName = SelectedPortName },
|
||||
CreateProfile(normalizedAddress));
|
||||
|
||||
await _device.ConnectAsync(cancellationToken);
|
||||
await UpdateStatusFromDeviceAsync(cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentSlot = CurrentSlot ?? 1;
|
||||
CurrentSlotText = GetCurrentSlotText();
|
||||
}
|
||||
|
||||
Address = normalizedAddress;
|
||||
IsConnected = true;
|
||||
StatusText = _simulate ? "模拟已连接" : $"已连接 {SelectedPortName}:{Address}";
|
||||
UpdateConnectionHint();
|
||||
Log.Information("ELLO connected name={Name} port={PortName} address={Address} simulate={Simulate}",
|
||||
Name, SelectedPortName, Address, _simulate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await DisposeDeviceAsync();
|
||||
IsConnected = false;
|
||||
CurrentSlot = null;
|
||||
CurrentSlotText = "未连接";
|
||||
StatusText = $"连接失败: {ex.Message}";
|
||||
UpdateConnectionHint();
|
||||
Log.Warning(ex, "ELLO connect failed name={Name} port={PortName} address={Address}",
|
||||
Name, SelectedPortName, Address);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DisconnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_device != null)
|
||||
await _device.DisconnectAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "ELLO disconnect command failed name={Name}", Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DisposeDeviceAsync();
|
||||
IsConnected = false;
|
||||
CurrentSlot = null;
|
||||
CurrentSlotText = "未连接";
|
||||
StatusText = "未连接";
|
||||
UpdateConnectionHint();
|
||||
Log.Information("ELLO disconnected name={Name}", Name);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task MoveToSlotAsync(int slot, CancellationToken cancellationToken)
|
||||
{
|
||||
if (slot < 1 || slot > SlotCount)
|
||||
return;
|
||||
if (IsMoving)
|
||||
return;
|
||||
if (!IsConnected)
|
||||
{
|
||||
StatusText = "滤光轮未连接";
|
||||
UpdateConnectionHint();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IsMoving = true;
|
||||
Log.Information("ELLO move start name={Name} slot={Slot} alias={Alias} simulate={Simulate}",
|
||||
Name, slot, Slots.FirstOrDefault(s => s.Slot == slot)?.DisplayName ?? "-", _simulate);
|
||||
StatusText = $"切换到轮{slot}";
|
||||
|
||||
if (_simulate)
|
||||
{
|
||||
await Task.Delay(250, cancellationToken);
|
||||
CurrentSlot = slot;
|
||||
CurrentSlotText = GetCurrentSlotText();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_device == null)
|
||||
throw new InvalidOperationException("ELLO device is not connected.");
|
||||
|
||||
await _device.MoveToAsync(_device.Profile.CreatePosition(slot), cancellationToken);
|
||||
await UpdateStatusFromDeviceAsync(cancellationToken);
|
||||
}
|
||||
|
||||
StatusText = $"当前: {CurrentSlotText}";
|
||||
Log.Information("ELLO move completed name={Name} slot={Slot} current={CurrentSlotText} simulate={Simulate}",
|
||||
Name, slot, CurrentSlotText, _simulate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusText = $"切换失败: {ex.Message}";
|
||||
UpdateConnectionHint();
|
||||
Log.Warning(ex, "ELLO move failed name={Name} slot={Slot}", Name, slot);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsMoving = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal ElloDeviceSettings ToSettings()
|
||||
{
|
||||
return new ElloDeviceSettings
|
||||
{
|
||||
Name = Name,
|
||||
PortName = SelectedPortName,
|
||||
Address = string.IsNullOrWhiteSpace(Address) ? "0" : Address.Trim().ToUpperInvariant(),
|
||||
SlotCount = SlotCount,
|
||||
ShowInLaserPanel = ShowInLaserPanel,
|
||||
Slots = Slots.Select(slot => new ElloSlotSettings
|
||||
{
|
||||
Slot = slot.Slot,
|
||||
Alias = slot.Alias,
|
||||
ShowInLaserPanel = slot.ShowInLaserPanel
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private void RebuildSlots(System.Collections.Generic.IReadOnlyList<ElloSlotSettings>? savedSlots = null)
|
||||
{
|
||||
var previous = Slots.ToDictionary(slot => slot.Slot, slot => slot);
|
||||
Slots.Clear();
|
||||
|
||||
for (int slot = 1; slot <= SlotCount; slot++)
|
||||
{
|
||||
var saved = savedSlots?.FirstOrDefault(s => s.Slot == slot);
|
||||
var alias = saved?.Alias
|
||||
?? (previous.TryGetValue(slot, out var old) ? old.Alias : null)
|
||||
?? $"轮{slot}";
|
||||
var show = saved?.ShowInLaserPanel
|
||||
?? (previous.TryGetValue(slot, out var oldShow) ? oldShow.ShowInLaserPanel : true);
|
||||
|
||||
var vm = new ElloSlotViewModel(this, slot, alias)
|
||||
{
|
||||
ShowInLaserPanel = show
|
||||
};
|
||||
vm.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(ElloSlotViewModel.Alias)
|
||||
|| e.PropertyName == nameof(ElloSlotViewModel.ShowInLaserPanel))
|
||||
{
|
||||
CurrentSlotText = GetCurrentSlotText();
|
||||
if (e.PropertyName == nameof(ElloSlotViewModel.ShowInLaserPanel))
|
||||
RefreshPanelSlots();
|
||||
Save();
|
||||
Log.Information("ELLO slot setting changed name={Name} slot={Slot} alias={Alias} show={Show}",
|
||||
Name, vm.Slot, vm.Alias, vm.ShowInLaserPanel);
|
||||
}
|
||||
};
|
||||
Slots.Add(vm);
|
||||
}
|
||||
|
||||
RefreshPanelSlots();
|
||||
CurrentSlotText = GetCurrentSlotText();
|
||||
CurrentSlotCountText = CurrentSlot is int current ? $"{current}/{SlotCount}" : $"-/{SlotCount}";
|
||||
ShowCurrentSlotText = IsConnected && CurrentSlot is not null;
|
||||
UpdateSlotSelection();
|
||||
}
|
||||
|
||||
private void RefreshPanelSlots()
|
||||
{
|
||||
PanelSlots.Clear();
|
||||
foreach (var slot in Slots.Where(slot => slot.ShowInLaserPanel))
|
||||
PanelSlots.Add(slot);
|
||||
}
|
||||
|
||||
partial void OnNameChanged(string value) => Save();
|
||||
|
||||
partial void OnSelectedPortNameChanged(string? value) => Save();
|
||||
|
||||
partial void OnAddressChanged(string value) => Save();
|
||||
|
||||
partial void OnShowInLaserPanelChanged(bool value) => Save();
|
||||
|
||||
partial void OnIsConnectedChanged(bool value)
|
||||
{
|
||||
foreach (var slot in Slots)
|
||||
slot.RefreshOwnerState();
|
||||
ShowCurrentSlotText = IsConnected && CurrentSlot is not null;
|
||||
UpdateConnectionHint();
|
||||
}
|
||||
|
||||
partial void OnIsMovingChanged(bool value)
|
||||
{
|
||||
foreach (var slot in Slots)
|
||||
slot.RefreshOwnerState();
|
||||
}
|
||||
|
||||
partial void OnStatusTextChanged(string value)
|
||||
{
|
||||
UpdateConnectionHint();
|
||||
}
|
||||
|
||||
partial void OnSlotCountChanged(int value)
|
||||
{
|
||||
if (value is not (4 or 6))
|
||||
return;
|
||||
|
||||
_suppressSave = true;
|
||||
try
|
||||
{
|
||||
RebuildSlots();
|
||||
if (CurrentSlot > value)
|
||||
CurrentSlot = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressSave = false;
|
||||
}
|
||||
|
||||
Save();
|
||||
CurrentSlotCountText = CurrentSlot is int current ? $"{current}/{SlotCount}" : $"-/{SlotCount}";
|
||||
}
|
||||
|
||||
partial void OnCurrentSlotChanged(int? value)
|
||||
{
|
||||
CurrentSlotText = GetCurrentSlotText();
|
||||
CurrentSlotCountText = value is int slot ? $"{slot}/{SlotCount}" : $"-/{SlotCount}";
|
||||
ShowCurrentSlotText = IsConnected && value is not null;
|
||||
UpdateSlotSelection();
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
if (!_suppressSave)
|
||||
_save();
|
||||
}
|
||||
|
||||
private FilterWheelProfile CreateProfile(string address)
|
||||
{
|
||||
return new FilterWheelProfile(
|
||||
$"{SelectedPortName}:{address}",
|
||||
FilterWheelAddress.Create(address),
|
||||
SlotCount,
|
||||
Slots.ToDictionary(slot => slot.Slot, slot => slot.DisplayName));
|
||||
}
|
||||
|
||||
private async Task UpdateStatusFromDeviceAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_device == null)
|
||||
return;
|
||||
|
||||
var status = await _device.GetFilterStatusAsync(cancellationToken);
|
||||
CurrentSlot = status.CurrentPosition?.Value;
|
||||
CurrentSlotText = GetCurrentSlotText();
|
||||
CurrentSlotCountText = CurrentSlot is int slot ? $"{slot}/{SlotCount}" : $"-/{SlotCount}";
|
||||
ShowCurrentSlotText = IsConnected && CurrentSlot is not null;
|
||||
UpdateSlotSelection();
|
||||
}
|
||||
|
||||
private string GetCurrentSlotText()
|
||||
{
|
||||
if (CurrentSlot is not int slot)
|
||||
return IsConnected ? "未知" : "未连接";
|
||||
|
||||
var alias = Slots.FirstOrDefault(s => s.Slot == slot)?.DisplayName;
|
||||
return string.IsNullOrWhiteSpace(alias) ? $"轮{slot}" : alias;
|
||||
}
|
||||
|
||||
private void UpdateSlotSelection()
|
||||
{
|
||||
foreach (var slot in Slots)
|
||||
slot.IsSelected = slot.Slot == CurrentSlot;
|
||||
}
|
||||
|
||||
private void UpdateConnectionHint()
|
||||
{
|
||||
ShowConnectionHint = !IsConnected;
|
||||
if (IsConnected)
|
||||
{
|
||||
ConnectionHintText = "";
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionHintText = StatusText.StartsWith("连接失败", StringComparison.Ordinal)
|
||||
|| StatusText.StartsWith("未选择", StringComparison.Ordinal)
|
||||
? StatusText
|
||||
: "未连接";
|
||||
}
|
||||
|
||||
private static string NormalizeAddress(string value)
|
||||
{
|
||||
return FilterWheelAddress.Create(value).Value.ToString();
|
||||
}
|
||||
|
||||
private async Task DisposeDeviceAsync()
|
||||
{
|
||||
if (_device == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
await _device.DisposeAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_device = null;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await DisposeDeviceAsync();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ElloSlotViewModel(ElloDeviceViewModel owner, int slot, string alias) : ObservableObject
|
||||
{
|
||||
private readonly ElloDeviceViewModel _owner = owner;
|
||||
|
||||
public int Slot { get; } = slot;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string Alias { get; set; } = alias;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowInLaserPanel { get; set; } = true;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsSelected { get; set; }
|
||||
|
||||
public string DisplayName => string.IsNullOrWhiteSpace(Alias)
|
||||
? $"轮{Slot}"
|
||||
: Alias;
|
||||
|
||||
public bool CanMove => _owner.IsConnected && !_owner.IsMoving;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanMove))]
|
||||
private Task MoveAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return _owner.MoveToSlotAsync(Slot, cancellationToken);
|
||||
}
|
||||
|
||||
partial void OnAliasChanged(string value)
|
||||
{
|
||||
OnPropertyChanged(nameof(DisplayName));
|
||||
}
|
||||
|
||||
internal void RefreshOwnerState()
|
||||
{
|
||||
OnPropertyChanged(nameof(CanMove));
|
||||
MoveCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,12 @@ public partial class MainWindowViewModel : ObservableObject
|
||||
[ObservableProperty]
|
||||
public partial LaserSettingsViewModel LaserSettings { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial EasyTtlViewModel EasyTtl { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ElloFilterViewModel Ello { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AcquisitionViewModel Acquisition { get; set; }
|
||||
|
||||
@@ -82,6 +88,8 @@ public partial class MainWindowViewModel : ObservableObject
|
||||
|
||||
DeviceSettings = new DeviceSettingsViewModel();
|
||||
LaserSettings = new LaserSettingsViewModel();
|
||||
EasyTtl = new EasyTtlViewModel(simulateIndex >= 1);
|
||||
Ello = new ElloFilterViewModel(simulateIndex >= 1);
|
||||
Acquisition = new AcquisitionViewModel();
|
||||
PeakDetection = new PeakDetectionViewModel();
|
||||
Coordinate = new CoordinateViewModel();
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<Window
|
||||
x:Class="RamanApp.Views.EasyTtlSettingsDialog"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:RamanApp.ViewModels"
|
||||
Title="EasyTTL 设置"
|
||||
Width="540" Height="500"
|
||||
x:DataType="vm:EasyTtlViewModel" CanResize="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<ScrollViewer Margin="12">
|
||||
<StackPanel Spacing="10">
|
||||
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto">
|
||||
<ComboBox
|
||||
Grid.Row="0" Grid.Column="0"
|
||||
Margin="0,0,0,4" HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding PortNames}" SelectedItem="{Binding SelectedPortName}"/>
|
||||
<Button Grid.Row="0" Grid.Column="1" Margin="6,0,0,4" Command="{Binding RefreshPortsCommand}" Content="刷新"/>
|
||||
|
||||
<UniformGrid Grid.Row="1" Grid.ColumnSpan="2" Columns="2">
|
||||
<Button Margin="0,0,3,0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Command="{Binding ConnectCommand}" Content="连接"/>
|
||||
<Button Margin="3,0,0,0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Command="{Binding DisconnectCommand}" Content="断开"/>
|
||||
</UniformGrid>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,4,0,0" ColumnDefinitions="*,Auto">
|
||||
<TextBlock VerticalAlignment="Center" Text="使能输出"/>
|
||||
<ToggleSwitch Grid.Column="1" IsChecked="{Binding OutputEnabled}" IsEnabled="{Binding IsConnected}" OffContent="OFF" OnContent="ON"/>
|
||||
</Grid>
|
||||
|
||||
<Separator/>
|
||||
|
||||
<TextBlock FontWeight="SemiBold" Text="通道配置"/>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Channels}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:EasyTtlChannelViewModel">
|
||||
<Border Padding="0,5" BorderBrush="{DynamicResource SemiColorBorder}" BorderThickness="0,0,0,1">
|
||||
<Grid ColumnDefinitions="36,*,Auto,Auto,Auto,Auto">
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" FontWeight="SemiBold" Text="{Binding Channel, StringFormat=CH{0}}"/>
|
||||
|
||||
<TextBox Grid.Column="1" Margin="0,0,8,0" Text="{Binding Alias}" Watermark="通道别名"/>
|
||||
|
||||
<TextBlock Grid.Column="2" Margin="0,0,6,0" VerticalAlignment="Center" Foreground="Gray" Text="面板"/>
|
||||
<ToggleSwitch Grid.Column="3" Margin="0,0,10,0" IsChecked="{Binding ShowInLaserPanel}" OffContent="OFF" OnContent="ON"/>
|
||||
<TextBlock Grid.Column="4" Margin="0,0,6,0" VerticalAlignment="Center" Foreground="Gray" Text="硬件"/>
|
||||
<ToggleSwitch Grid.Column="5" IsChecked="{Binding HardwareEnabled}" IsEnabled="{Binding $parent[Window].DataContext.IsConnected}" OffContent="OFF" OnContent="ON"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<TextBlock FontSize="11" Foreground="Gray" Text="{Binding StatusText}" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Window>
|
||||
@@ -0,0 +1,10 @@
|
||||
using Avalonia.Controls;
|
||||
namespace RamanApp.Views;
|
||||
|
||||
public partial class EasyTtlSettingsDialog : Window
|
||||
{
|
||||
public EasyTtlSettingsDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<UserControl
|
||||
x:Class="RamanApp.Views.EasyTtlView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:RamanApp.ViewModels"
|
||||
x:DataType="vm:EasyTtlViewModel">
|
||||
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="激光通道" FontWeight="SemiBold" />
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Channels}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:EasyTtlChannelViewModel">
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="0,2"
|
||||
IsVisible="{Binding ShowInLaserPanel}">
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
VerticalAlignment="Center" />
|
||||
<ToggleSwitch Grid.Column="1" IsChecked="{Binding IsEnabled}"
|
||||
OnContent="ON" OffContent="OFF" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace RamanApp.Views;
|
||||
|
||||
public partial class EasyTtlView : UserControl
|
||||
{
|
||||
public EasyTtlView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<UserControl
|
||||
x:Class="RamanApp.Views.ElloFilterView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:RamanApp.ViewModels"
|
||||
x:DataType="vm:ElloFilterViewModel">
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Border.ello-slot">
|
||||
<Setter Property="Background" Value="{DynamicResource SemiColorFillTertiary}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource SemiColorBorder}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="Padding" Value="8,10" />
|
||||
<Setter Property="MinHeight" Value="54" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
<Style Selector="ItemsControl.ello-slot-list ContentPresenter">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
<Style Selector="Grid.ello-slot-hit">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
<Style Selector="Grid.ello-slot-hit:pointerover Border.ello-slot">
|
||||
<Setter Property="Background" Value="{DynamicResource SemiColorFillSecondary}" />
|
||||
</Style>
|
||||
<Style Selector="Border.ello-slot.selected">
|
||||
<Setter Property="Background" Value="#2F6F62" />
|
||||
<Setter Property="BorderBrush" Value="#2F6F62" />
|
||||
</Style>
|
||||
<Style Selector="Border.ello-slot.selected TextBlock">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Style>
|
||||
<Style Selector="Grid.ello-slot-hit:disabled Border.ello-slot">
|
||||
<Setter Property="Opacity" Value="0.48" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<StackPanel Spacing="8">
|
||||
<ItemsControl ItemsSource="{Binding Devices}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:ElloDeviceViewModel">
|
||||
<StackPanel Spacing="8" Margin="0,0,0,14"
|
||||
IsVisible="{Binding ShowInLaserPanel}">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding ConnectionHintText}"
|
||||
IsVisible="{Binding ShowConnectionHint}"
|
||||
FontSize="10"
|
||||
Foreground="#C62828"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
|
||||
<ItemsControl Classes="ello-slot-list" ItemsSource="{Binding PanelSlots}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="2" RowSpacing="6" ColumnSpacing="6" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:ElloSlotViewModel">
|
||||
<Grid Classes="ello-slot-hit"
|
||||
IsEnabled="{Binding CanMove}"
|
||||
Tapped="OnSlotTapped">
|
||||
<Border Classes="ello-slot"
|
||||
Classes.selected="{Binding IsSelected}"
|
||||
IsHitTestVisible="False">
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,24 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using RamanApp.ViewModels;
|
||||
|
||||
namespace RamanApp.Views;
|
||||
|
||||
public partial class ElloFilterView : UserControl
|
||||
{
|
||||
public ElloFilterView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnSlotTapped(object? sender, TappedEventArgs e)
|
||||
{
|
||||
if (sender is not Control { DataContext: ElloSlotViewModel slot })
|
||||
return;
|
||||
if (!slot.CanMove || !slot.MoveCommand.CanExecute(null))
|
||||
return;
|
||||
|
||||
slot.MoveCommand.Execute(null);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<Window
|
||||
x:Class="RamanApp.Views.ElloSettingsDialog"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:RamanApp.ViewModels"
|
||||
Title="ELLO 设置"
|
||||
Width="620" Height="560"
|
||||
x:DataType="vm:ElloFilterViewModel" CanResize="False"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<ScrollViewer Margin="12">
|
||||
<StackPanel Spacing="10">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Text="滤光轮设备" FontWeight="SemiBold" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1" Command="{Binding AddDeviceCommand}" Content="添加 ELLO" />
|
||||
</Grid>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Devices}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:ElloDeviceViewModel">
|
||||
<Border Padding="0,0,0,10" Margin="0,0,0,10"
|
||||
BorderBrush="{DynamicResource SemiColorBorder}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<StackPanel Spacing="8">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Grid.Column="0" Text="名称" VerticalAlignment="Center" Margin="0,0,6,0" />
|
||||
<TextBox Grid.Column="1" Text="{Binding Name}" />
|
||||
<ToggleSwitch Grid.Column="2" Margin="8,0,8,0"
|
||||
IsChecked="{Binding ShowInLaserPanel}"
|
||||
OffContent="面板OFF" OnContent="面板ON" />
|
||||
<Button Grid.Column="3"
|
||||
Command="{Binding $parent[Window].DataContext.RemoveDeviceCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="移除" />
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto,Auto,Auto,Auto,Auto">
|
||||
<ComboBox Grid.Column="0"
|
||||
ItemsSource="{Binding PortNames}"
|
||||
SelectedItem="{Binding SelectedPortName}" />
|
||||
<Button Grid.Column="1" Margin="6,0,8,0"
|
||||
Command="{Binding RefreshPortsCommand}" Content="刷新" />
|
||||
<TextBlock Grid.Column="2" Text="地址" VerticalAlignment="Center" Margin="0,0,6,0" />
|
||||
<TextBox Grid.Column="3" Width="48" Text="{Binding Address}" IsEnabled="{Binding !IsConnected}" />
|
||||
<TextBlock Grid.Column="4" Text="槽数" VerticalAlignment="Center" Margin="8,0,6,0" />
|
||||
<ComboBox Grid.Column="5" Width="70"
|
||||
ItemsSource="{Binding SlotCountOptions}"
|
||||
SelectedItem="{Binding SlotCount}"
|
||||
IsEnabled="{Binding !IsConnected}" />
|
||||
</Grid>
|
||||
|
||||
<UniformGrid Columns="2">
|
||||
<Button Margin="0,0,3,0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||
Command="{Binding ConnectCommand}" Content="连接" />
|
||||
<Button Margin="3,0,0,0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||
Command="{Binding DisconnectCommand}" Content="断开" />
|
||||
</UniformGrid>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Slots}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:ElloSlotViewModel">
|
||||
<Grid ColumnDefinitions="36,*,Auto,Auto,Auto" Margin="0,2">
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" FontWeight="SemiBold"
|
||||
Text="{Binding Slot, StringFormat=轮{0}}" />
|
||||
<TextBox Grid.Column="1" Margin="0,0,8,0"
|
||||
Text="{Binding Alias}" Watermark="轮位别名" />
|
||||
<TextBlock Grid.Column="2" Margin="0,0,6,0" VerticalAlignment="Center"
|
||||
Foreground="Gray" Text="面板" />
|
||||
<ToggleSwitch Grid.Column="3" Margin="0,0,10,0"
|
||||
IsChecked="{Binding ShowInLaserPanel}"
|
||||
OffContent="OFF" OnContent="ON" />
|
||||
<Button Grid.Column="4" Padding="10,3"
|
||||
Command="{Binding MoveCommand}"
|
||||
Content="切换" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<TextBlock FontSize="11" Foreground="Gray" Text="{Binding StatusText}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Window>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace RamanApp.Views;
|
||||
|
||||
public partial class ElloSettingsDialog : Window
|
||||
{
|
||||
public ElloSettingsDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,37 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Roman.Host.Filters.Contracts;
|
||||
|
||||
namespace Roman.Host.Filters.Pipe;
|
||||
|
||||
public static class PipeJsonSerializer
|
||||
{
|
||||
private static readonly JsonSerializerOptions Options = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
public static string Serialize(FilterRequest request)
|
||||
{
|
||||
return JsonSerializer.Serialize(request, Options);
|
||||
return JsonSerializer.Serialize(request, PipeJsonContext.Default.FilterRequest);
|
||||
}
|
||||
|
||||
public static string Serialize(FilterResponse response)
|
||||
{
|
||||
return JsonSerializer.Serialize(response, Options);
|
||||
return JsonSerializer.Serialize(response, PipeJsonContext.Default.FilterResponse);
|
||||
}
|
||||
|
||||
public static FilterRequest DeserializeRequest(string json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<FilterRequest>(json, Options)
|
||||
return JsonSerializer.Deserialize(json, PipeJsonContext.Default.FilterRequest)
|
||||
?? throw new InvalidOperationException("Pipe request JSON was empty.");
|
||||
}
|
||||
|
||||
public static FilterResponse DeserializeResponse(string json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<FilterResponse>(json, Options)
|
||||
return JsonSerializer.Deserialize(json, PipeJsonContext.Default.FilterResponse)
|
||||
?? throw new InvalidOperationException("Pipe response JSON was empty.");
|
||||
}
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(
|
||||
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
||||
WriteIndented = false)]
|
||||
[JsonSerializable(typeof(FilterRequest))]
|
||||
[JsonSerializable(typeof(FilterResponse))]
|
||||
internal sealed partial class PipeJsonContext : JsonSerializerContext;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<TargetFrameworks>net9.0;net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
Reference in New Issue
Block a user