initial commit

This commit is contained in:
Matthias Heil
2026-08-10 14:46:18 +02:00
commit c946aa63e6
69 changed files with 8865 additions and 0 deletions
@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using Avalonia.PropertyGrid.Models;
namespace Avalonia.PropertyGrid.Editors;
/// <summary>
/// Registry for custom property editors
/// </summary>
public static class EditorRegistry
{
private static readonly Dictionary<Type, Type> _typeEditors = new();
private static readonly Dictionary<string, Type> _propertyEditors = new();
/// <summary>
/// Register a custom editor for a specific type
/// </summary>
public static void RegisterEditor(Type propertyType, Type editorType)
{
if (!typeof(PropertyEditor).IsAssignableFrom(editorType))
{
throw new ArgumentException($"Editor type must inherit from PropertyEditor", nameof(editorType));
}
_typeEditors[propertyType] = editorType;
}
/// <summary>
/// Register a custom editor for a specific property by name
/// </summary>
public static void RegisterEditor(string propertyName, Type editorType)
{
if (!typeof(PropertyEditor).IsAssignableFrom(editorType))
{
throw new ArgumentException($"Editor type must inherit from PropertyEditor", nameof(editorType));
}
_propertyEditors[propertyName] = editorType;
}
/// <summary>
/// Get the editor type for a property item
/// </summary>
public static Type? GetEditorType(PropertyItem item)
{
// Check for explicit editor type on the item
if (item.EditorType != null)
{
return item.EditorType;
}
// Check for property-specific editor
if (_propertyEditors.TryGetValue(item.Name, out var propertyEditor))
{
return propertyEditor;
}
// Check for type-specific editor
if (_typeEditors.TryGetValue(item.PropertyType, out var typeEditor))
{
return typeEditor;
}
// Return default editor based on type
return GetDefaultEditorType(item.PropertyType);
}
private static Type? GetDefaultEditorType(Type propertyType)
{
if (propertyType == typeof(string))
return typeof(StringEditor);
if (propertyType == typeof(bool))
return typeof(BooleanEditor);
if (propertyType == typeof(int) || propertyType == typeof(long) ||
propertyType == typeof(short) || propertyType == typeof(byte) ||
propertyType == typeof(uint) || propertyType == typeof(ulong) ||
propertyType == typeof(ushort) || propertyType == typeof(sbyte) ||
propertyType == typeof(float) || propertyType == typeof(double) ||
propertyType == typeof(decimal))
return typeof(NumericEditor);
if (propertyType.IsEnum)
return typeof(EnumEditor);
if (propertyType == typeof(DateTime))
return typeof(DateTimeEditor);
return null; // Will use default TextBox
}
}
@@ -0,0 +1,288 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.PropertyGrid.Models;
namespace Avalonia.PropertyGrid.Editors;
/// <summary>
/// Base class for property editors
/// </summary>
public abstract class PropertyEditor : ContentControl
{
/// <summary>
/// Defines the PropertyItem property.
/// </summary>
public static readonly StyledProperty<PropertyItem?> PropertyItemProperty =
AvaloniaProperty.Register<PropertyEditor, PropertyItem?>(nameof(PropertyItem));
/// <summary>
/// Gets or sets the property item being edited.
/// </summary>
public PropertyItem? PropertyItem
{
get => GetValue(PropertyItemProperty);
set => SetValue(PropertyItemProperty, value);
}
/// <summary>
/// Called when the PropertyItem changes.
/// </summary>
protected virtual void OnPropertyItemChanged(PropertyItem? oldValue, PropertyItem? newValue)
{
if (oldValue != null)
{
UnbindFromProperty(oldValue);
}
if (newValue != null)
{
BindToProperty(newValue);
}
}
/// <summary>
/// Binds the editor to the property item.
/// </summary>
protected abstract void BindToProperty(PropertyItem item);
/// <summary>
/// Unbinds the editor from the property item.
/// </summary>
protected abstract void UnbindFromProperty(PropertyItem item);
}
/// <summary>
/// Editor for string properties
/// </summary>
public class StringEditor : PropertyEditor
{
private TextBox? _textBox;
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_textBox = new TextBox();
Content = _textBox;
}
/// <inheritdoc/>
protected override void BindToProperty(PropertyItem item)
{
if (_textBox != null)
{
_textBox.Bind(TextBox.TextProperty,
new Avalonia.Data.Binding(nameof(PropertyItem.Value))
{
Source = item,
Mode = Avalonia.Data.BindingMode.TwoWay
});
_textBox.IsReadOnly = item.IsReadOnly;
}
}
/// <inheritdoc/>
protected override void UnbindFromProperty(PropertyItem item)
{
_textBox?.ClearValue(TextBox.TextProperty);
}
}
/// <summary>
/// Editor for numeric properties
/// </summary>
public class NumericEditor : PropertyEditor
{
private NumericUpDown? _numericUpDown;
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_numericUpDown = new NumericUpDown
{
Increment = 1,
FormatString = "0.##"
};
Content = _numericUpDown;
}
/// <inheritdoc/>
protected override void BindToProperty(PropertyItem item)
{
if (_numericUpDown != null)
{
_numericUpDown.Bind(NumericUpDown.ValueProperty,
new Avalonia.Data.Binding(nameof(PropertyItem.Value))
{
Source = item,
Mode = Avalonia.Data.BindingMode.TwoWay
});
_numericUpDown.IsReadOnly = item.IsReadOnly;
// Adjust increment based on type
if (item.PropertyType == typeof(int) || item.PropertyType == typeof(long))
{
_numericUpDown.Increment = 1;
_numericUpDown.FormatString = "0";
}
else if (item.PropertyType == typeof(double) || item.PropertyType == typeof(float))
{
_numericUpDown.Increment = 0.1m;
_numericUpDown.FormatString = "0.00";
}
}
}
/// <inheritdoc/>
protected override void UnbindFromProperty(PropertyItem item)
{
_numericUpDown?.ClearValue(NumericUpDown.ValueProperty);
}
}
/// <summary>
/// Editor for boolean properties
/// </summary>
public class BooleanEditor : PropertyEditor
{
private CheckBox? _checkBox;
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_checkBox = new CheckBox
{
HorizontalAlignment = Layout.HorizontalAlignment.Left
};
Content = _checkBox;
}
/// <inheritdoc/>
protected override void BindToProperty(PropertyItem item)
{
if (_checkBox != null)
{
_checkBox.Bind(CheckBox.IsCheckedProperty,
new Avalonia.Data.Binding(nameof(PropertyItem.Value))
{
Source = item,
Mode = Avalonia.Data.BindingMode.TwoWay
});
_checkBox.IsEnabled = !item.IsReadOnly;
}
}
/// <inheritdoc/>
protected override void UnbindFromProperty(PropertyItem item)
{
_checkBox?.ClearValue(CheckBox.IsCheckedProperty);
}
}
/// <summary>
/// Editor for enum properties
/// </summary>
public class EnumEditor : PropertyEditor
{
private ComboBox? _comboBox;
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_comboBox = new ComboBox();
Content = _comboBox;
}
/// <inheritdoc/>
protected override void BindToProperty(PropertyItem item)
{
if (_comboBox != null && item.PropertyType.IsEnum)
{
_comboBox.ItemsSource = Enum.GetValues(item.PropertyType);
_comboBox.Bind(ComboBox.SelectedItemProperty,
new Avalonia.Data.Binding(nameof(PropertyItem.Value))
{
Source = item,
Mode = Avalonia.Data.BindingMode.TwoWay
});
_comboBox.IsEnabled = !item.IsReadOnly;
}
}
/// <inheritdoc/>
protected override void UnbindFromProperty(PropertyItem item)
{
_comboBox?.ClearValue(ComboBox.SelectedItemProperty);
}
}
/// <summary>
/// Editor for DateTime properties
/// </summary>
public class DateTimeEditor : PropertyEditor
{
private DatePicker? _datePicker;
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_datePicker = new DatePicker();
Content = _datePicker;
}
/// <inheritdoc/>
protected override void BindToProperty(PropertyItem item)
{
if (_datePicker != null)
{
_datePicker.Bind(DatePicker.SelectedDateProperty,
new Avalonia.Data.Binding(nameof(PropertyItem.Value))
{
Source = item,
Mode = Avalonia.Data.BindingMode.TwoWay,
Converter = new DateTimeToDateTimeOffsetConverter()
});
_datePicker.IsEnabled = !item.IsReadOnly;
}
}
/// <inheritdoc/>
protected override void UnbindFromProperty(PropertyItem item)
{
_datePicker?.ClearValue(DatePicker.SelectedDateProperty);
}
}
/// <summary>
/// Converter for DateTime to DateTimeOffset
/// </summary>
internal class DateTimeToDateTimeOffsetConverter : Avalonia.Data.Converters.IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
{
if (value is DateTime dt)
{
return new DateTimeOffset(dt);
}
return null;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
{
if (value is DateTimeOffset dto)
{
return dto.DateTime;
}
return null;
}
}