feat: start to refactor.

This commit is contained in:
rabbitism
2023-12-27 00:58:50 +08:00
parent 5ac95a9965
commit 0c4b6edfc2
6 changed files with 111 additions and 171 deletions

View File

@@ -144,6 +144,7 @@
IPAddress="{Binding Address}" /> IPAddress="{Binding Address}" />
<u:IPv4Box HorizontalAlignment="Stretch" IsEnabled="False" /> <u:IPv4Box HorizontalAlignment="Stretch" IsEnabled="False" />
</StackPanel> </StackPanel>
<!--
<u:Timeline Grid.Column="1"> <u:Timeline Grid.Column="1">
<u:TimelineItem <u:TimelineItem
Content="ToDo" Content="ToDo"
@@ -166,6 +167,7 @@
ItemType="Error" ItemType="Error"
Time="2022-01-05" /> Time="2022-01-05" />
</u:Timeline> </u:Timeline>
-->
<StackPanel Grid.Column="2" Spacing="20"> <StackPanel Grid.Column="2" Spacing="20">
<u:ButtonGroup Classes="Primary Solid" ItemsSource="{Binding ButtonGroupItems}" /> <u:ButtonGroup Classes="Primary Solid" ItemsSource="{Binding ButtonGroupItems}" />
<u:ButtonGroup Classes="Primary" ItemsSource="{Binding ButtonGroupItems}" /> <u:ButtonGroup Classes="Primary" ItemsSource="{Binding ButtonGroupItems}" />

View File

@@ -8,51 +8,19 @@
xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels" xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
x:CompileBindings="False"
x:DataType="viewModels:TimelineDemoViewModel" x:DataType="viewModels:TimelineDemoViewModel"
x:CompileBindings="True"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources>
<u:TimelineFormatConverter x:Key="FormatConverter" />
</UserControl.Resources>
<StackPanel> <StackPanel>
<u:Timeline> <u:Timeline ItemsSource="{Binding Items}"
<u:TimelineItem HeaderMemberBinding="{ReflectionBinding Header}"
Content="Start" DescriptionMemberBinding="{ReflectionBinding Description}"
ItemType="Warning" >
Time="2022-01-01" />
<u:TimelineItem
Content="In between"
ItemType="Ongoing"
Time="2022-01-02" />
<u:TimelineItem
Content="Finished"
ItemType="Error"
Time="2022-01-03" />
<u:TimelineItem
Content="Finished"
IconForeground="Yellow"
ItemType="Default"
Time="2022-01-03" />
</u:Timeline> </u:Timeline>
<u:Timeline HorizontalAlignment="Left" ItemsSource="{Binding Items}"> <u:Timeline>
<u:Timeline.ItemTemplate> <u:TimelineItem Header="第一步" Content="Step 1"></u:TimelineItem>
<DataTemplate x:DataType="viewModels:TimelineItemViewModel"> <u:TimelineItem Header="第二步" Content="Step 2"></u:TimelineItem>
<u:TimelineItem <u:TimelineItem Header="第三步" Content="Step 3"></u:TimelineItem>
Content="{Binding Content}"
ItemType="{Binding ItemType}"
Time="{Binding Time}"
TimeFormat="{Binding TimeFormat}">
<u:TimelineItem.ContentTemplate>
<DataTemplate>
<TextBlock
MaxWidth="100"
Text="{Binding}"
TextWrapping="Wrap" />
</DataTemplate>
</u:TimelineItem.ContentTemplate>
</u:TimelineItem>
</DataTemplate>
</u:Timeline.ItemTemplate>
</u:Timeline> </u:Timeline>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@@ -13,7 +13,7 @@ public class TimelineDemoViewModel: ViewModelBase
Time = DateTime.Now, Time = DateTime.Now,
TimeFormat = "yyyy-MM-dd HH:mm:ss", TimeFormat = "yyyy-MM-dd HH:mm:ss",
Description = "Item 1", Description = "Item 1",
Content = "First", Header = "审核中",
ItemType = TimelineItemType.Success, ItemType = TimelineItemType.Success,
}, },
new() new()
@@ -21,7 +21,7 @@ public class TimelineDemoViewModel: ViewModelBase
Time = DateTime.Now, Time = DateTime.Now,
TimeFormat = "HH:mm:ss", TimeFormat = "HH:mm:ss",
Description = "Item 2", Description = "Item 2",
Content = "Content 2", Header = "发布成功",
ItemType = TimelineItemType.Success, ItemType = TimelineItemType.Success,
}, },
new() new()
@@ -29,23 +29,9 @@ public class TimelineDemoViewModel: ViewModelBase
Time = DateTime.Now, Time = DateTime.Now,
TimeFormat = "HH:mm:ss", TimeFormat = "HH:mm:ss",
Description = "Item 3", Description = "Item 3",
Content = "Content 3", Header = "审核失败",
ItemType = TimelineItemType.Ongoing, ItemType = TimelineItemType.Ongoing,
}, }
new()
{
Time = DateTime.Now,
TimeFormat = "HH:mm:ss",
Description = "Item 4",
Content = "Content 4"
},
new()
{
Time = DateTime.Now,
TimeFormat = "HH:mm:ss",
Description = "Item 5",
Content = "Content 5"
},
}; };
} }
@@ -54,6 +40,6 @@ public class TimelineItemViewModel: ObservableObject
public DateTime Time { get; set; } public DateTime Time { get; set; }
public string? TimeFormat { get; set; } public string? TimeFormat { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public string? Content { get; set; } public string? Header { get; set; }
public TimelineItemType ItemType { get; set; } public TimelineItemType ItemType { get; set; }
} }

