diff --git a/README.md b/README.md index 6e79940..cd4e1c3 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,16 @@ https://jamsoft.github.io/JamSoft.AvaloniaUI.Dialogs/ ## Installation ```shell -dotnet add package JamSoft.AvaloniaUI.Dialogs --version 1.1.4 +dotnet add package JamSoft.AvaloniaUI.Dialogs --version 1.2.0 ``` ```shell -Install-Package JamSoft.AvaloniaUI.Dialogs -Version 1.1.4 +Install-Package JamSoft.AvaloniaUI.Dialogs -Version 1.2.0 ``` ```xml - + ``` ```shell -paket add JamSoft.AvaloniaUI.Dialogs --version 1.1.4 +paket add JamSoft.AvaloniaUI.Dialogs --version 1.2.0 ``` ## Import Styles ### All Defaults @@ -368,7 +368,8 @@ See the Sample Application for a complete implementation example and guidance. You can easily target elements of the dialogs via their names and types, such as: ```xml - + + @@ -382,7 +383,13 @@ You can easily target elements of the dialogs via their names and types, such as - + + + + ``` \ No newline at end of file diff --git a/src/JamSoft.AvaloniaUI.Dialogs.Sample/App.axaml b/src/JamSoft.AvaloniaUI.Dialogs.Sample/App.axaml index e59edd5..e0f60fb 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs.Sample/App.axaml +++ b/src/JamSoft.AvaloniaUI.Dialogs.Sample/App.axaml @@ -4,7 +4,11 @@ x:Class="JamSoft.AvaloniaUI.Dialogs.Sample.App"> - + + + + + - + + + + diff --git a/src/JamSoft.AvaloniaUI.Dialogs.Sample/JamSoft.AvaloniaUI.Dialogs.Sample.csproj b/src/JamSoft.AvaloniaUI.Dialogs.Sample/JamSoft.AvaloniaUI.Dialogs.Sample.csproj index 292d3fe..6df7f59 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs.Sample/JamSoft.AvaloniaUI.Dialogs.Sample.csproj +++ b/src/JamSoft.AvaloniaUI.Dialogs.Sample/JamSoft.AvaloniaUI.Dialogs.Sample.csproj @@ -1,10 +1,12 @@  WinExe - net7.0 + net8.0 enable true app.manifest + 12 + Assets\avalonia-logo.ico @@ -18,15 +20,15 @@ - - + + + - - + + - - - + + diff --git a/src/JamSoft.AvaloniaUI.Dialogs.Sample/ViewModels/CustomBaseChildWindowViewModel.cs b/src/JamSoft.AvaloniaUI.Dialogs.Sample/ViewModels/CustomBaseChildWindowViewModel.cs index e44ed53..f3b3f53 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs.Sample/ViewModels/CustomBaseChildWindowViewModel.cs +++ b/src/JamSoft.AvaloniaUI.Dialogs.Sample/ViewModels/CustomBaseChildWindowViewModel.cs @@ -1,7 +1,6 @@ using System; using System.ComponentModel; using System.Windows.Input; -using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Media.Imaging; @@ -20,9 +19,7 @@ public CustomBaseChildWindowViewModel() { AcceptCommand = new DelegateCommand(null, null); CancelCommand = new DelegateCommand(null, null); - - var assets = AvaloniaLocator.Current.GetService(); - CloseIcon = new Bitmap(assets?.Open(new Uri("avares://JamSoft.AvaloniaUI.Dialogs/Assets/CloseIcon/icons8-close-30.png"))); + CloseIcon = new Bitmap(AssetLoader.Open(new Uri("avares://JamSoft.AvaloniaUI.Dialogs/Assets/CloseIcon/icons8-close-30.png"))); _cancelCommand = new DelegateCommand(() => InvokeRequestCloseDialog(new RequestCloseDialogEventArgs(false)), CanCancel); CancelCommandText = "Cancel"; diff --git a/src/JamSoft.AvaloniaUI.Dialogs.Sample/ViewModels/MainWindowViewModel.cs b/src/JamSoft.AvaloniaUI.Dialogs.Sample/ViewModels/MainWindowViewModel.cs index 2f9154f..2cbcd94 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs.Sample/ViewModels/MainWindowViewModel.cs +++ b/src/JamSoft.AvaloniaUI.Dialogs.Sample/ViewModels/MainWindowViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Windows.Input; using Avalonia.Controls; +using Avalonia.Platform.Storage; using JamSoft.AvaloniaUI.Dialogs.Commands; using JamSoft.AvaloniaUI.Dialogs.Helpers; using JamSoft.AvaloniaUI.Dialogs.Sample.Models; @@ -14,7 +15,6 @@ namespace JamSoft.AvaloniaUI.Dialogs.Sample.ViewModels; public class MainWindowViewModel : ViewModelBase { private readonly IDialogService _dialogService; - private string? _message; private ICommand? _openFileCommand; private ICommand? _openWordFileCommand; private ICommand? _saveFileCommand; @@ -29,7 +29,8 @@ public class MainWindowViewModel : ViewModelBase private ICommand? _childWindowRememberPositionCommand; private ICommand? _missingViewCommand; private ICommand? _wizardViewCommand; - + private string? _message; + public MainWindowViewModel(IDialogService dialogService) { _dialogService = dialogService; @@ -137,12 +138,6 @@ public ICommand? SaveWordFileCommand set => this.RaiseAndSetIfChanged(ref _saveWordFileCommand, value); } - public string? Message - { - get => _message; - set => this.RaiseAndSetIfChanged(ref _message, value); - } - public ICommand? OpenFileCommand { get => _openFileCommand; @@ -155,6 +150,12 @@ public ICommand? OpenWordFileCommand set => this.RaiseAndSetIfChanged(ref _openWordFileCommand, value); } + public string? Message + { + get => _message; + set => this.RaiseAndSetIfChanged(ref _message, value); + } + private async void OpenFileCommandExecuted() { Message = await _dialogService.OpenFile("Open Any File"); @@ -162,7 +163,7 @@ private async void OpenFileCommandExecuted() private async void OpenWordFileCommandExecuted() { - Message = await _dialogService.OpenFile("Open Word File", new List + Message = await _dialogService.OpenFile("Open Word File", new List { CommonFilters.WordFilter }); @@ -180,7 +181,7 @@ private async void SaveFileCommandExecuted() private async void SaveWordFileCommandExecuted() { - Message = await _dialogService.SaveFile("Save Word File", new List + Message = await _dialogService.SaveFile("Save Word File", new List { CommonFilters.WordFilter }); diff --git a/src/JamSoft.AvaloniaUI.Dialogs.Sample/Views/MyChildWindowView.axaml b/src/JamSoft.AvaloniaUI.Dialogs.Sample/Views/MyChildWindowView.axaml index bdd5fa4..e156415 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs.Sample/Views/MyChildWindowView.axaml +++ b/src/JamSoft.AvaloniaUI.Dialogs.Sample/Views/MyChildWindowView.axaml @@ -8,7 +8,7 @@ - diff --git a/src/JamSoft.AvaloniaUI.Dialogs.Sample/Views/MyWizardView.axaml b/src/JamSoft.AvaloniaUI.Dialogs.Sample/Views/MyWizardView.axaml index d8120f0..56f0f8d 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs.Sample/Views/MyWizardView.axaml +++ b/src/JamSoft.AvaloniaUI.Dialogs.Sample/Views/MyWizardView.axaml @@ -8,7 +8,7 @@ x:Class="JamSoft.AvaloniaUI.Dialogs.Sample.Views.MyWizardView"> @@ -16,7 +16,7 @@ Page 1 - @@ -54,7 +54,7 @@ - + diff --git a/src/JamSoft.AvaloniaUI.Dialogs/Controls/Wizard.cs b/src/JamSoft.AvaloniaUI.Dialogs/Controls/Wizard.cs index dde642e..b9d8c0e 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/Controls/Wizard.cs +++ b/src/JamSoft.AvaloniaUI.Dialogs/Controls/Wizard.cs @@ -1,13 +1,12 @@ using System.Windows.Input; using Avalonia; -using Avalonia.Collections; using Avalonia.Controls; -using Avalonia.Controls.Generators; using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; -using Avalonia.LogicalTree; +using Avalonia.Data; +using Avalonia.Metadata; using JamSoft.AvaloniaUI.Dialogs.Commands; using JamSoft.AvaloniaUI.Dialogs.ViewModels; @@ -16,17 +15,53 @@ namespace JamSoft.AvaloniaUI.Dialogs.Controls /// /// A wizard control that displays a series of elements in a workflow style progression. /// - [TemplatePart("PART_StepsPresenter", typeof(ItemsPresenter))] + [TemplatePart("PART_StepsPresenter", typeof(ItemsControl))] [TemplatePart("PART_ButtonsPresenter", typeof(DockPanel))] - public class Wizard : SelectingItemsControl, IContentPresenterHost + public class Wizard : TemplatedControl { + internal ContentPresenter? ContentPart; + + internal DockPanel? ButtonsPresenterPart { get; private set; } + + private Button? CompleteButton { get; set; } + + private Button? PreviousButton { get; set; } + + private Button? NextButton { get; set; } + private ICommand MoveNextCommand => new DelegateCommand(MoveNextCommandExecuted); private ICommand MovePreviousCommand => new DelegateCommand(MovePreviousCommandExecuted); + + /// + /// Gets or sets the selected item. + /// + public WizardStep? SelectedItem { get; set; } + + /// + /// The index of the active WizardStep + /// + public int SelectedIndex { get; set; } /// - /// The default value for the property. + /// Defines the property. /// - private static readonly FuncTemplate DefaultPanel = new(() => new WrapPanel()); + public static readonly DirectProperty SelectedIndexProperty = + AvaloniaProperty.RegisterDirect( + nameof(SelectedIndex), + o => o.SelectedIndex, + (o, v) => o.SelectedIndex = v, + unsetValue: 0, + defaultBindingMode: BindingMode.OneWay); + + /// + /// Defines the property. + /// + public static readonly DirectProperty SelectedItemProperty = + AvaloniaProperty.RegisterDirect( + nameof(SelectedItem), + o => o.SelectedItem, + (o, v) => o.SelectedItem = v, + defaultBindingMode: BindingMode.OneWay, enableDataValidation: true); /// /// Defines the property. @@ -43,7 +78,7 @@ public class Wizard : SelectingItemsControl, IContentPresenterHost /// /// Defines the property. /// - public static readonly StyledProperty ContentTemplateProperty = + public static readonly StyledProperty ContentTemplateProperty = ContentControl.ContentTemplateProperty.AddOwner(); /// @@ -61,31 +96,45 @@ public class Wizard : SelectingItemsControl, IContentPresenterHost /// /// Defines the property. /// - public static readonly StyledProperty NextButtonContentProperty = - AvaloniaProperty.Register(nameof(NextButtonContent), "Next"); + public static readonly StyledProperty NextButtonContentProperty = + AvaloniaProperty.Register(nameof(NextButtonContent), "Next"); /// /// Defines the property. /// - public static readonly StyledProperty PreviousButtonContentProperty = - AvaloniaProperty.Register(nameof(PreviousButtonContent), "Back"); + public static readonly StyledProperty PreviousButtonContentProperty = + AvaloniaProperty.Register(nameof(PreviousButtonContent), "Back"); /// /// Defines the property. /// - public static readonly StyledProperty CompleteButtonContentProperty = - AvaloniaProperty.Register(nameof(CompleteButtonContent), "Complete"); + public static readonly StyledProperty CompleteButtonContentProperty = + AvaloniaProperty.Register(nameof(CompleteButtonContent), "Complete"); + /// + /// Defines the property. + /// + public static readonly StyledProperty?> StepsProperty = + AvaloniaProperty.Register?>(nameof(Steps)); + + /// + /// Gets or sets a collection used to generate the content of the . + /// + [Content] + public IList? Steps + { + get => GetValue(StepsProperty); + set => SetValue(StepsProperty, value); + } + /// /// Initializes static members of the class. /// - static Wizard() + public Wizard() { - SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected); - ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); AffectsMeasure(ButtonPlacementProperty); - SelectedItemProperty.Changed.AddClassHandler((x, _) => x.UpdateSelectedContent()); DataContextProperty.Changed.AddClassHandler((x, _) => x.HandleDataContextChanged()); + Steps = new List(); } /// @@ -111,7 +160,7 @@ public Dock ProgressPlacement /// public IDataTemplate ContentTemplate { - get { return GetValue(ContentTemplateProperty); } + get { return GetValue(ContentTemplateProperty)!; } set { SetValue(ContentTemplateProperty, value); } } @@ -142,7 +191,7 @@ public IDataTemplate? SelectedContentTemplate /// /// Next button content /// - public object NextButtonContent + public object? NextButtonContent { get { return GetValue(NextButtonContentProperty); } set { SetValue(NextButtonContentProperty, value); } @@ -151,7 +200,7 @@ public object NextButtonContent /// /// Previous button content /// - public object PreviousButtonContent + public object? PreviousButtonContent { get { return GetValue(PreviousButtonContentProperty); } set { SetValue(PreviousButtonContentProperty, value); } @@ -160,107 +209,63 @@ public object PreviousButtonContent /// /// Complete button content /// - public object CompleteButtonContent + public object? CompleteButtonContent { get { return GetValue(CompleteButtonContentProperty); } set { SetValue(CompleteButtonContentProperty, value); } } - internal ItemsPresenter? ItemsPresenterPart; - - internal IContentPresenter? ContentPart; - - internal DockPanel? ButtonsPresenterPart { get; private set; } - - /// - IAvaloniaList IContentPresenterHost.LogicalChildren => LogicalChildren; - - /// - bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter) + private void StepCompleted(AvaloniaPropertyChangedEventArgs args) { - return RegisterContentPresenter(presenter); - } - - /// - protected override void OnContainersMaterialized(ItemContainerEventArgs e) - { - base.OnContainersMaterialized(e); - UpdateSelectedContent(); - } - - /// - protected override void OnContainersRecycled(ItemContainerEventArgs e) - { - base.OnContainersRecycled(e); - UpdateSelectedContent(); - } - - private void UpdateSelectedContent() - { - if (SelectedIndex == -1) + if (args.NewValue is bool && (bool)args.NewValue) { - SelectedContent = SelectedContentTemplate = null; + NextButton!.IsEnabled = true; } else { - var container = SelectedItem as IContentControl ?? - ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as IContentControl; - SelectedContentTemplate = container?.ContentTemplate; - SelectedContent = container?.Content; + NextButton!.IsEnabled = false; } - } - - /// - /// Called when an is registered with the control. - /// - /// The presenter. - protected virtual bool RegisterContentPresenter(IContentPresenter presenter) - { - if (presenter.Name == "PART_SelectedContentHost") + + var list = Steps; + if (args.Sender.Equals(list?.LastOrDefault()) && (args.NewValue is bool value && value)) { - ContentPart = presenter; - return true; + CompleteButton!.IsEnabled = true; + } + else + { + CompleteButton!.IsEnabled = false; } - - return false; } - - /// - protected override IItemContainerGenerator CreateItemContainerGenerator() + + private void UpdateSelectedContent() { - return new WizardContainerGenerator(this); + if (ContentPart == null) return; + + if (SelectedItem != null && Steps?[SelectedIndex] != null) + { + SelectedItem.IsSelected = false; + } + + SelectedItem = Steps?[SelectedIndex]; + + if (SelectedItem != null) + { + ContentPart.SetValue(ContentControl.ContentProperty, SelectedItem.Content); + SelectedItem.IsSelected = true; + } } private void HandleDataContextChanged() { - WizardStep.StepCompleteProperty.Changed.Subscribe(x => - { - if (x.Sender.Equals(SelectedItem) && x.NewValue.Value) - { - NextButton!.IsEnabled = true; - } - else - { - NextButton!.IsEnabled = false; - } - - var list = (AvaloniaList)Items; - if (x.Sender.Equals(list.LastOrDefault()) && x.NewValue.Value) - { - CompleteButton!.IsEnabled = true; - } - else - { - CompleteButton!.IsEnabled = false; - } - }); + WizardStep.StepCompleteProperty.Changed.AddClassHandler((_, args) => StepCompleted(args)); } /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { - ItemsPresenterPart = e.NameScope.Get("PART_StepsPresenter"); + //ItemsPresenterPart = e.NameScope.Get("PART_StepsPresenter"); ButtonsPresenterPart = e.NameScope.Get("PART_ButtonsPresenter"); + ContentPart = e.NameScope.Get("PART_SelectedContentHost"); PreviousButton = new Button { @@ -290,23 +295,31 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) CompleteButton.IsVisible = false; NextButton.IsEnabled = false; PreviousButton.IsEnabled = false; + + UpdateSelectedContent(); } - private Button? CompleteButton { get; set; } - - private Button? PreviousButton { get; set; } - - private Button? NextButton { get; set; } - private void MoveNextCommandExecuted() { + if (SelectedIndex == Steps?.Count - 1) + { + return; + } + SelectedIndex++; + UpdateSelectedContent(); SetButtons(); } private void MovePreviousCommandExecuted() { + if (SelectedIndex == 0) + { + return; + } + SelectedIndex--; + UpdateSelectedContent(); SetButtons(); } @@ -315,10 +328,10 @@ private void SetButtons() NextButton!.IsEnabled = false; PreviousButton!.IsEnabled = true; - var list = (AvaloniaList)Items; - var selected = SelectedItem as WizardStep; + var list = Steps; + var selected = SelectedItem; - if (selected!.Equals(list.LastOrDefault())) + if (selected!.Equals(list?.LastOrDefault())) { NextButton!.IsVisible = false; CompleteButton!.IsVisible = true; diff --git a/src/JamSoft.AvaloniaUI.Dialogs/Controls/WizardContainerGenerator.cs b/src/JamSoft.AvaloniaUI.Dialogs/Controls/WizardContainerGenerator.cs index b3da3d8..99ca8b5 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/Controls/WizardContainerGenerator.cs +++ b/src/JamSoft.AvaloniaUI.Dialogs/Controls/WizardContainerGenerator.cs @@ -5,20 +5,20 @@ using Avalonia.Controls.Templates; using Avalonia.LogicalTree; using Avalonia.Reactive; +using Avalonia.Threading; namespace JamSoft.AvaloniaUI.Dialogs.Controls; /// /// The Wizard container generator /// -public class WizardContainerGenerator : ItemContainerGenerator +public class WizardContainerGenerator { /// /// Default constructor /// /// public WizardContainerGenerator(Wizard owner) - : base(owner, ContentControl.ContentProperty, ContentControl.ContentTemplateProperty) { Owner = owner; } @@ -26,12 +26,12 @@ public WizardContainerGenerator(Wizard owner) /// /// The owner wizard control /// - public new Wizard Owner; + public Wizard Owner; /// - protected override IControl CreateContainer(object item) + public Control CreateContainer(object item) { - var step = (WizardStep)base.CreateContainer(item); + var step = (WizardStep)base.CreateContainer(item, 0, null); step.Bind(WizardStep.ProgressPlacementProperty, new OwnerBinding( step, @@ -41,35 +41,35 @@ protected override IControl CreateContainer(object item) { step.Bind(HeaderedContentControl.HeaderTemplateProperty, new OwnerBinding( step, - ItemsControl.ItemTemplateProperty)); + ItemsControl.ItemTemplateProperty!)); } if (step.Header == null) { - if (item is IHeadered headered) + if (item is HeaderedContentControl headered) { step.Header = headered.Header; } else { - if (!(step.DataContext is IControl)) + if (!(step.DataContext is Control)) { step.Header = step.DataContext; } } } - if (!(step.Content is IControl)) + if (!(step.Content is Control)) { step.Bind(ContentControl.ContentTemplateProperty, new OwnerBinding( step, - Wizard.ContentTemplateProperty)); + Wizard.ContentTemplateProperty!)); } return step; } - private class OwnerBinding : SingleSubscriberObservableBase + private class OwnerBinding : AvSingleSubscriberObservableBase { private readonly WizardStep _step; private readonly StyledProperty _ownerProperty; @@ -84,7 +84,7 @@ public OwnerBinding(WizardStep step, StyledProperty ownerProperty) protected override void Subscribed() { - _ownerSubscription = ControlLocator.Track(_step, 0, typeof(Wizard)).Subscribe(OwnerChanged); + //_ownerSubscription = ControlLocator.Track(_step, 0, typeof(Wizard)).Subscribe(OwnerChanged); } protected override void Unsubscribed() @@ -100,9 +100,82 @@ private void OwnerChanged(ILogical c) if (c is Wizard wizard) { - _propertySubscription = wizard.GetObservable(_ownerProperty) - .Subscribe(x => PublishNext(x)); + // _propertySubscription = wizard.GetObservable(_ownerProperty) + // .Subscribe(x => PublishNext(x)); } } } + + private abstract class AvSingleSubscriberObservableBase : IObservable, IDisposable + { + private Exception? _error; + private IObserver? _observer; + private bool _completed; + + public IDisposable Subscribe(IObserver observer) + { + _ = observer ?? throw new ArgumentNullException(nameof(observer)); + Dispatcher.UIThread.VerifyAccess(); + + if (_observer != null) + { + throw new InvalidOperationException("The observable can only be subscribed once."); + } + + if (_error != null) + { + observer.OnError(_error); + } + else if (_completed) + { + observer.OnCompleted(); + } + else + { + _observer = observer; + Subscribed(); + } + + return this; + } + + public virtual void Dispose() + { + Unsubscribed(); + _observer = null; + } + + protected abstract void Unsubscribed(); + + protected void PublishNext(T value) + { + _observer?.OnNext(value); + } + + protected void PublishCompleted() + { + _completed = true; + + if (_observer != null) + { + _observer.OnCompleted(); + Unsubscribed(); + _observer = null; + } + } + + protected void PublishError(Exception error) + { + _error = error; + + if (_observer != null) + { + _observer.OnError(error); + Unsubscribed(); + _observer = null; + } + } + + protected abstract void Subscribed(); + } } \ No newline at end of file diff --git a/src/JamSoft.AvaloniaUI.Dialogs/Controls/WizardStep.cs b/src/JamSoft.AvaloniaUI.Dialogs/Controls/WizardStep.cs index 5c631a4..6aee11e 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/Controls/WizardStep.cs +++ b/src/JamSoft.AvaloniaUI.Dialogs/Controls/WizardStep.cs @@ -80,7 +80,7 @@ private void UpdateHeader(AvaloniaPropertyChangedEventArgs obj) { if (Header == null) { - if (obj.NewValue is IHeadered headered) + if (obj.NewValue is HeaderedContentControl headered) { if (Header != headered.Header) { @@ -89,7 +89,7 @@ private void UpdateHeader(AvaloniaPropertyChangedEventArgs obj) } else { - if (!(obj.NewValue is IControl)) + if (!(obj.NewValue is Control)) { Header = obj.NewValue; } diff --git a/src/JamSoft.AvaloniaUI.Dialogs/DialogService.cs b/src/JamSoft.AvaloniaUI.Dialogs/DialogService.cs index 1019996..ddf1721 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/DialogService.cs +++ b/src/JamSoft.AvaloniaUI.Dialogs/DialogService.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform.Storage; using JamSoft.AvaloniaUI.Dialogs.ViewModels; using JamSoft.AvaloniaUI.Dialogs.Views; @@ -63,7 +64,7 @@ public async void ShowDialog(TView view, TViewModel viewModel }; var contentControl = win.FindControl("Host"); - contentControl.Content = view; + contentControl!.Content = view; win.DataContext = viewModel; var accept = await win.ShowDialog(GetActiveWindowOrMainWindow()); @@ -115,7 +116,7 @@ public void ShowChildWindow(TView view, TViewModel viewModel, viewModel.ChildWindowTitle = CreateTitle(viewModel.ChildWindowTitle); var contentControl = win.FindControl("Host"); - contentControl.Content = view; + contentControl!.Content = view; win.DataContext = viewModel; _openChildren.Add(viewModel); @@ -151,7 +152,7 @@ public void StartWizard(TViewModel viewModel, Action? ca var viewInstance = CreateViewInstance(viewType!, viewName); win.DataContext = viewModel; - contentControl.Content = viewInstance; + contentControl!.Content = viewInstance; _openChildren.Add(viewModel); win.Closing += (sender, _) => @@ -176,17 +177,19 @@ public void StartWizard(TViewModel viewModel, Action? ca /// the selected folder path or null if the dialog was cancelled public async Task OpenFolder(string? title, string? startDirectory = null) { - var fd = new OpenFolderDialog + var storageProvider = GetStorageProvider(); + var folder = await storageProvider.TryGetFolderFromPathAsync((startDirectory ?? _lastDirectorySelected)!); + var path = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { - Directory = startDirectory ?? _lastDirectorySelected, + SuggestedStartLocation = folder, Title = CreateTitle(title) - }; + }); - var path = await fd.ShowAsync(GetActiveWindowOrMainWindow()); + //var path = await fd.ShowAsync(GetActiveWindowOrMainWindow()); - _lastDirectorySelected = path!; + _lastDirectorySelected = path[0].Path.AbsolutePath; - return path; + return _lastDirectorySelected; } /// @@ -196,17 +199,19 @@ public void StartWizard(TViewModel viewModel, Action? ca /// The file extension filters /// The default file extension /// the selected file path or null if the dialog was cancelled - public async Task SaveFile(string title, IEnumerable? filters = null, string? defaultExtension = null) + public async Task SaveFile(string title, IEnumerable? filters = null, string? defaultExtension = null) { - var fd = new SaveFileDialog + var storageProvider = GetStorageProvider(); + var folder = await storageProvider.TryGetFolderFromPathAsync(_lastDirectorySelected!); + var fd = await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions { Title = CreateTitle(title), - Filters = filters?.ToList(), - Directory = _lastDirectorySelected, + FileTypeChoices = filters?.ToList(), + SuggestedStartLocation = folder, DefaultExtension = defaultExtension - }; + }); - return await fd.ShowAsync(GetActiveWindowOrMainWindow()); + return fd?.Path.AbsolutePath; } /// @@ -215,7 +220,7 @@ public void StartWizard(TViewModel viewModel, Action? ca /// The dialog title /// The file extension filters /// the selected file path or null if the dialog was cancelled - public async Task OpenFile(string title, IEnumerable? filters = null) + public async Task OpenFile(string title, IEnumerable? filters = null) { var paths = await OpenFile(title, false, filters); if (paths != null && paths.Any()) @@ -232,7 +237,7 @@ public void StartWizard(TViewModel viewModel, Action? ca /// The dialog title /// The file extension filters /// the selected file paths or null if the dialog was cancelled - public async Task OpenFiles(string title, IEnumerable? filters = null) + public async Task OpenFiles(string title, IEnumerable? filters = null) { var paths = await OpenFile(title, true, filters); if (paths != null && paths.Any()) @@ -243,30 +248,37 @@ public void StartWizard(TViewModel viewModel, Action? ca return null; } - private async Task OpenFile(string title, bool allowMulti, IEnumerable? filters = null) + private async Task OpenFile(string title, bool allowMultiFileSelection, IEnumerable? filters = null) { - var fd = new OpenFileDialog + var storageProvider = GetStorageProvider(); + var folder = await storageProvider.TryGetFolderFromPathAsync(_lastDirectorySelected!); + var fd = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { Title = CreateTitle(title), - AllowMultiple = allowMulti, - Filters = filters?.ToList(), - Directory = _lastDirectorySelected - }; + AllowMultiple = allowMultiFileSelection, + FileTypeFilter = filters?.ToList(), + SuggestedStartLocation = folder + }); - var paths = await fd.ShowAsync(GetActiveWindowOrMainWindow()); - if (paths != null && paths.Any()) + return fd.Select(x => x.Path.AbsolutePath).ToArray(); + } + + private IStorageProvider GetStorageProvider() + { + var topLevel = TopLevel.GetTopLevel(GetActiveWindowOrMainWindow()); + if (topLevel != null) { - return paths; + return topLevel.StorageProvider; } - return null; + throw new InvalidOperationException("Cannot find a StorageProvider when TopLevel is null"); } private Window GetActiveWindowOrMainWindow() { if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - return desktop.Windows.SingleOrDefault(x => x.IsActive) ?? desktop.MainWindow; + return (desktop.Windows.SingleOrDefault(x => x.IsActive) ?? desktop.MainWindow)!; } throw new InvalidOperationException("Cannot find a Window when ApplicationLifetime is not ClassicDesktopStyleApplicationLifetime"); diff --git a/src/JamSoft.AvaloniaUI.Dialogs/Helpers/CommonFilters.cs b/src/JamSoft.AvaloniaUI.Dialogs/Helpers/CommonFilters.cs index d51ac8e..8636c7d 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/Helpers/CommonFilters.cs +++ b/src/JamSoft.AvaloniaUI.Dialogs/Helpers/CommonFilters.cs @@ -1,220 +1,389 @@ using Avalonia.Controls; +using Avalonia.Platform.Storage; namespace JamSoft.AvaloniaUI.Dialogs.Helpers; /// /// A class of common file filters /// +// https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html public static class CommonFilters { /// /// Word files /// - public static FileDialogFilter WordFilter = BuildFilter("Word Files", new[] { "docx", "doc" }); + public static FilePickerFileType WordFilter = BuildFilter( + "Word Files", + new[] { "*.docx", "*.doc" }, + new []{ "com.microsoft.word.doc", "org.openxmlformats.wordprocessingml.document" }, + new []{ "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }); /// /// Excel files /// - public static FileDialogFilter ExcelFilter = BuildFilter("Excel Files", new[] { "xlsx", "xls", "csv" }); + public static FilePickerFileType ExcelFilter = BuildFilter( + "Excel Files", + new[] { "*.xlsx", "*.xls", "*.csv" }, + new []{"com.microsoft.excel.xls", "org.openxmlformats.spreadsheetml.sheet", "public.comma-separated-values-text"}, + new []{"application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}); /// /// HTML files /// - public static FileDialogFilter HtmlFilter = BuildFilter("HTML Files", new[] { "html", "htm" }); + public static FilePickerFileType HtmlFilter = BuildFilter( + "HTML Files", + new[] { "*.html", "*.htm" }, + new []{"public.html"}, + new []{"text/html"}); /// /// Txt Files /// - public static FileDialogFilter TxtFilter = BuildFilter("Text File", new[] { "txt" }); + public static FilePickerFileType TxtFilter = BuildFilter( + "Text File", + new[] { "*.txt" }, + new []{"public.plain-text"}, + new []{"text/plain"}); /// /// CSV Files /// - public static FileDialogFilter CsvFilter = BuildFilter("CSV File", new[] { "csv" }); + public static FilePickerFileType CsvFilter = BuildFilter( + "CSV File", + new[] { "*.csv" }, + new []{"public.comma-separated-values-text"}, + new []{"text/csv"}); /// /// ZIP archive files /// - public static FileDialogFilter ZipFilter = BuildFilter("Zip File", new[] { "zip" }); + public static FilePickerFileType ZipFilter = BuildFilter( + "Zip File", + new[] { "*.zip" }, + new []{"public.zip-archive"}, + new []{"application/zip"}); /// /// Rar Files /// - public static FileDialogFilter RarFilter = BuildFilter("Rar File", new[] { "rar" }); + public static FilePickerFileType RarFilter = BuildFilter( + "Rar File", + new[] { "*.rar" }, + new []{"com.rar-archive"}, + new []{"application/x-rar-compressed"}); /// /// Rpm Files /// - public static FileDialogFilter RpmFilter = BuildFilter("Rpm File", new[] { "rpm" }); + public static FilePickerFileType RpmFilter = BuildFilter( + "Rpm File", + new[] { "*.rpm" }, + new []{"com.redhat.package-archive"}, + new []{"application/x-rpm"}); /// /// DMG Files /// - public static FileDialogFilter DmgFilter = BuildFilter("Dmg File", new[] { "dmg" }); + public static FilePickerFileType DmgFilter = BuildFilter( + "Dmg File", + new[] { "*.dmg" }, + new []{"com.apple.disk-image"}, + new []{"application/x-apple-diskimage"}); /// /// Tarball Files /// - public static FileDialogFilter TarFilter = BuildFilter("Tarball File", new[] { "tar.gz" }); + public static FilePickerFileType TarFilter = BuildFilter( + "Tarball File", + new[] { "*.tar.gz" }, + new []{"public.tar-archive"}, + new []{"application/x-tar"}); /// /// Iso Files /// - public static FileDialogFilter IsoFilter = BuildFilter("Iso File",new[] { "iso" }); + public static FilePickerFileType IsoFilter = BuildFilter( + "Iso File", + new[] { "*.iso" }, + new []{"public.iso-image"}, + new []{"application/x-iso9660-image"}); /// /// Wav Files /// - public static FileDialogFilter WavFilter = BuildFilter("Wav File", new[] { "wav" } ); + public static FilePickerFileType WavFilter = BuildFilter( + "Wav File", + new[] { "*.wav" }, + new []{"com.microsoft.waveform-audio"}, + new []{ "audio/wav", "audio/wave" }); /// /// MP3 Files /// - public static FileDialogFilter Mp3Filter = BuildFilter("Mp3 File", new[] { "mp3" } ); + public static FilePickerFileType Mp3Filter = BuildFilter( + "Mp3 File", new[] { "*.mp3" }, + new []{"public.mp3"}, + new []{"audio/mpeg", "audio/mpeg3", "audio/mpg", "audio/mp3", "audio/x-mpeg", "audio/x-mpeg3", " audio/x-mpg", "audio/x-mp3"}); /// /// DLL Files /// - public static FileDialogFilter DllFilter = BuildFilter("Dll File", new[] { "dll" }); + public static FilePickerFileType DllFilter = BuildFilter( + "Dll File", + new[] { "dll" }, + new []{"com.microsoft.windows-dynamic-link-library"}, + new []{"application/x-msdownload"}); /// /// XML Files /// - public static FileDialogFilter XmlFilter = BuildFilter("XML File", new[] { "xml" }); + public static FilePickerFileType XmlFilter = BuildFilter( + "XML File", + new[] { "*.xml" }, + new []{"public.xml"}, + new []{"text/xml", "application/xml"}); /// /// Email Files /// - public static FileDialogFilter EmailFilter = BuildFilter("Email Files", new[] { "eml", "email", "msg", "oft", "ost", "pst" }); + public static FilePickerFileType EmailFilter = BuildFilter( + "Email Files", + new[] { "*.eml", "*.email", "*.msg", "*.oft", "*.ost", "*.pst" }, + new []{"com.microsoft.outlook.email-message"}, + new []{"message/rfc822"}); /// /// EXE Files /// - public static FileDialogFilter ExeFilter = BuildFilter("Windows Executable", new[] { "exe" }); + public static FilePickerFileType ExeFilter = BuildFilter( + "Windows Executable", + new[] { "*.exe" }, + new []{"com.microsoft.windows-executable"}, + new []{"application/x-msdownload"}); /// /// Batch Files /// - public static FileDialogFilter BatchFilter = BuildFilter("Windows Batch File", new[] { "bat" }); + public static FilePickerFileType BatchFilter = BuildFilter( + "Windows Batch File", + new[] { "*.bat" }, + new []{"com.microsoft.windows-batch"}, + new []{"application/x-msdownload"}); /// /// TTF Font Files /// - public static FileDialogFilter TtfFontFilter = BuildFilter("TrueType Font File", new[] { "ttf" }); + public static FilePickerFileType TtfFontFilter = BuildFilter( + "TrueType Font File", + new[] { "*.ttf" }, + new []{"public.truetype-ttf-font"}, + new []{"application/x-font-ttf"}); /// /// Illustrator Files /// - public static FileDialogFilter IllustratorFilter = BuildFilter("Adobe Illustrator File", new[] { "ai" }); + public static FilePickerFileType IllustratorFilter = BuildFilter( + "Adobe Illustrator File", + new[] { "*.ai" }, + new []{"com.adobe.illustrator.ai-image"}, + new []{"application/illustrator"}); /// /// Bitmap Files /// - public static FileDialogFilter BitmapFilter = BuildFilter("Bitmap Image", new[] { "bmp" }); + public static FilePickerFileType BitmapFilter = BuildFilter( + "Bitmap Image", + new[] { "*.bmp" }, + new []{"com.microsoft.bmp"}, + new []{"image/bmp"} ); /// /// GIF files /// - public static FileDialogFilter GifFilter = BuildFilter("GIF Image", new[] { "gif" }); + public static FilePickerFileType GifFilter = BuildFilter( + "GIF Image", + new[] { "*.gif" }, + new []{"com.compuserve.gif"}, + new []{"image/gif"}); /// /// ICO Files /// - public static FileDialogFilter IcoFilter = BuildFilter("Icon File", new[] { "ico" }); + public static FilePickerFileType IcoFilter = BuildFilter( + "Icon File", + new[] { "*.ico" }, + new []{"com.microsoft.ico"}, + new []{"image/x-icon"}); /// /// JPEG Files /// - public static FileDialogFilter JpegFilter = BuildFilter("JPEG Image", new[] { "jpg", "jpeg" } ); + public static FilePickerFileType JpegFilter = BuildFilter( + "JPEG Image", + new[] { "*.jpg", "*.jpeg" }, + new []{"public.jpeg"}, + new []{"image/jpeg"} ); /// /// PNG Files /// - public static FileDialogFilter PngFilter = BuildFilter("PNG Image", new[] { "png" }); + public static FilePickerFileType PngFilter = BuildFilter( + "PNG Image", + new[] { "*.png" }, + new []{"public.png"}, + new []{"image/png"}); /// /// PostScript Files /// - public static FileDialogFilter PostScriptFilter = BuildFilter("PostScript File", new[] { "ps" }); + public static FilePickerFileType PostScriptFilter = BuildFilter( + "PostScript File", + new[] { "*.ps" }, + new []{"com.adobe.postscript"}, + new []{"application/postscript"}); /// /// PSD Files /// - public static FileDialogFilter PsdFilter = BuildFilter("Photoshop Image", new[] { "psd" }); + public static FilePickerFileType PsdFilter = BuildFilter( + "Photoshop Image", + new[] { "*.psd" }, + new []{"com.adobe.photoshop-image"}, + new []{"image/vnd.adobe.photoshop"} ); /// /// SVG Files /// - public static FileDialogFilter SvgFilter = BuildFilter("Scalable Vector Graphics File", new[] { "svg" }); + public static FilePickerFileType SvgFilter = BuildFilter( + "Scalable Vector Graphics File", + new[] { "*.svg" }, + new []{"public.svg-image"}, + new []{"image/svg+xml"} ); /// /// TIFF Files /// - public static FileDialogFilter TiffFilter = BuildFilter("TIFF Image", new[] { "tif", "tiff" }); + public static FilePickerFileType TiffFilter = BuildFilter( + "TIFF Image", + new[] { "*.tif", "*.tiff" }, + new []{"public.tiff"}, + new []{"image/tiff"} ); /// /// WebP Files /// - public static FileDialogFilter WebpFilter = BuildFilter("WebP Image", new[] { "webp" }); + public static FilePickerFileType WebpFilter = BuildFilter( + "WebP Image", + new[] { "*.webp" }, + new []{"public.webp"}, + new []{"image/webp"}); /// /// Apple Keynote Files /// - public static FileDialogFilter KeynoteFilter = BuildFilter("Keynote Presentation", new[] { "key" } ); + public static FilePickerFileType KeynoteFilter = BuildFilter( + "Keynote Presentation", + new[] { "*.key" }, + new []{"com.apple.keynote.key"}, + new []{"application/x-iwork-keynote-sffkey"}); /// /// OpenOffice Impress Files /// - public static FileDialogFilter OpenOfficeImpressFilter = BuildFilter("OpenOffice Impress Presentation File", new[] { "odp" }); + public static FilePickerFileType OpenOfficeImpressFilter = BuildFilter( + "OpenOffice Impress Presentation File", + new[] { "*.odp" }, + new []{"org.openoffice.presentation"}, + new []{"application/vnd.oasis.opendocument.presentation"}); /// /// Powerpoint Slide Show Files /// - public static FileDialogFilter PowerPointFilter = BuildFilter("PowerPoint Slide Show", new[]{ "pps" }); + public static FilePickerFileType PowerPointFilter = BuildFilter( + "PowerPoint Slide Show", + new[]{ "*.pps" }, + new []{"com.microsoft.powerpoint.pps"}, + new []{"application/vnd.ms-powerpoint" }); /// /// Powerpoint Presentation Files /// - public static FileDialogFilter PowerpointFilter = BuildFilter("PowerPoint Presentation", new[] { "pptx", "ppt" }); + public static FilePickerFileType PowerpointFilter = BuildFilter( + "PowerPoint Presentation", + new[] { "*.pptx", "*.ppt" }, + new []{"com.microsoft.powerpoint.ppt", "org.openxmlformats.presentationml.presentation"}, + new []{"application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}); /// /// AVI Files /// - public static FileDialogFilter AviFilter = BuildFilter("AVI Video File", new[] { "avi" }); + public static FilePickerFileType AviFilter = BuildFilter( + "AVI Video File", + new[] { "*.avi" }, + new []{"public.avi"}, + new []{"video/avi"} ); /// /// MP4 Files /// - public static FileDialogFilter Mp4Filter = BuildFilter("MPEG4 Video File", new[] { "mp4" }); + public static FilePickerFileType Mp4Filter = BuildFilter( + "MPEG4 Video File", + new[] { "*.mp4" }, + new []{"public.mpeg-4"}, + new []{"video/mp4"} ); /// /// MPEG Files /// - public static FileDialogFilter MpegFilter = BuildFilter("MPEG Video File", new[] { "mpg", "mpeg" } ); + public static FilePickerFileType MpegFilter = BuildFilter( + "MPEG Video File", + new[] { "mpg", "mpeg" }, + new []{"public.mpeg"}, + new []{"video/mpeg"}); /// /// PDF Files /// - public static FileDialogFilter PdfFilter = BuildFilter("PDF File", new[] { "pdf" }); + public static FilePickerFileType PdfFilter = BuildFilter( + "PDF File", + new[] { "*.pdf" }, + new []{"com.adobe.pdf"}, + new []{"application/pdf"}); /// /// Rich Text Files /// - public static FileDialogFilter RichTextFilter = BuildFilter("Rich Text Format", new[] { "rtf" }); + public static FilePickerFileType RichTextFilter = BuildFilter( + "Rich Text Format", + new[] { "*.rtf" }, + new []{"public.rtf"}, + new []{"text/rtf"} ); /// /// ODT Files /// - public static FileDialogFilter OdtFilter = BuildFilter("OpenOffice Writer Document File", new[] { "odt" }); - + public static FilePickerFileType OdtFilter = BuildFilter( + "OpenOffice Writer Document File", + new[] { "*.odt" }, + new []{"org.openoffice.text"}, + new []{"application/vnd.oasis.opendocument.text"}); + /// /// Builds a filter instance /// /// The name of the filter /// the array of file extensions + /// the array of apple UTIs + /// the array of mime types /// a instance - public static FileDialogFilter BuildFilter(string name, string[] extensions) + // ReSharper disable once MemberCanBePrivate.Global + public static FilePickerFileType BuildFilter(string name, string[] extensions, string[] appleIds, string[] mimeTypes) { - return new FileDialogFilter { Name = name, Extensions = new List(extensions) }; + return new FilePickerFileType(name) + { + Patterns = new List(extensions), + AppleUniformTypeIdentifiers = new List(appleIds), + MimeTypes = new List(mimeTypes) + }; } } \ No newline at end of file diff --git a/src/JamSoft.AvaloniaUI.Dialogs/Helpers/CommonFiltersExtensionMethods.cs b/src/JamSoft.AvaloniaUI.Dialogs/Helpers/CommonFiltersExtensionMethods.cs new file mode 100644 index 0000000..9b12142 --- /dev/null +++ b/src/JamSoft.AvaloniaUI.Dialogs/Helpers/CommonFiltersExtensionMethods.cs @@ -0,0 +1,102 @@ +using Avalonia.Platform.Storage; + +namespace JamSoft.AvaloniaUI.Dialogs.Helpers; + +/// +/// A class containing extension methods for to allow for easier merging of file types. +/// +public static class CommonFiltersExtensionMethods +{ + /// + /// Merges the specified file type with the other file types. + /// + /// + /// + /// + /// + public static FilePickerFileType MergeWith(this FilePickerFileType fileType, FilePickerFileType[] others, string? name) + { + if (!string.IsNullOrWhiteSpace(name)) + { + for(int i = 0;i< others.Length;i++) + { + if (i < others.Length - 1) + { + fileType.MergeWith(others[i], name); + } + else + { + fileType.MergeWith(others[i]); + } + } + } + + foreach (var ft in others) + { + fileType.MergeWith(ft); + } + + return fileType; + } + + /// + /// merges the specified file type with the other file type. + /// + /// + /// + /// + /// + public static FilePickerFileType MergeWith(this FilePickerFileType fileType, FilePickerFileType? other, string? name = null) + { + if (other == null) + return fileType; + + if (fileType.Patterns != null) + { + if (other.Patterns != null) + { + fileType.Patterns = fileType.Patterns.Concat(other.Patterns).ToArray(); + } + } + else + { + fileType.Patterns = other.Patterns; + } + + if (fileType.AppleUniformTypeIdentifiers != null) + { + if (other.AppleUniformTypeIdentifiers != null) + { + fileType.AppleUniformTypeIdentifiers = fileType.AppleUniformTypeIdentifiers.Concat(other.AppleUniformTypeIdentifiers).ToArray(); + } + } + else + { + fileType.AppleUniformTypeIdentifiers = other.AppleUniformTypeIdentifiers; + } + + if (fileType.MimeTypes != null) + { + if (other.MimeTypes != null) + { + fileType.MimeTypes = fileType.MimeTypes.Concat(other.MimeTypes).ToArray(); + } + } + else + { + fileType.MimeTypes = other.MimeTypes; + } + + if (!string.IsNullOrWhiteSpace(name)) + { + return new FilePickerFileType(name) + { + Patterns = fileType.Patterns, + AppleUniformTypeIdentifiers = fileType.AppleUniformTypeIdentifiers, + MimeTypes = fileType.MimeTypes + }; + } + + return fileType; + } +} \ No newline at end of file diff --git a/src/JamSoft.AvaloniaUI.Dialogs/IDialogService.cs b/src/JamSoft.AvaloniaUI.Dialogs/IDialogService.cs index d70f82a..d58b9f6 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/IDialogService.cs +++ b/src/JamSoft.AvaloniaUI.Dialogs/IDialogService.cs @@ -1,4 +1,5 @@ using Avalonia.Controls; +using Avalonia.Platform.Storage; using JamSoft.AvaloniaUI.Dialogs.ViewModels; namespace JamSoft.AvaloniaUI.Dialogs; @@ -67,7 +68,7 @@ public interface IDialogService /// The file extension filters /// The default file extension /// the selected file path or null if the dialog was cancelled - Task SaveFile(string title, IEnumerable? filters = null, string? defaultExtension = null); + Task SaveFile(string title, IEnumerable? filters = null, string? defaultExtension = null); /// /// The an individual file path @@ -75,7 +76,7 @@ public interface IDialogService /// The dialog title /// The file extension filters /// the selected file path or null if the dialog was cancelled - Task OpenFile(string title, IEnumerable? filters = null); + Task OpenFile(string title, IEnumerable? filters = null); /// /// Returns multiple existing file paths @@ -83,5 +84,5 @@ public interface IDialogService /// The dialog title /// The file extension filters /// the selected file paths or null if the dialog was cancelled - Task OpenFiles(string title, IEnumerable? filters = null); + Task OpenFiles(string title, IEnumerable? filters = null); } \ No newline at end of file diff --git a/src/JamSoft.AvaloniaUI.Dialogs/JamSoft.AvaloniaUI.Dialogs.csproj b/src/JamSoft.AvaloniaUI.Dialogs/JamSoft.AvaloniaUI.Dialogs.csproj index 6a27bce..eb9f792 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/JamSoft.AvaloniaUI.Dialogs.csproj +++ b/src/JamSoft.AvaloniaUI.Dialogs/JamSoft.AvaloniaUI.Dialogs.csproj @@ -21,11 +21,11 @@ true JamSoft.AssemblyKeyFile.snk true - 1.1.4 - 1.1.4.0 - 1.1.4.0 + 1.2.0 + 1.2.0.0 + 1.2.0.0 MIT - 1.1.4.0-rel + 1.2.0.0-rel true README.md @@ -40,9 +40,10 @@ - + - + + @@ -59,4 +60,8 @@ + + + + diff --git a/src/JamSoft.AvaloniaUI.Dialogs/Themes/WizardStepStyle.axaml b/src/JamSoft.AvaloniaUI.Dialogs/Themes/WizardStepStyle.axaml index 05c59fd..7c514ca 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/Themes/WizardStepStyle.axaml +++ b/src/JamSoft.AvaloniaUI.Dialogs/Themes/WizardStepStyle.axaml @@ -34,7 +34,7 @@ CornerRadius="{TemplateBinding CornerRadius}" Padding="{TemplateBinding Padding}"> - + diff --git a/src/JamSoft.AvaloniaUI.Dialogs/Themes/WizardStyle.axaml b/src/JamSoft.AvaloniaUI.Dialogs/Themes/WizardStyle.axaml index 4a34feb..3b44c2c 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/Themes/WizardStyle.axaml +++ b/src/JamSoft.AvaloniaUI.Dialogs/Themes/WizardStyle.axaml @@ -40,13 +40,17 @@ DockPanel.Dock="{TemplateBinding ButtonPlacement}" LastChildFill="False"/> - - + + + + + + + + + + @@ -72,9 +78,11 @@ + + diff --git a/src/JamSoft.AvaloniaUI.Dialogs/ViewModels/ChildWindowViewModel.cs b/src/JamSoft.AvaloniaUI.Dialogs/ViewModels/ChildWindowViewModel.cs index 1016b9c..3cf9c4b 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/ViewModels/ChildWindowViewModel.cs +++ b/src/JamSoft.AvaloniaUI.Dialogs/ViewModels/ChildWindowViewModel.cs @@ -1,5 +1,4 @@ -using Avalonia; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Platform; @@ -23,8 +22,7 @@ public abstract class ChildWindowViewModel : DialogViewModel, IChildWindowViewMo /// protected ChildWindowViewModel() { - var assets = AvaloniaLocator.Current.GetService(); - _closeIcon = new Bitmap(assets?.Open(new Uri("avares://JamSoft.AvaloniaUI.Dialogs/Assets/CloseIcon/icons8-close-30.png"))); + _closeIcon = new Bitmap(AssetLoader.Open(new Uri("avares://JamSoft.AvaloniaUI.Dialogs/Assets/CloseIcon/icons8-close-30.png"))); } /// diff --git a/src/JamSoft.AvaloniaUI.Dialogs/Views/ChildWindow.axaml.cs b/src/JamSoft.AvaloniaUI.Dialogs/Views/ChildWindow.axaml.cs index 8234b91..e5502e6 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/Views/ChildWindow.axaml.cs +++ b/src/JamSoft.AvaloniaUI.Dialogs/Views/ChildWindow.axaml.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Markup.Xaml; +using Avalonia.Reactive; using JamSoft.AvaloniaUI.Dialogs.Events; using JamSoft.AvaloniaUI.Dialogs.ViewModels; @@ -25,8 +26,8 @@ public ChildWindow() #if DEBUG this.AttachDevTools(); #endif - this.FindControl("ChromeDockPanel").PointerPressed += OnChromePointerPressed; - this.FindControl("Host").DataContextChanged += DialogPresenterDataContextChanged; + this.FindControl("ChromeDockPanel")!.PointerPressed += OnChromePointerPressed; + this.FindControl("Host")!.DataContextChanged += DialogPresenterDataContextChanged; Closed += ChildWindowClosed; PositionChanged += OnPositionChanged; } @@ -46,14 +47,14 @@ private void OnChromePointerPressed(object? sender, PointerPressedEventArgs e) var p = e.GetCurrentPoint(null); if (p.Properties.IsLeftButtonPressed) { - ClientSizeProperty.Changed.Subscribe(size => + this.GetObservable(ClientSizeProperty).Subscribe(new AnonymousObserver((s)=> { - if (ReferenceEquals(size.Sender, this)) - { + //if (ReferenceEquals(size.Sender, this)) + //{ _vm.RequestedLeft = Position.X; _vm.RequestedTop = Position.Y; - } - }); + //} + })); BeginMoveDrag(e); e.Handled = false; diff --git a/src/JamSoft.AvaloniaUI.Dialogs/Views/DialogWindow.axaml.cs b/src/JamSoft.AvaloniaUI.Dialogs/Views/DialogWindow.axaml.cs index 14812cc..a23aa97 100644 --- a/src/JamSoft.AvaloniaUI.Dialogs/Views/DialogWindow.axaml.cs +++ b/src/JamSoft.AvaloniaUI.Dialogs/Views/DialogWindow.axaml.cs @@ -22,7 +22,7 @@ public DialogWindow() #if DEBUG this.AttachDevTools(); #endif - this.FindControl("Host").DataContextChanged += DialogPresenterDataContextChanged; + this.FindControl("Host")!.DataContextChanged += DialogPresenterDataContextChanged; Closed += DialogWindowClosed; }