settings for avalonia

libcamera bugfix
This commit is contained in:
EugeneTes
2026-04-01 14:05:37 +02:00
parent 1032a5a28c
commit bb861834fd
26 changed files with 1139 additions and 59 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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;
}
}
}
}

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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>

View 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();
}
}
}