View File

@@ -6,9 +6,9 @@
<Design.PreviewWith> <Design.PreviewWith>
<StackPanel Width="100" Spacing="20"> <StackPanel Width="100" Spacing="20">
<u:Timeline> <u:Timeline>
<u:TimelineItem Content="Hello" Time="2022-01-01" /> <u:TimelineItem Content="Hello" />
<u:TimelineItem Content="World" Time="2022-02-01" /> <u:TimelineItem Content="World" />
<u:TimelineItem Content="!" Time="2022-03-01" /> <u:TimelineItem Content="!" />
<u:TimelineItem /> <u:TimelineItem />
</u:Timeline> </u:Timeline>
</StackPanel> </StackPanel>
@@ -81,13 +81,9 @@
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Foreground="Gray"> Foreground="Gray">
<ContentPresenter.Content>
<MultiBinding Converter="{StaticResource FormatConverter}">
<Binding Path="Time" RelativeSource="{RelativeSource TemplatedParent}" />
<Binding Path="TimeFormat" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</ContentPresenter.Content>
</ContentPresenter> </ContentPresenter>
<ContentPresenter <ContentPresenter
Name="content" Name="content"

View File

@@ -1,56 +1,104 @@
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.ComTypes;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Metadata;
namespace Ursa.Controls; namespace Ursa.Controls;
public class Timeline: ItemsControl public class Timeline: ItemsControl
{ {
public static readonly StyledProperty<IBinding?> IconMemberBindingProperty = AvaloniaProperty.Register<Timeline, IBinding?>(
nameof(IconMemberBinding));
public static readonly StyledProperty<IDataTemplate?> ItemDescriptionTemplateProperty = AvaloniaProperty.Register<Timeline, IDataTemplate?>( [AssignBinding]
nameof(ItemDescriptionTemplate)); [InheritDataTypeFromItems(nameof(ItemsSource))]
public IBinding? IconMemberBinding
public IDataTemplate? ItemDescriptionTemplate
{ {
get => GetValue(ItemDescriptionTemplateProperty); get => GetValue(IconMemberBindingProperty);
set => SetValue(ItemDescriptionTemplateProperty, value); set => SetValue(IconMemberBindingProperty, value);
} }
public Timeline() public static readonly StyledProperty<IBinding?> HeaderMemberBindingProperty = AvaloniaProperty.Register<Timeline, IBinding?>(
nameof(HeaderMemberBinding));
[AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))]
public IBinding? HeaderMemberBinding
{ {
ItemsView.CollectionChanged+=ItemsViewOnCollectionChanged; get => GetValue(HeaderMemberBindingProperty);
set => SetValue(HeaderMemberBindingProperty, value);
} }
private void ItemsViewOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) public static readonly StyledProperty<IBinding?> DescriptionMemberBindingProperty = AvaloniaProperty.Register<Timeline, IBinding?>(
nameof(DescriptionMemberBinding));
[AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))]
public IBinding? DescriptionMemberBinding
{ {
RefreshTimelineItems(); get => GetValue(DescriptionMemberBindingProperty);
set => SetValue(DescriptionMemberBindingProperty, value);
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
public static readonly StyledProperty<IDataTemplate?> IconTemplateProperty = AvaloniaProperty.Register<Timeline, IDataTemplate?>(
nameof(IconTemplate));
[InheritDataTypeFromItems(nameof(ItemsSource))]
public IDataTemplate? IconTemplate
{ {
base.OnPropertyChanged(change); get => GetValue(IconTemplateProperty);
RefreshTimelineItems(); set => SetValue(IconTemplateProperty, value);
} }
private void RefreshTimelineItems() public static readonly StyledProperty<IDataTemplate?> DescriptionTemplateProperty = AvaloniaProperty.Register<Timeline, IDataTemplate?>(
nameof(DescriptionTemplate));
[InheritDataTypeFromItems(nameof(ItemsSource))]
public IDataTemplate? DescriptionTemplate
{ {
for (int i = 0; i < this.LogicalChildren.Count; i++) get => GetValue(DescriptionTemplateProperty);
set => SetValue(DescriptionTemplateProperty, value);
}
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
{
recycleKey = null;
return item is not TimelineItem;
}
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{
if (item is TimelineItem t) return t;
return new TimelineItem();
}
protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
{
base.PrepareContainerForItemOverride(container, item, index);
if (container is TimelineItem t)
{ {
if (this.LogicalChildren[i] is TimelineItem t) if (IconMemberBinding != null)
{ {
t.SetIndex(i == 0, i == this.LogicalChildren.Count - 1); t.Bind(TimelineItem.IconProperty, IconMemberBinding);
} }
else if (this.LogicalChildren[i] is ContentPresenter { Child: TimelineItem t2 }) if (HeaderMemberBinding != null)
{ {
t2.SetIndex(i == 0, i == this.LogicalChildren.Count - 1); t.Bind(HeaderedContentControl.HeaderProperty, HeaderMemberBinding);
} }
if (DescriptionMemberBinding != null)
{
t.Bind(ContentControl.ContentProperty, DescriptionMemberBinding);
}
t.SetCurrentValue(TimelineItem.IconTemplateProperty, IconTemplate);
t.SetCurrentValue(HeaderedContentControl.HeaderTemplateProperty, ItemTemplate);
t.SetCurrentValue(ContentControl.ContentTemplateProperty, DescriptionTemplate);
} }
}
}
} }

View File

@@ -1,101 +1,41 @@
using System.Globalization;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Media; using Avalonia.Media;
namespace Ursa.Controls; namespace Ursa.Controls;
[PseudoClasses(PC_First, PC_Last, PC_Default, PC_Ongoing, PC_Success, PC_Warning, PC_Error, PC_None)] public class TimelineItem: HeaderedContentControl
public class TimelineItem: ContentControl
{ {
private const string PC_First = ":first"; public static readonly StyledProperty<object?> IconProperty = AvaloniaProperty.Register<TimelineItem, object?>(
private const string PC_Last = ":last"; nameof(Icon));
private const string PC_Default = ":default";
private const string PC_Ongoing = ":ongoing";
private const string PC_Success = ":success";
private const string PC_Warning = ":warning";
private const string PC_Error = ":error";
private const string PC_None = ":none";
private static readonly IReadOnlyDictionary<TimelineItemType, string> _itemTypeMapping = new Dictionary<TimelineItemType, string> public object? Icon
{ {
{TimelineItemType.Default, PC_Default}, get => GetValue(IconProperty);
{TimelineItemType.Ongoing, PC_Ongoing}, set => SetValue(IconProperty, value);
{TimelineItemType.Success, PC_Success},
{TimelineItemType.Warning, PC_Warning},
{TimelineItemType.Error, PC_Error},
};
public static readonly StyledProperty<IBrush> IconForegroundProperty =
AvaloniaProperty.Register<TimelineItem, IBrush>(nameof(IconForeground));
public IBrush IconForeground
{
get => GetValue(IconForegroundProperty);
set => SetValue(IconForegroundProperty, value);
} }
public static readonly StyledProperty<DateTime> TimeProperty = AvaloniaProperty.Register<TimelineItem, DateTime>( public static readonly StyledProperty<IDataTemplate?> IconTemplateProperty = AvaloniaProperty.Register<TimelineItem, IDataTemplate?>(
nameof(Time)); nameof(IconTemplate));
public DateTime Time
public IDataTemplate? IconTemplate
{ {
get => GetValue(TimeProperty); get => GetValue(IconTemplateProperty);
set => SetValue(TimeProperty, value); set => SetValue(IconTemplateProperty, value);
} }
public static readonly StyledProperty<string?> TimeFormatProperty = AvaloniaProperty.Register<TimelineItem, string?>( public static readonly StyledProperty<TimelineItemType> TypeProperty = AvaloniaProperty.Register<TimelineItem, TimelineItemType>(
nameof(TimeFormat), defaultValue:CultureInfo.CurrentUICulture.DateTimeFormat.ShortDatePattern); nameof(Type));
public string? TimeFormat public TimelineItemType Type
{ {
get => GetValue(TimeFormatProperty); get => GetValue(TypeProperty);
set => SetValue(TimeFormatProperty, value); set => SetValue(TypeProperty, value);
} }
public static readonly StyledProperty<IDataTemplate> DescriptionTemplateProperty = AvaloniaProperty.Register<TimelineItem, IDataTemplate>(
nameof(DescriptionTemplate));
public IDataTemplate DescriptionTemplate
{
get => GetValue(DescriptionTemplateProperty);
set => SetValue(DescriptionTemplateProperty, value);
}
public static readonly StyledProperty<TimelineItemType> ItemTypeProperty = AvaloniaProperty.Register<TimelineItem, TimelineItemType>(
nameof(ItemType));
public TimelineItemType ItemType
{
get => GetValue(ItemTypeProperty);
set => SetValue(ItemTypeProperty, value);
}
internal void SetIndex(bool isFirst, bool isLast)
{
PseudoClasses.Set(PC_First, isFirst);
PseudoClasses.Set(PC_Last, isLast);
}
static TimelineItem()
{
ItemTypeProperty.Changed.AddClassHandler<TimelineItem>((o, e) => { o.OnItemTypeChanged(e); });
IconForegroundProperty.Changed.AddClassHandler<TimelineItem>((o, e) => { o.OnIconForegroundChanged(e); });
}
private void OnItemTypeChanged(AvaloniaPropertyChangedEventArgs args)
{
var oldValue = args.GetOldValue<TimelineItemType>();
var newValue = args.GetNewValue<TimelineItemType>();
PseudoClasses.Set(_itemTypeMapping[oldValue], false);
PseudoClasses.Set(_itemTypeMapping[newValue], true);
}
private void OnIconForegroundChanged(AvaloniaPropertyChangedEventArgs args)
{
IBrush? newValue = args.GetOldValue<IBrush?>();
PseudoClasses.Set(PC_None, newValue is null);
}
} }