19 changed files with 751 additions and 6 deletions
@ -0,0 +1,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<EmbeddedResource Include="Internal\ManagedFileChooser.xaml"> |
||||
|
<SubType>Designer</SubType> |
||||
|
</EmbeddedResource> |
||||
|
<EmbeddedResource Include="Internal\ManagedFileDialog.xaml"> |
||||
|
<SubType>Designer</SubType> |
||||
|
</EmbeddedResource> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> |
||||
|
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Layout; |
||||
|
|
||||
|
namespace Avalonia.Dialogs.Internal |
||||
|
{ |
||||
|
class ChildFitter : Decorator |
||||
|
{ |
||||
|
protected override Size MeasureOverride(Size availableSize) |
||||
|
{ |
||||
|
return new Size(0, 0); |
||||
|
} |
||||
|
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
Child.Measure(finalSize); |
||||
|
base.ArrangeOverride(finalSize); |
||||
|
return finalSize; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Avalonia.Dialogs.Internal |
||||
|
{ |
||||
|
class InternalViewModelBase : INotifyPropertyChanged |
||||
|
{ |
||||
|
public event PropertyChangedEventHandler PropertyChanged; |
||||
|
|
||||
|
[NotifyPropertyChangedInvocator] |
||||
|
protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null) |
||||
|
{ |
||||
|
if (!EqualityComparer<T>.Default.Equals(field, value)) |
||||
|
{ |
||||
|
field = value; |
||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
[NotifyPropertyChangedInvocator] |
||||
|
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) |
||||
|
{ |
||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal" |
||||
|
xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal"> |
||||
|
<UserControl.Resources> |
||||
|
<DrawingGroup x:Key="LevelUp"> |
||||
|
<GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" /> |
||||
|
<GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M14.5,0L6.39,0 5.39,2 2.504,2C1.677,2,1,2.673,1,3.5L1,10.582 1,10.586 1,15.414 3,13.414 3,16 7,16 7,13.414 9,15.414 9,13 14.5,13C15.327,13,16,12.327,16,11.5L16,1.5C16,0.673,15.327,0,14.5,0" /> |
||||
|
<GeometryDrawing Brush="#FFDCB679" Geometry="F1M14,3L7.508,3 8.008,2 8.012,2 14,2z M14.5,1L7.008,1 6.008,3 2.504,3C2.227,3,2,3.224,2,3.5L2,9.582 4.998,6.586 9,10.586 9,12 14.5,12C14.775,12,15,11.776,15,11.5L15,1.5C15,1.224,14.775,1,14.5,1" /> |
||||
|
<GeometryDrawing Brush="#FF00529C" Geometry="F1M8,11L5,8 2,11 2,13 4,11 4,15 6,15 6,11 8,13z" /> |
||||
|
<GeometryDrawing Brush="#FFF0EFF1" Geometry="F1M8.0001,1.9996L7.5001,3.0006 14.0001,3.0006 14.0001,1.9996z" /> |
||||
|
</DrawingGroup> |
||||
|
<dialogs:ResourceSelectorConverter x:Key="Icons"> |
||||
|
<DrawingGroup x:Key="Icon_Folder"><GeometryDrawing Brush="#00FFFFFF" Geometry="F1M0,0L16,0 16,16 0,16z" /><GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M1.5,1L9.61,1 10.61,3 13.496,3C14.323,3,14.996,3.673,14.996,4.5L14.996,12.5C14.996,13.327,14.323,14,13.496,14L1.5,14C0.673,14,0,13.327,0,12.5L0,2.5C0,1.673,0.673,1,1.5,1" /><GeometryDrawing Brush="#FFDCB67A" Geometry="F1M2,3L8.374,3 8.874,4 2,4z M13.496,4L10,4 9.992,4 8.992,2 1.5,2C1.225,2,1,2.224,1,2.5L1,12.5C1,12.776,1.225,13,1.5,13L13.496,13C13.773,13,13.996,12.776,13.996,12.5L13.996,4.5C13.996,4.224,13.773,4,13.496,4" /><GeometryDrawing Brush="#FFEFEFF0" Geometry="F1M2,3L8.374,3 8.874,4 2,4z" /></DrawingGroup> |
||||
|
<DrawingGroup x:Key="Icon_File"><GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" /><GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M4,15C3.03,15,2,14.299,2,13L2,3C2,1.701,3.03,1,4,1L10.061,1 14,4.556 14,13C14,13.97,13.299,15,12,15z" /><GeometryDrawing Brush="#FF9B4E96" Geometry="F1M12,13L4,13 4,3 9,3 9,6 12,6z M9.641,2L3.964,2C3.964,2,3,2,3,3L3,13C3,14,3.964,14,3.964,14L11.965,14C12.965,14,13,13,13,13L13,5z" /><GeometryDrawing Brush="#FFF0EFF1" Geometry="F1M4,3L9,3 9,6 12,6 12,13 4,13z" /></DrawingGroup> |
||||
|
</dialogs:ResourceSelectorConverter> |
||||
|
</UserControl.Resources> |
||||
|
<UserControl.DataTemplates> |
||||
|
<DataTemplate DataType="internal:ManagedFileChooserItemViewModel"> |
||||
|
<StackPanel Background="Transparent" Orientation="Horizontal" |
||||
|
Cursor="Hand"> |
||||
|
|
||||
|
<DrawingPresenter Width="16" Height="16" Drawing="{Binding IconKey, Converter={StaticResource Icons}}"/> |
||||
|
<TextBlock Text="{Binding DisplayName}"/> |
||||
|
</StackPanel> |
||||
|
</DataTemplate> |
||||
|
</UserControl.DataTemplates> |
||||
|
<DockPanel> |
||||
|
<DockPanel DockPanel.Dock="Top" Margin="5"> |
||||
|
|
||||
|
<dialogs:ChildFitter DockPanel.Dock="Right" Width="{Binding ElementName=Location, Path=Bounds.Height}"> |
||||
|
<Button Command="{Binding GoUp}" > |
||||
|
|
||||
|
<DrawingPresenter Drawing="{StaticResource LevelUp}" Stretch="Fill"/> |
||||
|
|
||||
|
</Button> |
||||
|
</dialogs:ChildFitter> |
||||
|
<TextBox x:Name="Location" IsReadOnly="True" Text="{Binding Location}" Margin="0 0 5 0"/> |
||||
|
</DockPanel> |
||||
|
<DockPanel DockPanel.Dock="Bottom"> |
||||
|
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left"> |
||||
|
<CheckBox IsChecked="{Binding ShowHiddenFiles}"> |
||||
|
<TextBlock>Show hidden files</TextBlock> |
||||
|
</CheckBox> |
||||
|
</StackPanel> |
||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right"> |
||||
|
<StackPanel.Styles> |
||||
|
<Style Selector="Button"> |
||||
|
<Setter Property="Margin">4</Setter> |
||||
|
</Style> |
||||
|
</StackPanel.Styles> |
||||
|
<Button Command="{Binding Ok}">OK</Button> |
||||
|
<Button Command="{Binding Cancel}">Cancel</Button> |
||||
|
</StackPanel> |
||||
|
</DockPanel> |
||||
|
<DropDown DockPanel.Dock="Bottom" |
||||
|
IsVisible="{Binding ShowFilters}" |
||||
|
Items="{Binding Filters}" |
||||
|
SelectedItem="{Binding SelectedFilter}" |
||||
|
/> |
||||
|
<ListBox Margin="5" BorderBrush="Transparent" x:Name="QuickLinks" Items="{Binding QuickLinks}" |
||||
|
SelectedIndex="{Binding QuickLinksSelectedIndex}" |
||||
|
DockPanel.Dock="Left" Background="{DynamicResource ThemeControlMidBrush}" Focusable="False" /> |
||||
|
<ListBox x:Name="Files" |
||||
|
VirtualizationMode="Simple" |
||||
|
Items="{Binding Items}" |
||||
|
Margin="5" |
||||
|
SelectionMode="{Binding SelectionMode}" |
||||
|
SelectedItems="{Binding SelectedItems}"/> |
||||
|
</DockPanel> |
||||
|
|
||||
|
</UserControl> |
||||
@ -0,0 +1,65 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Interactivity; |
||||
|
using Avalonia.LogicalTree; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace Avalonia.Dialogs.Internal |
||||
|
{ |
||||
|
class ManagedFileChooser : UserControl |
||||
|
{ |
||||
|
private Control _quickLinksRoot; |
||||
|
private ListBox _filesView; |
||||
|
|
||||
|
public ManagedFileChooser() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel); |
||||
|
_quickLinksRoot = this.FindControl<Control>("QuickLinks"); |
||||
|
_filesView = this.FindControl<ListBox>("Files"); |
||||
|
} |
||||
|
|
||||
|
ManagedFileChooserViewModel Model => DataContext as ManagedFileChooserViewModel; |
||||
|
|
||||
|
private void OnPointerPressed(object sender, PointerPressedEventArgs e) |
||||
|
{ |
||||
|
var model = (e.Source as StyledElement)?.DataContext as ManagedFileChooserItemViewModel; |
||||
|
if(model == null) |
||||
|
return; |
||||
|
|
||||
|
var isQuickLink = _quickLinksRoot.IsLogicalParentOf(e.Source as Control); |
||||
|
if (e.ClickCount == 2 || isQuickLink) |
||||
|
{ |
||||
|
if (model.IsDirectory) |
||||
|
Model?.Navigate(model.Path); |
||||
|
else |
||||
|
Model?.SelectSingleFile(model); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override async void OnDataContextChanged(EventArgs e) |
||||
|
{ |
||||
|
base.OnDataContextChanged(e); |
||||
|
var model = (DataContext as ManagedFileChooserViewModel); |
||||
|
if (model == null) |
||||
|
return; |
||||
|
var preselected = model.SelectedItems.FirstOrDefault(); |
||||
|
if(preselected == null) |
||||
|
return; |
||||
|
|
||||
|
//Let everything to settle down and scroll to selected item
|
||||
|
await Task.Delay(100); |
||||
|
if (preselected != model.SelectedItems.FirstOrDefault()) |
||||
|
return; |
||||
|
|
||||
|
// Workaround for ListBox bug, scroll to the previous file
|
||||
|
var indexOfPreselected = model.Items.IndexOf(preselected); |
||||
|
if (indexOfPreselected > 1) |
||||
|
_filesView.ScrollIntoView(model.Items[indexOfPreselected - 1]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Dialogs.Internal |
||||
|
{ |
||||
|
class ManagedFileChooserFilterViewModel : InternalViewModelBase |
||||
|
{ |
||||
|
private readonly string[] _extensions; |
||||
|
public string Name { get; } |
||||
|
|
||||
|
public ManagedFileChooserFilterViewModel(FileDialogFilter filter) |
||||
|
{ |
||||
|
Name = filter.Name; |
||||
|
if (filter.Extensions.Contains("*")) |
||||
|
return; |
||||
|
_extensions = filter.Extensions?.Select(e => "." + e.ToLowerInvariant()).ToArray(); |
||||
|
} |
||||
|
|
||||
|
public ManagedFileChooserFilterViewModel() |
||||
|
{ |
||||
|
Name = "All files"; |
||||
|
} |
||||
|
|
||||
|
public bool Match(string filename) |
||||
|
{ |
||||
|
if (_extensions == null) |
||||
|
return true; |
||||
|
foreach(var ext in _extensions) |
||||
|
if (filename.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase)) |
||||
|
return true; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public override string ToString() => Name; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
namespace Avalonia.Dialogs.Internal |
||||
|
{ |
||||
|
class ManagedFileChooserItemViewModel : InternalViewModelBase |
||||
|
{ |
||||
|
private string _displayName; |
||||
|
private string _path; |
||||
|
private bool _isDirectory; |
||||
|
|
||||
|
public string DisplayName |
||||
|
{ |
||||
|
get => _displayName; |
||||
|
set => RaiseAndSetIfChanged(ref _displayName, value); |
||||
|
} |
||||
|
|
||||
|
public string Path |
||||
|
{ |
||||
|
get => _path; |
||||
|
set => RaiseAndSetIfChanged(ref _path, value); |
||||
|
} |
||||
|
|
||||
|
public string IconKey => IsDirectory ? "Icon_Folder" : "Icon_File"; |
||||
|
|
||||
|
public bool IsDirectory |
||||
|
{ |
||||
|
get => _isDirectory; |
||||
|
set |
||||
|
{ |
||||
|
if (RaiseAndSetIfChanged(ref _isDirectory, value)) |
||||
|
RaisePropertyChanged(nameof(IconKey)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public ManagedFileChooserItemViewModel() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public ManagedFileChooserItemViewModel(ManagedFileChooserNavigationItem item) |
||||
|
{ |
||||
|
IsDirectory = true; |
||||
|
Path = item.Path; |
||||
|
DisplayName = item.DisplayName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace Avalonia.Dialogs.Internal |
||||
|
{ |
||||
|
public class ManagedFileChooserSources |
||||
|
{ |
||||
|
public Func<ManagedFileChooserNavigationItem[]> GetUserDirectories { get; set; } |
||||
|
= DefaultGetUserDirectories; |
||||
|
|
||||
|
public Func<ManagedFileChooserNavigationItem[]> GetFileSystemRoots { get; set; } |
||||
|
= DefaultGetFileSystemRoots; |
||||
|
|
||||
|
public Func<ManagedFileChooserSources, ManagedFileChooserNavigationItem[]> GetAllItemsDelegate { get; set; } |
||||
|
= DefaultGetAllItems; |
||||
|
|
||||
|
public ManagedFileChooserNavigationItem[] GetAllItems() => GetAllItemsDelegate(this); |
||||
|
|
||||
|
public static ManagedFileChooserNavigationItem[] DefaultGetAllItems(ManagedFileChooserSources sources) |
||||
|
{ |
||||
|
return sources.GetUserDirectories().Concat(sources.GetFileSystemRoots()).ToArray(); |
||||
|
} |
||||
|
|
||||
|
private static Environment.SpecialFolder[] s_folders = new[] |
||||
|
{ |
||||
|
Environment.SpecialFolder.Desktop, |
||||
|
Environment.SpecialFolder.UserProfile, |
||||
|
Environment.SpecialFolder.MyDocuments, |
||||
|
Environment.SpecialFolder.MyMusic, |
||||
|
Environment.SpecialFolder.MyPictures, |
||||
|
Environment.SpecialFolder.MyVideos |
||||
|
}; |
||||
|
|
||||
|
public static ManagedFileChooserNavigationItem[] DefaultGetUserDirectories() |
||||
|
{ |
||||
|
return s_folders.Select(Environment.GetFolderPath).Distinct() |
||||
|
.Where(d => !string.IsNullOrWhiteSpace(d)) |
||||
|
.Where(Directory.Exists) |
||||
|
.Select(d => new ManagedFileChooserNavigationItem |
||||
|
{ |
||||
|
Path = d, |
||||
|
DisplayName = Path.GetFileName(d) |
||||
|
}).ToArray(); |
||||
|
} |
||||
|
|
||||
|
public static ManagedFileChooserNavigationItem[] DefaultGetFileSystemRoots() |
||||
|
{ |
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
|
{ |
||||
|
return DriveInfo.GetDrives().Select(d => new ManagedFileChooserNavigationItem |
||||
|
{ |
||||
|
DisplayName = d.Name, |
||||
|
Path = d.RootDirectory.FullName |
||||
|
}).ToArray(); |
||||
|
} |
||||
|
|
||||
|
return new[] |
||||
|
{ |
||||
|
new ManagedFileChooserNavigationItem |
||||
|
{ |
||||
|
DisplayName = "File System", |
||||
|
Path = "/" |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class ManagedFileChooserNavigationItem |
||||
|
{ |
||||
|
public string DisplayName { get; set; } |
||||
|
public string Path { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,223 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Specialized; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Collections; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace Avalonia.Dialogs.Internal |
||||
|
{ |
||||
|
class ManagedFileChooserViewModel : InternalViewModelBase |
||||
|
{ |
||||
|
public event Action CancelRequested; |
||||
|
public event Action<string[]> CompleteRequested; |
||||
|
|
||||
|
public AvaloniaList<ManagedFileChooserItemViewModel> QuickLinks { get; } = |
||||
|
new AvaloniaList<ManagedFileChooserItemViewModel>(); |
||||
|
|
||||
|
public AvaloniaList<ManagedFileChooserItemViewModel> Items { get; } = |
||||
|
new AvaloniaList<ManagedFileChooserItemViewModel>(); |
||||
|
|
||||
|
public AvaloniaList<ManagedFileChooserFilterViewModel> Filters { get; } = |
||||
|
new AvaloniaList<ManagedFileChooserFilterViewModel>(); |
||||
|
|
||||
|
public AvaloniaList<ManagedFileChooserItemViewModel> SelectedItems { get; } = |
||||
|
new AvaloniaList<ManagedFileChooserItemViewModel>(); |
||||
|
|
||||
|
string _location; |
||||
|
private bool _showHiddenFiles; |
||||
|
private ManagedFileChooserFilterViewModel _selectedFilter; |
||||
|
private bool _selectingDirectory; |
||||
|
private bool _scheduledSelectionValidation; |
||||
|
|
||||
|
public string Location |
||||
|
{ |
||||
|
get => _location; |
||||
|
private set => RaiseAndSetIfChanged(ref _location, value); |
||||
|
} |
||||
|
|
||||
|
public bool ShowFilters { get; } |
||||
|
public SelectionMode SelectionMode { get; } |
||||
|
public string Title { get; } |
||||
|
|
||||
|
public int QuickLinksSelectedIndex |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
for (var index = 0; index < QuickLinks.Count; index++) |
||||
|
{ |
||||
|
var i = QuickLinks[index]; |
||||
|
if (i.Path == Location) |
||||
|
return index; |
||||
|
} |
||||
|
|
||||
|
return -1; |
||||
|
} |
||||
|
set => RaisePropertyChanged(nameof(QuickLinksSelectedIndex)); |
||||
|
} |
||||
|
|
||||
|
public ManagedFileChooserFilterViewModel SelectedFilter |
||||
|
{ |
||||
|
get => _selectedFilter; |
||||
|
set |
||||
|
{ |
||||
|
RaiseAndSetIfChanged(ref _selectedFilter, value); |
||||
|
Refresh(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool ShowHiddenFiles |
||||
|
{ |
||||
|
get => _showHiddenFiles; |
||||
|
set |
||||
|
{ |
||||
|
RaiseAndSetIfChanged(ref _showHiddenFiles, value); |
||||
|
Refresh(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public ManagedFileChooserViewModel(FileSystemDialog dialog) |
||||
|
{ |
||||
|
var quickSources = AvaloniaLocator.Current.GetService<ManagedFileChooserSources>() |
||||
|
?? new ManagedFileChooserSources(); |
||||
|
QuickLinks.Clear(); |
||||
|
|
||||
|
QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i))); |
||||
|
Title = dialog.Title ?? ( |
||||
|
dialog is OpenFileDialog ? "Open file" |
||||
|
: dialog is SaveFileDialog ? "Save file" |
||||
|
: dialog is OpenFolderDialog ? "Select directory" |
||||
|
: throw new ArgumentException(nameof(dialog))); |
||||
|
|
||||
|
var directory = dialog.Directory; |
||||
|
if (directory == null || !Directory.Exists(directory)) |
||||
|
directory = Directory.GetCurrentDirectory(); |
||||
|
|
||||
|
if (dialog is FileDialog fd) |
||||
|
{ |
||||
|
if (fd.Filters?.Count > 0) |
||||
|
{ |
||||
|
Filters.AddRange(fd.Filters.Select(f => new ManagedFileChooserFilterViewModel(f))); |
||||
|
_selectedFilter = Filters[0]; |
||||
|
ShowFilters = true; |
||||
|
} |
||||
|
|
||||
|
if (dialog is OpenFileDialog ofd) |
||||
|
{ |
||||
|
if (ofd.AllowMultiple) |
||||
|
SelectionMode = SelectionMode.Multiple; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_selectingDirectory = dialog is OpenFolderDialog; |
||||
|
|
||||
|
Navigate(directory, (dialog as FileDialog)?.InitialFileName); |
||||
|
SelectedItems.CollectionChanged += OnSelectionChanged; |
||||
|
} |
||||
|
|
||||
|
private async void OnSelectionChanged(object sender, NotifyCollectionChangedEventArgs e) |
||||
|
{ |
||||
|
if(_scheduledSelectionValidation) |
||||
|
return; |
||||
|
_scheduledSelectionValidation = true; |
||||
|
await Dispatcher.UIThread.InvokeAsync(() => |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
if(_selectingDirectory) |
||||
|
SelectedItems.Clear(); |
||||
|
else |
||||
|
{ |
||||
|
var invalidItems = SelectedItems.Where(i => i.IsDirectory).ToList(); |
||||
|
foreach (var item in invalidItems) |
||||
|
SelectedItems.Remove(item); |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_scheduledSelectionValidation = false; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
void NavigateRoot(string initialSelectionName) |
||||
|
{ |
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
|
Navigate(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)), initialSelectionName); |
||||
|
else |
||||
|
Navigate("/", initialSelectionName); |
||||
|
} |
||||
|
|
||||
|
public void Refresh() => Navigate(Location); |
||||
|
|
||||
|
public void Navigate(string path, string initialSelectionName = null) |
||||
|
{ |
||||
|
if (!Directory.Exists(path)) |
||||
|
NavigateRoot(initialSelectionName); |
||||
|
else |
||||
|
{ |
||||
|
Location = path; |
||||
|
Items.Clear(); |
||||
|
SelectedItems.Clear(); |
||||
|
var infos = new DirectoryInfo(path).EnumerateFileSystemInfos(); |
||||
|
if (!ShowHiddenFiles) |
||||
|
{ |
||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
||||
|
infos = infos.Where(i => (i.Attributes & (FileAttributes.Hidden | FileAttributes.System)) != 0); |
||||
|
else |
||||
|
infos = infos.Where(i => !i.Name.StartsWith(".")); |
||||
|
} |
||||
|
|
||||
|
if (SelectedFilter != null) |
||||
|
infos = infos.Where(i => i is DirectoryInfo || SelectedFilter.Match(i.Name)); |
||||
|
|
||||
|
Items.AddRange(infos.Select(info => new ManagedFileChooserItemViewModel |
||||
|
{ |
||||
|
DisplayName = info.Name, |
||||
|
Path = info.FullName, |
||||
|
IsDirectory = info is DirectoryInfo |
||||
|
}).OrderByDescending(x => x.IsDirectory) |
||||
|
.ThenBy(x => x.DisplayName, StringComparer.InvariantCultureIgnoreCase)); |
||||
|
|
||||
|
if (initialSelectionName != null) |
||||
|
{ |
||||
|
var sel = Items.FirstOrDefault(i => !i.IsDirectory && i.DisplayName == initialSelectionName); |
||||
|
if (sel != null) |
||||
|
SelectedItems.Add(sel); |
||||
|
} |
||||
|
|
||||
|
RaisePropertyChanged(nameof(QuickLinksSelectedIndex)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void GoUp() |
||||
|
{ |
||||
|
var parent = Path.GetDirectoryName(Location); |
||||
|
if (string.IsNullOrWhiteSpace(parent)) |
||||
|
return; |
||||
|
Navigate(parent); |
||||
|
} |
||||
|
|
||||
|
public void Cancel() |
||||
|
{ |
||||
|
CancelRequested?.Invoke(); |
||||
|
} |
||||
|
|
||||
|
public void Ok() |
||||
|
{ |
||||
|
if (_selectingDirectory) |
||||
|
CompleteRequested?.Invoke(new[] {Location}); |
||||
|
else |
||||
|
CompleteRequested?.Invoke(SelectedItems.Select(i => i.Path).ToArray()); |
||||
|
} |
||||
|
|
||||
|
public void SelectSingleFile(ManagedFileChooserItemViewModel item) |
||||
|
{ |
||||
|
CompleteRequested?.Invoke(new[] {item.Path}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
<Window xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal" |
||||
|
xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal" |
||||
|
Title="{Binding Title}" MinWidth="700" MinHeight="500"> |
||||
|
<internal:ManagedFileChooser Margin="4"/> |
||||
|
</Window> |
||||
@ -0,0 +1,18 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace Avalonia.Dialogs.Internal |
||||
|
{ |
||||
|
class ManagedFileDialog : Window |
||||
|
{ |
||||
|
private ManagedFileChooserViewModel _model; |
||||
|
public ManagedFileDialog() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
#if DEBUG
|
||||
|
this.AttachDevTools(); |
||||
|
#endif
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Data.Converters; |
||||
|
|
||||
|
namespace Avalonia.Dialogs.Internal |
||||
|
{ |
||||
|
public class ResourceSelectorConverter : ResourceDictionary, IValueConverter |
||||
|
{ |
||||
|
public object Convert(object key, Type targetType, object parameter, CultureInfo culture) |
||||
|
{ |
||||
|
TryGetResource((string)key, out var value); |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Platform; |
||||
|
using Avalonia.Dialogs.Internal; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Dialogs |
||||
|
{ |
||||
|
public static class ManagedFileDialogExtensions |
||||
|
{ |
||||
|
class ManagedSystemDialogImpl : ISystemDialogImpl |
||||
|
{ |
||||
|
async Task<string[]> Show(SystemDialog d, IWindowImpl parent) |
||||
|
{ |
||||
|
var model = new ManagedFileChooserViewModel((FileSystemDialog)d); |
||||
|
|
||||
|
var dialog = new ManagedFileDialog |
||||
|
{ |
||||
|
DataContext = model |
||||
|
}; |
||||
|
|
||||
|
string[] result = null; |
||||
|
model.CompleteRequested += items => |
||||
|
{ |
||||
|
result = items; |
||||
|
dialog.Close(); |
||||
|
}; |
||||
|
model.CancelRequested += dialog.Close; |
||||
|
|
||||
|
await dialog.ShowDialog<object>(parent); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) |
||||
|
{ |
||||
|
return await Show(dialog, parent); |
||||
|
} |
||||
|
|
||||
|
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) |
||||
|
{ |
||||
|
return (await Show(dialog, parent))?.FirstOrDefault(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static TAppBuilder UseManagedSystemDialogs<TAppBuilder>(this TAppBuilder builder) |
||||
|
where TAppBuilder : AppBuilderBase<TAppBuilder>, new() |
||||
|
{ |
||||
|
builder.AfterPlatformServicesSetup(_ => |
||||
|
AvaloniaLocator.CurrentMutable.Bind<ISystemDialogImpl>().ToSingleton<ManagedSystemDialogImpl>()); |
||||
|
return builder; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue