[New Control] Descriptions (#791)

* feat: add ColumnWrapPanel.

* feat: add Descriptions and DescriptionsItem controls

* WIP: extract a LabeledContentControl for this particular need.

* wip.

* feat: setup demo. fix various initial status issue.

* fix: fix a layout issue.

* feat: improve demo.

* feat: update demo.

* fix: remove a redundant calculation.

* feat: update per copilot feedback.

* feat: add tests, fix a calculation issue.

* feat: refactor to change the default label and value binding assignment logic.

* feat: introduce orientation.

* misc: format codes.

* feat: clarify LabelPosition property.

* feat: extract Font resources.

* revert AvatarDemo.

* feat: assign specific value to Font resources for testing.

* misc: resolve copilot comment.

---------

Co-authored-by: Zhang Dian <54255897+zdpcdt@users.noreply.github.com>
This commit is contained in:
Dong Bin
2025-10-22 20:49:02 +08:00
committed by GitHub
parent c6ac019a4e
commit dcaef1c8ed
22 changed files with 1076 additions and 32 deletions

View File

@@ -15,20 +15,28 @@
<vm:MainWindowViewModel />
</Design.DataContext>
<StackPanel>
<Button Content="???" Click="Button_OnClick"></Button>
<ContentControl Name="content">
<ContentControl.DataTemplates>
<DataTemplate DataType="x:Int32">
<u:Breadcrumb Separator="·" Classes="Margin">
<u:BreadcrumbItem Content="a" />
<u:BreadcrumbItem Content="b" />
<u:BreadcrumbItem Content="c" />
</u:Breadcrumb>
</DataTemplate>
<DataTemplate DataType="x:Double">
<TextBlock Text="Hello World"/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
<StackPanel.Styles>
<Style Selector="Button">
<Setter Property="Margin" Value="10" />
</Style>
</StackPanel.Styles>
<u:ColumnWrapPanel Column="4" Width="500" Background="AntiqueWhite">
<Button Content="Hello" Theme="{DynamicResource SolidButton}" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" Width="200" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" Width="200" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" Width="150" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" Width="200" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" Width="200" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" />
<Button Content="Hello" Theme="{DynamicResource SolidButton}" />
</u:ColumnWrapPanel>
</StackPanel>
</Window>

View File

@@ -10,16 +10,4 @@ public partial class MainWindow : Window
{
InitializeComponent();
}
private async void Button_OnClick(object? sender, RoutedEventArgs e)
{
if (content.Content is int s)
{
content.Content = 1.1;
}
else
{
content.Content = 1;
}
}
}

View File

@@ -0,0 +1,124 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:u="https://irihi.tech/ursa"
xmlns:vm="clr-namespace:Ursa.Demo.ViewModels"
xmlns:common="clr-namespace:Ursa.Common;assembly=Ursa"
mc:Ignorable="d" d:DesignWidth="800"
x:DataType="vm:DescriptionsDemoViewModel"
x:Class="Ursa.Demo.Pages.DescriptionsDemo">
<Design.DataContext>
<vm:DescriptionsDemoViewModel />
</Design.DataContext>
<ScrollViewer>
<StackPanel Spacing="20">
<TextBlock Text="Descriptions support different orientations and item alignments. " />
<u:Form LabelPosition="Left">
<u:EnumSelector
Name="orientation"
u:FormItem.Label="Orientation"
EnumType="Orientation"
Value="{x:Static Orientation.Vertical}" />
<u:EnumSelector
Name="labelPosition"
u:FormItem.Label="Label Position"
EnumType="common:Position"
Value="{x:Static common:Position.Left}" />
<u:EnumSelector
Name="itemAlignment"
u:FormItem.Label="Item Alignment"
EnumType="common:ItemAlignment"
Value="{x:Static common:ItemAlignment.Center}" />
</u:Form>
<u:Descriptions
HorizontalAlignment="Left"
ItemsSource="{Binding Items}"
LabelMemberBinding="{Binding Label}"
DisplayMemberBinding="{Binding Description}"
Orientation="{Binding #orientation.Value}"
LabelPosition="{Binding #labelPosition.Value}"
ItemAlignment="{Binding #itemAlignment.Value}"
MaxWidth="400" />
<TextBlock Text="Descriptions support XAML inline declaration. " />
<u:Descriptions
MaxWidth="400"
HorizontalAlignment="Left"
Orientation="{Binding #orientation.Value}"
LabelPosition="{Binding #labelPosition.Value}"
ItemAlignment="{Binding #itemAlignment.Value}">
<u:DescriptionsItem Label="实际用户数量" Content="1,480,000" />
<u:DescriptionsItem Label="7天留存">
<StackPanel Orientation="Horizontal">
<TextBlock Text="98%" />
<PathIcon
Theme="{DynamicResource InnerPathIcon}"
Classes="Small"
Margin="2 0 0 0"
Data="{DynamicResource SemiIconArrowUp}"
Foreground="{DynamicResource SemiGreen5}" />
</StackPanel>
</u:DescriptionsItem>
<u:DescriptionsItem Label="安全等级" Content="3级" />
<u:DescriptionsItem Label="垂类标签">
<Label Theme="{DynamicResource TagLabel}" Content="电商" />
</u:DescriptionsItem>
<u:DescriptionsItem Label="认证状态" Content="未认证" />
</u:Descriptions>
<TextBlock Text="Use ColumnWrapPanel to display horizontally" />
<u:Descriptions
ItemsSource="{Binding Items2}"
LabelMemberBinding="{Binding Label}"
ItemAlignment="Plain">
<u:Descriptions.ItemsPanel>
<ItemsPanelTemplate>
<u:ColumnWrapPanel Column="5" />
</ItemsPanelTemplate>
</u:Descriptions.ItemsPanel>
<u:Descriptions.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Description}"
Margin="0 0 8 0" />
</DataTemplate>
</u:Descriptions.ItemTemplate>
</u:Descriptions>
<TextBlock Text="Setting Orientation to Horizontal support Classes Small and Large. " />
<StackPanel>
<Border Theme="{DynamicResource CardBorder}">
<u:Descriptions
Classes="Small"
Orientation="Horizontal"
LabelPosition="Top">
<u:DescriptionsItem Label="实际用户数量" Content="1,480,000" />
<u:DescriptionsItem Label="7天留存" Content="98%" />
<u:DescriptionsItem Label="安全等级" Content="3级" />
</u:Descriptions>
</Border>
<Border Theme="{DynamicResource CardBorder}">
<u:Descriptions
Orientation="Horizontal"
LabelPosition="Top">
<u:DescriptionsItem Label="实际用户数量" Content="1,480,000" />
<u:DescriptionsItem Label="7天留存" Content="98%" />
<u:DescriptionsItem Label="安全等级" Content="3级" />
</u:Descriptions>
</Border>
<Border Theme="{DynamicResource CardBorder}">
<u:Descriptions
Classes="Large"
Orientation="Horizontal"
LabelPosition="Top">
<u:DescriptionsItem Label="实际用户数量" Content="1,480,000" />
<u:DescriptionsItem Label="7天留存" Content="98%" />
<u:DescriptionsItem Label="安全等级" Content="3级" />
</u:Descriptions>
</Border>
</StackPanel>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace Ursa.Demo.Pages;
public partial class DescriptionsDemo : UserControl
{
public DescriptionsDemo()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace Ursa.Demo.ViewModels;
public partial class DescriptionsDemoViewModel : ObservableObject
{
public ObservableCollection<DescriptionItemViewModel> Items { get; set; }
public ObservableCollection<DescriptionItemViewModel> Items2 { get; set; }
public DescriptionsDemoViewModel()
{
Items = new ObservableCollection<DescriptionItemViewModel>()
{
new() { Label = "Actual Users", Description = "1,480,000" },
new() { Label = "7-day Retention", Description = "98%" },
new() { Label = "Security Level", Description = "III" },
new() { Label = "Category Tag", Description = "E-commerce" },
new() { Label = "Authorized State", Description = "Unauthorized" },
};
Items2 = new ObservableCollection<DescriptionItemViewModel>()
{
new() { Label = "抖音号", Description = "SemiDesign" },
new() { Label = "主播类型", Description = "自由主播" },
new() { Label = "安全等级", Description = "3级" },
new() { Label = "垂类标签", Description = "编程" },
new() { Label = "作品数量", Description = "88888888" },
new() { Label = "认证状态", Description = "这是一个很长很长很长很长很长很长很长很长很长的值" },
new() { Label = "上次直播时间", Description = "2024-05-01 12:00:00" }
};
}
}
public partial class DescriptionItemViewModel : ObservableObject
{
[ObservableProperty] private string? _label;
[ObservableProperty] private object? _description;
}

View File

@@ -47,6 +47,7 @@ public partial class MainViewViewModel : ViewModelBase
MenuKeys.MenuKeyDatePicker => new DatePickerDemoViewModel(),
MenuKeys.MenuKeyDateRangePicker => new DateRangePickerDemoViewModel(),
MenuKeys.MenuKeyDateTimePicker => new DateTimePickerDemoViewModel(),
MenuKeys.MenuKeyDescriptions => new DescriptionsDemoViewModel(),
MenuKeys.MenuKeyDialog => new DialogDemoViewModel(),
MenuKeys.MenuKeyDisableContainer => new DisableContainerDemoViewModel(),
MenuKeys.MenuKeyDivider => new DividerDemoViewModel(),

View File

@@ -24,7 +24,10 @@ public class MenuViewModel : ViewModelBase
new() { MenuHeader = "KeyGestureInput", Key = MenuKeys.MenuKeyKeyGestureInput },
new() { MenuHeader = "IPv4Box", Key = MenuKeys.MenuKeyIpBox },
new() { MenuHeader = "MultiComboBox", Key = MenuKeys.MenuKeyMultiComboBox },
new() { MenuHeader = "Multi AutoCompleteBox", Key = MenuKeys.MenuKeyMultiAutoCompleteBox, Status = "New" },
new()
{
MenuHeader = "Multi AutoCompleteBox", Key = MenuKeys.MenuKeyMultiAutoCompleteBox, Status = "New"
},
new() { MenuHeader = "Numeric UpDown", Key = MenuKeys.MenuKeyNumericUpDown },
new() { MenuHeader = "NumPad", Key = MenuKeys.MenuKeyNumPad },
new() { MenuHeader = "PathPicker", Key = MenuKeys.MenuKeyPathPicker, Status = "New" },
@@ -56,11 +59,17 @@ public class MenuViewModel : ViewModelBase
MenuHeader = "Date & Time", Children = new ObservableCollection<MenuItemViewModel>
{
new() { MenuHeader = "Date Picker", Key = MenuKeys.MenuKeyDatePicker, Status = "Updated" },
new() { MenuHeader = "Date Range Picker", Key = MenuKeys.MenuKeyDateRangePicker, Status = "Updated" },
new()
{
MenuHeader = "Date Range Picker", Key = MenuKeys.MenuKeyDateRangePicker, Status = "Updated"
},
new() { MenuHeader = "Date Time Picker", Key = MenuKeys.MenuKeyDateTimePicker, Status = "Updated" },
new() { MenuHeader = "Time Box", Key = MenuKeys.MenuKeyTimeBox },
new() { MenuHeader = "Time Picker", Key = MenuKeys.MenuKeyTimePicker, Status = "Updated" },
new() { MenuHeader = "Time Range Picker", Key = MenuKeys.MenuKeyTimeRangePicker, Status = "Updated" },
new()
{
MenuHeader = "Time Range Picker", Key = MenuKeys.MenuKeyTimeRangePicker, Status = "Updated"
},
new() { MenuHeader = "Clock", Key = MenuKeys.MenuKeyClock }
}
},
@@ -83,13 +92,14 @@ public class MenuViewModel : ViewModelBase
new() { MenuHeader = "AspectRatioLayout", Key = MenuKeys.MenuKeyAspectRatioLayout },
new() { MenuHeader = "Avatar", Key = MenuKeys.MenuKeyAvatar, Status = "WIP" },
new() { MenuHeader = "Badge", Key = MenuKeys.MenuKeyBadge },
new() { MenuHeader = "Banner", Key = MenuKeys.MenuKeyBanner, Status = "Updated" },
new() { MenuHeader = "Banner", Key = MenuKeys.MenuKeyBanner },
new() { MenuHeader = "Descriptions", Key = MenuKeys.MenuKeyDescriptions, Status = "New" },
new() { MenuHeader = "Disable Container", Key = MenuKeys.MenuKeyDisableContainer },
new() { MenuHeader = "Divider", Key = MenuKeys.MenuKeyDivider },
new() { MenuHeader = "DualBadge", Key = MenuKeys.MenuKeyDualBadge },
new() { MenuHeader = "ImageViewer", Key = MenuKeys.MenuKeyImageViewer },
new() { MenuHeader = "ElasticWrapPanel", Key = MenuKeys.MenuKeyElasticWrapPanel },
new() { MenuHeader = "Marquee", Key = MenuKeys.MenuKeyMarquee, Status = "New" },
new() { MenuHeader = "Marquee", Key = MenuKeys.MenuKeyMarquee },
new() { MenuHeader = "Number Displayer", Key = MenuKeys.MenuKeyNumberDisplayer },
new() { MenuHeader = "Scroll To", Key = MenuKeys.MenuKeyScrollToButton },
new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline },
@@ -117,6 +127,7 @@ public static class MenuKeys
public const string MenuKeyDatePicker = "DatePicker";
public const string MenuKeyDateRangePicker = "DateRangePicker";
public const string MenuKeyDateTimePicker = "DateTimePicker";
public const string MenuKeyDescriptions = "Descriptions";
public const string MenuKeyDialog = "Dialog";
public const string MenuKeyDisableContainer = "DisableContainer";
public const string MenuKeyDivider = "Divider";