settings for avalonia
libcamera bugfix
This commit is contained in:
@@ -3,11 +3,11 @@ using VisionBuilder.UI.Common;
|
|||||||
|
|
||||||
namespace Hawkeye.VisionBuilder.UI.Sources.Raspberry.Libcamera;
|
namespace Hawkeye.VisionBuilder.UI.Sources.Raspberry.Libcamera;
|
||||||
|
|
||||||
public class CaptureSettings : ISettings
|
public class LibcameraCaptureSettings : ISettings
|
||||||
{
|
{
|
||||||
public string CameraName { get; }
|
public string CameraName { get; }
|
||||||
|
|
||||||
public CaptureSettings(string cameraName)
|
public LibcameraCaptureSettings(string cameraName)
|
||||||
{
|
{
|
||||||
CameraName = cameraName;
|
CameraName = cameraName;
|
||||||
}
|
}
|
||||||
@@ -8,9 +8,9 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Raspberry.Libcamera
|
|||||||
{
|
{
|
||||||
public class LibcameraImageSource : IImageSource, IVisionBuilderModule
|
public class LibcameraImageSource : IImageSource, IVisionBuilderModule
|
||||||
{
|
{
|
||||||
private CaptureSettings _settings;
|
private LibcameraCaptureSettings _settings;
|
||||||
|
|
||||||
public LibcameraImageSource(CaptureSettings settings)
|
public LibcameraImageSource(LibcameraCaptureSettings settings)
|
||||||
{
|
{
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
}
|
}
|
||||||
@@ -41,72 +41,80 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Raspberry.Libcamera
|
|||||||
|
|
||||||
private void StartLoop()
|
private void StartLoop()
|
||||||
{
|
{
|
||||||
var psi = new ProcessStartInfo
|
try
|
||||||
{
|
{
|
||||||
FileName = "rpicam-vid",
|
var psi = new ProcessStartInfo
|
||||||
Arguments = _settings.BuildArguments(),
|
{
|
||||||
RedirectStandardOutput = true,
|
FileName = "rpicam-vid",
|
||||||
UseShellExecute = false
|
Arguments = _settings.BuildArguments(),
|
||||||
};
|
RedirectStandardOutput = true,
|
||||||
|
UseShellExecute = false
|
||||||
|
};
|
||||||
|
|
||||||
using (Process process = Process.Start(psi))
|
using (Process process = Process.Start(psi))
|
||||||
using (BinaryReader br = new BinaryReader(process.StandardOutput.BaseStream))
|
using (BinaryReader br = new BinaryReader(process.StandardOutput.BaseStream))
|
||||||
{
|
|
||||||
|
|
||||||
byte[] imageBuffer = new byte[1024 * 1024];
|
|
||||||
|
|
||||||
|
|
||||||
// decode MJPEG buffer
|
|
||||||
var buff = br.ReadBytes(ChunkSize);
|
|
||||||
int frameId = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
|
byte[] imageBuffer = new byte[1024 * 1024];
|
||||||
|
|
||||||
var imageStart = Find(buff, JpegHeader);
|
|
||||||
if (imageStart != -1)
|
// decode MJPEG buffer
|
||||||
|
var buff = br.ReadBytes(ChunkSize);
|
||||||
|
int frameId = 0;
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
var size = buff.Length - imageStart;
|
|
||||||
|
|
||||||
Array.Copy(buff, imageStart, imageBuffer, 0, size);
|
|
||||||
while (true)
|
var imageStart = Find(buff, JpegHeader);
|
||||||
|
if (imageStart != -1)
|
||||||
{
|
{
|
||||||
buff = br.ReadBytes(ChunkSize);
|
var size = buff.Length - imageStart;
|
||||||
var imageEnd = Find(buff, JpegFooter);
|
|
||||||
if (imageEnd != -1)
|
Array.Copy(buff, imageStart, imageBuffer, 0, size);
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
var frame = new byte[size + imageEnd];
|
buff = br.ReadBytes(ChunkSize);
|
||||||
Array.Copy(imageBuffer, frame, size);
|
var imageEnd = Find(buff, JpegFooter);
|
||||||
Array.Copy(buff, 0, frame, size, imageEnd);
|
if (imageEnd != -1)
|
||||||
// process frame
|
{
|
||||||
frameId++;
|
var frame = new byte[size + imageEnd];
|
||||||
var decoded = Mat.ImDecode(frame);
|
Array.Copy(imageBuffer, frame, size);
|
||||||
|
Array.Copy(buff, 0, frame, size, imageEnd);
|
||||||
|
// process frame
|
||||||
|
frameId++;
|
||||||
|
var decoded = Mat.ImDecode(frame);
|
||||||
|
|
||||||
_imageChannel.Writer.WriteAsync(decoded, CancellationToken.None);
|
_imageChannel.Writer.WriteAsync(decoded, CancellationToken.None);
|
||||||
|
|
||||||
// copy the leftover data to the start
|
// copy the leftover data to the start
|
||||||
Array.Copy(buff, imageEnd, buff, 0, buff.Length - imageEnd);
|
Array.Copy(buff, imageEnd, buff, 0, buff.Length - imageEnd);
|
||||||
// fill the remainder of the buffer with new data and start over
|
// fill the remainder of the buffer with new data and start over
|
||||||
var temp = br.ReadBytes(imageEnd);
|
var temp = br.ReadBytes(imageEnd);
|
||||||
Array.Copy(temp, 0, buff, buff.Length - imageEnd, temp.Length);
|
Array.Copy(temp, 0, buff, buff.Length - imageEnd, temp.Length);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy all of the data to the imageBuffer
|
||||||
|
Array.Copy(buff, 0, imageBuffer, size, buff.Length);
|
||||||
|
size += buff.Length;
|
||||||
}
|
}
|
||||||
// copy all of the data to the imageBuffer
|
|
||||||
Array.Copy(buff, 0, imageBuffer, size, buff.Length);
|
|
||||||
size += buff.Length;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("JPEG header not found.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("JPEG header not found.");
|
process.Kill();
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
process.Kill();
|
{
|
||||||
|
Serilog.Log.Error($"Error starting libcamera: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Ninject.Extensions.ChildKernel;
|
|||||||
using VisionBuilder.UI.Common;
|
using VisionBuilder.UI.Common;
|
||||||
using VisionBuilder.UI.Common.Plugins;
|
using VisionBuilder.UI.Common.Plugins;
|
||||||
using VisionBuilder.UI.Common.Processing;
|
using VisionBuilder.UI.Common.Processing;
|
||||||
|
using VisionBuilder.UI.Common.Services;
|
||||||
using VisionBuilder.UI.Common.ViewModel;
|
using VisionBuilder.UI.Common.ViewModel;
|
||||||
using VisionBuilder.UI.IOCommander;
|
using VisionBuilder.UI.IOCommander;
|
||||||
using VisionBuilder.UI.Recipes.HawkeyeRecipe;
|
using VisionBuilder.UI.Recipes.HawkeyeRecipe;
|
||||||
@@ -15,6 +16,7 @@ using VisionBuilder.UI.Statistics;
|
|||||||
using VisionBuilder.UI.Avalonia;
|
using VisionBuilder.UI.Avalonia;
|
||||||
using VisionBuilder.UI.Avalonia.Uno.Views;
|
using VisionBuilder.UI.Avalonia.Uno.Views;
|
||||||
using Lindt.Colorballs.Duo.Utils;
|
using Lindt.Colorballs.Duo.Utils;
|
||||||
|
using VisionBuilder.UI.Camera;
|
||||||
|
|
||||||
namespace VisionBuilder.UI.Avalonia.Uno;
|
namespace VisionBuilder.UI.Avalonia.Uno;
|
||||||
|
|
||||||
@@ -79,10 +81,12 @@ public class App : Application
|
|||||||
];
|
];
|
||||||
|
|
||||||
var avaloniaUiSettings = mainKernel.Get<AvaloniaUISettings>();
|
var avaloniaUiSettings = mainKernel.Get<AvaloniaUISettings>();
|
||||||
|
var uiConfiguration = mainKernel.Get<UIConfiguration>();
|
||||||
|
var passwordInputService = mainKernel.Get<IPasswordInputService>();
|
||||||
|
|
||||||
settings.LoadSettings();
|
settings.LoadSettings();
|
||||||
|
|
||||||
desktop.MainWindow = new MainWindow(mainWindowVm, avaloniaUiSettings);
|
desktop.MainWindow = new MainWindow(mainWindowVm, avaloniaUiSettings, uiConfiguration, passwordInputService);
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.LinuxFramebuffer;
|
using Avalonia.LinuxFramebuffer;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace VisionBuilder.UI.Avalonia.Uno;
|
namespace VisionBuilder.UI.Avalonia.Uno;
|
||||||
|
|
||||||
@@ -8,6 +9,11 @@ public class Program
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Debug()
|
||||||
|
.WriteTo.Console()
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
if (args.Contains("--drm"))
|
if (args.Contains("--drm"))
|
||||||
BuildAvaloniaApp().StartLinuxDrm(args, card: null, scaling: 1.0);
|
BuildAvaloniaApp().StartLinuxDrm(args, card: null, scaling: 1.0);
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -20,6 +20,16 @@
|
|||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Spacing="8">
|
Spacing="8">
|
||||||
|
<Button x:Name="BtnSettings"
|
||||||
|
Content="SETTINGS"
|
||||||
|
Width="176" Height="48"
|
||||||
|
FontSize="14" FontWeight="Bold"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
BorderBrush="#D32F2F"
|
||||||
|
BorderThickness="2"
|
||||||
|
Background="White"
|
||||||
|
Foreground="#D32F2F" />
|
||||||
<Button x:Name="BtnMinimize"
|
<Button x:Name="BtnMinimize"
|
||||||
Content="MINIMIZE"
|
Content="MINIMIZE"
|
||||||
Width="176" Height="48"
|
Width="176" Height="48"
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ using Avalonia.Interactivity;
|
|||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using VisionBuilder.UI.Avalonia;
|
using VisionBuilder.UI.Avalonia;
|
||||||
|
using VisionBuilder.UI.Common;
|
||||||
|
using VisionBuilder.UI.Common.Services;
|
||||||
using VisionBuilder.UI.Common.ViewModel;
|
using VisionBuilder.UI.Common.ViewModel;
|
||||||
using VisionBuilder.UI.Common.ViewModel.Classes;
|
using VisionBuilder.UI.Common.ViewModel.Classes;
|
||||||
|
|
||||||
@@ -17,12 +19,17 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
private readonly MainWindowVM _mainWindowVm;
|
private readonly MainWindowVM _mainWindowVm;
|
||||||
private readonly AvaloniaUISettings _avaloniaUiSettings;
|
private readonly AvaloniaUISettings _avaloniaUiSettings;
|
||||||
|
private readonly UIConfiguration _uiConfiguration;
|
||||||
|
private readonly IPasswordInputService _passwordInputService;
|
||||||
private readonly Dictionary<string, Control> _dynamicButtonControls = new();
|
private readonly Dictionary<string, Control> _dynamicButtonControls = new();
|
||||||
|
|
||||||
public MainWindow(MainWindowVM mainWindowVm, AvaloniaUISettings avaloniaUiSettings)
|
public MainWindow(MainWindowVM mainWindowVm, AvaloniaUISettings avaloniaUiSettings,
|
||||||
|
UIConfiguration uiConfiguration, IPasswordInputService passwordInputService)
|
||||||
{
|
{
|
||||||
_mainWindowVm = mainWindowVm;
|
_mainWindowVm = mainWindowVm;
|
||||||
_avaloniaUiSettings = avaloniaUiSettings;
|
_avaloniaUiSettings = avaloniaUiSettings;
|
||||||
|
_uiConfiguration = uiConfiguration;
|
||||||
|
_passwordInputService = passwordInputService;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
@@ -30,6 +37,7 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
SingleCameraView.SetViewModel(_mainWindowVm.SingleCameraVms[0]);
|
SingleCameraView.SetViewModel(_mainWindowVm.SingleCameraVms[0]);
|
||||||
|
|
||||||
|
BtnSettings.Click += BtnSettings_Click;
|
||||||
BtnMinimize.Click += BtnMinimize_Click;
|
BtnMinimize.Click += BtnMinimize_Click;
|
||||||
BtnExit.Click += BtnExit_Click;
|
BtnExit.Click += BtnExit_Click;
|
||||||
|
|
||||||
@@ -80,6 +88,17 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void BtnSettings_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_uiConfiguration.ProtectSettingsWithPassword)
|
||||||
|
{
|
||||||
|
var passwordOk = await _passwordInputService.GetPasswordAsync() == _uiConfiguration.AdminPassword;
|
||||||
|
if (!passwordOk)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_mainWindowVm.SettingsCommand.Execute(null);
|
||||||
|
}
|
||||||
|
|
||||||
private void BtnMinimize_Click(object? sender, RoutedEventArgs e)
|
private void BtnMinimize_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
WindowState = WindowState.Minimized;
|
WindowState = WindowState.Minimized;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu.Windows" Version="1.18.0" ExcludeAssets="all" />
|
<PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu.Windows" Version="1.18.0" ExcludeAssets="all" />
|
||||||
<PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu.Linux" Version="1.18.0" ExcludeAssets="all" />
|
<PackageReference Include="Microsoft.ML.OnnxRuntime.Gpu.Linux" Version="1.18.0" ExcludeAssets="all" />
|
||||||
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.18.0" />
|
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.18.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,11 +1,38 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Inspectron.Settings;
|
||||||
|
using Inspectron.Settings.Avalonia.Configuration;
|
||||||
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
|
||||||
|
|
||||||
namespace VisionBuilder.UI.Avalonia.Services;
|
namespace VisionBuilder.UI.Avalonia.Services;
|
||||||
|
|
||||||
public class AvaloniaSettingsService : ISettingsService
|
public class AvaloniaSettingsService : ISettingsService
|
||||||
{
|
{
|
||||||
public Task ShowSettingsDialogAsync()
|
private readonly InspectronSettings _settings;
|
||||||
|
|
||||||
|
public AvaloniaSettingsService(InspectronSettings settings)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
_settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ShowSettingsDialogAsync()
|
||||||
|
{
|
||||||
|
var dialog = new OptionsWindow(new DefaultControlFactory());
|
||||||
|
dialog.LoadFromSettings(_settings);
|
||||||
|
var parent = GetMainWindow();
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
var result = await dialog.ShowDialog<bool>(parent);
|
||||||
|
if (result)
|
||||||
|
_settings.SaveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Window? GetMainWindow()
|
||||||
|
{
|
||||||
|
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
return desktop.MainWindow;
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\framework\Inspectron.Settings\Inspectron.Settings.csproj" />
|
<ProjectReference Include="..\framework\Inspectron.Settings\Inspectron.Settings.csproj" />
|
||||||
|
<ProjectReference Include="..\framework\Inspectron.Settings.Avalonia\Inspectron.Settings.Avalonia.csproj" />
|
||||||
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
|
<ProjectReference Include="..\VisionBuilder.UI.Common\VisionBuilder.UI.Common.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using MudBlazor.Services;
|
|||||||
using Ninject;
|
using Ninject;
|
||||||
using Ninject.Extensions.ChildKernel;
|
using Ninject.Extensions.ChildKernel;
|
||||||
using VisionBuilder.UI.Blazor;
|
using VisionBuilder.UI.Blazor;
|
||||||
|
using VisionBuilder.UI.Camera;
|
||||||
using VisionBuilder.UI.Common;
|
using VisionBuilder.UI.Common;
|
||||||
using VisionBuilder.UI.Common.Plugins;
|
using VisionBuilder.UI.Common.Plugins;
|
||||||
using VisionBuilder.UI.Common.Processing;
|
using VisionBuilder.UI.Common.Processing;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using Ninject.Extensions.ChildKernel;
|
|||||||
using VisionBuilder.UI.Common;
|
using VisionBuilder.UI.Common;
|
||||||
using VisionBuilder.UI.Common.Processing;
|
using VisionBuilder.UI.Common.Processing;
|
||||||
|
|
||||||
namespace Lindt.Colorballs.Duo.Utils;
|
namespace VisionBuilder.UI.Camera;
|
||||||
|
|
||||||
public static class ModuleExtensions
|
public static class ModuleExtensions
|
||||||
{
|
{
|
||||||
@@ -17,6 +17,7 @@ public static class ModuleExtensions
|
|||||||
self.Bind<EmulationSettings, ISettings>().ToConstant(new EmulationSettings(cameraName));
|
self.Bind<EmulationSettings, ISettings>().ToConstant(new EmulationSettings(cameraName));
|
||||||
self.Bind<HawkeyeSettings, ISettings>().ToConstant(new HawkeyeSettings(cameraName));
|
self.Bind<HawkeyeSettings, ISettings>().ToConstant(new HawkeyeSettings(cameraName));
|
||||||
self.Bind<IDSImageSourceSettings, ISettings>().ToConstant(new IDSImageSourceSettings(cameraName));
|
self.Bind<IDSImageSourceSettings, ISettings>().ToConstant(new IDSImageSourceSettings(cameraName));
|
||||||
|
self.Bind<LibcameraCaptureSettings, ISettings>().ToConstant(new LibcameraCaptureSettings(cameraName));
|
||||||
self.Bind<SingleCameraVM>().ToSelf().InSingletonScope();
|
self.Bind<SingleCameraVM>().ToSelf().InSingletonScope();
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Lindt.Colorballs.Duo.Utils;
|
using Lindt.Colorballs.Duo.Utils;
|
||||||
using Ninject;
|
using Ninject;
|
||||||
using Ninject.Extensions.ChildKernel;
|
using Ninject.Extensions.ChildKernel;
|
||||||
|
using VisionBuilder.UI.Camera;
|
||||||
using VisionBuilder.UI.Common;
|
using VisionBuilder.UI.Common;
|
||||||
using VisionBuilder.UI.Common.Processing;
|
using VisionBuilder.UI.Common.Processing;
|
||||||
using VisionBuilder.UI.Common.ViewModel;
|
using VisionBuilder.UI.Common.ViewModel;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using OpenCvSharp;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using VisionBuilder.UI.Camera;
|
||||||
using VisionBuilder.UI.Common;
|
using VisionBuilder.UI.Common;
|
||||||
using VisionBuilder.UI.Common.Commands;
|
using VisionBuilder.UI.Common.Commands;
|
||||||
using VisionBuilder.UI.Common.Plugins;
|
using VisionBuilder.UI.Common.Plugins;
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LindtLeerformPlugin", "Plug
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Avalonia", "VisionBuilder.UI.Avalonia\VisionBuilder.UI.Avalonia.csproj", "{0549063E-5B22-4332-AD54-0D799B6DB4D2}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Avalonia", "VisionBuilder.UI.Avalonia\VisionBuilder.UI.Avalonia.csproj", "{0549063E-5B22-4332-AD54-0D799B6DB4D2}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inspectron.Settings.Avalonia", "framework\Inspectron.Settings.Avalonia\Inspectron.Settings.Avalonia.csproj", "{85063197-582A-4335-8298-431CF5CB95E1}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -643,6 +645,18 @@ Global
|
|||||||
{0549063E-5B22-4332-AD54-0D799B6DB4D2}.Release|x64.Build.0 = Release|Any CPU
|
{0549063E-5B22-4332-AD54-0D799B6DB4D2}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{0549063E-5B22-4332-AD54-0D799B6DB4D2}.Release|x86.ActiveCfg = Release|Any CPU
|
{0549063E-5B22-4332-AD54-0D799B6DB4D2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{0549063E-5B22-4332-AD54-0D799B6DB4D2}.Release|x86.Build.0 = Release|Any CPU
|
{0549063E-5B22-4332-AD54-0D799B6DB4D2}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -685,6 +699,7 @@ Global
|
|||||||
{149E2AFC-A714-4645-883A-2EE685E63FB9} = {F3414823-B70E-435E-B4EA-80ABF4371449}
|
{149E2AFC-A714-4645-883A-2EE685E63FB9} = {F3414823-B70E-435E-B4EA-80ABF4371449}
|
||||||
{F70920C6-EF98-42B7-9F21-6E94781E9900} = {F3414823-B70E-435E-B4EA-80ABF4371449}
|
{F70920C6-EF98-42B7-9F21-6E94781E9900} = {F3414823-B70E-435E-B4EA-80ABF4371449}
|
||||||
{5A1D9E37-353C-4E30-A302-48487DC3A9E8} = {583A77DF-A293-4F3E-AB96-7310BC495822}
|
{5A1D9E37-353C-4E30-A302-48487DC3A9E8} = {583A77DF-A293-4F3E-AB96-7310BC495822}
|
||||||
|
{85063197-582A-4335-8298-431CF5CB95E1} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {3CE42AE5-D79F-4E97-A246-AA8FD228B677}
|
SolutionGuid = {3CE42AE5-D79F-4E97-A246-AA8FD228B677}
|
||||||
|
|||||||
@@ -0,0 +1,320 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
using Inspectron.Settings.Attributes;
|
||||||
|
|
||||||
|
namespace Inspectron.Settings.Avalonia.Configuration;
|
||||||
|
|
||||||
|
public class DefaultControlFactory : IControlFactory
|
||||||
|
{
|
||||||
|
public virtual Control CreateControl(string name, string description, PropertyInfo propertyInfo,
|
||||||
|
object initialValue, Action<object> valueChangedCallback, OptionsWindow optionsWindow)
|
||||||
|
{
|
||||||
|
var type = propertyInfo.PropertyType;
|
||||||
|
|
||||||
|
var settingDescriptionAttribute = propertyInfo.GetCustomAttribute<SettingDescriptionAttribute>();
|
||||||
|
string? settingDescription = settingDescriptionAttribute?.Description;
|
||||||
|
|
||||||
|
var previewAttribute = propertyInfo.GetCustomAttribute<SettingPreviewAttribute>();
|
||||||
|
TextBlock? previewLabel = null;
|
||||||
|
Func<object, string>? getPreview = null;
|
||||||
|
|
||||||
|
if (previewAttribute != null)
|
||||||
|
{
|
||||||
|
var method = previewAttribute.PreviewClass.GetMethod(previewAttribute.PreviewFunction, BindingFlags.Public | BindingFlags.Static);
|
||||||
|
if (method != null)
|
||||||
|
{
|
||||||
|
getPreview = value => (string)method.Invoke(null, new[] { value })!;
|
||||||
|
previewLabel = new TextBlock
|
||||||
|
{
|
||||||
|
Width = 600,
|
||||||
|
Height = 30,
|
||||||
|
Padding = new Thickness(15, 0, 0, 0),
|
||||||
|
FontStyle = FontStyle.Italic,
|
||||||
|
FontSize = 13,
|
||||||
|
Text = getPreview(initialValue)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
|
||||||
|
{
|
||||||
|
return CreateListControl(name, description, type, initialValue, valueChangedCallback, optionsWindow, settingDescription, previewLabel, getPreview);
|
||||||
|
}
|
||||||
|
else if (type == typeof(string) && propertyInfo.GetCustomAttribute<FileAttribute>() != null)
|
||||||
|
{
|
||||||
|
return CreateFileControl(name, description, initialValue, valueChangedCallback, optionsWindow, settingDescription, previewLabel, getPreview, propertyInfo.GetCustomAttribute<FileAttribute>()!.Filter);
|
||||||
|
}
|
||||||
|
else if (type == typeof(string) && name.EndsWith("Path", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return CreatePathControl(name, description, initialValue, valueChangedCallback, optionsWindow, settingDescription, previewLabel, getPreview);
|
||||||
|
}
|
||||||
|
else if (type == typeof(string))
|
||||||
|
{
|
||||||
|
return CreateStringControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
|
||||||
|
}
|
||||||
|
else if (type == typeof(int))
|
||||||
|
{
|
||||||
|
return CreateIntControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
|
||||||
|
}
|
||||||
|
else if (type == typeof(bool))
|
||||||
|
{
|
||||||
|
return CreateBoolControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
|
||||||
|
}
|
||||||
|
else if (type.IsEnum)
|
||||||
|
{
|
||||||
|
return CreateEnumControl(description, type, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new TextBlock { Text = $"Unsupported type: {type.Name}" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control CreateListControl(string name, string description, Type listType, object initialValue,
|
||||||
|
Action<object> valueChangedCallback, OptionsWindow optionsWindow, string? settingDescription,
|
||||||
|
TextBlock? previewLabel, Func<object, string>? getPreview)
|
||||||
|
{
|
||||||
|
var elementType = listType.GetGenericArguments()[0];
|
||||||
|
var list = (IList)initialValue ?? (IList)Activator.CreateInstance(listType)!;
|
||||||
|
|
||||||
|
var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };
|
||||||
|
var textBox = new TextBox
|
||||||
|
{
|
||||||
|
Text = GetListDisplayText(list),
|
||||||
|
Width = 500,
|
||||||
|
Height = 60,
|
||||||
|
IsReadOnly = true,
|
||||||
|
AcceptsReturn = true,
|
||||||
|
TextWrapping = TextWrapping.Wrap
|
||||||
|
};
|
||||||
|
var editButton = new Button { Content = "Edit" };
|
||||||
|
|
||||||
|
editButton.Click += async (s, e) =>
|
||||||
|
{
|
||||||
|
var listEditor = new ListEditorDialog(elementType, list, this, optionsWindow, name);
|
||||||
|
var result = await listEditor.ShowDialog<IList?>(optionsWindow);
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
list.Clear();
|
||||||
|
foreach (object o in result)
|
||||||
|
{
|
||||||
|
list.Add(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
textBox.Text = GetListDisplayText(list);
|
||||||
|
valueChangedCallback(list);
|
||||||
|
if (previewLabel != null && getPreview != null)
|
||||||
|
previewLabel.Text = getPreview(list);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return CreateOuterPanel(settingDescription, previewLabel, label, textBox, editButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetListDisplayText(IList list)
|
||||||
|
{
|
||||||
|
if (list == null || list.Count == 0)
|
||||||
|
return "(empty list)";
|
||||||
|
|
||||||
|
var items = new List<string>();
|
||||||
|
foreach (var item in list)
|
||||||
|
{
|
||||||
|
items.Add(item?.ToString() ?? "(null)");
|
||||||
|
}
|
||||||
|
return string.Join(Environment.NewLine, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control CreatePathControl(string name, string description, object initialValue, Action<object> valueChangedCallback,
|
||||||
|
OptionsWindow optionsWindow, string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview)
|
||||||
|
{
|
||||||
|
var textBox = new TextBox { Text = initialValue as string, Width = 400 };
|
||||||
|
var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };
|
||||||
|
var browseButton = new Button { Content = "Browse" };
|
||||||
|
|
||||||
|
browseButton.Click += async (s, e) =>
|
||||||
|
{
|
||||||
|
var storageProvider = optionsWindow.StorageProvider;
|
||||||
|
var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
||||||
|
{
|
||||||
|
AllowMultiple = false,
|
||||||
|
Title = "Select Folder"
|
||||||
|
});
|
||||||
|
if (result.Count > 0)
|
||||||
|
{
|
||||||
|
var path = result[0].Path.LocalPath;
|
||||||
|
textBox.Text = path;
|
||||||
|
valueChangedCallback(path);
|
||||||
|
if (previewLabel != null && getPreview != null)
|
||||||
|
previewLabel.Text = getPreview(path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
textBox.TextChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
valueChangedCallback(textBox.Text ?? "");
|
||||||
|
if (previewLabel != null && getPreview != null)
|
||||||
|
previewLabel.Text = getPreview(textBox.Text ?? "");
|
||||||
|
};
|
||||||
|
|
||||||
|
var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
|
||||||
|
row.Children.Add(textBox);
|
||||||
|
row.Children.Add(browseButton);
|
||||||
|
|
||||||
|
return CreateOuterPanel(settingDescription, previewLabel, label, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control CreateFileControl(string name, string description, object initialValue, Action<object> valueChangedCallback,
|
||||||
|
OptionsWindow optionsWindow, string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview, string filter)
|
||||||
|
{
|
||||||
|
var textBox = new TextBox { Text = initialValue as string, Width = 400 };
|
||||||
|
var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };
|
||||||
|
var browseButton = new Button { Content = "Browse" };
|
||||||
|
|
||||||
|
browseButton.Click += async (s, e) =>
|
||||||
|
{
|
||||||
|
var storageProvider = optionsWindow.StorageProvider;
|
||||||
|
var result = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||||
|
{
|
||||||
|
AllowMultiple = false,
|
||||||
|
Title = "Select File",
|
||||||
|
FileTypeFilter = new[] { new FilePickerFileType(filter) { Patterns = new[] { filter } } }
|
||||||
|
});
|
||||||
|
if (result.Count > 0)
|
||||||
|
{
|
||||||
|
var path = result[0].Path.LocalPath;
|
||||||
|
textBox.Text = path;
|
||||||
|
valueChangedCallback(path);
|
||||||
|
if (previewLabel != null && getPreview != null)
|
||||||
|
previewLabel.Text = getPreview(path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
textBox.TextChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
valueChangedCallback(textBox.Text ?? "");
|
||||||
|
if (previewLabel != null && getPreview != null)
|
||||||
|
previewLabel.Text = getPreview(textBox.Text ?? "");
|
||||||
|
};
|
||||||
|
|
||||||
|
var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
|
||||||
|
row.Children.Add(textBox);
|
||||||
|
row.Children.Add(browseButton);
|
||||||
|
|
||||||
|
return CreateOuterPanel(settingDescription, previewLabel, label, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control CreateStringControl(string description, object initialValue, Action<object> valueChangedCallback,
|
||||||
|
string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview)
|
||||||
|
{
|
||||||
|
var textBox = new TextBox { Text = initialValue as string, Width = 600 };
|
||||||
|
var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };
|
||||||
|
|
||||||
|
textBox.TextChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
valueChangedCallback(textBox.Text ?? "");
|
||||||
|
if (previewLabel != null && getPreview != null)
|
||||||
|
previewLabel.Text = getPreview(textBox.Text ?? "");
|
||||||
|
};
|
||||||
|
|
||||||
|
return CreateOuterPanel(settingDescription, previewLabel, label, textBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control CreateIntControl(string description, object initialValue, Action<object> valueChangedCallback,
|
||||||
|
string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview)
|
||||||
|
{
|
||||||
|
var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };
|
||||||
|
var numericUpDown = new NumericUpDown
|
||||||
|
{
|
||||||
|
Minimum = 0,
|
||||||
|
Maximum = 99999999999,
|
||||||
|
Value = Convert.ToDecimal(initialValue),
|
||||||
|
Width = 200,
|
||||||
|
FormatString = "0"
|
||||||
|
};
|
||||||
|
|
||||||
|
numericUpDown.ValueChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
valueChangedCallback(Convert.ToInt32(numericUpDown.Value));
|
||||||
|
if (previewLabel != null && getPreview != null)
|
||||||
|
previewLabel.Text = getPreview(Convert.ToInt32(numericUpDown.Value));
|
||||||
|
};
|
||||||
|
|
||||||
|
return CreateOuterPanel(settingDescription, previewLabel, label, numericUpDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control CreateBoolControl(string description, object initialValue, Action<object> valueChangedCallback,
|
||||||
|
string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview)
|
||||||
|
{
|
||||||
|
var checkBox = new CheckBox { Content = description, IsChecked = (bool)initialValue };
|
||||||
|
|
||||||
|
checkBox.IsCheckedChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
var isChecked = checkBox.IsChecked == true;
|
||||||
|
valueChangedCallback(isChecked);
|
||||||
|
if (previewLabel != null && getPreview != null)
|
||||||
|
previewLabel.Text = getPreview(isChecked);
|
||||||
|
};
|
||||||
|
|
||||||
|
return CreateOuterPanel(settingDescription, previewLabel, checkBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control CreateEnumControl(string description, Type enumType, object initialValue, Action<object> valueChangedCallback,
|
||||||
|
string? settingDescription, TextBlock? previewLabel, Func<object, string>? getPreview)
|
||||||
|
{
|
||||||
|
var label = new TextBlock { Text = description, Padding = new Thickness(0, 5, 0, 0) };
|
||||||
|
var comboBox = new ComboBox { Width = 200 };
|
||||||
|
|
||||||
|
var enumValues = Enum.GetValues(enumType);
|
||||||
|
foreach (var value in enumValues)
|
||||||
|
{
|
||||||
|
comboBox.Items.Add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
comboBox.SelectedItem = initialValue;
|
||||||
|
|
||||||
|
comboBox.SelectionChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (comboBox.SelectedItem != null)
|
||||||
|
{
|
||||||
|
valueChangedCallback(comboBox.SelectedItem);
|
||||||
|
if (previewLabel != null && getPreview != null)
|
||||||
|
previewLabel.Text = getPreview(comboBox.SelectedItem);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return CreateOuterPanel(settingDescription, previewLabel, label, comboBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control CreateOuterPanel(string? settingDescription, TextBlock? previewLabel, params Control[] controls)
|
||||||
|
{
|
||||||
|
var outerPanel = new StackPanel { Orientation = Orientation.Vertical, Spacing = 4, Margin = new Thickness(0, 25, 0, 0), HorizontalAlignment = HorizontalAlignment.Left };
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(settingDescription))
|
||||||
|
{
|
||||||
|
var descriptionLabel = new TextBlock
|
||||||
|
{
|
||||||
|
Text = settingDescription,
|
||||||
|
FontStyle = FontStyle.Italic,
|
||||||
|
Padding = new Thickness(0, 5, 0, 0)
|
||||||
|
};
|
||||||
|
outerPanel.Children.Add(descriptionLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var control in controls)
|
||||||
|
{
|
||||||
|
outerPanel.Children.Add(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previewLabel != null)
|
||||||
|
outerPanel.Children.Add(previewLabel);
|
||||||
|
|
||||||
|
return outerPanel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Inspectron.Settings.Avalonia.Configuration;
|
||||||
|
|
||||||
|
public interface IControlFactory
|
||||||
|
{
|
||||||
|
Control CreateControl(string name, string description, PropertyInfo propertyInfo, object initialValue,
|
||||||
|
Action<object> valueChangedCallback, OptionsWindow optionsWindow);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="Inspectron.Settings.Avalonia.Configuration.ItemEditorDialog"
|
||||||
|
Title="Edit Item"
|
||||||
|
Width="650" Height="420"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
CanResize="False">
|
||||||
|
|
||||||
|
<Grid RowDefinitions="*,Auto" Margin="12">
|
||||||
|
<!-- Properties panel -->
|
||||||
|
<ScrollViewer Grid.Row="0"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel x:Name="PropertyPanel"
|
||||||
|
Orientation="Vertical"
|
||||||
|
Spacing="0" />
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- Bottom: OK/Cancel -->
|
||||||
|
<StackPanel Grid.Row="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Spacing="8"
|
||||||
|
Margin="0,12,0,0">
|
||||||
|
<Button x:Name="BtnOK" Content="OK" Width="75" HorizontalContentAlignment="Center" />
|
||||||
|
<Button x:Name="BtnCancel" Content="Cancel" Width="75" HorizontalContentAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
|
namespace Inspectron.Settings.Avalonia.Configuration;
|
||||||
|
|
||||||
|
public partial class ItemEditorDialog : Window
|
||||||
|
{
|
||||||
|
private readonly Type _itemType;
|
||||||
|
private object? _workingValue;
|
||||||
|
private readonly DefaultControlFactory _controlFactory;
|
||||||
|
private readonly OptionsWindow _parentOptionsWindow;
|
||||||
|
|
||||||
|
public ItemEditorDialog(Type itemType, object? initialValue, DefaultControlFactory controlFactory, OptionsWindow parentOptionsWindow)
|
||||||
|
{
|
||||||
|
_itemType = itemType;
|
||||||
|
_workingValue = CloneObject(initialValue);
|
||||||
|
_controlFactory = controlFactory;
|
||||||
|
_parentOptionsWindow = parentOptionsWindow;
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
Title = $"Edit {_itemType.Name}";
|
||||||
|
|
||||||
|
CreatePropertyControls();
|
||||||
|
|
||||||
|
BtnOK.Click += BtnOK_Click;
|
||||||
|
BtnCancel.Click += BtnCancel_Click;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreatePropertyControls()
|
||||||
|
{
|
||||||
|
if (_itemType.IsValueType || _itemType == typeof(string))
|
||||||
|
{
|
||||||
|
CreateSimpleValueControl();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CreateComplexObjectControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateSimpleValueControl()
|
||||||
|
{
|
||||||
|
var dummyProperty = new SimplePropertyInfo(_itemType, "Value");
|
||||||
|
var control = _controlFactory.CreateControl("Value", "Value", dummyProperty, _workingValue!,
|
||||||
|
newValue => _workingValue = newValue, _parentOptionsWindow);
|
||||||
|
|
||||||
|
PropertyPanel.Children.Add(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateComplexObjectControls()
|
||||||
|
{
|
||||||
|
var properties = _itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||||
|
.Where(p => p.CanRead && p.CanWrite)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
var currentValue = property.GetValue(_workingValue);
|
||||||
|
var control = _controlFactory.CreateControl(property.Name, property.Name, property, currentValue!,
|
||||||
|
newValue => property.SetValue(_workingValue, newValue), _parentOptionsWindow);
|
||||||
|
|
||||||
|
PropertyPanel.Children.Add(control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnOK_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close(_workingValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnCancel_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private object? CloneObject(object? obj)
|
||||||
|
{
|
||||||
|
if (obj == null) return GetDefaultValue(_itemType);
|
||||||
|
|
||||||
|
var type = obj.GetType();
|
||||||
|
if (type.IsValueType || type == typeof(string))
|
||||||
|
{
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var clone = Activator.CreateInstance(type);
|
||||||
|
foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (prop.CanRead && prop.CanWrite)
|
||||||
|
{
|
||||||
|
prop.SetValue(clone, prop.GetValue(obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return GetDefaultValue(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object? GetDefaultValue(Type type)
|
||||||
|
{
|
||||||
|
if (type.IsValueType)
|
||||||
|
{
|
||||||
|
return Activator.CreateInstance(type);
|
||||||
|
}
|
||||||
|
else if (type == typeof(string))
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Activator.CreateInstance(type);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="Inspectron.Settings.Avalonia.Configuration.ListEditorDialog"
|
||||||
|
Title="Edit List"
|
||||||
|
Width="600" Height="400"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
CanResize="False">
|
||||||
|
|
||||||
|
<Grid RowDefinitions="*,Auto" Margin="12">
|
||||||
|
<!-- Main content -->
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="*,Auto">
|
||||||
|
<!-- List -->
|
||||||
|
<ListBox x:Name="ItemListBox"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
SelectionMode="Single" />
|
||||||
|
|
||||||
|
<!-- Action buttons -->
|
||||||
|
<StackPanel Grid.Column="1"
|
||||||
|
Orientation="Vertical"
|
||||||
|
Spacing="8"
|
||||||
|
Width="75"
|
||||||
|
VerticalAlignment="Top">
|
||||||
|
<Button x:Name="BtnAdd" Content="Add" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" />
|
||||||
|
<Button x:Name="BtnEdit" Content="Edit" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" IsEnabled="False" />
|
||||||
|
<Button x:Name="BtnRemove" Content="Remove" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" IsEnabled="False" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Bottom: OK/Cancel -->
|
||||||
|
<StackPanel Grid.Row="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Spacing="8"
|
||||||
|
Margin="0,12,0,0">
|
||||||
|
<Button x:Name="BtnOK" Content="OK" Width="75" HorizontalContentAlignment="Center" />
|
||||||
|
<Button x:Name="BtnCancel" Content="Cancel" Width="75" HorizontalContentAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
|
namespace Inspectron.Settings.Avalonia.Configuration;
|
||||||
|
|
||||||
|
public partial class ListEditorDialog : Window
|
||||||
|
{
|
||||||
|
private readonly Type _elementType;
|
||||||
|
private readonly IList _workingList;
|
||||||
|
private readonly DefaultControlFactory _controlFactory;
|
||||||
|
private readonly OptionsWindow _parentOptionsWindow;
|
||||||
|
private readonly string _name;
|
||||||
|
|
||||||
|
public ListEditorDialog(Type elementType, IList originalList, DefaultControlFactory controlFactory, OptionsWindow parentOptionsWindow, string name)
|
||||||
|
{
|
||||||
|
_elementType = elementType;
|
||||||
|
_controlFactory = controlFactory;
|
||||||
|
_parentOptionsWindow = parentOptionsWindow;
|
||||||
|
_name = name;
|
||||||
|
|
||||||
|
var listType = typeof(List<>).MakeGenericType(elementType);
|
||||||
|
_workingList = (IList)Activator.CreateInstance(listType)!;
|
||||||
|
|
||||||
|
foreach (var item in originalList)
|
||||||
|
{
|
||||||
|
_workingList.Add(CloneObject(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
Title = $"Edit {_name}";
|
||||||
|
|
||||||
|
LoadListItems();
|
||||||
|
|
||||||
|
ItemListBox.SelectionChanged += ItemListBox_SelectionChanged;
|
||||||
|
BtnAdd.Click += BtnAdd_Click;
|
||||||
|
BtnEdit.Click += BtnEdit_Click;
|
||||||
|
BtnRemove.Click += BtnRemove_Click;
|
||||||
|
BtnOK.Click += BtnOK_Click;
|
||||||
|
BtnCancel.Click += BtnCancel_Click;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadListItems()
|
||||||
|
{
|
||||||
|
ItemListBox.Items.Clear();
|
||||||
|
foreach (var item in _workingList)
|
||||||
|
{
|
||||||
|
ItemListBox.Items.Add(item?.ToString() ?? "(null)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ItemListBox_SelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
bool hasSelection = ItemListBox.SelectedIndex >= 0;
|
||||||
|
BtnEdit.IsEnabled = hasSelection;
|
||||||
|
BtnRemove.IsEnabled = hasSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BtnAdd_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var defaultValue = GetDefaultValue(_elementType);
|
||||||
|
var itemEditor = new ItemEditorDialog(_elementType, defaultValue, _controlFactory, _parentOptionsWindow);
|
||||||
|
var result = await itemEditor.ShowDialog<object?>(this);
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
_workingList.Add(result);
|
||||||
|
LoadListItems();
|
||||||
|
ItemListBox.SelectedIndex = ItemListBox.Items.Count - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BtnEdit_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ItemListBox.SelectedIndex >= 0)
|
||||||
|
{
|
||||||
|
var selectedItem = _workingList[ItemListBox.SelectedIndex];
|
||||||
|
var itemEditor = new ItemEditorDialog(_elementType, selectedItem, _controlFactory, _parentOptionsWindow);
|
||||||
|
var result = await itemEditor.ShowDialog<object?>(this);
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
_workingList[ItemListBox.SelectedIndex] = result;
|
||||||
|
LoadListItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnRemove_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ItemListBox.SelectedIndex >= 0)
|
||||||
|
{
|
||||||
|
int selectedIndex = ItemListBox.SelectedIndex;
|
||||||
|
_workingList.RemoveAt(selectedIndex);
|
||||||
|
LoadListItems();
|
||||||
|
|
||||||
|
if (ItemListBox.Items.Count > 0)
|
||||||
|
{
|
||||||
|
ItemListBox.SelectedIndex = Math.Min(selectedIndex, ItemListBox.Items.Count - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnOK_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close(_workingList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnCancel_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private object? GetDefaultValue(Type type)
|
||||||
|
{
|
||||||
|
if (type.IsValueType)
|
||||||
|
{
|
||||||
|
return Activator.CreateInstance(type);
|
||||||
|
}
|
||||||
|
else if (type == typeof(string))
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Activator.CreateInstance(type);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object? CloneObject(object? obj)
|
||||||
|
{
|
||||||
|
if (obj == null) return null;
|
||||||
|
|
||||||
|
var type = obj.GetType();
|
||||||
|
if (type.IsValueType || type == typeof(string))
|
||||||
|
{
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var clone = Activator.CreateInstance(type);
|
||||||
|
foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (prop.CanRead && prop.CanWrite)
|
||||||
|
{
|
||||||
|
prop.SetValue(clone, prop.GetValue(obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Inspectron.Settings.Avalonia.Configuration;
|
||||||
|
|
||||||
|
internal class OptionSetting
|
||||||
|
{
|
||||||
|
public object Owner { get; set; }
|
||||||
|
public PropertyInfo Property { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string TreePath { get; set; }
|
||||||
|
public object Value { get; set; } // Temporary value until OK is pressed
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="Inspectron.Settings.Avalonia.Configuration.OptionsWindow"
|
||||||
|
Title="Options"
|
||||||
|
Width="1089" Height="649"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
CanResize="True">
|
||||||
|
|
||||||
|
<Grid RowDefinitions="*,Auto" Margin="12">
|
||||||
|
<!-- Main content -->
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="210,12,*">
|
||||||
|
<!-- Left: Category tree -->
|
||||||
|
<TreeView x:Name="TreeViewCategories"
|
||||||
|
Grid.Column="0"
|
||||||
|
SelectionMode="Single" />
|
||||||
|
|
||||||
|
<!-- Right: Settings panel -->
|
||||||
|
<ScrollViewer Grid.Column="2"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel x:Name="SettingsPanel"
|
||||||
|
Orientation="Vertical"
|
||||||
|
Spacing="0"
|
||||||
|
HorizontalAlignment="Left" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Bottom: OK/Cancel buttons -->
|
||||||
|
<StackPanel Grid.Row="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Spacing="8"
|
||||||
|
Margin="0,12,0,0">
|
||||||
|
<Button x:Name="BtnOK"
|
||||||
|
Content="OK"
|
||||||
|
Width="75" Height="30"
|
||||||
|
HorizontalContentAlignment="Center" />
|
||||||
|
<Button x:Name="BtnCancel"
|
||||||
|
Content="Cancel"
|
||||||
|
Width="75" Height="30"
|
||||||
|
HorizontalContentAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
|
namespace Inspectron.Settings.Avalonia.Configuration;
|
||||||
|
|
||||||
|
public partial class OptionsWindow : Window
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, List<OptionSetting>> _settingsByCategory;
|
||||||
|
private readonly IControlFactory _controlFactory;
|
||||||
|
|
||||||
|
public OptionsWindow(IControlFactory controlFactory)
|
||||||
|
{
|
||||||
|
_controlFactory = controlFactory;
|
||||||
|
_settingsByCategory = new Dictionary<string, List<OptionSetting>>();
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
TreeViewCategories.SelectionChanged += TreeViewCategories_SelectionChanged;
|
||||||
|
BtnOK.Click += BtnOK_Click;
|
||||||
|
BtnCancel.Click += BtnCancel_Click;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadFromSettings(InspectronSettings inspectronSettings)
|
||||||
|
{
|
||||||
|
void Traverse(object nodeObj, string path)
|
||||||
|
{
|
||||||
|
var node = nodeObj as dynamic;
|
||||||
|
object value = node.Value;
|
||||||
|
|
||||||
|
if (value is string folderName)
|
||||||
|
{
|
||||||
|
string newPath = string.IsNullOrEmpty(path) ? folderName : $"{path}/{folderName}";
|
||||||
|
foreach (var child in node.Children)
|
||||||
|
Traverse(child, newPath);
|
||||||
|
}
|
||||||
|
else if (value is InspectronSettings.UserSettingsInfo info)
|
||||||
|
{
|
||||||
|
string groupName = info.Name;
|
||||||
|
string groupPath = string.IsNullOrEmpty(path) ? groupName : $"{path}/{groupName}";
|
||||||
|
foreach (var pd in info.Settings)
|
||||||
|
{
|
||||||
|
object owner = null;
|
||||||
|
string description = pd.Description;
|
||||||
|
if (pd is BoundPropertyDescriptor bpd)
|
||||||
|
{
|
||||||
|
owner = bpd.Owner;
|
||||||
|
if (string.IsNullOrEmpty(description))
|
||||||
|
description = bpd.DisplayName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RegisterUserSetting(owner, bpd.PropertyInfo.Name, groupPath, description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Traverse(inspectronSettings.UserSettings.Root, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterUserSetting(object owner, string propertyName, string treePath, string? description = null)
|
||||||
|
{
|
||||||
|
var property = owner.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
if (property == null)
|
||||||
|
throw new ArgumentException($"Property '{propertyName}' not found on owner type.");
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(description))
|
||||||
|
description = propertyName;
|
||||||
|
|
||||||
|
var setting = new OptionSetting
|
||||||
|
{
|
||||||
|
Owner = owner,
|
||||||
|
Property = property,
|
||||||
|
Description = description,
|
||||||
|
TreePath = treePath,
|
||||||
|
Value = property.GetValue(owner)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!_settingsByCategory.TryGetValue(treePath, out var settingsList))
|
||||||
|
{
|
||||||
|
settingsList = new List<OptionSetting>();
|
||||||
|
_settingsByCategory[treePath] = settingsList;
|
||||||
|
}
|
||||||
|
settingsList.Add(setting);
|
||||||
|
|
||||||
|
AddTreeNodes(treePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddTreeNodes(string treePath)
|
||||||
|
{
|
||||||
|
var parts = treePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
var items = TreeViewCategories.Items;
|
||||||
|
string currentPath = "";
|
||||||
|
|
||||||
|
foreach (var part in parts)
|
||||||
|
{
|
||||||
|
currentPath = string.IsNullOrEmpty(currentPath) ? part : $"{currentPath}/{part}";
|
||||||
|
|
||||||
|
var existing = items.OfType<TreeViewItem>().FirstOrDefault(n => (string)n.Header == part);
|
||||||
|
if (existing == null)
|
||||||
|
{
|
||||||
|
existing = new TreeViewItem { Header = part, Tag = currentPath, IsExpanded = false };
|
||||||
|
items.Add(existing);
|
||||||
|
}
|
||||||
|
items = existing.Items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TreeViewCategories_SelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (TreeViewCategories.SelectedItem is TreeViewItem selectedItem)
|
||||||
|
{
|
||||||
|
var path = selectedItem.Tag as string;
|
||||||
|
if (path != null)
|
||||||
|
DisplaySettings(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplaySettings(string treePath)
|
||||||
|
{
|
||||||
|
SettingsPanel.Children.Clear();
|
||||||
|
|
||||||
|
if (_settingsByCategory.TryGetValue(treePath, out var settingsList))
|
||||||
|
{
|
||||||
|
foreach (var setting in settingsList)
|
||||||
|
{
|
||||||
|
var control = _controlFactory.CreateControl(
|
||||||
|
setting.Property.Name,
|
||||||
|
setting.Description,
|
||||||
|
setting.Property,
|
||||||
|
setting.Value,
|
||||||
|
(newValue) =>
|
||||||
|
{
|
||||||
|
setting.Value = newValue;
|
||||||
|
},
|
||||||
|
this);
|
||||||
|
|
||||||
|
SettingsPanel.Children.Add(control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnOK_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
foreach (var settingsList in _settingsByCategory.Values)
|
||||||
|
{
|
||||||
|
foreach (var setting in settingsList)
|
||||||
|
{
|
||||||
|
setting.Property.SetValue(setting.Owner, setting.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnCancel_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Inspectron.Settings.Avalonia.Configuration;
|
||||||
|
|
||||||
|
internal class SimplePropertyInfo : PropertyInfo
|
||||||
|
{
|
||||||
|
private readonly Type _propertyType;
|
||||||
|
private readonly string _name;
|
||||||
|
|
||||||
|
public SimplePropertyInfo(Type propertyType, string name)
|
||||||
|
{
|
||||||
|
_propertyType = propertyType;
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PropertyAttributes Attributes => PropertyAttributes.None;
|
||||||
|
public override bool CanRead => true;
|
||||||
|
public override bool CanWrite => true;
|
||||||
|
public override Type PropertyType => _propertyType;
|
||||||
|
public override string Name => _name;
|
||||||
|
public override Type DeclaringType => typeof(object);
|
||||||
|
public override Type ReflectedType => typeof(object);
|
||||||
|
|
||||||
|
public override MethodInfo[] GetAccessors(bool nonPublic) => new MethodInfo[0];
|
||||||
|
public override MethodInfo GetGetMethod(bool nonPublic) => null;
|
||||||
|
public override ParameterInfo[] GetIndexParameters() => new ParameterInfo[0];
|
||||||
|
public override MethodInfo GetSetMethod(bool nonPublic) => null;
|
||||||
|
public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture) => obj;
|
||||||
|
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture) { }
|
||||||
|
public override object[] GetCustomAttributes(Type attributeType, bool inherit) => new object[0];
|
||||||
|
public override object[] GetCustomAttributes(bool inherit) => new object[0];
|
||||||
|
public override bool IsDefined(Type attributeType, bool inherit) => false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="11.2.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Inspectron.Settings\Inspectron.Settings.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
18
framework/Inspectron.Settings.Avalonia/PropHelper.cs
Normal file
18
framework/Inspectron.Settings.Avalonia/PropHelper.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Inspectron.Settings.Avalonia
|
||||||
|
{
|
||||||
|
public static class PropHelper
|
||||||
|
{
|
||||||
|
public static PropertyInfo GetPrp(object obj, string name)
|
||||||
|
{
|
||||||
|
return obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||||
|
.First(x => x.Name == name);
|
||||||
|
}
|
||||||
|
public static string SplitCamelCase(string input)
|
||||||
|
{
|
||||||
|
return System.Text.RegularExpressions.Regex.Replace(input, "([A-Z])", " $1", System.Text.RegularExpressions.RegexOptions.Compiled).Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user