diff --git a/demo/Ursa.Demo/Pages/PathPickerDemo.axaml b/demo/Ursa.Demo/Pages/PathPickerDemo.axaml new file mode 100644 index 0000000..50fa8c1 --- /dev/null +++ b/demo/Ursa.Demo/Pages/PathPickerDemo.axaml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/Ursa.Demo/Pages/PathPickerDemo.axaml.cs b/demo/Ursa.Demo/Pages/PathPickerDemo.axaml.cs new file mode 100644 index 0000000..2651845 --- /dev/null +++ b/demo/Ursa.Demo/Pages/PathPickerDemo.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Ursa.Demo.Pages; + +public partial class PathPickerDemo : UserControl +{ + public PathPickerDemo() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs index 6b8d953..66038c1 100644 --- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs @@ -4,6 +4,7 @@ using Avalonia; using Avalonia.Styling; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; +using Ursa.Controls; using Ursa.Themes.Semi; namespace Ursa.Demo.ViewModels; @@ -79,6 +80,7 @@ public partial class MainViewViewModel : ViewModelBase MenuKeys.MenuKeyTreeComboBox => new TreeComboBoxDemoViewModel(), MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(), MenuKeys.AspectRatioLayout => new AspectRatioLayoutDemoViewModel(), + MenuKeys.PathPicker => new PathPickerDemoViewModel(), _ => throw new ArgumentOutOfRangeException(nameof(s), s, null) }; } diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs index 9b90094..b0afd2a 100644 --- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs @@ -59,7 +59,8 @@ public class MenuViewModel : ViewModelBase new() { MenuHeader = "ToolBar", Key = MenuKeys.MenuKeyToolBar }, new() { MenuHeader = "TreeComboBox", Key = MenuKeys.MenuKeyTreeComboBox }, new() { MenuHeader = "TwoTonePathIcon", Key = MenuKeys.MenuKeyTwoTonePathIcon }, - new() { MenuHeader = "AspectRatioLayout", Key = MenuKeys.AspectRatioLayout ,Status = "WIP"}, + new() { MenuHeader = "AspectRatioLayout", Key = MenuKeys.AspectRatioLayout, Status = "New" }, + new() { MenuHeader = "PathPicker", Key = MenuKeys.PathPicker, Status = "WIP" }, }; } } @@ -115,4 +116,5 @@ public static class MenuKeys public const string MenuKeyTreeComboBox = "TreeComboBox"; public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon"; public const string AspectRatioLayout = "AspectRatioLayout"; + public const string PathPicker = "PathPicker"; } \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/PathPickerDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/PathPickerDemoViewModel.cs new file mode 100644 index 0000000..6bb9d2a --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/PathPickerDemoViewModel.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +namespace Ursa.Demo.ViewModels; + +public partial class PathPickerDemoViewModel : ViewModelBase +{ + [ObservableProperty] private string? _path; + [ObservableProperty] private IReadOnlyList? _paths; + [ObservableProperty] private int _commandTriggerCount = 0; + + [RelayCommand] + private void Selected(IReadOnlyList paths) + { + CommandTriggerCount++; + } +} \ No newline at end of file diff --git a/src/Ursa.ReactiveUIExtension/Ursa.ReactiveUIExtension.csproj b/src/Ursa.ReactiveUIExtension/Ursa.ReactiveUIExtension.csproj index b0d99e6..a40b3b8 100644 --- a/src/Ursa.ReactiveUIExtension/Ursa.ReactiveUIExtension.csproj +++ b/src/Ursa.ReactiveUIExtension/Ursa.ReactiveUIExtension.csproj @@ -14,14 +14,14 @@ 这个是一个Ursa拓展包。这个包整合并互相兼容了UrsaWindow和UrsaView与Avalonia.ReactiveUI的功能。【Avalonia.ReactiveUI参见:https://docs.avaloniaui.net/docs/concepts/reactiveui/】 - 1.0.1 + 1.0.2 https://github.com/irihitech/Ursa.Avalonia true snupkg - + diff --git a/src/Ursa.Themes.Semi/Controls/PathPicker.axaml b/src/Ursa.Themes.Semi/Controls/PathPicker.axaml new file mode 100644 index 0000000..cc0ec95 --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/PathPicker.axaml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 0c8421c..541005b 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -55,5 +55,6 @@ + diff --git a/src/Ursa/Controls/PathPicker/PathPicker.cs b/src/Ursa/Controls/PathPicker/PathPicker.cs new file mode 100644 index 0000000..e4e69d2 --- /dev/null +++ b/src/Ursa/Controls/PathPicker/PathPicker.cs @@ -0,0 +1,310 @@ +using System.Text; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Logging; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using Irihi.Avalonia.Shared.Common; +using Irihi.Avalonia.Shared.Helpers; + +namespace Ursa.Controls; + +[TemplatePart(Name = "PART_Button", Type = typeof(Button))] +public class PathPicker : TemplatedControl +{ + public static readonly StyledProperty SuggestedStartPathProperty = + AvaloniaProperty.Register( + nameof(SuggestedStartPath), string.Empty); + + public static readonly StyledProperty UsePickerTypeProperty = + AvaloniaProperty.Register( + nameof(UsePickerType)); + + public static readonly StyledProperty SuggestedFileNameProperty = + AvaloniaProperty.Register( + nameof(SuggestedFileName), string.Empty); + + public static readonly StyledProperty FileFilterProperty = AvaloniaProperty.Register( + nameof(FileFilter), string.Empty); + + public static readonly StyledProperty TitleProperty = AvaloniaProperty.Register( + nameof(Title), string.Empty); + + public static readonly StyledProperty DefaultFileExtensionProperty = + AvaloniaProperty.Register( + nameof(DefaultFileExtension), string.Empty); + + public static readonly DirectProperty> SelectedPathsProperty = + AvaloniaProperty.RegisterDirect>( + nameof(SelectedPaths), o => o.SelectedPaths, (o, v) => o.SelectedPaths = v); + + public static readonly StyledProperty CommandProperty = AvaloniaProperty.Register( + nameof(Command)); + + public static readonly StyledProperty AllowMultipleProperty = AvaloniaProperty.Register( + nameof(AllowMultiple)); + + public static readonly StyledProperty SelectedPathsTextProperty = + AvaloniaProperty.Register( + nameof(SelectedPathsText), defaultBindingMode: BindingMode.TwoWay); + + public static readonly StyledProperty IsOmitCommandOnCancelProperty = + AvaloniaProperty.Register( + nameof(IsOmitCommandOnCancel)); + + public static readonly StyledProperty IsClearSelectionOnCancelProperty = + AvaloniaProperty.Register( + nameof(IsClearSelectionOnCancel)); + + public bool IsClearSelectionOnCancel + { + get => GetValue(IsClearSelectionOnCancelProperty); + set => SetValue(IsClearSelectionOnCancelProperty, value); + } + + public bool IsOmitCommandOnCancel + { + get => GetValue(IsOmitCommandOnCancelProperty); + set => SetValue(IsOmitCommandOnCancelProperty, value); + } + + public string? SelectedPathsText + { + get => GetValue(SelectedPathsTextProperty); + set => SetValue(SelectedPathsTextProperty, value); + } + + private Button? _button; + + private IReadOnlyList _selectedPaths = []; + + public bool AllowMultiple + { + get => GetValue(AllowMultipleProperty); + set => SetValue(AllowMultipleProperty, value); + } + + public ICommand? Command + { + get => GetValue(CommandProperty); + set => SetValue(CommandProperty, value); + } + + public IReadOnlyList SelectedPaths + { + get => _selectedPaths; + private set => SetAndRaise(SelectedPathsProperty, ref _selectedPaths, value); + } + + public string SuggestedFileName + { + get => GetValue(SuggestedFileNameProperty); + set => SetValue(SuggestedFileNameProperty, value); + } + + public string DefaultFileExtension + { + get => GetValue(DefaultFileExtensionProperty); + set => SetValue(DefaultFileExtensionProperty, value); + } + + + public string Title + { + get => GetValue(TitleProperty); + set => SetValue(TitleProperty, value); + } + + public string FileFilter + { + get => GetValue(FileFilterProperty); + set => SetValue(FileFilterProperty, value); + } + + public UsePickerTypes UsePickerType + { + get => GetValue(UsePickerTypeProperty); + set => SetValue(UsePickerTypeProperty, value); + } + + public string SuggestedStartPath + { + get => GetValue(SuggestedStartPathProperty); + set => SetValue(SuggestedStartPathProperty, value); + } + + private bool _twoConvertLock; + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + if (_twoConvertLock) return; + if (change.Property == SelectedPathsProperty) + { + _twoConvertLock = true; + var stringBuilder = new StringBuilder(); + if (SelectedPaths.Count != 0) + { + stringBuilder.Append(SelectedPaths[0]); + foreach (var item in SelectedPaths.Skip(1)) + { + stringBuilder.AppendLine(item); + } + } + + SelectedPathsText = stringBuilder.ToString(); + _twoConvertLock = false; + } + + if (change.Property == SelectedPathsTextProperty) + { + _twoConvertLock = true; + string[] separatedStrings = ["\r", "\n", "\r\n"]; + // var list = SelectedPathsText?.Split(separatedStrings, StringSplitOptions.RemoveEmptyEntries) + // .Select(RemoveNewLine).ToArray() + // ?? []; + // if (list.Length == SelectedPaths.Count) + // { + // if (SelectedPaths.Select(x => list.Any(y => x == y)).All(x => x is false)) + // } + + SelectedPaths = SelectedPathsText?.Split(separatedStrings, StringSplitOptions.RemoveEmptyEntries) + .Select(RemoveNewLine).ToArray() + ?? []; + _twoConvertLock = false; + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + Button.ClickEvent.RemoveHandler(LaunchPicker, _button); + _button = e.NameScope.Find