feat: add more properties.

This commit is contained in:
rabbitism
2024-02-18 17:10:59 +08:00
parent 14c7e6b21f
commit 66835a8efa
8 changed files with 232 additions and 127 deletions

View File

@@ -1,36 +1,97 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa">
<!-- Add Resources Here -->
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa">
<!-- Add Resources Here -->
<ControlTheme x:Key="{x:Type u:Form}" TargetType="u:Form">
<Setter Property="Grid.IsSharedSizeScope" Value="False" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Template">
<ControlTemplate TargetType="u:Form">
<DataValidationErrors>
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}"></ItemsPresenter>
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" />
</DataValidationErrors>
</ControlTemplate>
</Setter>
<Style Selector="^:fixed-width">
<Setter Property="Grid.IsSharedSizeScope" Value="True" />
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type u:FormGroup}" TargetType="u:FormGroup">
<Setter Property="Template">
<ControlTemplate TargetType="u:FormGroup">
<StackPanel>
<ContentPresenter Content="{TemplateBinding Header}"/>
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}"/>
<ContentPresenter Content="{TemplateBinding Header}" FontWeight="Bold" />
<Rectangle
Height="1"
Margin="0,8"
HorizontalAlignment="Stretch"
Fill="LightGray" />
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" />
</StackPanel>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type u:FormItem}" TargetType="u:FormItem">
<Setter Property="Margin" Value="0 8" />
<Setter Property="Template">
<ControlTemplate TargetType="u:FormItem">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{TemplateBinding Label}" />
<ContentPresenter Content="{TemplateBinding Content}"/>
<StackPanel>
<StackPanel
Name="PART_LabelPanel"
Margin="0,0,0,4"
HorizontalAlignment="{TemplateBinding LabelAlignment}"
Orientation="Horizontal">
<ContentPresenter Content="{TemplateBinding Label}" FontWeight="Bold" />
<TextBlock
Foreground="{DynamicResource SemiRed6}"
IsVisible="{TemplateBinding IsRequired}"
Text="*" />
</StackPanel>
<ContentPresenter Content="{TemplateBinding Content}" />
</StackPanel>
</ControlTemplate>
</Setter>
<Style Selector="^:not(:no-label):horizontal">
<Setter Property="Template">
<ControlTemplate TargetType="u:FormItem">
<Grid RowDefinitions="*, *">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Label" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Grid.Row="0"
Grid.Column="0"
Width="{TemplateBinding LabelWidth}">
<StackPanel
Name="PART_LabelPanel"
Margin="8,8,8,0"
HorizontalAlignment="{TemplateBinding LabelAlignment}"
Orientation="Horizontal">
<ContentPresenter Content="{TemplateBinding Label}" FontWeight="Bold" />
<TextBlock
Foreground="{DynamicResource SemiRed6}"
IsVisible="{TemplateBinding IsRequired}"
Text="*" />
</StackPanel>
</Border>
<ContentPresenter
Grid.Row="0"
Grid.Column="1"
Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="^:no-label">
<Setter Property="Template">
<ControlTemplate TargetType="u:FormItem">
<ContentPresenter Content="{TemplateBinding Content}" />
</ControlTemplate>
</Setter>
</Style>
</ControlTheme>
</ResourceDictionary>

View File

@@ -1,59 +1,65 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Layout;
using Ursa.Common;
namespace Ursa.Controls;
[PseudoClasses(PC_FixedWidth)]
public class Form: ItemsControl
{
#region Attached Properties
public static readonly AttachedProperty<object?> LabelProperty =
AvaloniaProperty.RegisterAttached<Form, Control, object?>("Label");
public static void SetLabel(Control obj, object? value) => obj.SetValue(LabelProperty, value);
public static object? GetLabel(Control obj) => obj.GetValue(LabelProperty);
public const string PC_FixedWidth = ":fixed-width";
public static readonly StyledProperty<GridLength> LabelWidthProperty = AvaloniaProperty.Register<Form, GridLength>(
nameof(LabelWidth));
public static readonly AttachedProperty<bool> IsRequiredProperty =
AvaloniaProperty.RegisterAttached<Form, Control, bool>("IsRequired");
public static void SetIsRequired(Control obj, bool value) => obj.SetValue(IsRequiredProperty, value);
public static bool GetIsRequired(Control obj) => obj.GetValue(IsRequiredProperty);
public static readonly AttachedProperty<GridLength> LabelWidthProperty =
AvaloniaProperty.RegisterAttached<Form, Control, GridLength>("LabelWidth");
public static void SetLabelWidth(Control obj, GridLength value) => obj.SetValue(LabelWidthProperty, value);
public static GridLength GetLabelWidth(Control obj) => obj.GetValue(LabelWidthProperty);
#endregion
/// <summary>
/// Behavior:
/// <para>Fixed Width: all labels are with fixed length. </para>
/// <para>Star: all labels are aligned by max length. </para>
/// <para>Auto: labels are not aligned. </para>
/// </summary>
public GridLength LabelWidth
{
get => GetValue(LabelWidthProperty);
set => SetValue(LabelWidthProperty, value);
}
public static readonly StyledProperty<Position> LabelPositionProperty = AvaloniaProperty.Register<Form, Position>(
nameof(LabelPosition));
/// <summary>
/// Only Left and Top work.
/// </summary>
nameof(LabelPosition), defaultValue: Position.Top);
public Position LabelPosition
{
get => GetValue(LabelPositionProperty);
set => SetValue(LabelPositionProperty, value);
}
public static readonly StyledProperty<Position> LabelAlignmentProperty = AvaloniaProperty.Register<Form, Position>(
nameof(LabelAlignment));
/// <summary>
/// Only Left and Right work.
/// </summary>
public Position LabelAlignment
public static readonly StyledProperty<HorizontalAlignment> LabelAlignmentProperty = AvaloniaProperty.Register<Form, HorizontalAlignment>(
nameof(LabelAlignment), defaultValue: HorizontalAlignment.Left);
public HorizontalAlignment LabelAlignment
{
get => GetValue(LabelAlignmentProperty);
set => SetValue(LabelAlignmentProperty, value);
}
static Form()
{
LabelWidthProperty.Changed.AddClassHandler<Form, GridLength>((x, args) => x.LabelWidthChanged(args));
}
private void LabelWidthChanged(AvaloniaPropertyChangedEventArgs<GridLength> args)
{
var newValue = args.NewValue.Value;
bool isFixed = newValue.IsStar || newValue.IsAbsolute;
PseudoClasses.Set(PC_FixedWidth, isFixed);
}
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
{
recycleKey = null;
return item is FormItem or FormGroup;
return item is not FormItem && item is not FormGroup;
}
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
@@ -62,27 +68,8 @@ public class Form: ItemsControl
return new FormItem()
{
Content = control,
[!LabelProperty] = control.GetObservable(Form.LabelProperty).ToBinding(),
[!IsRequiredProperty] = control.GetObservable(Form.IsRequiredProperty).ToBinding(),
[!FormItem.LabelProperty] = control[!FormItem.LabelProperty],
[!FormItem.IsRequiredProperty] = control[!FormItem.IsRequiredProperty],
};
}
protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
{
base.PrepareContainerForItemOverride(container, item, index);
if (container is FormGroup group)
{
if (!group.IsSet(FormGroup.LabelWidthProperty))
{
group[!LabelWidthProperty] = this.GetObservable(LabelWidthProperty).ToBinding();
}
}
else if (container is FormItem formItem)
{
if(!formItem.IsSet(FormItem.LabelWidthProperty))
{
formItem[!LabelWidthProperty] = this.GetObservable(LabelWidthProperty).ToBinding();
}
}
}
}

View File

@@ -6,13 +6,6 @@ namespace Ursa.Controls;
public class FormGroup: HeaderedItemsControl
{
public static readonly StyledProperty<GridLength> LabelWidthProperty = Form.LabelWidthProperty.AddOwner<FormGroup>();
public GridLength LabelWidth
{
get => GetValue(LabelWidthProperty);
set => SetValue(LabelWidthProperty, value);
}
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
{
recycleKey = null;
@@ -25,17 +18,9 @@ public class FormGroup: HeaderedItemsControl
return new FormItem
{
Content = control,
[!FormItem.LabelProperty] = control.GetObservable(Form.LabelProperty).ToBinding(),
[!FormItem.IsRequiredProperty] = control.GetObservable(Form.IsRequiredProperty).ToBinding(),
[!FormItem.LabelProperty] = control[!FormItem.LabelProperty],
[!FormItem.IsRequiredProperty] = control[!FormItem.IsRequiredProperty],
};
}
protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
{
base.PrepareContainerForItemOverride(container, item, index);
if (container is FormItem formItem)
{
formItem.LabelWidth = LabelWidth;
}
}
}

View File

