Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form Dynamic Generation #482

Merged
merged 2 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions demo/Ursa.Demo/DataTemplates/FormDataTemplateSelector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Ursa.Demo.ViewModels;

namespace Ursa.Demo.Converters;

public class FormDataTemplateSelector: ResourceDictionary, IDataTemplate
{
public Control? Build(object? param)
{
if (param is null) return null;
var type = param.GetType();
if (this.TryGetResource(type, null, out var template) && template is IDataTemplate dataTemplate)
{
return dataTemplate.Build(param);
}
return null;
}

public bool Match(object? data)
{
return data is IFromItemViewModel;
}
}
26 changes: 26 additions & 0 deletions demo/Ursa.Demo/Pages/FormDemo.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:u="https://irihi.tech/ursa"
xmlns:vm="clr-namespace:Ursa.Demo.ViewModels;assembly=Ursa.Demo"
xmlns:converters="clr-namespace:Ursa.Demo.Converters"
d:DesignHeight="450"
d:DesignWidth="800"
x:CompileBindings="True"
Expand Down Expand Up @@ -50,6 +51,31 @@
<TextBox Width="300" u:FormItem.Label="Name" Text="{Binding Name}"/>
<TextBox Width="300" u:FormItem.Label="Email" Text="{Binding Email}" />
</u:Form>
<u:Divider Content="MVVM setup for dynamic items. " />
<u:Form ItemsSource="{Binding FormGroups}" HorizontalAlignment="Stretch" LabelPosition="Left" LabelWidth="*">
<u:Form.Styles>
<Style Selector="u|FormGroup" x:DataType="vm:IFormGroupViewModel">
<Setter Property="Header" Value="{Binding Title}" />
<Setter Property="ItemsSource" Value="{Binding Items}" />
</Style>
<Style Selector="u|FormItem" x:DataType="vm:IFromItemViewModel">
<Setter Property="Label" Value="{Binding Label}" />
</Style>
</u:Form.Styles>
<u:Form.ItemTemplate>
<converters:FormDataTemplateSelector>
<DataTemplate x:Key="{x:Type vm:FormTextViewModel}" DataType="vm:FormTextViewModel">
<TextBox Text="{Binding Value}"/>
</DataTemplate>
<DataTemplate x:Key="{x:Type vm:FormAgeViewModel}" DataType="vm:FormAgeViewModel">
<u:NumericUIntUpDown Value="{Binding Age}"/>
</DataTemplate>
<DataTemplate x:Key="{x:Type vm:FormDateRangeViewModel}" DataType="vm:FormDateRangeViewModel">
<u:DateRangePicker SelectedStartDate="{Binding Start}" SelectedEndDate="{Binding End}"/>
</DataTemplate>
</converters:FormDataTemplateSelector>
</u:Form.ItemTemplate>
</u:Form>
</StackPanel>
</ScrollViewer>
</UserControl>
89 changes: 78 additions & 11 deletions demo/Ursa.Demo/ViewModels/FormDemoViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
using Irihi.Avalonia.Shared.Contracts;

namespace Ursa.Demo.ViewModels;

Expand All @@ -11,48 +13,113 @@ public partial class FormDemoViewModel : ObservableObject
public FormDemoViewModel()
{
Model = new DataModel();
FormGroups = new ObservableCollection<IFormElement>
{
new FormGroupViewModel
{
Title = "Basic Information",
Items = new ObservableCollection<IFromItemViewModel>
{
new FormTextViewModel { Label = "Name" },
new FormAgeViewModel { Label = "Age" },
new FormTextViewModel { Label = "Email" }
}
},
new FormGroupViewModel
{
Title = "Education Information",
Items = new ObservableCollection<IFromItemViewModel>
{
new FormTextViewModel { Label = "College" },
new FormDateRangeViewModel { Label = "Study Time" }
}
},
new FormTextViewModel(){ Label = "Other" }
};
}

public ObservableCollection<IFormElement> FormGroups { get; set; }
}

public partial class DataModel : ObservableObject
public class DataModel : ObservableObject
{
private DateTime _date;

private string _email = string.Empty;
private string _name = string.Empty;

private double _number;

public DataModel()
{
Date = DateTime.Today;
}

[MinLength(10)]
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}