@@ -1,46 +1,93 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
using Avalonia.Reactive;
using Avalonia.VisualTree;
using Irihi.Avalonia.Shared.Helpers;
using Ursa.Common;
namespace Ursa.Controls;
[PseudoClasses(PC_Horizontal, PC_NoLabel)]
public class FormItem: ContentControl
{
public static readonly StyledProperty<object?> LabelProperty = AvaloniaProperty.Register<FormItem, object?>(
nameof(Label));
public const string PC_Horizontal = ":horizontal";
public const string PC_NoLabel = ":no-label";
#region Attached Properties
public static readonly AttachedProperty<object?> LabelProperty =
AvaloniaProperty.RegisterAttached<FormItem, Control, object?>("Label");
public static void SetLabel(Control obj, object? value) => obj.SetValue(LabelProperty, value);
public static object? GetLabel(Control obj) => obj.GetValue(LabelProperty);
public object? Label
{
get => GetValue(LabelProperty);
set => SetValue(LabelProperty, value);
}
public static readonly AttachedProperty<bool> IsRequiredProperty =
AvaloniaProperty.RegisterAttached<FormItem, Control, bool>("IsRequired");
public static void SetIsRequired(Control obj, bool value) => obj.SetValue(IsRequiredProperty, value);
public static bool GetIsRequired(Control obj) => obj.GetValue(IsRequiredProperty);
public static readonly StyledProperty<bool> IsRequiredProperty = AvaloniaProperty.Register<FormItem, bool>(
nameof(IsRequired));
public static readonly AttachedProperty<bool> NoLabelProperty =
AvaloniaProperty.RegisterAttached<FormItem, Control, bool>("NoLabel");
public bool IsRequired
{
get => GetValue(IsRequiredProperty);
set => SetValue(IsRequiredProperty, value);
}
public static void SetNoLabel(Control obj, bool value) => obj.SetValue(NoLabelProperty, value);
public static bool GetNoLabel(Control obj) => obj.GetValue(NoLabelProperty);
#endregion
private List<IDisposable> _formSubscriptions = new List<IDisposable>();
public static readonly StyledProperty<GridLength> LabelWidthProperty = AvaloniaProperty.Register<FormItem, GridLength>(
public static readonly StyledProperty<double> LabelWidthProperty = AvaloniaProperty.Register<FormItem, double>(
nameof(LabelWidth));
public GridLength LabelWidth
public double LabelWidth
{
get => GetValue(LabelWidthProperty);
set => SetValue(LabelWidthProperty, value);
}
public static readonly StyledProperty<HorizontalAlignment> LabelAlignmentProperty = AvaloniaProperty.Register<FormItem, HorizontalAlignment>(
nameof(LabelAlignment));
public HorizontalAlignment LabelAlignment
{
get => GetValue(LabelAlignmentProperty);
set => SetValue(LabelAlignmentProperty, value);
}
static FormItem()
{
LabelWidthProperty.Changed.AddClassHandler<FormItem, GridLength>((x, args) => x.LabelWidthChanged(args));
PropertyToPseudoClassMixin.Attach<FormItem>(NoLabelProperty, PC_NoLabel);
}
private void LabelWidthChanged(AvaloniaPropertyChangedEventArgs<GridLength> args)
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
GridLength? length = args.GetNewValue<GridLength>();
base.OnAttachedToVisualTree(e);
var form = this.GetVisualAncestors().OfType<Form>().FirstOrDefault();
if (form is not null)
{
_formSubscriptions.Clear();
var labelSubscription = form
.GetObservable(Form.LabelWidthProperty)
.Subscribe(new AnonymousObserver<GridLength>(length => { LabelWidth = length.IsAbsolute ? length.Value : double.NaN; }));
var positionSubscription = form
.GetObservable(Form.LabelPositionProperty)
.Subscribe(new AnonymousObserver<Position>(position => { PseudoClasses.Set(PC_Horizontal, position == Position.Left);}));
var alignmentSubscription = form
.GetObservable(Form.LabelAlignmentProperty)
.Subscribe(new AnonymousObserver<HorizontalAlignment>(alignment => { LabelAlignment = alignment; }));
_formSubscriptions.Add(labelSubscription);
_formSubscriptions.Add(positionSubscription);
_formSubscriptions.Add(alignmentSubscription);
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
foreach (var subscription in _formSubscriptions)
{
subscription.Dispose();
}
}
}

View File

@@ -1,6 +0,0 @@
namespace Ursa.Controls;
public class FormPanel
{
}