private double _number;

[Range(0.0, 10.0)]
public double Number
{
get => _number;
set => SetProperty(ref _number, value);
}

private string _email = string.Empty;

[EmailAddress]
public string Email
{
get => _email;
set => SetProperty(ref _email, value);
}

private DateTime _date;

public DateTime Date
{
get => _date;
set => SetProperty(ref _date, value);
}
}

public DataModel()
{
Date = DateTime.Today;
}
public interface IFormElement
{

}

public interface IFormGroupViewModel : IFormGroup, IFormElement
{
public string? Title { get; set; }
public ObservableCollection<IFromItemViewModel> Items { get; set; }
}

public interface IFromItemViewModel: IFormElement
{
public string? Label { get; set; }
}

public partial class FormGroupViewModel : ObservableObject, IFormGroupViewModel
{
[ObservableProperty] private string? _title;
public ObservableCollection<IFromItemViewModel> Items { get; set; } = [];
}

public partial class FormTextViewModel : ObservableObject, IFromItemViewModel
{
[ObservableProperty] private string? _label;
[ObservableProperty] private string? _value;
}

public partial class FormAgeViewModel : ObservableObject, IFromItemViewModel
{
[ObservableProperty] private uint? _age;
[ObservableProperty] private string? _label;
}

public partial class FormDateRangeViewModel : ObservableObject, IFromItemViewModel
{
[ObservableProperty] private DateTime? _end;
[ObservableProperty] private string? _label;
[ObservableProperty] private DateTime? _start;
}
3 changes: 3 additions & 0 deletions src/Ursa.Themes.Semi/Controls/Form.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
</StackPanel>
<ContentPresenter
Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" />
</StackPanel>
</ControlTemplate>
Expand Down Expand Up @@ -111,6 +112,7 @@
Grid.Column="1"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
Expand All @@ -120,6 +122,7 @@
<Setter Property="Template">
<ControlTemplate TargetType="u:FormItem">
<ContentPresenter
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" />
</ControlTemplate>
</Setter>
Expand Down
20 changes: 19 additions & 1 deletion src/Ursa/Controls/Form/Form.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Layout;
using Irihi.Avalonia.Shared.Contracts;
using Ursa.Common;

namespace Ursa.Controls;
Expand Down Expand Up @@ -64,7 +65,11 @@ protected override bool NeedsContainerOverride(object? item, int index, out obje

protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{
if (item is not Control control) return new FormItem();
if (item is not Control control)
{
if (item is IFormGroup) return new FormGroup();
return new FormItem();
}
return new FormItem()
{
Content = control,
Expand All @@ -73,4 +78,17 @@ protected override Control CreateContainerForItemOverride(object? item, int inde
[!FormItem.NoLabelProperty] = control[!FormItem.NoLabelProperty],
};
}

protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
{
base.PrepareContainerForItemOverride(container, item, index);
if(container is FormItem formItem && !formItem.IsSet(ContentControl.ContentTemplateProperty))
{
formItem.ContentTemplate = ItemTemplate;
}
if (container is FormGroup group && !group.IsSet(FormGroup.ItemTemplateProperty))
{
group.ItemTemplate = ItemTemplate;
}
}
}
10 changes: 9 additions & 1 deletion src/Ursa/Controls/Form/FormGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,13 @@ protected override Control CreateContainerForItemOverride(object? item, int inde
[!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.IsSet(ContentControl.ContentTemplateProperty))
{
formItem.ContentTemplate = ItemTemplate;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Metadata;

namespace HeadlessTest.Ursa.Controls.FormTests.Dynamic_Item_Generation;

internal class DataTemplateSelector : IDataTemplate
{
[Content] public Dictionary<Type, IDataTemplate?> Templates { get; } = new();

public Control? Build(object? param)
{
if (param is null) return null;
var type = param.GetType();
return Templates.TryGetValue(type, out var template) ? template?.Build(param) : null;
}

public bool Match(object? data)
{
return data is IFromItemViewModel;
}
}
Loading